android 自定義view詳解


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

分類 方法 描述
創建 Constructors

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

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

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();

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

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

⑤執行invalidate類的方法將會設置view為無效,最終導致onDraw方法被重新調用。}

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

注:

①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  //1、該方法必須在onMeasure調用,否者報異常。  
 5  setMeasuredDimension(h , l) ;  
 6    
 7  //2、如果該View是ViewGroup類型,則對它的每個子View進行measure()過程  
 8  int childCount = getChildCount() ;  
 9    
10  for(int i=0 ;i<childCount ;i++){  
11   //2.1、獲得每個子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.1、獲得每個子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  //2、為繪制漸變框做一些准備操作  
17  //3、調用onDraw()方法繪制視圖本身  
18  //4、調用dispatchDraw()方法繪制每個子視圖,dispatchDraw()已經在Android框架中實現了,在ViewGroup方法中。  
19       // 應用程序程序一般不需要重寫該方法,但可以捕獲該方法的發生,做一些特別的事情。  
20  //5、繪制漸變框    
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());

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM