Android 自定義View小結(重於理解,不作爲教程)

感謝 郭神 的思路。

大家都知道 SetContextView是給Activity設置佈局的, 但是內部還是用的LayoutInflater去實現的,關於LayoutInFlater的實例有兩種方法獲取得到。

    通過傳入Contxt對象生成
    LayoutInflater layoutInflater = LayoutInflater.from(context); 

    第二種就是通過系統服務拿到
        LayoutInflater layoutInflater = (LayoutInflater) context  
    .getSystemService(Context.LAYOUT_INFLATER_SERVICE); 

其實兩種方法沒有什麼異同, 第一種是系統幫我們封裝好了的,然後通過實例的方法inflate方法去加載佈局。

其實LayoutInflater技術廣泛應用於需要動態添加View的時候,通過addview方法, 添加某一個子View 比如在ScrollView和ListView中,經常都可以看到LayoutInflater的身影。
在Activity佈局中, 最外層的其實是FrameLayout,這並不是我們自己去寫的, 而是系統給我們

LayoutInflater其實就是使用Android提供的pull解析方式來解析佈局文件的,把整個佈局文件都解析完成後就形成了一個完整的DOM結構,最終會把最頂層的根佈局返回,它是於根據節點名來創建View對象的,在createViewFromTag()方法的內部又會去調用createView()方法,然後使用反射的方式創建出View的實例並返回。

其實不管你將Button的layout_width和layout_height的值修改成多少,都不會有任何效果的,因爲這兩個值現在已經完全失去了作用。平時我們經常使用layout_width和layout_height來設置View的大小,並且一直都能正常工作,就好像這兩個屬性確實是用於設置View的大小的。而實際上則不然,它們其實是用於設置View在佈局中的大小的,也就是說,首先View必須存在於一個佈局中,之後如果將layout_width設置成match_parent表示讓View的寬度填充滿布局,如果設置成wrap_content表示讓View的寬度剛好可以包含其內容,如果設置成具體的數值則View的寬度會變成相應的數值。這也是爲什麼這兩個屬性叫作layout_width和layout_height,而不是width和height。

那麼我相信到這裏很多哥們都是心存疑慮的, 我們平常用的佈局也是 不在任何佈局當中阿, 其實不然,在setContentView()方法中,Android會自動在佈局文件的最外層再嵌套一個FrameLayout,所以layout_width和layout_height屬性纔會有效果。就是說,系統自動的幫我們弄了一個FrameLayout佈局放在最外面。

說到這裏其實SetContentView()方法大家都會用,但是實際上Android 界面顯示的原理要比我們所看到的東西複雜得多,其實任何一個Activity中顯示的界面都是有兩部分組成的,那就是標題欄和內容佈局,標題欄就是在很多界面頂部顯示的那部分內容,比如我們剛剛的那個例子當中就有標題欄, 可以在代碼中控制讓他是否顯示, 內容佈局就是一個FrameLayout,這個佈局的id就叫做content,我們調用SetContentView所傳入的佈局其實就是放到這個FrameLayout中的,這也是爲什麼這個方法名稱叫做SetContentView(),而不是SetView()。

這裏寫圖片描述

1.View的繪製流程

要知道,任何一個試圖都不可能憑空突然出現在屏幕上,他們都是要經過非常科學的繪製流程後才能顯示出來的,每一個視圖的繪製過程都必須經歷三個主要的階段, 即onMeasure()、onLayout()和onDraw(),下面我們就逐個對這三個階段展開探討,

1.1 onMeasure()

Measure(測量) Measure是測量的意思, 在代碼中也是來測量控件的大小,其onMeasure(int widthMeasureSpec, int heightMeasureSpec)是可以被重寫的,如果你不喜歡系統給你畫的,完全可以自己設置;
public class MyView extends View {  

    ......  

    @Override  
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {  
        setMeasuredDimension(200, 200);  
    }  

}  

而且需要注意的是, 在重寫onMesure方法時,內部要調用setMeasuredDimension 方法之後,我們纔可以調用getMeasuredWidth()和getMeasureHeight()來獲取試圖測量出的寬高,如果在此之前調用這兩個方法得到的值都會是0.

1.2onLayout

測量完了具體的寬高,那麼就該說說具體的控件位置了, onlayout的主要作用就是確定好控件的位置,而且這個方法還是抽象的,意思就是說, 我們必須要重寫他, view的方法layout其中有四個參數,就是上下左右,然後在layout方法中會調用onlayout方法,然後判斷是不是又子視圖, 如果有的話就設置好位置,

其實這裏有一個點相信大家不是很明白,

在onLayout()過程結束後,我們就可以調用getWidth()方法和getHeight()方法來獲取視圖的寬高了。說到這裏,我相信很多朋友長久以來都會有一個疑問,getWidth()方法和getMeasureWidth()方法到底有什麼區別呢?它們的值好像永遠都是相同的。其實它們的值之所以會相同基本都是因爲佈局設計者的編碼習慣非常好,實際上它們之間的差別還是挺大的。

首先getMeasureWidth()方法在measure()過程結束後就可以獲取到了,而getWidth()方法要在layout()過程結束後才能獲取到。另外,getMeasureWidth()方法中的值是通過setMeasuredDimension()方法來進行設置的,而getWidth()方法中的值則是通過視圖右邊的座標減去左邊的座標計算出來的。

首先 getMesyreWidth()方法是在measure()過程結束之後就可以獲取到了,而GetWidth()方法要再layout()過程結束後纔可以獲取得到, 另外, getMeasureWidth()方法中的值是通過setmeasuredDimension()方法來進行設置的, 而getWidth()方法中的值則是通過視圖右邊的座標減去左邊的座標計算出來的。

1.3 onDraw

有了寬高,有個位置,下面就是繪製了,正如同Draw英文意思一樣,這主要是繪製,她會首先在VireRoot中的代碼 創建出 Canvas 然後利用Canvas去設置一系列的操作,然後還有設置畫筆 Paint,這一點不再簡述, 因爲太多了。。

大家已經知道,View是不會幫我們繪製內容部分的,因此需要每個視圖根據想要展示的內容來自行繪製。如果你去觀察TextView、ImageView等類的源碼,你會發現它們都有重寫onDraw()這個方法,並且在裏面執行了相當不少的繪製邏輯。繪製的方式主要是藉助Canvas這個類,它會作爲參數傳入到onDraw()方法中,供給每個視圖使用。Canvas這個類的用法非常豐富,基本可以把它當成一塊畫布,在上面繪製任意的東西,所以想要實現複雜的動畫,算法是不可少的,因爲這些都是需要畫布配合畫筆畫出來的。

2.View的狀態切換

1.enabled
表示當前視圖是否可用, 可以調用setEnable ()方法來改變視圖的可用狀態,傳入true表示可用, 傳入false表示不可用, 他們之間最大的區別就是, 不可用的視圖是無法響應onTouch事件的,

2.forcused
表示當前視圖是否獲得到焦點, 通常情況下有兩種方法可以讓視圖獲得焦點, 即通過鍵盤的上下左右鍵切換視圖, 以及調用requestFocus()方法,但是現在基本上沒有帶鍵盤的手機了, 因此只有一個請求焦點的方法了,而且requestfocus也不一定可以讓視圖獲得焦點, 他會有一個布爾值的返回值, 如果返回true說明獲取成功了, 返回false說明獲得焦點失敗, 一般只有視圖在focusable和 focusable in touch mode 同時成立的情況下才能成功的獲取焦點,比如說,Edittext。

3.window_focused
表示當前視圖是否處於正在交互的窗口中,這個值由系統去自動決定, 應用程序不能進行改變。

  1. selected
    表示當前視圖是否處於選中狀態。一個界面當中可以有多個視圖處於選中狀態,調用setSelected()方法能夠改變視圖的選中狀態,傳入true表示選中,傳入false表示未選中。

4.Selected
表示當前視圖是否處於選中狀態, 一個界面當中可以有多個視圖處於選中狀態, 調用setSelected()方法能夠改變視圖的選中狀態, 傳入True表示選中, 傳入False表示未選中, 有一個狀態選擇器(selector 可以設置背景,根據是否處於pressed狀態去動態改變)

5.pressed
表示當前視圖是否處於按下狀態,可以調用seetPressed()方法來對這一狀態進行改變, 傳入true表示按下, 傳入flase表示未按下, 通常情況下這個狀態都是由系統自動賦值的, 但是我們也可以自己調用這個方法來進行改變。

3.控件的自繪及組合、繼承

3.1 控件的自繪

這個無需多講, 其實控件的自繪,就是視圖全部由我們自己去繪製, 最重要的就是onDraw方法,,調用invalidate()方法會導致視圖進行重繪,因此onDraw()方法在稍後就將會得到調用,

3.2 組合控件

組合控件的意思就是,我們並不需要自己去繪製視圖上顯示的內容,而只是用系統原生的控件就好了,但我們可以將幾個系統原生的控件組合到一起,這樣創建出的控件就被稱爲組合控件。

就是將原生的控件組合在一起,例如說一個按鈕,一個textview,都在一個佈局中,那麼我就可以寫一個佈局,繼承自FrameLayout,然後獲得這兩個按鈕的實例(通過layoutInflate),將一些列的set/get方法暴露出去,下次的時候要用這兩個控件,直接引用就好了。 例如說標題欄的自定義!

3.3繼承控件

我的理解主要是, 你在某一個控件的基礎上去增加功能,例如listveiw。

繼承控件的意思就是,我們並不需要自己重頭去實現一個控件,只需要去繼承一個現有的控件,然後在這個控件上增加一些新的功能,就可以形成一個自定義的控件了。這種自定義控件的特點就是不僅能夠按照我們的需求加入相應的功能,還可以保留原生控件的所有功能,比如 Android PowerImageView實現,可以播放動畫的強大ImageView 這篇文章中介紹的PowerImageView就是一個典型的繼承控件。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章