(二)UI绘制流程-绘制过程源码分析
2017-05-19 17:16
483 查看
版权声明:本文为博主原创文章,未经博主允许不得转载。
本文纯个人学习笔记,由于水平有限,难免有所出错,有发现的可以交流一下。
从上一篇可以可以知道,UI的绘制流程的起点在 ViewRootImpl 下的performTraversals()方法。并且按顺序调用了 performMeasure(),performLayout()和 performDraw()这三个方法。
测量模式分三种:
点击查看 MeasureSpec 下的 makeMeasureSpec() 方法,返回基于 size 跟 mode 的一个测量规范。
安卓绘制的时候是从最外层开始绘制,然后再一层层往下绘制子控件。这时候父容器的布局就会影响到子容器的布局,MeasureSpec 在这里传递父容器布局对子容器布局的一些限制。
我们点击查看 getRootMeasureSpec() 方法。
getRootMeasureSpec()是用于获取测量跟节点的测量规范,由上一篇我们知道这里的根节点就是我们的 DecorView,并且 DecorView 的布局为 MATCH_PARENT,所以走第一个分支。
2.点击查看我们的 performMeasure()方法。
这时候我们的 mView 是 DecorView,DecorView 本身没有 measure()方法,最终会调到 View 里面的 measure()方法。首先会去判断该容器是否有光影的效果(边沿阴影),有的话会加上去光影的宽高。
在 measure()方法里面没有去进行具体的测量,具体的测量是在 onMeasure()这个方法里面进行。
这是 View 中的 onMeasure(),调用了一个 setMeasuredDimension()方法, setMeasuredDimension() 又直接调用 setMeasuredDimensionRaw(),在这里面进行宽高的直接赋值。getMeasuredWidth()获取的值就是在这里的赋值,所以 getMeasuredWidth()必须在 onMeasure()之后。
当 DecorView 调用 onMeasure()的时候会调用到 FrameLayout 中的 onMeasure()方法。
在 FrameLayout 中的 onMeasure()中,先通过一个 for 循环对所有的子容器进行遍历。其中有个判断 child.getVisibility() != GONE 的时候才进行测量,这也是为什么 Visibility 设置为 GONE 的时候不占位置的原因。
每个子布局会调用 measureChildWithMargins()这个方法,在 measureChildWithMargins()最后又调用 measure()这个方法,如果子容器是 ViewGroup 的话重复上述布局,如果是非 ViewGroup 的 View 容器最终调到前面讲的 View 的 onMeasure()方法,从而结束。
这是一个递归的过程,跟上一篇中加载布局是一样的。测量完成之后一样会调用 setMeasuredDimension() 方法进行测量大小的设置。
查看 getChildMeasureSpec()这个方法,getChildMeasureSpec()是根据当前父容器的测量规格(里面有测量模式和测量大小)以及子容器的一些属性来确定子容器的测量规格。子容器的测量规格受父容器的测量规格影响。
非 ViewGroup 的 View 的测量:onMeasure 方法里面调用 setMeasuredDimension()确定当前View的大小。
ViewGroup 的测量:1、通过 measureChildWithMargins()、measureChild() 或 measureChildren()三个方法来遍历测量 Child。2、setMeasuredDimension 确定当前ViewGroup的大小。
我们获取容器的宽度 getWidth() 方法是直接返回 右边沿 - 左边沿,所以 getWidth() 方法要在 Layout()方法之后使用。
可以看见在 View 中 onLayout()是一个空方法,什么都没做。在 ViewGroup 中 onLayout()是一个抽象方法,我们继续查看他的实现类 FrameLayout 下 onLayout()的实现。
FrameLayout 下 onLayout()直接调用 layoutChildren(),在这里面也是一个 for 循环对子容器的遍历,根据 Gravity 属性对子容器的上下左右边沿进行处理。这是 Gravity 属性生效的地方。最后调用各个子容器的 layout(),子容器是一个 ViewGroup 的话一样又进行了递归。
这是 View 的 draw()方法,很只是很清楚的写出了主要要做的事情。在这里,主要是对背景进行绘制。
本文纯个人学习笔记,由于水平有限,难免有所出错,有发现的可以交流一下。
从上一篇可以可以知道,UI的绘制流程的起点在 ViewRootImpl 下的performTraversals()方法。并且按顺序调用了 performMeasure(),performLayout()和 performDraw()这三个方法。
一、MeasureSpec 测量规格
位于 View 类的内部类,由于在安卓布局中有自适应尺寸,而不是全部都是固定的宽高,所以在这里引入 MeasureSpec 来进行测量。MeasureSpec 主要包含的信息是 测量模mode 和 测量大小size 。MeasureSpec 是一个 32 位的数据,高2位表示 mode, 低30位表示 size 。测量模式分三种:
EXACTLY :父容器已经测量出所需要的精确大小,这也是 childview 的最终大小 ------match_parent,精确值 ATMOST : child view 最终的大小不能超过父容器的给的 ------wrap_content UNSPECIFIED: 不确定,没有限制 -------一般在 ScrollView,ListView
点击查看 MeasureSpec 下的 makeMeasureSpec() 方法,返回基于 size 跟 mode 的一个测量规范。
public static int makeMeasureSpec(int size, int mode) { if (sUseBrokenMakeMeasureSpec) { return size + mode; } else { return (size & ~MODE_MASK) | (mode & MODE_MASK); } }
安卓绘制的时候是从最外层开始绘制,然后再一层层往下绘制子控件。这时候父容器的布局就会影响到子容器的布局,MeasureSpec 在这里传递父容器布局对子容器布局的一些限制。
二、Measure()
1.在 performTraversals()下调用 performMeasure() 方法会传进去两个参数,这两个参数在调用的上面初始化。int childWidthMeasureSpec = getRootMeasureSpec(mWidth, lp.width); int childHeightMeasureSpec = getRootMeasureSpec(mHeight, lp.height);
我们点击查看 getRootMeasureSpec() 方法。
private static int getRootMeasureSpec(int windowSize, int rootDimension) { int measureSpec; switch (rootDimension) { case ViewGroup.LayoutParams.MATCH_PARENT: // Window can't resize. Force root view to be windowSize. measureSpec = MeasureSpec.makeMeasureSpec(windowSize, MeasureSpec.EXACTLY); break; case ViewGroup.LayoutParams.WRAP_CONTENT: // Window can resize. Set max size for root view. measureSpec = MeasureSpec.makeMeasureSpec(windowSize, MeasureSpec.AT_MOST); break; default: // Window wants to be an exact size. Force root view to be that size. measureSpec = MeasureSpec.makeMeasureSpec(rootDimension, MeasureSpec.EXACTLY); break; } return measureSpec; }
getRootMeasureSpec()是用于获取测量跟节点的测量规范,由上一篇我们知道这里的根节点就是我们的 DecorView,并且 DecorView 的布局为 MATCH_PARENT,所以走第一个分支。
2.点击查看我们的 performMeasure()方法。
private void performMeasure(int childWidthMeasureSpec, int childHeightMeasureSpec) { Trace.traceBegin(Trace.TRACE_TAG_VIEW, "measure"); try { mView.measure(childWidthMeasureSpec, childHeightMeasureSpec); } finally { Trace.traceEnd(Trace.TRACE_TAG_VIEW); } }
这时候我们的 mView 是 DecorView,DecorView 本身没有 measure()方法,最终会调到 View 里面的 measure()方法。首先会去判断该容器是否有光影的效果(边沿阴影),有的话会加上去光影的宽高。
boolean optical = isLayoutModeOptical(this); if (optical != isLayoutModeOptical(mParent)) { Insets insets = getOpticalInsets(); int oWidth = insets.left + insets.right; int oHeight = insets.top + insets.bottom; widthMeasureSpec = MeasureSpec.adjust(widthMeasureSpec, optical ? -oWidth : oWidth); heightMeasureSpec = MeasureSpec.adjust(heightMeasureSpec, optical ? -oHeight : oHeight); }
在 measure()方法里面没有去进行具体的测量,具体的测量是在 onMeasure()这个方法里面进行。
这是 View 中的 onMeasure(),调用了一个 setMeasuredDimension()方法, setMeasuredDimension() 又直接调用 setMeasuredDimensionRaw(),在这里面进行宽高的直接赋值。getMeasuredWidth()获取的值就是在这里的赋值,所以 getMeasuredWidth()必须在 onMeasure()之后。
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { setMeasuredDimension(getDefaultSize(getSuggestedMinimumWidth(), widthMeasureSpec), getDefaultSize(getSuggestedMinimumHeight(), heightMeasureSpec)); }
当 DecorView 调用 onMeasure()的时候会调用到 FrameLayout 中的 onMeasure()方法。
在 FrameLayout 中的 onMeasure()中,先通过一个 for 循环对所有的子容器进行遍历。其中有个判断 child.getVisibility() != GONE 的时候才进行测量,这也是为什么 Visibility 设置为 GONE 的时候不占位置的原因。
for (int i = 0; i < count; i++) { final View child = getChildAt(i); if (mMeasureAllChildren || child.getVisibility() != GONE) { measureChildWithMargins(child, widthMeasureSpec, 0, heightMeasureSpec, 0); final LayoutParams lp = (LayoutParams) child.getLayoutParams(); maxWidth = Math.max(maxWidth, child.getMeasuredWidth() + lp.leftMargin + lp.rightMargin); maxHeight = Math.max(maxHeight, child.getMeasuredHeight() + lp.topMargin + lp.bottomMargin); childState = combineMeasuredStates(childState, child.getMeasuredState()); if (measureMatchParentChildren) { if (lp.width == LayoutParams.MATCH_PARENT || lp.height == LayoutParams.MATCH_PARENT) { mMatchParentChildren.add(child); } } } }
每个子布局会调用 measureChildWithMargins()这个方法,在 measureChildWithMargins()最后又调用 measure()这个方法,如果子容器是 ViewGroup 的话重复上述布局,如果是非 ViewGroup 的 View 容器最终调到前面讲的 View 的 onMeasure()方法,从而结束。
protected void measureChildWithMargins(View child, int parentWidthMeasureSpec, int widthUsed, int parentHeightMeasureSpec, int heightUsed) { final MarginLayoutParams lp = (MarginLayoutParams) child.getLayoutParams(); final int childWidthMeasureSpec = getChildMeasureSpec(parentWidthMeasureSpec, mPaddingLeft + mPaddingRight + lp.leftMargin + lp.rightMargin + widthUsed, lp.width); final int childHeightMeasureSpec = getChildMeasureSpec(parentHeightMeasureSpec, mPaddingTop + mPaddingBottom + lp.topMargin + lp.bottomMargin + heightUsed, lp.height); child.measure(childWidthMeasureSpec, childHeightMeasureSpec); }
这是一个递归的过程,跟上一篇中加载布局是一样的。测量完成之后一样会调用 setMeasuredDimension() 方法进行测量大小的设置。
查看 getChildMeasureSpec()这个方法,getChildMeasureSpec()是根据当前父容器的测量规格(里面有测量模式和测量大小)以及子容器的一些属性来确定子容器的测量规格。子容器的测量规格受父容器的测量规格影响。
非 ViewGroup 的 View 的测量:onMeasure 方法里面调用 setMeasuredDimension()确定当前View的大小。
ViewGroup 的测量:1、通过 measureChildWithMargins()、measureChild() 或 measureChildren()三个方法来遍历测量 Child。2、setMeasuredDimension 确定当前ViewGroup的大小。
三、Layout()
performTraversals()下调用完 performMeasure() 后续调用 performLayout()方法。主要的内容在 Layout()方法里,在 Layout()方法里,确认了容器布局的左上右下四个边沿的值,然后调用容器的 onLayout()方法。我们获取容器的宽度 getWidth() 方法是直接返回 右边沿 - 左边沿,所以 getWidth() 方法要在 Layout()方法之后使用。
可以看见在 View 中 onLayout()是一个空方法,什么都没做。在 ViewGroup 中 onLayout()是一个抽象方法,我们继续查看他的实现类 FrameLayout 下 onLayout()的实现。
FrameLayout 下 onLayout()直接调用 layoutChildren(),在这里面也是一个 for 循环对子容器的遍历,根据 Gravity 属性对子容器的上下左右边沿进行处理。这是 Gravity 属性生效的地方。最后调用各个子容器的 layout(),子容器是一个 ViewGroup 的话一样又进行了递归。
四、Draw()
performTraversals()下调用完 performLayout() 后续调用 performDraw()方法。一样的主要在 draw()方法里,在 draw()方法里真正开始绘制是在最后面调用的 drawSoftware()方法里。在这里有会调用的 DecorView 的 draw()方法,由于 DecorView 本身没有这个方法,从而就调用到了 View 的 draw()方法。这是 View 的 draw()方法,很只是很清楚的写出了主要要做的事情。在这里,主要是对背景进行绘制。
public void draw(Canvas canvas) { final int privateFlags = mPrivateFlags; final boolean dirtyOpaque = (privateFlags & PFLAG_DIRTY_MASK) == PFLAG_DIRTY_OPAQUE && (mAttachInfo == null || !mAttachInfo.mIgnoreDirtyState); mPrivateFlags = (privateFlags & ~PFLAG_DIRTY_MASK) | PFLAG_DRAWN; /* * 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 needed int saveCount; if (!dirtyOpaque) { drawBackground(canvas); } // skip step 2 & 5 if possible (common case) final int viewFlags = mViewFlags; 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 dispatchDraw(canvas); // Step 6, draw decorations (scrollbars) onDrawScrollBars(canvas); if (mOverlay != null && !mOverlay.isEmpty()) { mOverlay.getOverlayView().dispatchDraw(canvas); } // we're done... return; }
相关文章推荐
- UI绘制流程分析(源码级分析)
- 源码分析android的UI绘制流程
- (一)UI绘制流程-源码分析
- Android中将布局文件/View添加至窗口过程分析 ---- 从setContentView()谈起(写的很好,这个不是从启动app说的,说的是UI是怎么绘制的)
- Android应用层View绘制流程与源码分析
- Android应用层View绘制流程与源码分析
- 源码分析Android 中ImageView的设置src与background绘制流程
- Android应用层View绘制流程与源码分析
- Android应用层View绘制流程与源码分析
- android应用程序窗口框架学习(2)-view绘制流程源代码解析-setContentView与LayoutInflater加载解析机制源码分析
- Android应用层View绘制流程与源码分析
- View绘制流程与源码分析
- Activity的绘制流程简单分析(基于android 4.0源码进行分析)
- Android应用层View绘制流程与源码分析
- 浅析android锁屏开机绘制流程(基于android4.0源码分析) .
- Android应用层View绘制流程与源码分析
- Duilib源码分析(四)绘制管理器—CPaintManagerUI—(前期准备三)
- Duilib源码分析(四)绘制管理器—CPaintManagerUI—(前期准备四)
- View的源码分析(绘制流程以及刷新机制)
- Android应用层View绘制流程与源码分析