前一篇文章Android O: View的绘制流程(二):测量中, 
我们分析了View的测量流程。 
当View测量完毕后,就要开始进行布局和绘制相关的工作, 
本篇文章就来分析下这部分流程。


一、View的layout 
我们从ViewRootImpl.java的performLayout函数开始分析:

private void performLayout(WindowManager.LayoutParams lp, int desiredWindowWidth,int desiredWindowHeight) {............//ViewRootImpl中的mView为DecorViewfinal View host = mView;............try {//进入View的layout函数//参数分别为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

跟进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方法:

@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表示当前View为其子View显示区域指定的一个左边界//也就是子View显示区域的左边缘到父View的左边缘的距离//parentRight、parentTop、parentBottom的含义类似final int parentLeft = getPaddingLeftWithForeground();final int parentRight = right - left - getPaddingRightWithForeground();final int parentTop = getPaddingTopWithForeground();final int parentBottom = bottom - top - getPaddingBottomWithForeground();//开始对子View进行布局for (int i = 0; i < count; i++) {final View child = getChildAt(i);//子View宽和高final int width = child.getMeasuredWidth();final int height = child.getMeasuredHeight();//仅计算left和top//结合child view的宽和高, 就能得到right和bottomint childLeft;int childTop;int gravity = lp.gravity;if (gravity == -1) {//对于FrameLayout, 为Gravity.TOP | Gravity.STARTgravity = DEFAULT_CHILD_GRAVITY;}//得到ViewGroup的布局方向final int layoutDirection = getLayoutDirection();//child view对应的layout_gravity字段信息final int absoluteGravity = Gravity.getAbsoluteGravity(gravity, layoutDirection);final int verticalGravity = gravity & Gravity.VERTICAL_GRAVITY_MASK;//水平方向switch (absoluteGravity & Gravity.HORIZONTAL_GRAVITY_MASK) {//水平居中的场景case Gravity.CENTER_HORIZONTAL:childLeft = parentLeft + (parentRight - parentLeft - width) / 2 +lp.leftMargin - lp.rightMargin;break;case Gravity.RIGHT://右对齐的场景if (!forceLeftGravity) {childLeft = parentRight - width - lp.rightMargin;break;}//默认的情况case Gravity.LEFT:default:childLeft = parentLeft + lp.leftMargin;}//垂直方向switch (verticalGravity) {//顶端对齐的场景case Gravity.TOP:childTop = parentTop + lp.topMargin;break;//垂直居中的场景case Gravity.CENTER_VERTICAL:childTop = parentTop + (parentBottom - parentTop - height) / 2 +lp.topMargin - lp.bottomMargin;break;//底部对齐的场景case Gravity.BOTTOM:childTop = parentBottom - height - lp.bottomMargin;break;//默认为顶对齐default:childTop = parentTop + lp.topMargin;}//子ViewGroup进行布局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的上边缘的距离。 
当计算出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函数的源码如下:

public void draw(Canvas canvas) {.............//draw函数的实现细节,可以参考注释/** 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里需要主动配置, 以支持边缘渐变效果boolean horizontalEdges = (viewFlags & FADING_EDGE_HORIZONTAL) != 0;boolean verticalEdges = (viewFlags & FADING_EDGE_VERTICAL) != 0;//一般情况下,不支持这种效果时if (!verticalEdges && !horizontalEdges) {// Step 3, draw the content// 绘制自身内容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;}//支持支持边缘渐变效果时的绘制//与前面不同的地方主要是:需要保存canvas' layer, 增加渐变效果后,再恢复canvas' layer//暂时不深入分析,等研究FADING_EDGE效果时,再来看........
}
  • 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相关的源码, 
故未做进一步分析。以后如果碰到相关的问题,再做进一步的补充。

版权声明:转载请注明:http://blog.csdn.net/gaugamela/article

Android O: View的绘制流程(三):布局和绘制相关推荐

  1. Android自定义View之实现流式布局

    Android自定义View之实现流式布局 运行效果 流式布局 把子控件从左到右摆放,如果一行放不下,自动放到下一行 自定义布局流程 1. 自定义属性:声明,设置,解析获取自定义值 在attr.xml ...

  2. Android源码解析:UI绘制流程之控件绘制

    带着问题看源码 再接再厉,我们来分析UI绘制流程最后一步绘制流程 入口ViewRootImpl.performDraw()方法 private void performDraw() {//...try ...

  3. android的构成和工作流程,分析Android中View的工作流程

    8种机械键盘轴体对比 本人程序员,要买一个写代码的键盘,请问红轴和茶轴怎么选? 在分析View的工作流程时,需要先分析一个很重要的类,MeasureSpec.这个类在View的测量(Measure)过 ...

  4. android音频开发6,Android 音视频开发(一) : 通过三种方式绘制图片

    想要逐步入门音视频开发,就需要一步步的去学习整理,并积累.本文是音视频开发积累的第一篇. 对应的要学习的内容是:在 Android 平台绘制一张图片,使用至少 3 种不同的 API,ImageView ...

  5. android 自定义view: 蛛网/雷达图(三)

    本系列自定义View全部采用kt 系统mac android studio: 4.1.3 kotlin version1.5.0 gradle: gradle-6.5-bin.zip 本篇效果: 蛛网 ...

  6. 工艺流程图绘制流程?试试这样绘制

    工艺流程图绘制流程?绘制工艺流程图可以帮助我们更好地理解工艺流程,确定生产流程,优化生产效率,并帮助人们更好地进行生产管理和质量控制.通过工艺流程图,我们可以清晰地了解每一步骤所需的设备和材料,以及每 ...

  7. Android翻页入门学习(三)阴影绘制

    对于阴影的绘制,首先需要使用的是渐变色的绘制,在Android中,可以使用GradientDrawable对象中的setBounds和draw来进行绘制. 参考文档如下:https://develop ...

  8. 数字IC后端流程——(三)布局Placement

    参考原博地址:https://blog.csdn.net/weixin_46752319/article/details/107360843 ICC布局阶段 在布局规划阶段完成了芯片的整体规划,而布局 ...

  9. ICC图文流程——(三)布局Placement

    ICC布局阶段 在布局规划阶段完成了芯片的整体规划,而布局阶段主要是软件自动的标准单元的摆放. 在布局开始之前,需要对设计进行确认和检查. 主要确认内容包括: ·检查设计中的所有macro是否设置为d ...

最新文章

  1. Datawhale组队学习周报(第012周)
  2. 这应该是你见过的最全前端下载总结
  3. 判断手机浏览器还是微信浏览器(PHP)
  4. mysql max_allowed_packet 查询和修改
  5. 基础知识(五)对齐变换相关函数
  6. java方便适配器_Java适配器
  7. 项目疑难杂症记录(三):EditText获取不到焦点了?
  8. 【新手基础教程】maix asr(自动语音识别)
  9. IDEA开发环境中maven 项目配置使用JDK9,JDK10,JDK11,JDK12等
  10. 360导航源码php,51zxw 仿360网址导航源码
  11. 怎样发表期刊才能快速通过
  12. epsfallback_广东移动通过EPS Fallback方式成功完成了5G高清语音和视频呼叫
  13. 悲观锁与乐观锁的区别 和 Redis中的watch
  14. 【瀑布流插件】vue-masonry
  15. mysql查询数学成绩信息_【MySQL】:利用DQL查询表中的数据
  16. 怎样快速实现整篇文档中英互译
  17. 【剑指Offer】二进制1的个数(减1后的数和原数相与,能将最低位的1置0)
  18. 网络工程师和java工程师,请问做网络工程师与程序员哪个更愉快呢
  19. MP4文件怎么转换GIF动态图?三步搞定
  20. Python实战之小说下载神器(一)看小说怎么能少了这款宝藏神器呢?全网小说书籍随便下,随便看,爆赞(你准备好了吗?)

热门文章

  1. Extjs4.2——Panel
  2. Mybatis学习之单表增删改查
  3. iPhone审核条例
  4. 使用Oracle数据库开发中的一个技巧
  5. 安装模块时提示Collecting package metadata (repodata.json): failed
  6. 一台服务器最多能创建多少个 TCP 连接?
  7. C++输出变量类型、max报错原因
  8. [云炬创业基础笔记]第六章商业模式测试23
  9. python与mysql数据库_python与MySQL数据库
  10. 3DSlicer13:Command Line Interface(CLI)