Android O: View的绘制流程(三):布局和绘制
前一篇文章Android O: View的繪制流程(二):測量中,?
我們分析了View的測量流程。?
當View測量完畢后,就要開始進行布局和繪制相關的工作,?
本篇文章就來分析下這部分流程。
一、View的layout?
我們從ViewRootImpl.java的performLayout函數開始分析:
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
跟進View的layout函數:
public void layout(int l, int t, int r, int b) {.............//保留舊數據int oldL = mLeft;int oldT = mTop;int oldB = mBottom;int oldR = mRight;//measure時也判斷過, 當前View為ViewGroup且設置為視覺邊界布局模式時,才返回true//setOpticalFrame最終也會調用setFrame//setFrame將會設置View的位置(mLeft, mTop, mRight, mBottom)//這四個參數描述了View相對其父View的位置//如果View的位置發生了變化,就會返回trueboolean changed = isLayoutModeOptical(mParent) ?setOpticalFrame(l, t, r, b) : setFrame(l, t, r, b);//View的measure函數中, 會判斷是否增加PFLAG_LAYOUT_REQUIREDif (changed || (mPrivateFlags & PFLAG_LAYOUT_REQUIRED) == PFLAG_LAYOUT_REQUIRED) {//布局其child viewonLayout(changed, l, t, r, b);.........mPrivateFlags &= ~PFLAG_LAYOUT_REQUIRED;//如果有觀察者, 回調通知 ListenerInfo li = mListenerInfo;if (li != null && li.mOnLayoutChangeListeners != null) {ArrayList<OnLayoutChangeListener> listenersCopy =(ArrayList<OnLayoutChangeListener>)li.mOnLayoutChangeListeners.clone();int numListeners = listenersCopy.size();for (int i = 0; i < numListeners; ++i) {listenersCopy.get(i).onLayoutChange(this, l, t, r, b, oldL, oldT, oldR, oldB);}}}........ }- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
從上面代碼可以看出,layout函數會判斷View的位置是否發生了改變。?
若發生了改變,則需要調用onLayout函數對子View進行重新布局。
由于普通View(非ViewGroup)不含子View,所以View.java中的onLayout方法為空實現。?
因此接下來,我們看看ViewGroup類的onLayout方法。
二、FrameLayout的onLayout?
ViewGroup中的onLayout為一個抽象方法,由具體的ViewGroup實現。?
對于DecorView而言,將調用FrameLayout的onLayout方法:
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
- 39
- 40
- 41
- 42
- 43
- 44
- 45
- 46
- 47
- 48
- 49
- 50
- 51
- 52
- 53
- 54
- 55
- 56
- 57
- 58
- 59
- 60
- 61
- 62
- 63
- 64
- 65
- 66
- 67
- 68
- 69
- 70
- 71
- 72
- 73
- 74
- 75
- 76
- 77
- 78
- 79
- 80
- 81
- 82
- 83
- 84
- 85
- 86
上面代碼中,childLeft代表了最終子View的左邊緣距父View左邊緣的距離;?
childTop代表了子View的上邊緣距父View的上邊緣的距離。?
當計算出child view的位置信息后,會繼續調用layout方法,繼續遞歸布局。
對于ViewGroup而言,onMeasure和onLayout應該是配套使用的。?
我們目前只以比較簡單的FrameLayout為例,分析了這部分過程。?
對于其它ViewGroup而言,遞歸的方式與FrameLayout類似,?
但具體的細節差異較大。
三、ViewRootImpl的performDraw?
完成了measure和layout階段后,View的大小和位置基本上就確定了,?
接下來就進入繪制階段。
我們同樣從ViewRootImpl的performDraw函數入手:
private void performDraw() {.......final boolean fullRedrawNeeded = mFullRedrawNeeded;mFullRedrawNeeded = false;mIsDrawing = true;........try {draw(fullRedrawNeeded);} finally {mIsDrawing = false;} ........ }- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
我們跟進ViewRootImpl的draw函數:
private void draw(boolean fullRedrawNeeded) {//省略滾動、動畫相關的細節...........if (!dirty.isEmpty() || mIsAnimating || accessibilityFocusDirty) {//如果采用硬件渲染繪制且ThreadedRenderer可用,進入該流程if (mAttachInfo.mThreadedRenderer != null && mAttachInfo.mThreadedRenderer.isEnabled()) {..........//最后將通過native函數nDrawRenderNode繪制mAttachInfo.mThreadedRenderer.draw(mView, mAttachInfo, this);} else {//如果需要進行硬件渲染,但ThreadedRenderer不可用//則進行ThreadedRenderer初始化工作(以便下次用)..........// 不用硬件渲染,或硬件渲染不可用,則靠軟件繪制if (!drawSoftware(surface, mAttachInfo, xOffset, yOffset, scalingRequired, dirty)) {return;}}}......... }- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
繼續分析drawSoftware函數:
private boolean drawSoftware(Surface surface, AttachInfo attachInfo, int xoff, int yoff,boolean scalingRequired, Rect dirty) {// Draw with software renderer.final Canvas canvas;try {..........//獲取畫布canvas = mSurface.lockCanvas(dirty);.........} catch (Surface.OutOfResourcesException e) {.........} catch (IllegalArgumentException e) {........}try {........try {........//關鍵在此//此時調用的是DecorView的draw函數mView.draw(canvas);........} finally {........}} finally {try {//unlocksurface.unlockCanvasAndPost(canvas);} catch (IllegalArgumentException e) {...........}.........}........ }- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
從上述代碼可以看出,在不使用硬件繪制的條件下,?
ViewRootImpl的performDraw函數最終會調用View的draw函數。
四、View的draw?
View.java中draw函數的源碼如下:
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
- 39
- 40
- 41
- 42
- 43
- 44
- 45
- 46
- 47
- 48
- 49
- 50
- 51
- 52
- 53
- 54
- 55
在上面的代碼中,我們目前最關心的是onDraw和dispatchDraw。?
其中,onDraw用于繪制View自身,需要每個View自己實現;?
dispatchDraw用于繪制child view,由ViewGroup實現。
最后,我們來看看ViewGroup中的dispatchDraw函數:
protected void dispatchDraw(Canvas canvas) {boolean usingRenderNodeProperties = canvas.isRecordingFor(mRenderNode);final int childrenCount = mChildrenCount;final View[] children = mChildren;int flags = mGroupFlags;//處理動畫相關的繪制if ((flags & FLAG_RUN_ANIMATION) != 0 && canAnimate()) {.............}................// Only use the preordered list if not HW accelerated, since the HW pipeline will do the// draw reordering internallyfinal ArrayList<View> preorderedList = usingRenderNodeProperties? null : buildOrderedChildList();final boolean customOrder = preorderedList == null&& isChildrenDrawingOrderEnabled();//默認先序遍歷繪制for (int i = 0; i < childrenCount; i++) {.......final int childIndex = getAndVerifyPreorderedIndex(childrenCount, i, customOrder);final View child = getAndVerifyPreorderedView(preorderedList, children, childIndex);if ((child.mViewFlags & VISIBILITY_MASK) == VISIBLE || child.getAnimation() != null) {//內部還是調用View的draw函數more |= drawChild(canvas, child, drawingTime);}}............. }- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
上述代碼中我們省略了許多細節,不過仍可以很清晰地看出,?
整個View的視圖結構是按照先序遍歷來繪制的(盡管沒有分析具體的實現細節,?
但繪制時肯定會依賴布局時得到的信息)。
對于一個ViewGroup而言,會先繪制自身,?
然后繪制child view,最后再繪制一些裝飾組件等。
五、總結?
至此,View繪制相關的主要流程全部分析完畢。?
毫無疑問,我們漏掉了太多的細節。
其中,有的細節不太重要,所以我們不需要關注;?
有的細節則不是行文的重點,我們也有意忽略掉了;?
還有些細節,則需要對View繪制有更深刻的理解,?
才能進一步分析。?
目前,由于自己也是第一次深入看View相關的源碼,?
故未做進一步分析。以后如果碰到相關的問題,再做進一步的補充。
總結
以上是生活随笔為你收集整理的Android O: View的绘制流程(三):布局和绘制的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: Android O: View的绘制流程
- 下一篇: Android内存分析工具:Memory