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

歡迎訪問 生活随笔!

生活随笔

當(dāng)前位置: 首頁 > 运维知识 > Android >内容正文

Android

Android O: View的绘制流程(三):布局和绘制

發(fā)布時(shí)間:2025/3/15 Android 31 豆豆
生活随笔 收集整理的這篇文章主要介紹了 Android O: View的绘制流程(三):布局和绘制 小編覺得挺不錯(cuò)的,現(xiàn)在分享給大家,幫大家做個(gè)參考.

前一篇文章Android O: View的繪制流程(二):測(cè)量中,?
我們分析了View的測(cè)量流程。?
當(dāng)View測(cè)量完畢后,就要開始進(jìn)行布局和繪制相關(guān)的工作,?
本篇文章就來分析下這部分流程。


一、View的layout?
我們從ViewRootImpl.java的performLayout函數(shù)開始分析:

private void performLayout(WindowManager.LayoutParams lp, int desiredWindowWidth,int desiredWindowHeight) {............//ViewRootImpl中的mView為DecorViewfinal View host = mView;............try {//進(jìn)入View的layout函數(shù)//參數(shù)分別為left position, top position, right postion, bottom postionhost.layout(0, 0, host.getMeasuredWidth(), host.getMeasuredHeight());....... }......... }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14

跟進(jìn)View的layout函數(shù):

public void layout(int l, int t, int r, int b) {.............//保留舊數(shù)據(jù)int oldL = mLeft;int oldT = mTop;int oldB = mBottom;int oldR = mRight;//measure時(shí)也判斷過, 當(dāng)前View為ViewGroup且設(shè)置為視覺邊界布局模式時(shí),才返回true//setOpticalFrame最終也會(huì)調(diào)用setFrame//setFrame將會(huì)設(shè)置View的位置(mLeft, mTop, mRight, mBottom)//這四個(gè)參數(shù)描述了View相對(duì)其父View的位置//如果View的位置發(fā)生了變化,就會(huì)返回trueboolean changed = isLayoutModeOptical(mParent) ?setOpticalFrame(l, t, r, b) : setFrame(l, t, r, b);//View的measure函數(shù)中, 會(huì)判斷是否增加PFLAG_LAYOUT_REQUIREDif (changed || (mPrivateFlags & PFLAG_LAYOUT_REQUIRED) == PFLAG_LAYOUT_REQUIRED) {//布局其child viewonLayout(changed, l, t, r, b);.........mPrivateFlags &= ~PFLAG_LAYOUT_REQUIRED;//如果有觀察者, 回調(diào)通知 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函數(shù)會(huì)判斷View的位置是否發(fā)生了改變。?
若發(fā)生了改變,則需要調(diào)用onLayout函數(shù)對(duì)子View進(jìn)行重新布局。

由于普通View(非ViewGroup)不含子View,所以View.java中的onLayout方法為空實(shí)現(xiàn)。?
因此接下來,我們看看ViewGroup類的onLayout方法。

二、FrameLayout的onLayout?
ViewGroup中的onLayout為一個(gè)抽象方法,由具體的ViewGroup實(shí)現(xiàn)。?
對(duì)于DecorView而言,將調(diào)用FrameLayout的onLayout方法:

@Override protected void onLayout(boolean changed, int left, int top, int right, int bottom) {layoutChildren(left, top, right, bottom, false /* no force left gravity */); }void layoutChildren(int left, int top, int right, int bottom, boolean forceLeftGravity) {final int count = getChildCount();//parentLeft表示當(dāng)前View為其子View顯示區(qū)域指定的一個(gè)左邊界//也就是子View顯示區(qū)域的左邊緣到父View的左邊緣的距離//parentRight、parentTop、parentBottom的含義類似final int parentLeft = getPaddingLeftWithForeground();final int parentRight = right - left - getPaddingRightWithForeground();final int parentTop = getPaddingTopWithForeground();final int parentBottom = bottom - top - getPaddingBottomWithForeground();//開始對(duì)子View進(jìn)行布局for (int i = 0; i < count; i++) {final View child = getChildAt(i);//子View寬和高final int width = child.getMeasuredWidth();final int height = child.getMeasuredHeight();//僅計(jì)算left和top//結(jié)合child view的寬和高, 就能得到right和bottomint childLeft;int childTop;int gravity = lp.gravity;if (gravity == -1) {//對(duì)于FrameLayout, 為Gravity.TOP | Gravity.STARTgravity = DEFAULT_CHILD_GRAVITY;}//得到ViewGroup的布局方向final int layoutDirection = getLayoutDirection();//child view對(duì)應(yīng)的layout_gravity字段信息final int absoluteGravity = Gravity.getAbsoluteGravity(gravity, layoutDirection);final int verticalGravity = gravity & Gravity.VERTICAL_GRAVITY_MASK;//水平方向switch (absoluteGravity & Gravity.HORIZONTAL_GRAVITY_MASK) {//水平居中的場(chǎng)景case Gravity.CENTER_HORIZONTAL:childLeft = parentLeft + (parentRight - parentLeft - width) / 2 +lp.leftMargin - lp.rightMargin;break;case Gravity.RIGHT://右對(duì)齊的場(chǎng)景if (!forceLeftGravity) {childLeft = parentRight - width - lp.rightMargin;break;}//默認(rèn)的情況case Gravity.LEFT:default:childLeft = parentLeft + lp.leftMargin;}//垂直方向switch (verticalGravity) {//頂端對(duì)齊的場(chǎng)景case Gravity.TOP:childTop = parentTop + lp.topMargin;break;//垂直居中的場(chǎng)景case Gravity.CENTER_VERTICAL:childTop = parentTop + (parentBottom - parentTop - height) / 2 +lp.topMargin - lp.bottomMargin;break;//底部對(duì)齊的場(chǎng)景case Gravity.BOTTOM:childTop = parentBottom - height - lp.bottomMargin;break;//默認(rèn)為頂對(duì)齊default:childTop = parentTop + lp.topMargin;}//子ViewGroup進(jìn)行布局child.layout(childLeft, childTop, childLeft + width, childTop + height);} }
  • 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的上邊緣的距離。?
當(dāng)計(jì)算出child view的位置信息后,會(huì)繼續(xù)調(diào)用layout方法,繼續(xù)遞歸布局。

對(duì)于ViewGroup而言,onMeasure和onLayout應(yīng)該是配套使用的。?
我們目前只以比較簡(jiǎn)單的FrameLayout為例,分析了這部分過程。?
對(duì)于其它ViewGroup而言,遞歸的方式與FrameLayout類似,?
但具體的細(xì)節(jié)差異較大。

三、ViewRootImpl的performDraw?
完成了measure和layout階段后,View的大小和位置基本上就確定了,?
接下來就進(jìn)入繪制階段。

我們同樣從ViewRootImpl的performDraw函數(shù)入手:

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

我們跟進(jìn)ViewRootImpl的draw函數(shù):

private void draw(boolean fullRedrawNeeded) {//省略滾動(dòng)、動(dòng)畫相關(guān)的細(xì)節(jié)...........if (!dirty.isEmpty() || mIsAnimating || accessibilityFocusDirty) {//如果采用硬件渲染繪制且ThreadedRenderer可用,進(jìn)入該流程if (mAttachInfo.mThreadedRenderer != null && mAttachInfo.mThreadedRenderer.isEnabled()) {..........//最后將通過native函數(shù)nDrawRenderNode繪制mAttachInfo.mThreadedRenderer.draw(mView, mAttachInfo, this);} else {//如果需要進(jìn)行硬件渲染,但ThreadedRenderer不可用//則進(jìn)行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

繼續(xù)分析drawSoftware函數(shù):

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 {........//關(guān)鍵在此//此時(shí)調(diào)用的是DecorView的draw函數(shù)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函數(shù)最終會(huì)調(diào)用View的draw函數(shù)。

四、View的draw?
View.java中draw函數(shù)的源碼如下:

public void draw(Canvas canvas) {.............//draw函數(shù)的實(shí)現(xiàn)細(xì)節(jié),可以參考注釋/** Draw traversal performs several drawing steps which must be executed* in the appropriate order:** 1. Draw the background* 2. If necessary, save the canvas' layers to prepare for fading* 3. Draw view's content* 4. Draw children* 5. If necessary, draw the fading edges and restore layers* 6. Draw decorations (scrollbars for instance)*/// Step 1, draw the background, if neededint saveCount;if (!dirtyOpaque) {//繪制背景drawBackground(canvas);}// skip step 2 & 5 if possible (common case)final int viewFlags = mViewFlags;// 判斷View是否具有Fading Edge, xml里需要主動(dòng)配置, 以支持邊緣漸變效果boolean horizontalEdges = (viewFlags & FADING_EDGE_HORIZONTAL) != 0;boolean verticalEdges = (viewFlags & FADING_EDGE_VERTICAL) != 0;//一般情況下,不支持這種效果時(shí)if (!verticalEdges && !horizontalEdges) {// Step 3, draw the content// 繪制自身內(nèi)容if (!dirtyOpaque) onDraw(canvas);// Step 4, draw the children// 繪制child viewdispatchDraw(canvas);..........// Step 6, draw decorations (foreground, scrollbars)onDrawForeground(canvas);// Step 7, draw the default focus highlightdrawDefaultFocusHighlight(canvas);.........// we're done...return;}//支持支持邊緣漸變效果時(shí)的繪制//與前面不同的地方主要是:需要保存canvas' layer, 增加漸變效果后,再恢復(fù)canvas' layer//暫時(shí)不深入分析,等研究FADING_EDGE效果時(shí),再來看........ }
  • 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

在上面的代碼中,我們目前最關(guān)心的是onDraw和dispatchDraw。?
其中,onDraw用于繪制View自身,需要每個(gè)View自己實(shí)現(xiàn);?
dispatchDraw用于繪制child view,由ViewGroup實(shí)現(xiàn)。

最后,我們來看看ViewGroup中的dispatchDraw函數(shù):

protected void dispatchDraw(Canvas canvas) {boolean usingRenderNodeProperties = canvas.isRecordingFor(mRenderNode);final int childrenCount = mChildrenCount;final View[] children = mChildren;int flags = mGroupFlags;//處理動(dòng)畫相關(guān)的繪制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();//默認(rèn)先序遍歷繪制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) {//內(nèi)部還是調(diào)用View的draw函數(shù)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

上述代碼中我們省略了許多細(xì)節(jié),不過仍可以很清晰地看出,?
整個(gè)View的視圖結(jié)構(gòu)是按照先序遍歷來繪制的(盡管沒有分析具體的實(shí)現(xiàn)細(xì)節(jié),?
但繪制時(shí)肯定會(huì)依賴布局時(shí)得到的信息)。

對(duì)于一個(gè)ViewGroup而言,會(huì)先繪制自身,?
然后繪制child view,最后再繪制一些裝飾組件等。

五、總結(jié)?
至此,View繪制相關(guān)的主要流程全部分析完畢。?
毫無疑問,我們漏掉了太多的細(xì)節(jié)。

其中,有的細(xì)節(jié)不太重要,所以我們不需要關(guān)注;?
有的細(xì)節(jié)則不是行文的重點(diǎn),我們也有意忽略掉了;?
還有些細(xì)節(jié),則需要對(duì)View繪制有更深刻的理解,?
才能進(jìn)一步分析。?
目前,由于自己也是第一次深入看View相關(guān)的源碼,?
故未做進(jìn)一步分析。以后如果碰到相關(guān)的問題,再做進(jìn)一步的補(bǔ)充。

版權(quán)聲明:轉(zhuǎn)載請(qǐng)注明:http://blog.csdn.net/gaugamela/article

總結(jié)

以上是生活随笔為你收集整理的Android O: View的绘制流程(三):布局和绘制的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問題。

如果覺得生活随笔網(wǎng)站內(nèi)容還不錯(cuò),歡迎將生活随笔推薦給好友。