Android在View拉丝工艺和invalidate()和其他相关方法
????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????轉(zhuǎn)載請注明出處:http://blog.csdn.net/qinjuning
?
?
?
?????????? ?前言: 本文是我讀《Android內(nèi)核剖析》第13章----View工作原理總結(jié)而成的,在此膜拜下作者?。
同一時候真摯地向渴望了解
? ? ?Android 框架層的網(wǎng)友,推薦這本書,希望你們能夠在Android開發(fā)里學(xué)到很多其它的知識 。?
?
? ? ? ? ?
?? ? ????? ?整個View樹的畫圖流程是在ViewRoot.java類的performTraversals()函數(shù)展開的,該函數(shù)做的運行過程可簡單概況為
?根據(jù)之前設(shè)置的狀態(tài),推斷是否須要又一次計算視圖大小(measure)、是否又一次須要安置視圖的位置(layout)、以及是否須要重繪
?(draw),其框架步驟例如以下:
? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ???步驟事實上為host.layout()?
???????????
?
?
???? ?接下來溫習(xí)一下整個View樹的結(jié)構(gòu),對每一個詳細(xì)View對象的操作,事實上就是個遞歸的實現(xiàn)。
?
??????????????????
?
? ? ? ? ? ?關(guān)于這個 DecorView 根視圖的說明,能夠參考我的這篇博客:
? ? ? ? ? ? ? ?
? ? ? ? ?《Android中將布局文件/View加入至窗體過程分析 ---- 從setContentView()談起》
? 流程一:????? mesarue()過程
? ? ? ??主要作用:為整個View樹計算實際的大小,即設(shè)置實際的高(相應(yīng)屬性:mMeasuredHeight)和寬(相應(yīng)屬性:
? mMeasureWidth),每一個View的控件的實際寬高都是由父視圖和本身視圖決定的。
?
???? 詳細(xì)的調(diào)用鏈例如以下:
????????? ViewRoot根對象地屬性mView(其類型一般為ViewGroup類型)調(diào)用measure()方法去計算View樹的大小,回調(diào)
View/ViewGroup對象的onMeasure()方法,該方法實現(xiàn)的功能例如以下: ? ?
? ? ? ? ?1、設(shè)置本View視圖的終于大小。該功能的實現(xiàn)通過調(diào)用setMeasuredDimension()方法去設(shè)置實際的高(相應(yīng)屬性:??
??????????????? mMeasuredHeight)和寬(相應(yīng)屬性:mMeasureWidth)?? ;
? ? ? ? ?2 、假設(shè)該View對象是個ViewGroup類型,須要重寫該onMeasure()方法,對其子視圖進(jìn)行遍歷的measure()過程。
??????????????
? ? ? ? ? ? ? ?2.1? 對每一個子視圖的measure()過程,是通過調(diào)用父類ViewGroup.java類里的measureChildWithMargins()方法去
? ? ? ? ? 實現(xiàn),該方法內(nèi)部僅僅是簡單地調(diào)用了View對象的measure()方法。(因為measureChildWithMargins()方法僅僅是一個過渡
? ? ? ? ? 層更簡單的做法是直接調(diào)用View對象的measure()方法)。
??????????????
??? ?整個measure調(diào)用流程就是個樹形的遞歸過程
?
? ???measure函數(shù)原型為 View.java 該函數(shù)不能被重載
? ? ??
public final void measure(int widthMeasureSpec, int heightMeasureSpec) {//....//回調(diào)onMeasure()方法 onMeasure(widthMeasureSpec, heightMeasureSpec);//more}? ? ?為了大家更好的理解,採用“二B程序猿”的方式利用偽代碼描寫敘述該measure流程
?
//回調(diào)View視圖里的onMeasure過程private void onMeasure(int height , int width){//設(shè)置該view的實際寬(mMeasuredWidth)高(mMeasuredHeight)//1、該方法必須在onMeasure調(diào)用,否者報異常。setMeasuredDimension(h , l) ; //2、假設(shè)該View是ViewGroup類型,則對它的每一個子View進(jìn)行measure()過程 int childCount = getChildCount() ; for(int i=0 ;i<childCount ;i++){ //2.1、獲得每一個子View對象引用 View child = getChildAt(i) ; //整個measure()過程就是個遞歸過程 //該方法僅僅是一個過濾器。最后會調(diào)用measure()過程 ;或者 measureChild(child , h, i)方法都 measureChildWithMargins(child , h, i) ; //事實上。對于我們自己寫的應(yīng)用來說,最好的辦法是去掉框架里的該方法。直接調(diào)用view.measure(),例如以下: //child.measure(h, l) } } //該方法詳細(xì)實如今ViewGroup.java里 。 protected void measureChildWithMargins(View v, int height , int width){ v.measure(h,l) }
流程二、 layout布局過程:
?
???? 主要作用 :為將整個依據(jù)子視圖的大小以及布局參數(shù)將View樹放到合適的位置上。
?
?????詳細(xì)的調(diào)用鏈例如以下:
???????host.layout()開始View樹的布局,繼而回調(diào)給View/ViewGroup類中的layout()方法。
詳細(xì)流程例如以下
??
????????1 、layout方法會設(shè)置該View視圖位于父視圖的坐標(biāo)軸,即mLeft,mTop,mLeft,mBottom(調(diào)用setFrame()函數(shù)去實現(xiàn))
? 接下來回調(diào)onLayout()方法(假設(shè)該View是ViewGroup對象,須要實現(xiàn)該方法,對每一個子視圖進(jìn)行布局) 。
???????
? ? ? ?2、假設(shè)該View是個ViewGroup類型。須要遍歷每一個子視圖chiildView,調(diào)用該子視圖的layout()方法去設(shè)置它的坐標(biāo)值。
?
? ? ? ? ? layout函數(shù)原型為 ,位于View.java
/* final 標(biāo)識符 。 不能被重載 , 參數(shù)為每一個視圖位于父視圖的坐標(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è)置每一個視圖位于父視圖的坐標(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è)置每一個子視圖的布局mPrivateFlags &= ~LAYOUT_REQUIRED;}mPrivateFlags &= ~FORCE_LAYOUT;}????相同地, 將上面layout調(diào)用流程,用偽代碼描寫敘述例如以下:?
// layout()過程 ViewRoot.java// 發(fā)起layout()的"發(fā)號者"在ViewRoot.java里的performTraversals()方法, mView.layout()private void performTraversals(){//...View mView ;mView.layout(left,top,right,bottom) ;//....}//回調(diào)View視圖里的onLayout過程 ,該方法僅僅由ViewGroup類型實現(xiàn)private void onLayout(int left , int top , right , bottom){//假設(shè)該View不是ViewGroup類型//調(diào)用setFrame()方法設(shè)置該控件的在父視圖上的坐標(biāo)軸setFrame(l ,t , r ,b) ;//--------------------------//假設(shè)該View是ViewGroup類型,則對它的每一個子View進(jìn)行l(wèi)ayout()過程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()方法調(diào)用draw()方法發(fā)起繪制該View樹,值得注意的是每次發(fā)起畫圖時,并不
? 會又一次繪制每一個View樹的視圖。而僅僅會又一次繪制那些“須要重繪”的視圖,View類內(nèi)部變量包含了一個標(biāo)志位DRAWN。當(dāng)該
視圖須要重繪時,就會為該View加入該標(biāo)志位。
?
?? 調(diào)用流程 :
?????mView.draw()開始繪制,draw()方法實現(xiàn)的功能例如以下:
??????????1 、繪制該View的背景
??????????2 、為顯示漸變框做一些準(zhǔn)備操作(見5,大多數(shù)情況下。不須要改漸變框)??????????
??????????3、調(diào)用onDraw()方法繪制視圖本身?? (每一個View都須要重載該方法。ViewGroup不須要實現(xiàn)該方法)
??????????4、調(diào)用dispatchDraw ()方法繪制子視圖(假設(shè)該View類型不為ViewGroup,即不包含子視圖。不須要重載該方法)
值得說明的是。ViewGroup類已經(jīng)為我們重寫了dispatchDraw ()的功能實現(xiàn)。應(yīng)用程序一般不須要重寫該方法。但能夠重載父類
? 函數(shù)實現(xiàn)詳細(xì)的功能。
?
????????????4.1 dispatchDraw()方法內(nèi)部會遍歷每一個子視圖。調(diào)用drawChild()去又一次回調(diào)每一個子視圖的draw()方法(注意。這個?
地方“須要重繪”的視圖才會調(diào)用draw()方法)。值得說明的是,ViewGroup類已經(jīng)為我們重寫了dispatchDraw()的功能
實現(xiàn),應(yīng)用程序一般不須要重寫該方法,但能夠重載父類函數(shù)實現(xiàn)詳細(xì)的功能。
????
?????5、繪制滾動欄
?
? 于是。整個調(diào)用鏈就這樣遞歸下去了。
????
???? 相同地,使用偽代碼描寫敘述例如以下:? ??
// draw()過程 ViewRoot.java// 發(fā)起draw()的"發(fā)號者"在ViewRoot.java里的performTraversals()方法, 該方法會繼續(xù)調(diào)用draw()方法開始畫圖private void draw(){//...View mView ;mView.draw(canvas) ; //....}//回調(diào)View視圖里的onLayout過程 ,該方法僅僅由ViewGroup類型實現(xiàn)private void draw(Canvas canvas){//該方法會做例如以下事情//1 、繪制該View的背景//2、為繪制漸變框做一些準(zhǔn)備操作//3、調(diào)用onDraw()方法繪制視圖本身//4、調(diào)用dispatchDraw()方法繪制每一個子視圖。dispatchDraw()已經(jīng)在Android框架中實現(xiàn)了,在ViewGroup方法中。// 應(yīng)用程序程序一般不須要重寫該方法,但能夠捕獲該方法的發(fā)生,做一些特別的事情。//5、繪制漸變框 } //ViewGroup.java中的dispatchDraw()方法。應(yīng)用程序一般不須要重寫該方法 @Override protected void dispatchDraw(Canvas canvas) { // //事實上現(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對象的draw()方法,遞歸就這么產(chǎn)生了。
child.draw(canvas) ; //......... }
? ?關(guān)于繪制背景圖片詳細(xì)的過程,請參考我的另外的博客:
? ? ? ? ? ?
? ? ? ? ? ? ? <<Android中View(視圖)繪制不同狀態(tài)背景圖片原理深入分析以及StateListDrawable使用詳細(xì)解釋>>
? ? 強調(diào)一點的就是,在這三個流程中。Google已經(jīng)幫我們把draw()過程框架已經(jīng)寫好了,自己定義的ViewGroup僅僅須要實現(xiàn)
?measure()過程和layout()過程就可以 。
? ? ?這三種情況,終于會直接或間接調(diào)用到三個函數(shù),分別為invalidate(),requsetLaytout()以及requestFocus() ,接著
這三個函數(shù)終于會調(diào)用到ViewRoot中的schedulTraversale()方法,該函數(shù)然后發(fā)起一個異步消息,消息處理中調(diào)用
performTraverser()方法對整個View進(jìn)行遍歷。
?
?
? ? invalidate()方法 :
?
?? 說明:請求重繪View樹。即draw()過程,假如視圖發(fā)生大小沒有變化就不會調(diào)用layout()過程。而且僅僅繪制那些“須要重繪的”
視圖,即誰(View的話。僅僅繪制該View ;ViewGroup,則繪制整個ViewGroup)請求invalidate()方法,就繪制該視圖。
?
???? 一般引起invalidate()操作的函數(shù)例如以下:
? ? ? ? ? ? 1、直接調(diào)用invalidate()方法。請求又一次draw(),但僅僅會繪制調(diào)用者本身。
? ? ? ? ? ? 2、setSelection()方法 :請求又一次draw(),但僅僅會繪制調(diào)用者本身。
? ? ? ? ? ? 3、setVisibility()方法 : 當(dāng)View可視狀態(tài)在INVISIBLE轉(zhuǎn)換VISIBLE時,會間接調(diào)用invalidate()方法,
? ? ? ? ? ? ? ? ? ? ?繼而繪制該View。
? ? ? ? ? ? 4 、setEnabled()方法 : 請求又一次draw()。但不會又一次繪制不論什么視圖包含該調(diào)用者本身。
?
? ? requestLayout()方法 :會導(dǎo)致調(diào)用measure()過程 和 layout()過程 。
?
???????????說明:僅僅是對View樹又一次布局layout過程包含measure()和layout()過程。不會調(diào)用draw()過程。但不會又一次繪制
不論什么視圖包含該調(diào)用者本身。
?
??? 一般引起invalidate()操作的函數(shù)例如以下:
? ? ? ? ?1、setVisibility()方法:
? ? ? ? ? ? ?當(dāng)View的可視狀態(tài)在INVISIBLE/ VISIBLE 轉(zhuǎn)換為GONE狀態(tài)時,會間接調(diào)用requestLayout() 和invalidate方法。
? ? 同一時候,因為整個個View樹大小發(fā)生了變化,會請求measure()過程以及draw()過程。相同地,僅僅繪制須要“又一次繪制”的視圖。
?
? ? requestFocus()函數(shù)說明:
?
??????????說明:請求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定義的屬性,須要該構(gòu)造函數(shù)public MyViewGroup(Context context , AttributeSet attrs){super(context,attrs) ;mContext = context ;init() ;}//為MyViewGroup加入三個子Viewprivate void init(){//調(diào)用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進(jìn)行measure():設(shè)置每子View的大小。即實際寬和高protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec){//通過init()方法,我們?yōu)樵揤iewGroup對象加入了三個視圖 , 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) ;//設(shè)置本ViewGroup的寬高setMeasuredDimension(specSize_Widht , specSize_Heigth) ;for(int i=0 ;i<childCount ; i++){View child = getChildAt(i) ; //獲得每一個對象的引用child.measure(50, 50) ; //簡單的設(shè)置每一個子View對象的寬高為 50px , 50px //或者能夠調(diào)用ViewGroup父類方法measureChild()或者measureChildWithMargins()方法//this.measureChild(child, widthMeasureSpec, heightMeasureSpec) ;}}@Override//對每一個子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對象加入了三個視圖 , Button、 ImageView、TextViewint childCount = getChildCount() ;int startLeft = 0 ;//設(shè)置每一個子View的起始橫坐標(biāo) int startTop = 10 ; //每一個子View距離父視圖的位置 , 簡單設(shè)置為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; //校準(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對象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對象,即存在默認(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)方法的使用。第一次啟動后捕獲的Log例如以下,網(wǎng)上找了些資料,第一次View樹繪制過程會走幾遍,詳細(xì)原因可能是某些
View 發(fā)生了改變。請求又一次繪制。但這根本不影響我們的界面顯示效果 。
?
? ? ? ? 總的來說: 整個繪制過程還是十分十分復(fù)雜地,每一個詳細(xì)方法的實現(xiàn)都是我輩難以馬上的。感到悲劇啊。
對Android提
?供的一些ViewGroup對象,比方LinearLayout、RelativeLayout布局對象的實現(xiàn)也非常有壓力。
本文重在介紹整個View樹的繪制
流程。希望大家在此基礎(chǔ)上,多接觸源碼進(jìn)行更深入地擴展。
?
?
??????
? ? ? ?演示樣例DEMO下載地址:http://download.csdn.net/detail/qinjuning/3982468
?
//==========================================================
// 本次更新于 2012-05-20 晚
//==========================================================
?Al Last,關(guān)于UI繪制的這塊。我博客里零零散散的敘說了一些知識。建議大家都能夠去看看:
? ?1、 ?詳細(xì)解釋measure過程以及怎樣設(shè)置View寬高的,建議看我的另外兩篇博客:
? ? ? ? ?<<Android中measure過程、WRAP_CONTENT詳細(xì)解釋以及xml布局文件解析流程淺析(上)>>
? ? ? ? ?<<Android中measure過程、WRAP_CONTENT詳細(xì)解釋以及xml布局文件解析流程淺析(下)>>
? ?2、詳細(xì)解釋DecorView以及Activity窗體相應(yīng)布局地說明
? ? ? <<Android中將布局文件/View加入至窗體過程分析 ---- 從setContentView()談起>>
? ?3、詳細(xì)解釋View繪制過程中怎樣繪制背景圖片:
? ? ? ?<<Android中View(視圖)繪制不同狀態(tài)背景圖片原理深入分析以及StateListDrawable使用詳細(xì)解釋>>
? ?希望各位能暫停你的腳步,踏踏實實學(xué)習(xí)。Best regards for U ~~ 。
//==========================================================
// 本次更新于 2012-10-29 晚
//==========================================================
?
? ? ? ?總結(jié)
以上是生活随笔為你收集整理的Android在View拉丝工艺和invalidate()和其他相关方法的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 预定义异常 - PHP手册笔记
- 下一篇: Android自定义AlertDialo