View的工作原理
- ViewRoot 和DecorView
ViewRoot對應(yīng)于ViewRootImpl類叹螟,它是連接DecorView和WindowManager的紐帶待锈,View的三大繪制流程都是通過ViewRoot來完成声登。
在ActivityThread中意蛀,當(dāng)Activity對象被創(chuàng)建完畢后志于,會將DecorView添加到Window中,同時會創(chuàng)建ViewRootImpl,并將ViewRootImpl對象和DecorView對象關(guān)聯(lián)装盯。
Activity中View的繪制流程是從ViewRoot的perfromTraversals()方法開始的坷虑,它經(jīng)過measure layout draw 最終將View繪制出來
- MeasureSpec 代表32位int值,高2位代表specMode,低30位代表specSize
specMode:測量模式
specSize:在某種測量模式下的規(guī)格大小
- SpecMode 類型
UNSPECIFIED: 父容器不對View限制验夯,要多大有多大猖吴,這種情況一般用于系統(tǒng)內(nèi)部,表示一種測量狀態(tài)挥转。
EXACTLY: 父容器已經(jīng)檢測出View所需要的精確大小,這個時候View的最終大小就是SpecSize所指定的值共屈,它對應(yīng)于LayoutParams中的Match_parent和具體的值绑谣。
AT_MOST: 父容器指定了一個可視大小,即SpecSize拗引,View的大小不能大于這個值,具體要看不同View的具體實現(xiàn)矾削,對應(yīng)于wrap_content
- ******父容器和LayoutParams一起決定View的MeasureSpec
系統(tǒng)會將LayoutParams在父容器的約束下轉(zhuǎn)換成對應(yīng)的MeasureSpec壤玫,然后再根據(jù)MeasurSpec來確定View測量后的寬高。
-
父容器測量子View P181
- DecorView對MeasureSpec的轉(zhuǎn)換測量有點差別哼凯,其MeasureSpec值由窗口尺寸和其自身的LayoutParams來決定
- View 的mesure方法會調(diào)用onMeasure方法欲间,所有只需要實現(xiàn)View的onMeasure就可以了。
View的measure方法又會被所在的ViewGroup的measureChild方法調(diào)用断部。measureChild方法會遍歷child猎贴,執(zhí)行child的measure。
- 在onMeasure中又調(diào)用了setMeasureDimension()方法設(shè)置View寬高的測量值蝴光。
- 在measure完以后她渴,通過getMeasuredWidth/Height()可獲得測量后的寬/高
- View的Measure過程和Activity的生命周期過程不同步,
-
如何在Activity中獲取View的寬/高蔑祟?
-
Activity/View #onWindowFouceChanged()方法趁耗,表示View已經(jīng)初始化完畢,寬高已經(jīng)準(zhǔn)備好
但該方法會在Acitivty焦點獲取或失去時都會調(diào)用疆虚,也就是onResume和onPause都會調(diào)用苛败。
-
view.post(runnable)
view初始化完畢會執(zhí)行runnable,在run()中view.getMesuredWidth()/height
-
ViewTreeObserver
當(dāng)View樹狀態(tài)發(fā)生改變满葛,或者View樹內(nèi)部的View發(fā)生改變,onGlobalLayout方法會被調(diào)用著拭,在這里獲取View的寬高纱扭。
@Override
protected void onStart() {
super.onStart();
ViewTreeObserver viewTreeObserver = vMarqueeView.getViewTreeObserver();
viewTreeObserver.addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() {
@Override
public void onGlobalLayout() {
vMarqueeView.getViewTreeObserver().removeOnGlobalLayoutListener(this);
int width = vMarqueeView.getMeasuredWidth();
int height = vMarqueeView.getMeasuredHeight();
}
});
}
- view.measure(int widthMesureSpec,int heightMesureSpec) p192
MATCH_PARENT:不可用,因為不知道父容器的尺寸
WRAP_CONTENT:
int widthMesureSpec = View.MeasureSpec.makeMeasureSpec((1 << 30) - 1, View.MeasureSpec.AT_MOST);
int heightMesureSpec = View.MeasureSpec.makeMeasureSpec((1 << 30) - 1, View.MeasureSpec.AT_MOST);
vMarqueeView.measure(widthMesureSpec, heightMesureSpec);
(1<<30)-1 代表(2^30-1) 即使用最大值去構(gòu)造MeasureSpec(View的尺寸使用30位二進(jìn)制表示)
- layout過程和mearsure過程一樣儡遮,調(diào)用父元素的layout方法乳蛾,layout方法調(diào)用onLayout方法,onlayout方法又會遍歷子元素鄙币,調(diào)用子元素的layout,子元素layout又會調(diào)用onLayout肃叶,如此循環(huán)
- View的測量寬高和最終寬高的區(qū)別:
getWidth()和getMesuredWidth()
本質(zhì)上是相等的,只是十嘿,getWidht()值mWitdh形成于onLayout之后因惭,而getMesuredWidth()的值形成于mesured之后
(p196)
注意:如過在onLayout中改變了l,r,t,b中某個值,則會影響到getWidth/getHeight绩衷,而不會影響getMesuredWidth/Height值
- draw過程有點差別蹦魔,但大致相同
調(diào)用draw方法,draw方法會執(zhí)行如下幾步
1.繪制背景(background.draw(cavans))
2.繪制自己(onDraw)
3.繪制children(dispatchDraw(),dispatchDraw又會循環(huán)child執(zhí)行其draw()方法)
4.繪制裝飾(onDrawScrollBars)
- View的setWillNotDraw(boolean willNotDraw) 優(yōu)化
如果View不做任何繪制內(nèi)容時咳燕,那么設(shè)置這個標(biāo)志位位true后勿决,系統(tǒng)會進(jìn)行相應(yīng)的優(yōu)化,默認(rèn)情況下招盲,View沒有啟用這個標(biāo)志位低缩,但ViewGroup會啟用
當(dāng)自定義View繼承ViewGroup且本身不具備繪制功能時,可以開啟這個標(biāo)志位曹货,從而便于系統(tǒng)的后續(xù)優(yōu)化咆繁,當(dāng)明確知道繼承ViewGroup的View需要通過onDraw繪制相應(yīng)內(nèi)容時,需要顯示的關(guān)閉這個標(biāo)志位
-
繪制自定義View須知: p201
- 讓View支持wrap_content
直接繼承View或ViewGroup的控件顶籽,如果不在onMeasure中對wrap_content做特殊處理顶别,那么在外界布局中使用wrap_content就無法達(dá)到預(yù)期效果豌骏。
- 如果有必要,讓View支持padding
直接繼承View的控件,如果不在draw方法中處理padding,那么padding屬性無法起作用缤剧。另外祭衩,直接繼承ViewGroup的控件要在其onMeasure和onLayout中要考慮padding和子元素margin對其造成的影響所坯,不然將導(dǎo)致padding和子元素的margin失效
-
盡量不要在View中使用Handler
View內(nèi)部本身提供了post方法
-
如果View中有線程或者動畫晒骇,需及時停止,參考View#onDetachedFromWindow
當(dāng)Activity退出或者當(dāng)前View被remove后镰吆,View的onDetachedFromWindow將會被調(diào)用帘撰,對應(yīng)的方法是onAttachedToWindow(),該方法在onDraw之前任何時候調(diào)用万皿,不確定在onMesure之前還是之后調(diào)用摧找,當(dāng)包含該View的Activity被啟動時調(diào)用核行。
- View帶有滑動嵌套時,需要處理好滑動嵌套
- 再次強調(diào)蹬耘,自定義組件繼承View或ViewGroup的芝雪,padding值需要自己處理,在onDraw中處理padding综苔,而margin值由父類處理惩系,對于wrap_content,默認(rèn)是按照match_parent處理如筛,所以也需要自己處理wrap_content情況堡牡,設(shè)置個最小值。
-
自定義屬性 p206