當前位置:
首頁 > 知識 > Android學習筆記View的工作原理

Android學習筆記View的工作原理

自定義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 所示:

Android學習筆記View的工作原理

圖1

Android學習筆記View的工作原理

圖2

View的繪製流程:

Android學習筆記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));}

Android學習筆記View的工作原理

圖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方法就能完成測量,如下圖所示:

Android學習筆記View的工作原理

圖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來繪製內容的時候,需要顯示的關閉它。

喜歡這篇文章嗎?立刻分享出去讓更多人知道吧!

本站內容充實豐富,博大精深,小編精選每日熱門資訊,隨時更新,點擊「搶先收到最新資訊」瀏覽吧!


請您繼續閱讀更多來自 科技優家 的精彩文章:

Ionic進行PC端Web開發時通過腳本壓縮提高第一次載入效率
容器擴展屬性 IExtenderProvider 實現WinForm通用數據驗證組件
TCP:三次握手、四次握手、backlog及其他
Django初探——工程創建以及models資料庫相關配置
js 給文本框增加快捷鍵

TAG:科技優家 |

您可能感興趣

Spring Cloud斷路器Hystrix原理讀書筆記
微軟機器學習Machine Learning Studio學習筆記
學習筆記之TensorFlow
Ethics for AI and Robotics 人工智慧與機器人的倫理學-筆記
讀書筆記:Eating for Cognitive Power
【Code筆記本】Valid Parentheses
TensorFlow基礎筆記
《Relation Networks for Object Detection》論文筆記
Spring Cloud Feign使用筆記
【咖啡筆記】Viennese&Mocha
Machine Learning Yearning 要點筆記
插畫師 Noel Badges Pugh 的植物筆記
論文筆記:Attention is All You Need
論文筆記:Sequence Generative Adversarial Nets with Policy Gradient
The Economist 精讀筆記:Catching the bitcoin bug
GCC工作筆記 Google Patent
Ahmedabad,India攝影筆記
Network In Network 論文筆記
《The innovators》讀書筆記
Python學習筆記-Python的安裝