日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 运维知识 > Android >内容正文

Android

Android中View绘制流程

發布時間:2023/12/4 Android 29 豆豆
生活随笔 收集整理的這篇文章主要介紹了 Android中View绘制流程 小編覺得挺不錯的,現在分享給大家,幫大家做個參考.

2019獨角獸企業重金招聘Python工程師標準>>>

整個View樹的繪圖流程是在ViewRoot.java類的performTraversals()函數展開的,該函數做的執行過程可簡單概況為

?根之前狀態,判斷是否需要重新計算視圖大小(measure)、是否重新需要安置視圖的位置(layout)、以及是否需要重繪

?(draw),其框架過程如下:

? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ???步驟其實為host.layout()?

???????????

?

?

???? ?接下來溫習一下整個View樹的結構,對每個具體View對象的操作,其實就是個遞歸的實現。

?

???????????????????

?

流程一:????? mesarue()過程


? ? ? ??主要作用:為整個View樹計算實際的大小,即設置實際的高(對應屬性:mMeasuredHeight)和寬(對應屬性:

??mMeasureWidth),每個View的控件的實際寬高都是由父視圖和本身視圖決定的。

?

???? 具體的調用鏈如下

????????? ViewRoot根對象地屬性mView(其類型一般為ViewGroup類型)調用measure()方法去計算View樹的大小,回調

View/ViewGroup對象的onMeasure()方法,該方法實現的功能如下: ? ?

? ? ? ? ?1、設置本View視圖的最終大小,該功能的實現通過調用setMeasuredDimension()方法去設置實際的高(對應屬性:??

??????????????? mMeasuredHeight)和寬(對應屬性:mMeasureWidth)?? ;

? ? ? ? ?2 、如果該View對象是個ViewGroup類型,需要重寫該onMeasure()方法,對其子視圖進行遍歷的measure()過程。

??????????????

? ? ? ? ? ? ? ?2.1? 對每個子視圖的measure()過程,是通過調用父類ViewGroup.java類里的measureChildWithMargins()方法去

? ? ? ? ? 實現,該方法內部只是簡單地調用了View對象的measure()方法。(由于measureChildWithMargins()方法只是一個過渡

? ? ? ? ? 層更簡單的做法是直接調用View對象的measure()方法)。

??????????????

??? ?整個measure調用流程就是個樹形的遞歸過程

?

?????measure函數原型為 View.java 該函數不能被重載

? ? ??

???public?final?void?measure(int?widthMeasureSpec,?int?heightMeasureSpec)?{//....//回調onMeasure()方法??onMeasure(widthMeasureSpec,?heightMeasureSpec);//more}

??調用流程,用偽代碼描述如下:

//回調View視圖里的onMeasure過程private?void?onMeasure(int?height?,?int?width){//設置該view的實際寬(mMeasuredWidth)高(mMeasuredHeight)//1、該方法必須在onMeasure調用,否者報異常。setMeasuredDimension(h?,?l)?;//2、如果該View是ViewGroup類型,則對它的每個子View進行measure()過程int?childCount?=?getChildCount()?;for(int?i=0?;i<childCount?;i++){//2.1、獲得每個子View對象引用View?child?=?getChildAt(i)?;//整個measure()過程就是個遞歸過程//該方法只是一個過濾器,最后會調用measure()過程?;或者?measureChild(child?,?h,?i)方法都measureChildWithMargins(child?,?h,?i)?;?//其實,對于我們自己寫的應用來說,最好的辦法是去掉框架里的該方法,直接調用view.measure(),如下://child.measure(h,?l)}}//該方法具體實現在ViewGroup.java里?。protected??void?measureChildWithMargins(View?v,?int?height?,?int?width){v.measure(h,l)???}

流程二、 layout布局過程:

?

???? 主要作用 :為將整個根據子視圖的大小以及布局參數將View樹放到合適的位置上。

?

?????具體的調用鏈如下:

???????host.layout()開始View樹的布局,繼而回調給View/ViewGroup類中的layout()方法。具體流程如下

??

????????1 、layout方法會設置該View視圖位于父視圖的坐標軸,即mLeft,mTop,mLeft,mBottom(調用setFrame()函數去實現)

? 接下來回調onLayout()方法(如果該View是ViewGroup對象,需要實現該方法,對每個子視圖進行布局) ;

???????

? ? ? ?2、如果該View是個ViewGroup類型,需要遍歷每個子視圖chiildView,調用該子視圖的layout()方法去設置它的坐標值。

?

? ? ? ? ??layout函數原型為 ,位于View.java

???/*?final?標識符?,?不能被重載?,?參數為每個視圖位于父視圖的坐標軸*?@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);?//設置每個視圖位于父視圖的坐標軸if?(changed?||?(mPrivateFlags?&?LAYOUT_REQUIRED)?==?LAYOUT_REQUIRED)?{if?(ViewDebug.TRACE_HIERARCHY)?{ViewDebug.trace(this,?ViewDebug.HierarchyTraceType.ON_LAYOUT);}onLayout(changed,?l,?t,?r,?b);//回調onLayout函數?,設置每個子視圖的布局mPrivateFlags?&=?~LAYOUT_REQUIRED;}mPrivateFlags?&=?~FORCE_LAYOUT;}

同樣地, 將上面layout調用流程,用偽代碼描述如下:

???//?layout()過程??ViewRoot.java//?發起layout()的"發號者"在ViewRoot.java里的performTraversals()方法,?mView.layout()private?void??performTraversals(){//...View?mView??;mView.layout(left,top,right,bottom)?;//....}//回調View視圖里的onLayout過程?,該方法只由ViewGroup類型實現private?void?onLayout(int?left?,?int?top?,?right?,?bottom){//如果該View不是ViewGroup類型//調用setFrame()方法設置該控件的在父視圖上的坐標軸setFrame(l?,t?,?r?,b)?;//--------------------------//如果該View是ViewGroup類型,則對它的每個子View進行layout()過程int?childCount?=?getChildCount()?;for(int?i=0?;i<childCount?;i++){//2.1、獲得每個子View對象引用View?child?=?getChildAt(i)?;//整個layout()過程就是個遞歸過程child.layout(l,?t,?r,?b)?;}}

??流程三、 draw()繪圖過程

? ? ?由ViewRoot對象的performTraversals()方法調用draw()方法發起繪制該View樹,值得注意的是每次發起繪圖時,并不

? 會重新繪制每個View樹的視圖,而只會重新繪制那些“需要重繪”的視圖,View類內部變量包含了一個標志位DRAWN,當該

視圖需要重繪時,就會為該View添加該標志位。

?

?? 調用流程 :

?????mView.draw()開始繪制,draw()方法實現的功能如下:

??????????1 、繪制該View的背景

??????????2 、為顯示漸變框做一些準備操作(見5,大多數情況下,不需要改漸變框)??????????

??????????3、調用onDraw()方法繪制視圖本身?? (每個View都需要重載該方法,ViewGroup不需要實現該方法)

??????????4、調用dispatchDraw ()方法繪制子視圖(如果該View類型不為ViewGroup,即不包含子視圖,不需要重載該方法)

值得說明的是,ViewGroup類已經為我們重寫了dispatchDraw ()的功能實現,應用程序一般不需要重寫該方法,但可以重載父類

? 函數實現具體的功能。

?

????????????4.1 dispatchDraw()方法內部會遍歷每個子視圖,調用drawChild()去重新回調每個子視圖的draw()方法(注意,這個?

地方“需要重繪”的視圖才會調用draw()方法)。值得說明的是,ViewGroup類已經為我們重寫了dispatchDraw()的功能

實現,應用程序一般不需要重寫該方法,但可以重載父類函數實現具體的功能。

????

?????5、繪制滾動條

?

? 于是,整個調用鏈就這樣遞歸下去了。

????

???? 同樣地,使用偽代碼描述如下:

???//?draw()過程?????ViewRoot.java//?發起draw()的"發號者"在ViewRoot.java里的performTraversals()方法,?該方法會繼續調用draw()方法開始繪圖private?void??draw(){//...View?mView??;mView.draw(canvas)?;??//....}//回調View視圖里的onLayout過程?,該方法只由ViewGroup類型實現private?void?draw(Canvas?canvas){//該方法會做如下事情//1?、繪制該View的背景//2、為繪制漸變框做一些準備操作//3、調用onDraw()方法繪制視圖本身//4、調用dispatchDraw()方法繪制每個子視圖,dispatchDraw()已經在Android框架中實現了,在ViewGroup方法中。//?應用程序程序一般不需要重寫該方法,但可以捕獲該方法的發生,做一些特別的事情。//5、繪制漸變框 }//ViewGroup.java中的dispatchDraw()方法,應用程序一般不需要重寫該方法@Overrideprotected?void?dispatchDraw(Canvas?canvas)?{//?//其實現方法類似如下:int?childCount?=?getChildCount()?;for(int?i=0?;i<childCount?;i++){View?child?=?getChildAt(i)?;//調用drawChild完成drawChild(child,canvas)?;} ???}//ViewGroup.java中的dispatchDraw()方法,應用程序一般不需要重寫該方法protected?void?drawChild(View?child,Canvas?canvas)?{//?....//簡單的回調View對象的draw()方法,遞歸就這么產生了。child.draw(canvas)?;//.........}

強調一點的就是,在這三個流程中,Google已經幫我們把draw()過程框架已經寫好了,自定義的ViewGroup只需要實現

?measure()過程和layout()過程即可 。


? ? ?這三種情況,最終會直接或間接調用到三個函數,分別為invalidate(),requsetLaytout()以及requestFocus() ,接著

這三個函數最終會調用到ViewRoot中的schedulTraversale()方法,該函數然后發起一個異步消息,消息處理中調用

performTraverser()方法對整個View進行遍歷。



? invalidate()方法 :

?

?? 說明:請求重繪View樹,即draw()過程,假如視圖發生大小沒有變化就不會調用layout()過程,并且只繪制那些“需要重繪的”

視圖,即誰(View的話,只繪制該View ;ViewGroup,則繪制整個ViewGroup)請求invalidate()方法,就繪制該視圖。

?

???? 一般引起invalidate()操作的函數如下:

? ? ? ? ? ? 1、直接調用invalidate()方法,請求重新draw(),但只會繪制調用者本身。

? ? ? ? ? ? 2、setSelection()方法 :請求重新draw(),但只會繪制調用者本身。

? ? ? ? ? ? 3、setVisibility()方法 : 當View可視狀態在INVISIBLE轉換VISIBLE時,會間接調用invalidate()方法,

? ? ? ? ? ? ? ? ? ? ?繼而繪制該View。

? ? ? ? ? ? 4 、setEnabled()方法 : 請求重新draw(),但不會重新繪制任何視圖包括該調用者本身。

?

??? requestLayout()方法?:會導致調用measure()過程 和 layout()過程 。

?

???????????說明:只是對View樹重新布局layout過程包括measure()和layout()過程,不會調用draw()過程,但不會重新繪制

任何視圖包括該調用者本身。

?

??? 一般引起invalidate()操作的函數如下:

? ? ? ? ?1、setVisibility()方法:

? ? ? ? ? ? ?當View的可視狀態在INVISIBLE/ VISIBLE 轉換為GONE狀態時,會間接調用requestLayout() 和invalidate方法。

? ? 同時,由于整個個View樹大小發生了變化,會請求measure()過程以及draw()過程,同樣地,只繪制需要“重新繪制”的視圖。

?

? ??requestFocus()函數說明:

?

??????????說明:請求View樹的draw()過程,但只繪制“需要重繪”的視圖。

下面寫個簡單的小Demo吧,主要目的是給大家演示繪圖的過程以及每個流程里該做的一些功能。截圖如下:


? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ??

1、??? MyViewGroup.java? 自定義ViewGroup類型

/***?@author?http://http://blog.csdn.net/qinjuning*///自定義ViewGroup?對象public?class?MyViewGroup??extends?ViewGroup{private?static?String?TAG?=?"MyViewGroup"?;private?Context?mContext?;public?MyViewGroup(Context?context)?{super(context);mContext?=?context?;init()?;}//xml定義的屬性,需要該構造函數public?MyViewGroup(Context?context?,?AttributeSet?attrs){super(context,attrs)?;mContext?=?context?;init()?;}//為MyViewGroup添加三個子Viewprivate?void?init(){//調用ViewGroup父類addView()方法添加子View//child?對象一?:?ButtonButton?btn=?new?Button(mContext)?;btn.setText("I?am?Button")?;this.addView(btn)?;//child?對象二?:?ImageView?ImageView?img?=?new?ImageView(mContext)?;img.setBackgroundResource(R.drawable.icon)?;this.addView(img)?;//child?對象三?:?TextViewTextView?txt?=?new?TextView(mContext)?;txt.setText("Only?Text")?;this.addView(txt)?;?//child?對象四?:?自定義ViewMyView?myView?=?new?MyView(mContext)?;this.addView(myView)?;?}@Override//對每個子View進行measure():設置每子View的大小,即實際寬和高protected?void?onMeasure(int?widthMeasureSpec,?int?heightMeasureSpec){//通過init()方法,我們為該ViewGroup對象添加了三個視圖?,?Button、?ImageView、TextViewint?childCount?=?getChildCount()?;Log.i(TAG,?"the?size?of?this?ViewGroup?is?---->?"?+?childCount)?;Log.i(TAG,?"****?onMeasure?start?*****")?;//獲取該ViewGroup的實際長和寬??涉及到MeasureSpec類的使用int?specSize_Widht?=?MeasureSpec.getSize(widthMeasureSpec)?;int?specSize_Heigth?=?MeasureSpec.getSize(heightMeasureSpec)?;Log.i(TAG,?"****?specSize_Widht?"?+?specSize_Widht+?"?*?specSize_Heigth???*****"?+?specSize_Heigth)?;//設置本ViewGroup的寬高setMeasuredDimension(specSize_Widht?,?specSize_Heigth)?;for(int?i=0?;i<childCount?;?i++){View?child?=?getChildAt(i)?;???//獲得每個對象的引用child.measure(50,?50)?;???//簡單的設置每個子View對象的寬高為?50px?,?50px??//或者可以調用ViewGroup父類方法measureChild()或者measureChildWithMargins()方法//this.measureChild(child,?widthMeasureSpec,?heightMeasureSpec)?;}}@Override//對每個子View視圖進行布局protected?void?onLayout(boolean?changed,?int?l,?int?t,?int?r,?int?b)?{//?TODO?Auto-generated?method?stub//通過init()方法,我們為該ViewGroup對象添加了三個視圖?,?Button、?ImageView、TextViewint?childCount?=?getChildCount()?;int?startLeft?=?0?;//設置每個子View的起始橫坐標?int?startTop?=?10?;?//每個子View距離父視圖的位置?,?簡單設置為10px吧?。?可以理解為?android:margin=10px?;Log.i(TAG,?"****?onLayout?start?****")?;for(int?i=0?;i<childCount?;?i++){View?child?=?getChildAt(i)?;???//獲得每個對象的引用child.layout(startLeft,?startTop,?startLeft+child.getMeasuredWidth(),?startTop+child.getMeasuredHeight())?;startLeft?=startLeft+child.getMeasuredWidth()?+?10;??//校準startLeft值,View之間的間距設為10px?;Log.i(TAG,?"****?onLayout?startLeft?****"?+startLeft)?;}??? ??? }//繪圖過程Android已經為我們封裝好了?,這兒只為了觀察方法調用程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對象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){//設置該View大小為?80?80setMeasuredDimension(50?,?50)?;}//存在canvas對象,即存在默認的顯示區域@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仔細分析下View的繪制流程以及

相關方法的使用。第一次啟動后捕獲的Log如下,網上找了些資料,第一次View樹繪制過程會走幾遍,具體原因可能是某些

View 發生了改變,請求重新繪制,但這根本不影響我們的界面顯示效果 。

?

? ? ? ? 總的來說: 整個繪制過程還是十分十分復雜地,每個具體方法的實現都是我輩難以立即的,感到悲劇啊。對Android提

?供的一些ViewGroup對象,比如LinearLayout、RelativeLayout布局對象的實現也很有壓力。 本文重在介紹整個View樹的繪制

流程,希望大家在此基礎上,多接觸源代碼進行更深入地擴展。


轉載于:https://my.oschina.net/u/1461069/blog/318177

總結

以上是生活随笔為你收集整理的Android中View绘制流程的全部內容,希望文章能夠幫你解決所遇到的問題。

如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。