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


※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的安裝