Android中View绘制流程
2019獨(dú)角獸企業(yè)重金招聘Python工程師標(biāo)準(zhǔn)>>>
整個(gè)View樹的繪圖流程是在ViewRoot.java類的performTraversals()函數(shù)展開的,該函數(shù)做的執(zhí)行過程可簡單概況為
?根據(jù)之前設(shè)置的狀態(tài),判斷是否需要重新計(jì)算視圖大小(measure)、是否重新需要安置視圖的位置(layout)、以及是否需要重繪
?(draw),其框架過程如下:
? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ???步驟其實(shí)為host.layout()?
???????????
?
?
???? ?接下來溫習(xí)一下整個(gè)View樹的結(jié)構(gòu),對(duì)每個(gè)具體View對(duì)象的操作,其實(shí)就是個(gè)遞歸的實(shí)現(xiàn)。
?
???????????????????
?
流程一:????? mesarue()過程
? ? ? ??主要作用:為整個(gè)View樹計(jì)算實(shí)際的大小,即設(shè)置實(shí)際的高(對(duì)應(yīng)屬性:mMeasuredHeight)和寬(對(duì)應(yīng)屬性:
??mMeasureWidth),每個(gè)View的控件的實(shí)際寬高都是由父視圖和本身視圖決定的。
?
???? 具體的調(diào)用鏈如下:
????????? ViewRoot根對(duì)象地屬性mView(其類型一般為ViewGroup類型)調(diào)用measure()方法去計(jì)算View樹的大小,回調(diào)
View/ViewGroup對(duì)象的onMeasure()方法,該方法實(shí)現(xiàn)的功能如下: ? ?
? ? ? ? ?1、設(shè)置本View視圖的最終大小,該功能的實(shí)現(xiàn)通過調(diào)用setMeasuredDimension()方法去設(shè)置實(shí)際的高(對(duì)應(yīng)屬性:??
??????????????? mMeasuredHeight)和寬(對(duì)應(yīng)屬性:mMeasureWidth)?? ;
? ? ? ? ?2 、如果該View對(duì)象是個(gè)ViewGroup類型,需要重寫該onMeasure()方法,對(duì)其子視圖進(jìn)行遍歷的measure()過程。
??????????????
? ? ? ? ? ? ? ?2.1? 對(duì)每個(gè)子視圖的measure()過程,是通過調(diào)用父類ViewGroup.java類里的measureChildWithMargins()方法去
? ? ? ? ? 實(shí)現(xiàn),該方法內(nèi)部只是簡單地調(diào)用了View對(duì)象的measure()方法。(由于measureChildWithMargins()方法只是一個(gè)過渡
? ? ? ? ? 層更簡單的做法是直接調(diào)用View對(duì)象的measure()方法)。
??????????????
??? ?整個(gè)measure調(diào)用流程就是個(gè)樹形的遞歸過程
?
?????measure函數(shù)原型為 View.java 該函數(shù)不能被重載
? ? ??
???public?final?void?measure(int?widthMeasureSpec,?int?heightMeasureSpec)?{//....//回調(diào)onMeasure()方法??onMeasure(widthMeasureSpec,?heightMeasureSpec);//more}??調(diào)用流程,用偽代碼描述如下:
//回調(diào)View視圖里的onMeasure過程private?void?onMeasure(int?height?,?int?width){//設(shè)置該view的實(shí)際寬(mMeasuredWidth)高(mMeasuredHeight)//1、該方法必須在onMeasure調(diào)用,否者報(bào)異常。setMeasuredDimension(h?,?l)?;//2、如果該View是ViewGroup類型,則對(duì)它的每個(gè)子View進(jìn)行measure()過程int?childCount?=?getChildCount()?;for(int?i=0?;i<childCount?;i++){//2.1、獲得每個(gè)子View對(duì)象引用View?child?=?getChildAt(i)?;//整個(gè)measure()過程就是個(gè)遞歸過程//該方法只是一個(gè)過濾器,最后會(huì)調(diào)用measure()過程?;或者?measureChild(child?,?h,?i)方法都measureChildWithMargins(child?,?h,?i)?;?//其實(shí),對(duì)于我們自己寫的應(yīng)用來說,最好的辦法是去掉框架里的該方法,直接調(diào)用view.measure(),如下://child.measure(h,?l)}}//該方法具體實(shí)現(xiàn)在ViewGroup.java里?。protected??void?measureChildWithMargins(View?v,?int?height?,?int?width){v.measure(h,l)???}流程二、 layout布局過程:
?
???? 主要作用 :為將整個(gè)根據(jù)子視圖的大小以及布局參數(shù)將View樹放到合適的位置上。
?
?????具體的調(diào)用鏈如下:
???????host.layout()開始View樹的布局,繼而回調(diào)給View/ViewGroup類中的layout()方法。具體流程如下
??
????????1 、layout方法會(huì)設(shè)置該View視圖位于父視圖的坐標(biāo)軸,即mLeft,mTop,mLeft,mBottom(調(diào)用setFrame()函數(shù)去實(shí)現(xiàn))
? 接下來回調(diào)onLayout()方法(如果該View是ViewGroup對(duì)象,需要實(shí)現(xiàn)該方法,對(duì)每個(gè)子視圖進(jìn)行布局) ;
???????
? ? ? ?2、如果該View是個(gè)ViewGroup類型,需要遍歷每個(gè)子視圖chiildView,調(diào)用該子視圖的layout()方法去設(shè)置它的坐標(biāo)值。
?
? ? ? ? ??layout函數(shù)原型為 ,位于View.java
???/*?final?標(biāo)識(shí)符?,?不能被重載?,?參數(shù)為每個(gè)視圖位于父視圖的坐標(biāo)軸*?@param?l?Left?position,?relative?to?parent*?@param?t?Top?position,?relative?to?parent*?@param?r?Right?position,?relative?to?parent*?@param?b?Bottom?position,?relative?to?parent*/public?final?void?layout(int?l,?int?t,?int?r,?int?b)?{boolean?changed?=?setFrame(l,?t,?r,?b);?//設(shè)置每個(gè)視圖位于父視圖的坐標(biāo)軸if?(changed?||?(mPrivateFlags?&?LAYOUT_REQUIRED)?==?LAYOUT_REQUIRED)?{if?(ViewDebug.TRACE_HIERARCHY)?{ViewDebug.trace(this,?ViewDebug.HierarchyTraceType.ON_LAYOUT);}onLayout(changed,?l,?t,?r,?b);//回調(diào)onLayout函數(shù)?,設(shè)置每個(gè)子視圖的布局mPrivateFlags?&=?~LAYOUT_REQUIRED;}mPrivateFlags?&=?~FORCE_LAYOUT;}同樣地, 將上面layout調(diào)用流程,用偽代碼描述如下:
???//?layout()過程??ViewRoot.java//?發(fā)起layout()的"發(fā)號(hào)者"在ViewRoot.java里的performTraversals()方法,?mView.layout()private?void??performTraversals(){//...View?mView??;mView.layout(left,top,right,bottom)?;//....}//回調(diào)View視圖里的onLayout過程?,該方法只由ViewGroup類型實(shí)現(xiàn)private?void?onLayout(int?left?,?int?top?,?right?,?bottom){//如果該View不是ViewGroup類型//調(diào)用setFrame()方法設(shè)置該控件的在父視圖上的坐標(biāo)軸setFrame(l?,t?,?r?,b)?;//--------------------------//如果該View是ViewGroup類型,則對(duì)它的每個(gè)子View進(jìn)行l(wèi)ayout()過程int?childCount?=?getChildCount()?;for(int?i=0?;i<childCount?;i++){//2.1、獲得每個(gè)子View對(duì)象引用View?child?=?getChildAt(i)?;//整個(gè)layout()過程就是個(gè)遞歸過程child.layout(l,?t,?r,?b)?;}}??流程三、 draw()繪圖過程
? ? ?由ViewRoot對(duì)象的performTraversals()方法調(diào)用draw()方法發(fā)起繪制該View樹,值得注意的是每次發(fā)起繪圖時(shí),并不
? 會(huì)重新繪制每個(gè)View樹的視圖,而只會(huì)重新繪制那些“需要重繪”的視圖,View類內(nèi)部變量包含了一個(gè)標(biāo)志位DRAWN,當(dāng)該
視圖需要重繪時(shí),就會(huì)為該View添加該標(biāo)志位。
?
?? 調(diào)用流程 :
?????mView.draw()開始繪制,draw()方法實(shí)現(xiàn)的功能如下:
??????????1 、繪制該View的背景
??????????2 、為顯示漸變框做一些準(zhǔn)備操作(見5,大多數(shù)情況下,不需要改漸變框)??????????
??????????3、調(diào)用onDraw()方法繪制視圖本身?? (每個(gè)View都需要重載該方法,ViewGroup不需要實(shí)現(xiàn)該方法)
??????????4、調(diào)用dispatchDraw ()方法繪制子視圖(如果該View類型不為ViewGroup,即不包含子視圖,不需要重載該方法)
值得說明的是,ViewGroup類已經(jīng)為我們重寫了dispatchDraw ()的功能實(shí)現(xiàn),應(yīng)用程序一般不需要重寫該方法,但可以重載父類
? 函數(shù)實(shí)現(xiàn)具體的功能。
?
????????????4.1 dispatchDraw()方法內(nèi)部會(huì)遍歷每個(gè)子視圖,調(diào)用drawChild()去重新回調(diào)每個(gè)子視圖的draw()方法(注意,這個(gè)?
地方“需要重繪”的視圖才會(huì)調(diào)用draw()方法)。值得說明的是,ViewGroup類已經(jīng)為我們重寫了dispatchDraw()的功能
實(shí)現(xiàn),應(yīng)用程序一般不需要重寫該方法,但可以重載父類函數(shù)實(shí)現(xiàn)具體的功能。
????
?????5、繪制滾動(dòng)條
?
? 于是,整個(gè)調(diào)用鏈就這樣遞歸下去了。
????
???? 同樣地,使用偽代碼描述如下:
???//?draw()過程?????ViewRoot.java//?發(fā)起draw()的"發(fā)號(hào)者"在ViewRoot.java里的performTraversals()方法,?該方法會(huì)繼續(xù)調(diào)用draw()方法開始繪圖private?void??draw(){//...View?mView??;mView.draw(canvas)?;??//....}//回調(diào)View視圖里的onLayout過程?,該方法只由ViewGroup類型實(shí)現(xiàn)private?void?draw(Canvas?canvas){//該方法會(huì)做如下事情//1?、繪制該View的背景//2、為繪制漸變框做一些準(zhǔn)備操作//3、調(diào)用onDraw()方法繪制視圖本身//4、調(diào)用dispatchDraw()方法繪制每個(gè)子視圖,dispatchDraw()已經(jīng)在Android框架中實(shí)現(xiàn)了,在ViewGroup方法中。//?應(yīng)用程序程序一般不需要重寫該方法,但可以捕獲該方法的發(fā)生,做一些特別的事情。//5、繪制漸變框 }//ViewGroup.java中的dispatchDraw()方法,應(yīng)用程序一般不需要重寫該方法@Overrideprotected?void?dispatchDraw(Canvas?canvas)?{//?//其實(shí)現(xiàn)方法類似如下:int?childCount?=?getChildCount()?;for(int?i=0?;i<childCount?;i++){View?child?=?getChildAt(i)?;//調(diào)用drawChild完成drawChild(child,canvas)?;} ???}//ViewGroup.java中的dispatchDraw()方法,應(yīng)用程序一般不需要重寫該方法protected?void?drawChild(View?child,Canvas?canvas)?{//?....//簡單的回調(diào)View對(duì)象的draw()方法,遞歸就這么產(chǎn)生了。child.draw(canvas)?;//.........}強(qiáng)調(diào)一點(diǎn)的就是,在這三個(gè)流程中,Google已經(jīng)幫我們把draw()過程框架已經(jīng)寫好了,自定義的ViewGroup只需要實(shí)現(xiàn)
?measure()過程和layout()過程即可 。
? ? ?這三種情況,最終會(huì)直接或間接調(diào)用到三個(gè)函數(shù),分別為invalidate(),requsetLaytout()以及requestFocus() ,接著
這三個(gè)函數(shù)最終會(huì)調(diào)用到ViewRoot中的schedulTraversale()方法,該函數(shù)然后發(fā)起一個(gè)異步消息,消息處理中調(diào)用
performTraverser()方法對(duì)整個(gè)View進(jìn)行遍歷。
? invalidate()方法 :
?
?? 說明:請(qǐng)求重繪View樹,即draw()過程,假如視圖發(fā)生大小沒有變化就不會(huì)調(diào)用layout()過程,并且只繪制那些“需要重繪的”
視圖,即誰(View的話,只繪制該View ;ViewGroup,則繪制整個(gè)ViewGroup)請(qǐng)求invalidate()方法,就繪制該視圖。
?
???? 一般引起invalidate()操作的函數(shù)如下:
? ? ? ? ? ? 1、直接調(diào)用invalidate()方法,請(qǐng)求重新draw(),但只會(huì)繪制調(diào)用者本身。
? ? ? ? ? ? 2、setSelection()方法 :請(qǐng)求重新draw(),但只會(huì)繪制調(diào)用者本身。
? ? ? ? ? ? 3、setVisibility()方法 : 當(dāng)View可視狀態(tài)在INVISIBLE轉(zhuǎn)換VISIBLE時(shí),會(huì)間接調(diào)用invalidate()方法,
? ? ? ? ? ? ? ? ? ? ?繼而繪制該View。
? ? ? ? ? ? 4 、setEnabled()方法 : 請(qǐng)求重新draw(),但不會(huì)重新繪制任何視圖包括該調(diào)用者本身。
?
??? requestLayout()方法?:會(huì)導(dǎo)致調(diào)用measure()過程 和 layout()過程 。
?
???????????說明:只是對(duì)View樹重新布局layout過程包括measure()和layout()過程,不會(huì)調(diào)用draw()過程,但不會(huì)重新繪制
任何視圖包括該調(diào)用者本身。
?
??? 一般引起invalidate()操作的函數(shù)如下:
? ? ? ? ?1、setVisibility()方法:
? ? ? ? ? ? ?當(dāng)View的可視狀態(tài)在INVISIBLE/ VISIBLE 轉(zhuǎn)換為GONE狀態(tài)時(shí),會(huì)間接調(diào)用requestLayout() 和invalidate方法。
? ? 同時(shí),由于整個(gè)個(gè)View樹大小發(fā)生了變化,會(huì)請(qǐng)求measure()過程以及draw()過程,同樣地,只繪制需要“重新繪制”的視圖。
?
? ??requestFocus()函數(shù)說明:
?
??????????說明:請(qǐng)求View樹的draw()過程,但只繪制“需要重繪”的視圖。
下面寫個(gè)簡單的小Demo吧,主要目的是給大家演示繪圖的過程以及每個(gè)流程里該做的一些功能。截圖如下:
? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ??
1、??? MyViewGroup.java? 自定義ViewGroup類型
/***?@author?http://http://blog.csdn.net/qinjuning*///自定義ViewGroup?對(duì)象public?class?MyViewGroup??extends?ViewGroup{private?static?String?TAG?=?"MyViewGroup"?;private?Context?mContext?;public?MyViewGroup(Context?context)?{super(context);mContext?=?context?;init()?;}//xml定義的屬性,需要該構(gòu)造函數(shù)public?MyViewGroup(Context?context?,?AttributeSet?attrs){super(context,attrs)?;mContext?=?context?;init()?;}//為MyViewGroup添加三個(gè)子Viewprivate?void?init(){//調(diào)用ViewGroup父類addView()方法添加子View//child?對(duì)象一?:?ButtonButton?btn=?new?Button(mContext)?;btn.setText("I?am?Button")?;this.addView(btn)?;//child?對(duì)象二?:?ImageView?ImageView?img?=?new?ImageView(mContext)?;img.setBackgroundResource(R.drawable.icon)?;this.addView(img)?;//child?對(duì)象三?:?TextViewTextView?txt?=?new?TextView(mContext)?;txt.setText("Only?Text")?;this.addView(txt)?;?//child?對(duì)象四?:?自定義ViewMyView?myView?=?new?MyView(mContext)?;this.addView(myView)?;?}@Override//對(duì)每個(gè)子View進(jìn)行measure():設(shè)置每子View的大小,即實(shí)際寬和高protected?void?onMeasure(int?widthMeasureSpec,?int?heightMeasureSpec){//通過init()方法,我們?yōu)樵揤iewGroup對(duì)象添加了三個(gè)視圖?,?Button、?ImageView、TextViewint?childCount?=?getChildCount()?;Log.i(TAG,?"the?size?of?this?ViewGroup?is?---->?"?+?childCount)?;Log.i(TAG,?"****?onMeasure?start?*****")?;//獲取該ViewGroup的實(shí)際長和寬??涉及到MeasureSpec類的使用int?specSize_Widht?=?MeasureSpec.getSize(widthMeasureSpec)?;int?specSize_Heigth?=?MeasureSpec.getSize(heightMeasureSpec)?;Log.i(TAG,?"****?specSize_Widht?"?+?specSize_Widht+?"?*?specSize_Heigth???*****"?+?specSize_Heigth)?;//設(shè)置本ViewGroup的寬高setMeasuredDimension(specSize_Widht?,?specSize_Heigth)?;for(int?i=0?;i<childCount?;?i++){View?child?=?getChildAt(i)?;???//獲得每個(gè)對(duì)象的引用child.measure(50,?50)?;???//簡單的設(shè)置每個(gè)子View對(duì)象的寬高為?50px?,?50px??//或者可以調(diào)用ViewGroup父類方法measureChild()或者measureChildWithMargins()方法//this.measureChild(child,?widthMeasureSpec,?heightMeasureSpec)?;}}@Override//對(duì)每個(gè)子View視圖進(jìn)行布局protected?void?onLayout(boolean?changed,?int?l,?int?t,?int?r,?int?b)?{//?TODO?Auto-generated?method?stub//通過init()方法,我們?yōu)樵揤iewGroup對(duì)象添加了三個(gè)視圖?,?Button、?ImageView、TextViewint?childCount?=?getChildCount()?;int?startLeft?=?0?;//設(shè)置每個(gè)子View的起始橫坐標(biāo)?int?startTop?=?10?;?//每個(gè)子View距離父視圖的位置?,?簡單設(shè)置為10px吧?。?可以理解為?android:margin=10px?;Log.i(TAG,?"****?onLayout?start?****")?;for(int?i=0?;i<childCount?;?i++){View?child?=?getChildAt(i)?;???//獲得每個(gè)對(duì)象的引用child.layout(startLeft,?startTop,?startLeft+child.getMeasuredWidth(),?startTop+child.getMeasuredHeight())?;startLeft?=startLeft+child.getMeasuredWidth()?+?10;??//校準(zhǔn)startLeft值,View之間的間距設(shè)為10px?;Log.i(TAG,?"****?onLayout?startLeft?****"?+startLeft)?;}??? ??? }//繪圖過程Android已經(jīng)為我們封裝好了?,這兒只為了觀察方法調(diào)用程protected?void?dispatchDraw(Canvas?canvas){Log.i(TAG,?"****?dispatchDraw?start?****")?;super.dispatchDraw(canvas)?;}protected?boolean?drawChild(Canvas?canvas?,?View?child,?long?drawingTime){Log.i(TAG,?"****?drawChild?start?****")?;return?super.drawChild(canvas,?child,?drawingTime)?;}}?2、MyView.java?自定義View類型,重寫onDraw()方法 ,
//自定義View對(duì)象public?class?MyView?extends?View{private?Paint?paint??=?new?Paint()?;public?MyView(Context?context)?{super(context);//?TODO?Auto-generated?constructor?stub}public?MyView(Context?context?,?AttributeSet?attrs){super(context,attrs);}protected?void?onMeasure(int?widthMeasureSpec,?int?heightMeasureSpec){//設(shè)置該View大小為?80?80setMeasuredDimension(50?,?50)?;}//存在canvas對(duì)象,即存在默認(rèn)的顯示區(qū)域@Overridepublic?void?onDraw(Canvas?canvas)?{//?TODO?Auto-generated?method?stubsuper.onDraw(canvas);Log.i("MyViewGroup",?"MyView?is?onDraw?")?;//加粗paint.setTypeface(Typeface.defaultFromStyle(Typeface.BOLD));paint.setColor(Color.RED);canvas.drawColor(Color.BLUE)?;canvas.drawRect(0,?0,?30,?30,?paint);canvas.drawText("MyView",?10,?40,?paint);}}主Activity只是顯示了該xml文件,在此也不羅嗦了。 大家可以查看該ViewGroup的Log仔細(xì)分析下View的繪制流程以及
相關(guān)方法的使用。第一次啟動(dòng)后捕獲的Log如下,網(wǎng)上找了些資料,第一次View樹繪制過程會(huì)走幾遍,具體原因可能是某些
View 發(fā)生了改變,請(qǐng)求重新繪制,但這根本不影響我們的界面顯示效果 。
?
? ? ? ? 總的來說: 整個(gè)繪制過程還是十分十分復(fù)雜地,每個(gè)具體方法的實(shí)現(xiàn)都是我輩難以立即的,感到悲劇啊。對(duì)Android提
?供的一些ViewGroup對(duì)象,比如LinearLayout、RelativeLayout布局對(duì)象的實(shí)現(xiàn)也很有壓力。 本文重在介紹整個(gè)View樹的繪制
流程,希望大家在此基礎(chǔ)上,多接觸源代碼進(jìn)行更深入地?cái)U(kuò)展。
轉(zhuǎn)載于:https://my.oschina.net/u/1461069/blog/318177
總結(jié)
以上是生活随笔為你收集整理的Android中View绘制流程的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: JAVA 异常库
- 下一篇: archlinux yaourt安装