您好,登錄后才能下訂單哦!
Android中View繪制流程的原理是什么,相信很多沒有經驗的人對此束手無策,為此本文總結了問題出現的原因和解決方法,通過這篇文章希望你能解決這個問題。
setContentView整個過程主要是如何把Activity的布局文件或者java的View添加至窗口里,重點概括為:
創建一個DecorView的對象mDecor,該mDecor對象將作為整個應用窗口的根視圖。
依據Feature等style theme創建不同的窗口修飾布局文件,并且通過findViewById獲取Activity布局文件該存放的地方(窗口修飾布局文件中id為content的FrameLayout)。
將Activity的布局文件添加至id為content的FrameLayout內。
當setContentView設置顯示OK以后會回調Activity的onContentChanged方法。Activity的各種View的findViewById()方法等都可以放到該方法中,系統會幫忙回調。
view繪制主要包括三個方面:
measure 測量組件本身的大小
layout 確定組件在視圖中的位置
draw 根據位置和大小,將組件畫出來
視圖繪制的起點在ViewRootImpl類的performTraversals()方法,該方法完成的工作主要是: 根據之前的狀態,判定是否重新計算測試視圖大小(measure)、是佛重新放置視圖位置(layout)和是否重新重繪視圖(draw) ,部分源碼如下:
private void performTraversals() { ...... //最外層的根視圖的widthMeasureSpec和heightMeasureSpec由來 //lp.width和lp.height在創建ViewGroup實例時等于MATCH_PARENT int childWidthMeasureSpec = getRootMeasureSpec(mWidth, lp.width); int childHeightMeasureSpec = getRootMeasureSpec(mHeight, lp.height); ...... mView.measure(childWidthMeasureSpec, childHeightMeasureSpec); ...... mView.layout(0, 0, mView.getMeasuredWidth(), mView.getMeasuredHeight()); ...... mView.draw(canvas); ...... }
幾乎所有的組件都是繼承View類的,而關于view的測量工作,日常開發用得多的方法就是measure和onMeasure兩個方法,measure不可重寫,當我們自定義時主要重寫onMeasure方法即可,在方法內部我們必須完成組件的mMeasuredWidth和mMeasuredHeight實際尺寸測量,而這個尺寸是需要父視圖和子視圖共同決定的
measure流程從根視圖measure遍歷整個view樹結構,如下:
這里寫圖片描述
還要注意視圖尺寸MeasureSpec是一個組合尺寸,它是一個32位bit值,高兩位是尺寸模式specMode,低30位是尺寸大小值,我們可以利用提供的原聲庫方法很方便的進行尺寸組合和拆解:
specMode有三種: MeasureSpec.EXACTLY表示確定大小, MeasureSpec.AT_MOST表示最大大小, MeasureSpec.UNSPECIFIED不確定
int measureSpec = MeasureSpec.makeMeasureSpec(windowSize, MeasureSpec.EXACTLY); //合成int specMode = MeasureSpec.getMode(measureSpec); //拆解int specSize = MeasureSpec.getSize(measureSpec);
而在視圖測量meause中,父組件傳給子組件的一般都是一個組合尺寸,我們可以拿出具體尺寸然后根據其他條件產生一個新的尺寸值,將這個值用setMeasuredDimension設置mMeasuredWidth和mMeasuredHeight具體尺寸,完成測量;
MeasureSpec(View的內部類)測量規格為int型,值由高2位規格模式specMode和低30位具體尺寸specSize組成。其中specMode只有三種值:
MeasureSpec.EXACTLY //確定模式,父View希望子View的大小是確定的,由specSize決定;MeasureSpec.AT_MOST //最多模式,父View希望子View的大小最多是specSize指定的值;MeasureSpec.UNSPECIFIED //未指定模式,父View完全依據子View的設計值來決定;
View的measure方法是final的,不允許重載,View子類只能重載onMeasure來完成自己的測量邏輯。
最頂層DecorView測量時的MeasureSpec是由ViewRootImpl中getRootMeasureSpec方法確定的(LayoutParams寬高參數均為MATCH_PARENT,specMode是EXACTLY,specSize為物理屏幕大小)。
ViewGroup類提供了measureChild,measureChild和measureChildWithMargins方法,簡化了父子View的尺寸計算。
只要是ViewGroup的子類就必須要求LayoutParams繼承子MarginLayoutParams,否則無法使用layout_margin參數。
View的布局大小由父View和子View共同決定。
使用View的getMeasuredWidth()和getMeasuredHeight()方法來獲取View測量的寬高,必須保證這兩個方法在onMeasure流程之后被調用才能返回有效值。
layout的流程主要也是遍歷整個view樹結構,調用view.layout(int l, int t, int r, int b)確定好view的具體坐標位置,流程圖如下
這里寫圖片描述
當我們自定義一個組件時,通常時重寫onLayout方法,里面實現好自己的邏輯,最后在調用layout方法完成視圖位置確定,如果自定義組件時一個ViewGroup的話,還需要我們去遍歷每一個child確定尺寸
整個layout過程比較容易理解,從上面分析可以看出layout也是從頂層父View向子View的遞歸調用view.layout方法的過程,即父View根據上一步measure子View所得到的布局大小和布局參數,將子View放在合適的位置上。具體layout核心主要有以下幾點:
View.layout方法可被重載,ViewGroup.layout為final的不可重載,ViewGroup.onLayout為abstract的,子類必須重載實現自己的位置邏輯。
measure操作完成后得到的是對每個View經測量過的measuredWidth和measuredHeight,layout操作完成之后得到的是對每個View進行位置分配后的mLeft、mTop、mRight、mBottom,這些值都是相對于父View來說的。
凡是layout_XXX的布局屬性基本都針對的是包含子View的ViewGroup的,當對一個沒有父容器的View設置相關layout_XXX屬性是沒有任何意義的(前面《Android應用setContentView與LayoutInflater加載解析機制源碼分析》也有提到過)。
使用View的getWidth()和getHeight()方法來獲取View測量的寬高,必須保證這兩個方法在onLayout流程之后被調用才能返回有效值。
完成measure和Layout后,ViewRootImpl中的代碼會創建一個Canvas對象,然后調用View的draw()方法來執行具體的繪制工。所以又回歸到了ViewGroup與View的樹狀遞歸draw過程
先來看下View樹的遞歸draw流程圖,如下:
這里寫圖片描述
可以看見,繪制過程就是把View對象繪制到屏幕上,整個draw過程需要注意如下細節:
如果該View是一個ViewGroup,則需要遞歸繪制其所包含的所有子View。
View默認不會繪制任何內容,真正的繪制都需要自己在子類中實現。
View的繪制是借助onDraw方法傳入的Canvas類來進行的。
區分View動畫和ViewGroup布局動畫,前者指的是View自身的動畫,可以通過setAnimation添加,后者是專門針對ViewGroup顯示內部子視圖時設置的動畫,可以在xml布局文件中對ViewGroup設置layoutAnimation屬性(譬如對LinearLayout設置子View在顯示時出現逐行、隨機、下等顯示等不同動畫效果)。
在獲取畫布剪切區(每個View的draw中傳入的Canvas)時會自動處理掉padding,子View獲取Canvas不用關注這些邏輯,只用關心如何繪制即可。
默認情況下子View的ViewGroup.drawChild繪制順序和子View被添加的順序一致,但是你也可以重載ViewGroup.getChildDrawingOrder()方法提供不同順序。
請求重新繪制視圖,調用draw
invalidate在主線程調用
postInvalidate是在非主線程調用
requestLayout()方法會調用measure過程和layout過程,不會調用draw過程,也不會重新繪制任何View包括該調用者本身。
看完上述內容,你們掌握Android中View繪制流程的原理是什么的方法了嗎?如果還想學到更多技能或想了解更多相關內容,歡迎關注億速云行業資訊頻道,感謝各位的閱讀!
免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。