android 自定義view詳解

1.自定義View前首先要了解一下View的方法,雖然有些不必定要實現。java

分類 方法 描述
建立 Constructors

View中有兩種類型的構造方法,一種是在代碼中構建View,另外一種是填充佈局文件構建Viewcanvas

第二種構造方法要解析並應用佈局文件中定義的任何屬性。框架

onFinishInflate() 在來自於XMLView和它全部的子節點填充以後被調用。
Layout onMeasure 調用該方法來肯定view及它全部子節點須要的尺寸
onLayout view須要爲它的全部子節點指定大小和佈局時,此方法被調用
onSizeChanged 當這個view的大小發生變化時,此方法被調用
Drawing onDraw view渲染它的內容時被調用
事件處理 onKeyDown Called when a new key event occurs.
onKeyUp Called when a key up event occurs.
onTrackballEvent 當軌跡球動做事件發生時被調用
onTouchEvent Called when a touch screen motion event occurs.
Focus onFocusChanged Called when the view gains or loses focus.
onWindowFocusChanged Called when the window containing the view gains or loses focus.
Attaching onAttachedToWindow Called when the view is attached to a window.
onDetachedFromWindow Called when the view is detached from its window.
onWindowVisibilityChanged Called when the visibility of the window containing the view has changed.

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

注:除以上表格內的方法,還有一個比較重要的方法,就是View的刷新方法:異步

{①整個view刷新 invalidate();ide

②刷新一個矩形區域invalidate(Rect dirty);函數

③刷新一個特性DrawableinvalidateDrawable(Drawable drawable);佈局

⑤執行invalidate類的方法將會設置view爲無效,最終致使onDraw方法被從新調用。}this

2.具體調用上述方法的過程,以下圖:spa

注:.net

①measure過程

做用:爲整個View樹計算實際的大小,即設置實際的高(對應屬性:mMeasuredHeight)和寬(對應屬性:mMeasureWidth),每一個View的控件的實際寬高都是由父視圖和自己視圖決定的。

流程:

ViewRoot根對象地屬性mView(其類型通常爲ViewGroup類型)調用measure()方法去計算View樹的大小,回調View/ViewGroup對象的onMeasure()方法,該方法實現的功能以下:

a、設置本View視圖的最終大小,該功能的實現經過調用setMeasuredDimension()方法去設置實際的高(對應屬性: mMeasuredHeight)和寬(對應屬性:mMeasureWidth) ;

b、若是該View對象是個ViewGroup類型,須要重寫該onMeasure()方法,對其子視圖進行遍歷的measure()過程;

 對每一個子視圖的measure()過程,是經過調用父類ViewGroup.java類裏的measureChildWithMargins()方法去實現,該方法內部只是簡單地調用了View對象的measure()方法。

(因爲measureChildWithMargins()方法只是一個過渡層更簡單的作法是直接調用View對象的measure()方法)。

c、整個measure調用流程就是個樹形的遞歸過程

僞代碼:

 1 //回調View視圖裏的onMeasure過程  
 2 private void onMeasure(int height , int width){  
 3  //設置該view的實際寬(mMeasuredWidth)高(mMeasuredHeight)  
 4  //一、該方法必須在onMeasure調用,否者報異常。  
 5  setMeasuredDimension(h , l) ;  
 6    
 7  //二、若是該View是ViewGroup類型,則對它的每一個子View進行measure()過程  
 8  int childCount = getChildCount() ;  
 9    
10  for(int i=0 ;i<childCount ;i++){  
11   //2.一、得到每一個子View對象引用  
12   View child = getChildAt(i) ;  
13     
14   //整個measure()過程就是個遞歸過程  
15   //該方法只是一個過濾器,最後會調用measure()過程 ;或者 measureChild(child , h, i)方法都  
16   measureChildWithMargins(child , h, i) ;   
17     
18   //其實,對於咱們本身寫的應用來講,最好的辦法是去掉框架裏的該方法,直接調用view.measure(),以下:  
19   //child.measure(h, l)  
20  }  
21 }  
22   
23 //該方法具體實如今ViewGroup.java裏 。  
24 protected  void measureChildWithMargins(View v, int height , int width){  
25  v.measure(h,l)     
26 }  

 

②layout過程

做用:爲將整個根據子視圖的大小以及佈局參數將View樹放到合適的位置上。

流程:

a 、layout方法會設置該View視圖位於父視圖的座標軸,即mLeft,mTop,mLeft,mBottom(調用setFrame()函數去實現)接下來回調onLayout()方法(若是該View是ViewGroup對象,需

要實現該方法,對每一個子視圖進行佈局) ;

b、若是該View是個ViewGroup類型,須要遍歷每一個子視圖chiildView,調用該子視圖的layout()方法去設置它的座標值。

代碼:

源代碼中的layout方法

 1  /**
 2      * Assign a size and position to a view and all of its
 3      * descendants
 4      *
 5      * <p>This is the second phase of the layout mechanism.
 6      * (The first is measuring). In this phase, each parent calls
 7      * layout on all of its children to position them.
 8      * This is typically done using the child measurements
 9      * that were stored in the measure pass().</p>
10      *
11      * <p>Derived classes should not override this method.
12      * Derived classes with children should override
13      * onLayout. In that method, they should
14      * call layout on each of their children.</p>
15      *
16      * @param l Left position, relative to parent
17      * @param t Top position, relative to parent
18      * @param r Right position, relative to parent
19      * @param b Bottom position, relative to parent
20      */
21     @SuppressWarnings({"unchecked"})
22     public void layout(int l, int t, int r, int b) {
23         int oldL = mLeft;
24         int oldT = mTop;
25         int oldB = mBottom;
26         int oldR = mRight;
27         boolean changed = setFrame(l, t, r, b);
28         if (changed || (mPrivateFlags & PFLAG_LAYOUT_REQUIRED) == PFLAG_LAYOUT_REQUIRED) {
29             onLayout(changed, l, t, r, b);
30             mPrivateFlags &= ~PFLAG_LAYOUT_REQUIRED;
31 
32             ListenerInfo li = mListenerInfo;
33             if (li != null && li.mOnLayoutChangeListeners != null) {
34                 ArrayList<OnLayoutChangeListener> listenersCopy =
35                         (ArrayList<OnLayoutChangeListener>)li.mOnLayoutChangeListeners.clone();
36                 int numListeners = listenersCopy.size();
37                 for (int i = 0; i < numListeners; ++i) {
38                     listenersCopy.get(i).onLayoutChange(this, l, t, r, b, oldL, oldT, oldR, oldB);
39                 }
40             }
41         }
42         mPrivateFlags &= ~PFLAG_FORCE_LAYOUT;
43     }

更易理解的僞代碼:

 1 // layout()過程  ViewRoot.java  
 2 // 發起layout()的"發號者"在ViewRoot.java裏的performTraversals()方法, mView.layout()  
 3   
 4 private void  performTraversals(){  
 5    
 6     //...  
 7       
 8     View mView  ;  
 9        mView.layout(left,top,right,bottom) ;  
10       
11     //....  
12 }  
13   
14 //回調View視圖裏的onLayout過程 ,該方法只由ViewGroup類型實現  
15 private void onLayout(int left , int top , right , bottom){  
16   
17  //若是該View不是ViewGroup類型  
18  //調用setFrame()方法設置該控件的在父視圖上的座標軸  
19    
20  setFrame(l ,t , r ,b) ;  
21    
22  //--------------------------  
23    
24  //若是該View是ViewGroup類型,則對它的每一個子View進行layout()過程  
25  int childCount = getChildCount() ;  
26    
27  for(int i=0 ;i<childCount ;i++){  
28   //2.一、得到每一個子View對象引用  
29   View child = getChildAt(i) ;  
30   //整個layout()過程就是個遞歸過程  
31   child.layout(l, t, r, b) ;  
32  }  
33 }  

③.draw()過程

做用:

由ViewRoot對象的performTraversals()方法調用draw()方法發起繪製該View樹,值得注意的是每次發起繪圖時,並不會從新繪製每一個View樹的視圖,而只會從新繪製那些「須要重繪」的視

圖,View類內部變量包含了一個標誌位DRAWN,當該視圖須要重繪時,就會爲該View添加該標誌位。

流程:

mView.draw()開始繪製,draw()方法實現的功能以下:

a、繪製該View的背景

b、爲顯示漸變框作一些準備操做(見5,大多數狀況下,不須要改漸變框)

c、調用onDraw()方法繪製視圖自己 (每一個View都須要重載該方法,ViewGroup不須要實現該方法)

d、調用dispatchDraw ()方法繪製子視圖(若是該View類型不爲ViewGroup,即不包含子視圖,不須要重載該方法)

dispatchDraw()方法內部會遍歷每一個子視圖,調用drawChild()去從新回調每一個子視圖的draw()方法(注意,這個 地方「須要重繪」的視圖纔會調用draw()方法)。

值得說明的是,ViewGroup類已經爲咱們重寫了dispatchDraw()的功能實現,應用程序通常不須要重寫該方法,但能夠重載父類函數實現具體的功能。

e、繪製滾動條

僞代碼:

 

 1 // draw()過程     ViewRoot.java  
 2 // 發起draw()的"發號者"在ViewRoot.java裏的performTraversals()方法, 該方法會繼續調用draw()方法開始繪圖  
 3 private void  draw(){  
 4    
 5     //...  
 6  View mView  ;  
 7     mView.draw(canvas) ;    
 8       
 9     //....  
10 }  
11   
12 //回調View視圖裏的onLayout過程 ,該方法只由ViewGroup類型實現  
13 private void draw(Canvas canvas){  
14  //該方法會作以下事情  
15  //1 、繪製該View的背景  
16  //二、爲繪製漸變框作一些準備操做  
17  //三、調用onDraw()方法繪製視圖自己  
18  //四、調用dispatchDraw()方法繪製每一個子視圖,dispatchDraw()已經在Android框架中實現了,在ViewGroup方法中。  
19       // 應用程序程序通常不須要重寫該方法,但能夠捕獲該方法的發生,作一些特別的事情。  
20  //五、繪製漸變框    
21 }  
22   
23 //ViewGroup.java中的dispatchDraw()方法,應用程序通常不須要重寫該方法  
24 @Override  
25 protected void dispatchDraw(Canvas canvas) {  
26  //   
27  //其實現方法相似以下:  
28  int childCount = getChildCount() ;  
29    
30  for(int i=0 ;i<childCount ;i++){  
31   View child = getChildAt(i) ;  
32   //調用drawChild完成  
33   drawChild(child,canvas) ;  
34  }       
35 }  
36 //ViewGroup.java中的dispatchDraw()方法,應用程序通常不須要重寫該方法  
37 protected void drawChild(View child,Canvas canvas) {  
38  // ....  
39  //簡單的回調View對象的draw()方法,遞歸就這麼產生了。  
40  child.draw(canvas) ;  
41    
42  //.........  
43 }  

 

④以上①②③過程,最終會直接或間接調用到三個函數,分別爲invalidate(),requsetLaytout()以及requestFocus() ,接着這三個函數最終會調用到ViewRoot中的schedulTraversale()方

法,該函數而後發起一個異步消息,消息處理中調用performTraverser()方法對整個View進行遍歷。

invalidate()方法:

說明:請求重繪View樹,即draw()過程,假如視圖發生大小沒有變化就不會調用layout()過程,而且只繪製那些「須要重繪的」視圖,即誰(View的話,只繪製該View ;ViewGroup,則繪製整

個ViewGroup)請求invalidate()方法,就繪製該視圖。

通常引發invalidate()操做的函數以下:

a、直接調用invalidate()方法,請求從新draw(),但只會繪製調用者自己。

b、setSelection()方法:請求從新draw(),但只會繪製調用者自己。

c、setVisibility()方法: 當View可視狀態在INVISIBLE轉換VISIBLE時,會間接調用invalidate()方法,繼而繪製該View。

d、setEnabled()方法: 請求從新draw(),但不會從新繪製任何視圖包括該調用者自己。

requestLayout()方法會致使調用measure()過程 和 layout()過程 。

說明:只是對View樹從新佈局layout過程包括measure()和layout()過程,不會調用draw()過程,但不會從新繪製任何視圖包括該調用者自己。

通常引發invalidate()操做的函數以下:

a、setVisibility()方法:

當View的可視狀態在INVISIBLE/ VISIBLE 轉換爲GONE狀態時,會間接調用requestLayout() 和invalidate方法。

同時,因爲整個個View樹大小發生了變化,會請求measure()過程以及draw()過程,一樣地,只繪製須要「從新繪製」的視圖。

requestFocus()方法:

說明:請求View樹的draw()過程,但只繪製「須要重繪」的視圖。

注:本博客第2條內容摘抄自:Android中View繪製流程以及invalidate()等相關方法分析

3.關於measure()方法

①該方法裏面傳入的參數是widthMeasureSpec和heightMeasureSpec,能夠經過這兩個參數獲取規定的寬和高以及模式。關於模式有三種:

a、UNSPECIFIED說明容器對組件自己的尺寸沒有任何限制,組件能夠根據本身的須要隨意規劃本身的尺寸。這種狀況下,容器提供的尺寸沒有任何意義了;

b、EXACTLY說明容器嚴格要求其組件的尺寸必須爲給定尺寸,不能本身決定尺寸大小;

c、AT_MOST說明容器提供的尺寸是一個最小值,也就是說,組件能夠隨意決定本身的尺寸,只要不大於容器指定的尺寸便可。能夠經過方法public static int makeMeasureSpec(int size,

int mode)進行定義模式。

②一些經常使用變量解釋

 

 1 //控件的width與height
 2 Log.d("My_TextView", "getWidth : " + getWidth());
 3 Log.d("My_TextView", "getHeight : " + getHeight());
 4 //畫布的width與height
 5 Log.d("My_TextView", "canvas.getWidth : " + canvas.getWidth());
 6 Log.d("My_TextView", "canvas.getHeight : " + canvas.getWidth());
 7 //控件指定width,height後,畫布會是個正方形,邊長為控件width,height中較大的值
 8         
 9 //控件離屏幕的上,下,左,右的距離
10 Log.d("My_TextView", "getTop : " + getTop());
11 Log.d("My_TextView", "getBottom : " + getBottom());
12 Log.d("My_TextView", "getLeft : " + getLeft());
13 Log.d("My_TextView", "getRight : " + getRight());
14         
15 //控件左上角的座標,getX = getLeft, getY = getTop
16 Log.d("My_TextView", "getX : " + getX());
17 Log.d("My_TextView", "getY : " + getY());
相關文章
相關標籤/搜索