Android學習筆記View的工作原理

來源:互聯網
上載者:User

標籤:code   最大   需要   隊列   應用   否則   ota   length   relative   

 

  自訂View,也可以稱為自訂控制項,通過自訂View可以使得控制項實現各種定製的效果。

  實現自訂View,需要掌握View的底層工作原理,比如View的測量過程、布局流程以及繪製流程,除此之外,還需要掌握View常見的回調方法。而對於那些具有滑動效果的自訂View,我們還需要處理View的滑動,如果遇到滑動衝突則需要處理相應的滑動衝突。

  下面是View的常見回調方法:

  • 構造方法
  • onAttach
  • onVisibilityChanged
  • onDetach
  • onFinishInflate
  • onSizeChanged
  • onMeasure
  • onLayout
  • onTouchEvent

  自訂控制項的實現手段可簡要分為四種類:

  • 繼承View重寫onDraw方法,這種方法主要是用於實現一些不規則的效果,採用這種方式需要自己支援wrap_content,並且處理padding。
  • 繼承ViewGroup派生特殊的Layout,這種方法主要是用於實現自訂的布局,當某種效果看起來像是幾個View組合在一起時,可以採用這種方法來實現。採用這種方法是需要合理處理ViewGroup的測量和布局這兩個過程,並同時處理子項目的測量和版面配置階段。
  • 繼承特定的View,用於拓展已有的View的功能。
  • 繼承特定的ViewGroup(如LinearLayout、RelativeLayout),其適用情形和方法2 類似。

  在自訂View中需要的注意點:

  應當遵守Android標準控制項的規範(如命名、可配置、事件處理、狀態儲存及恢複等)

  • 命名表意明確
  • 控制項屬性可以在XML中配置
  • 讓View支援wrap_content和padding(下文會具體講到)
  • 在View中盡量不使用Handler,因為View中內建post系列的方法。
  • 自訂View的記憶體流失問題(如果有線程或者動畫,需要及時停止)
  • View的滑動衝突(在View帶有滑動嵌套的情形,需要處理好滑動衝突)
  • 具有一定的互動性,如按下、點擊等
  • 自訂View內部實現狀態儲存和恢複的機制
  • 相容性

  下面主要從View的基本知識、View的繪製過程講一下View的工作原理。

1.從Activity中的View結構講起

  每個Activity都含有一個Window對象,而這個Window對象一般都是PhoneWindow。PhoneWindow將以DecorView設定為整個應用視窗的根View。DecorView作為視窗介面的頂層視圖,封裝了一些視窗操作的通用方法。可以這麼說,DecorView將要顯示的具體內容呈現在了PhoneWindow中,這裡面的所有的View的監聽事件都是通過WindowManagerService來接收的,並通過Activity對象來回調相應的onClickListenr。

  在顯示上,將螢幕分成兩部分,一個是TitleView,另一個是ContentView,這個ContentView想必大家都很熟悉,它是一個ID為content的FrameLayout,activity_main就是設定在這樣一個FrameLayout中。

如1 和圖2 所示:

 

                 圖1

 

                 圖2

 

View的繪製流程:

 

              圖3

  如所示,performTraversals會依次調用performMeasure、performLayout、performDraw三個方法,這三個方法分別完成頂級View的measure、layout、draw這三大流程。

  其中在performMeasure中又會調用measure,接著在measure中調用onMeasure方法,在onMeasure中會對所有的子項目進行measure過程,這個時候measure流程就從父容器傳遞到子項目中了,即完成依次measure操作,接著子項目進行同樣的measure過程,如此方法直至完成整個View樹的遍曆。同理,performLayout和performDraw的傳遞流程和performmeasure是類似的(performDraw的傳遞過程是在draw方法中的dispatchDraw完成的,並無實質區別)。

       measure過程決定了View的寬高,measure完成後,可以通過getMeasuredWidth和getMeasuredHeight方法來擷取到View測量後的寬高,在幾乎所有的情況下它都等同於View的最終高度,但特殊情況除外。Layout過程確定了View的四個頂點的座標和實際的View的寬高,完成以後,可以通過getTop、getBottom、getLeft、getRight來得到四個頂點的位置,並可以通過getWidth和getHeight來得到View的最終寬高。Draw過程決定了View的顯示,只有draw方法完成後,View的內容才會顯示在螢幕上。

2.如何完成測量過程呢?

  Android系統提供了一個MeasureSpec類,通過它可以協助我們測量View。MeasureSpec是一個32位的int值,其中高2位為測量的模式,低30位為測量的大小。

  EXACTLY:精確值模式, 當我們將空間的layout_width或者layout_height屬性指定為具體值時,或者指定為match_parent屬性時,系統使用的是EXACTLY。

  AT_MOST:最大值模式,當空間的layout_width屬性或者layout_height屬性為wrap_content時,控制項大小一般隨著空間的子控制項或者內容的變化而變化,此時,控制項的尺寸只要不超過父控制項允許的最大尺寸即可。

  UNSPECIFIED:不指定其測量大小,通常情況下在繪製自訂View時才會使用它。

在view的測量過程中,系統會將LayoutParams在父容器的約束下轉換為對應的MeasureSpec,然後根據這個MeasureSpec來確定View測量後的寬高。MeasureSpec由父容器和LayoutParams共同決定。

  對於DecorView,其MeasureSpec由視窗的尺寸和其自身的LayoutParams決定

  對於普通View,其MeasureSpec由父容器的MeasureSpec和自身的LayoutParams決定

       當View的LayoutParams採用精確值時,不管父容器的MeasureSpec是什麼,View的MeasureSpec模式都是EXACTLY,並且大小遵循LayoutParms的大小。

       當View的寬高是match_parent模式,view的MeasureSpec模式遵循父容器的MeasureSpec模式。

       當View的寬高是wrap_content,不管父容器的模式是EXACTLY還是AT_MOST,View的模式都是AT_MOST並且大小不超過父容器的剩餘空間。

   下面分別簡要講一下View的measure過程和ViewGroup的measure過程。

1)View的measure過程:

  參考源碼:

protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {              setMeasuredDimension(getDefaultSize(getSuggestedMinimumWidth(), widthMeasureSpec),         getDefaultSize(getSuggestedMinimumHeight(), heightMeasureSpec));}     

                                 圖4

  由上述源碼可知,在調用onMeasure方法時會調用setMeasuredDimension方法,在這個方法中會傳入其寬高。由此可知,在自訂View中,需要重新定義view的寬和高。

View類預設的onMeasure方法只支援EXACTLY模式,如果在自訂控制項的時候不重寫onMeasure方法,就只能使用EXACTLY模式。控制項可以相應你指定的具體寬高值或者match_parent屬性,如果要讓自訂View支援wrap_content屬性,則必須要重寫onMeasure方法,否則在布局中使用wrap_content就相當於使用match_parent)

 

2)ViewGroup的measure過程:

       對於ViewGroup而言,除了完成自己的measure過程,還要遍曆去調用所有子項目的measure方法,各個子項目再遞迴執行這個過程。

       ViewGroup是一個抽象類別,它沒有重寫View的onMeasure方法,但它提供了一個measureChildren的方法,在measureChildren方法中它會遍曆ViewGroup中的子項目,並調用measureChild方法,對子項目進行measure。measureChild的思想就是取出子項目的LayoutParams,然後通過getChildMeasureSpec來建立子項目的measureSpec,最後將子項目的measureSpec傳遞給measure方法就能完成測量,如所示:

 

                                                      圖5

 

  正如前面提到的ViewGroup是一個抽象類別,它沒有重寫onMeasure方法,其測量過程中的onMeasure需要其子類去具體實現。如LinearLayout、RelativeLayout。不同的ViewGroup子類的布局特性不同,這也導致其測量細節不同。

  下面簡要瞭解一下LinearLyaout和RelativeLayout的onMeasure實現

1)LinearLayout的Measure實現:

LinearLayout的布局方向有兩種,所以LinearLayout會根據mOrientation來分別調用measureVertical或者是measureHorizontal。以水平布局為例,

遍曆所有的view,跳過為null或者屬性為View.GONE的,加上分割線寬度mDividerWidth和左右margin,計算所有View的childWidth之和mTotalLength,統計所有View的weight和totalWeight,並且對子view進行測量。

 

2)RelativeLayout的Measure實現:

  當第一次執行onMeasure或者requestLayout後,需要調用sortChildren方法,根據添加順序對所有的子view進行排序,橫著一次,豎著一次,然後對兩個序列進行檢查,通過依賴圖靜態類中的getSortedViews方法根據依賴關係進行排序。

  之後在onMeasure中,對子view進行遍曆,即對兩個序列進行分別遍曆。

  首先是橫向遍曆,調用mSortedHorizontalChildren,擷取RelativeLayout.layoutParams,並依次調用方法,計算控制項的橫向位置及mLeft和mRight,然後橫向測量子View,接下去根據前面的結果很想擺放子View,如果此時父RelativeLayout的寬度是WRAP_CONTENT,會在此時對寬高進行修正。

  橫向完畢後進行垂直排列的View序列進行上述操在,步驟大致相同,在此處會對子view進行measure時就會正確的測量,之後的操作就是對父RelativeLayout的寬高等屬性進行再次修正。

  從上面的分析中,一個最明顯的不同就是RelativeLayout在進行measure過程中需要進行兩次遍曆,而LinearLayout則只需要一次遍曆過程。

      此外,需要注意的是,在某些極端情況下,系統可能需要調用多次measure才能確定最終的測量寬高,在這種情況下,在onMeasure方法中拿到的測量高很可能是不準確的。所以最好在onLayout方法中擷取View的高寬。

 

3.如何擷取View的寬和高

(1)調用onWindowFocusChanged方法(焦點變化),這個時候View已經初始化完畢,這個時候去擷取View的寬高是沒有問題的。然而當頻繁進行onResume和onPause,onWindowFocusChanged方法也會被頻繁調用。

(2)調用view.post(runnable)

         通過post將一個Runnable投遞到訊息佇列的尾部,然後等待Looper調用此Runnable,view也已經初始化好了。

(3)ViewTreeObserver

         使用ViewTreeObserver的眾多回調可以使用這個功能,如OnGlobalLayoutListener,當View樹的狀態發生改變或者View樹的View的可見度發生改變時,OnGlobalLayoutListener會被回調,需要注意的是,伴隨著View樹狀態的改變,onGlobalLayoutListener會被回調多次。

(4)View.measure(int widthMeasureSpec,int heightMeasureSpec)

  • match_parent 不能
  •  具體值和wrap_content可以。

4.Layout過程

         Layout過程用於ViewGroup確定子項目的位置,當ViewGroup的位置被確定後,它在onLayout中會遍曆所有的子項目,並調用其layout方法,在layout方法中onLayout方法又會被調用。

         layout方法首先通過setFrame方法倆設定view的四個頂點的位置,接著調用onLayout方法,確定子項目的位置。

         由於onLayout的實現同樣與布局有關,因此View和ViewGroup均沒有實現onLayout方法。

5.draw過程

  • 將View繪製到螢幕上,大概的幾個步驟
    1.繪製背景background.draw(canvas)
    2.繪製自己(onDraw)
    3.繪製children(dispatchDraw)
    4.繪製裝飾(onDrawScrollBars)
  • View的繪製過程是通過dispatchDraw來實現的,它會遍曆所有子項目的draw方法。
  • 如果一個View不需要繪製任何內容,那麼設定setWillNotDraw為true後,系統會進行相應的最佳化;ViewGroup預設為true,如果我們的自訂ViewGroup需要通過onDraw來繪製內容的時候,需要顯示的關閉它。

 

        

 

Android學習筆記View的工作原理

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.