Android O: View的绘制流程(三):布局和绘制
前一篇文章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相关的源码,
故未做进一步分析。以后如果碰到相关的问题,再做进一步的补充。
Android O: View的绘制流程(三):布局和绘制相关推荐
- Android自定义View之实现流式布局
Android自定义View之实现流式布局 运行效果 流式布局 把子控件从左到右摆放,如果一行放不下,自动放到下一行 自定义布局流程 1. 自定义属性:声明,设置,解析获取自定义值 在attr.xml ...
- Android源码解析:UI绘制流程之控件绘制
带着问题看源码 再接再厉,我们来分析UI绘制流程最后一步绘制流程 入口ViewRootImpl.performDraw()方法 private void performDraw() {//...try ...
- android的构成和工作流程,分析Android中View的工作流程
8种机械键盘轴体对比 本人程序员,要买一个写代码的键盘,请问红轴和茶轴怎么选? 在分析View的工作流程时,需要先分析一个很重要的类,MeasureSpec.这个类在View的测量(Measure)过 ...
- android音频开发6,Android 音视频开发(一) : 通过三种方式绘制图片
想要逐步入门音视频开发,就需要一步步的去学习整理,并积累.本文是音视频开发积累的第一篇. 对应的要学习的内容是:在 Android 平台绘制一张图片,使用至少 3 种不同的 API,ImageView ...
- android 自定义view: 蛛网/雷达图(三)
本系列自定义View全部采用kt 系统mac android studio: 4.1.3 kotlin version1.5.0 gradle: gradle-6.5-bin.zip 本篇效果: 蛛网 ...
- 工艺流程图绘制流程?试试这样绘制
工艺流程图绘制流程?绘制工艺流程图可以帮助我们更好地理解工艺流程,确定生产流程,优化生产效率,并帮助人们更好地进行生产管理和质量控制.通过工艺流程图,我们可以清晰地了解每一步骤所需的设备和材料,以及每 ...
- Android翻页入门学习(三)阴影绘制
对于阴影的绘制,首先需要使用的是渐变色的绘制,在Android中,可以使用GradientDrawable对象中的setBounds和draw来进行绘制. 参考文档如下:https://develop ...
- 数字IC后端流程——(三)布局Placement
参考原博地址:https://blog.csdn.net/weixin_46752319/article/details/107360843 ICC布局阶段 在布局规划阶段完成了芯片的整体规划,而布局 ...
- ICC图文流程——(三)布局Placement
ICC布局阶段 在布局规划阶段完成了芯片的整体规划,而布局阶段主要是软件自动的标准单元的摆放. 在布局开始之前,需要对设计进行确认和检查. 主要确认内容包括: ·检查设计中的所有macro是否设置为d ...
最新文章
- Datawhale组队学习周报(第012周)
- 这应该是你见过的最全前端下载总结
- 判断手机浏览器还是微信浏览器(PHP)
- mysql max_allowed_packet 查询和修改
- 基础知识(五)对齐变换相关函数
- java方便适配器_Java适配器
- 项目疑难杂症记录(三):EditText获取不到焦点了?
- 【新手基础教程】maix asr(自动语音识别)
- IDEA开发环境中maven 项目配置使用JDK9,JDK10,JDK11,JDK12等
- 360导航源码php,51zxw 仿360网址导航源码
- 怎样发表期刊才能快速通过
- epsfallback_广东移动通过EPS Fallback方式成功完成了5G高清语音和视频呼叫
- 悲观锁与乐观锁的区别 和 Redis中的watch
- 【瀑布流插件】vue-masonry
- mysql查询数学成绩信息_【MySQL】:利用DQL查询表中的数据
- 怎样快速实现整篇文档中英互译
- 【剑指Offer】二进制1的个数(减1后的数和原数相与,能将最低位的1置0)
- 网络工程师和java工程师,请问做网络工程师与程序员哪个更愉快呢
- MP4文件怎么转换GIF动态图?三步搞定
- Python实战之小说下载神器(一)看小说怎么能少了这款宝藏神器呢?全网小说书籍随便下,随便看,爆赞(你准备好了吗?)
热门文章
- Extjs4.2——Panel
- Mybatis学习之单表增删改查
- iPhone审核条例
- 使用Oracle数据库开发中的一个技巧
- 安装模块时提示Collecting package metadata (repodata.json): failed
- 一台服务器最多能创建多少个 TCP 连接?
- C++输出变量类型、max报错原因
- [云炬创业基础笔记]第六章商业模式测试23
- python与mysql数据库_python与MySQL数据库
- 3DSlicer13:Command Line Interface(CLI)