时间:2021-05-21
一、绘制流程源码路径
1、Activity加载ViewRootImpl
ActivityThread.handleResumeActivity() --> WindowManagerImpl.addView(decorView, layoutParams) --> WindowManagerGlobal.addView()2、ViewRootImpl启动View树的遍历
ViewRootImpl.setView(decorView, layoutParams, parentView)-->ViewRootImpl.requestLayout()-->scheduleTraversals()-->TraversalRunnable.run()-->doTraversal()-->performTraversals()(performMeasure、performLayout、performDraw)二、View绘制流程
1、measure
(1)MeasureSpec是什么?
重写过onMeasure()方法都知道,测量需要用到MeasureSpec类获取View的测量模式和大小,那么这个类是怎样存储这两个信息呢?
留心观察的话会发现,onMeasure方法的两个参数实际是32位int类型数据,即:
00 000000 00000000 00000000 00000000
而其结构为 mode + size ,前2位为mode,而后30位为size。
==> getMode()方法(measureSpec --> mode):
private static final int MODE_SHIFT = 30;// 0x3转换为二进制即为:11// 左移30位后:11000000 00000000 00000000 00000000private static final int MODE_MASK = 0x3 << MODE_SHIFT;public static int getMode(int measureSpec) { // 与MODE_MASK按位与运算后,即将低30位清零,结果为mode左移30位后的值 return (measureSpec & MODE_MASK);}getSize()方法同理。
==> makeMeasureSpec()方法(mode + size --> measureSpec):
public static int makeMeasureSpec( @IntRange(from = 0, to = (1 << MeasureSpec.MODE_SHIFT) - 1) int size, @MeasureSpecMode int mode) { if (sUseBrokenMakeMeasureSpec) { return size + mode; } else { return (size & ~MODE_MASK) | (mode & MODE_MASK); }}这里解释一下,按位或左侧为size的高2位清零后的结果,右侧为mode的低30位清零后的结果,两者按位或运算的结果正好为高2位mode、低30位size,例:
01000000 00000000 00000000 00000000 | 00001000 00001011 11110101 10101101 =01001000 00001011 11110101 10101101二进制计算规则可参考:
==> 测量模式:
public static final int UNSPECIFIED = 0 << MODE_SHIFT;public static final int EXACTLY = 1 << MODE_SHIFT;public static final int AT_MOST = 2 << MODE_SHIFT;UNSPECIFIED:父容器不对View作任何限制,系统内部使用。
EXACTLY:精确模式,父容器检测出View大小,即为SpecSize;对应LayoutParams中的match_parent和指定大小的情况。
AT_MOST:最大模式,父容器指定可用大小,View的大小不能超出这个值;对应wrap_content。
(2)ViewGroup的测量流程
回到ViewRootImpl的performMeasure方法,这里传入的参数为顶层DecorView的测量规格,其测量方式为:
private static int getRootMeasureSpec(int windowSize, int rootDimension) { int measureSpec; switch (rootDimension) { case ViewGroup.LayoutParams.MATCH_PARENT: measureSpec = MeasureSpec.makeMeasureSpec(windowSize, MeasureSpec.EXACTLY); break; case ViewGroup.LayoutParams.WRAP_CONTENT: measureSpec = MeasureSpec.makeMeasureSpec(windowSize, MeasureSpec.AT_MOST); break; default: measureSpec = MeasureSpec.makeMeasureSpec(rootDimension, MeasureSpec.EXACTLY); break; } return measureSpec;}match_parent和具体数值大小为EXACTLY模式,wrap_content则为AT_MOST模式。
往下走,performMeasure方法中调用了DecorView的onMeasure方法,而DecorView继承自FrameLayout,可以看到FL的onMeasure方法中调用了measureChildWithMargins方法,并传入自身的测量规格:
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);}即测量子控件的大小,测量规则详情可看getChildMeasureSpec方法,总结如下:
childLayoutParams\parentSpecMode EXACTLY AT_MOST UNSPECIFIED dp EXACTLY/childSize EXACTLY/childSize EXCATLY/childSize match_parent EXACTLY/parentSize AT_MOST/parentSize UNSPECIFIED/0 wrap_content AT_MOST/parentSize AT_MOST/parentSize UNSPECIFIED/0
回到onMeasure方法,测完子控件之后,ViewGroup会经过一些计算,得出自身大小:
// 加上paddingmaxWidth += getPaddingLeftWithForeground() + getPaddingRightWithForeground();maxHeight += getPaddingTopWithForeground() + getPaddingBottomWithForeground();// 检查是否小于最小宽度、最小高度maxHeight = Math.max(maxHeight, getSuggestedMinimumHeight());maxWidth = Math.max(maxWidth, getSuggestedMinimumWidth());// 检查Drawable的最小高度和宽度final Drawable drawable = getForeground();if (drawable != null) { maxHeight = Math.max(maxHeight, drawable.getMinimumHeight()); maxWidth = Math.max(maxWidth, drawable.getMinimumWidth());}setMeasuredDimension(resolveSizeAndState(maxWidth, widthMeasureSpec, childState), resolveSizeAndState(maxHeight, heightMeasureSpec, childState << MEASURED_HEIGHT_STATE_SHIFT));综上,ViewGroup的测量需要先测量子View的大小,而后结合padding等属性计算得出自身大小。
(3)View的测量流程
View.performMeasure()-->onMeasure(int widthMeasureSpec, int heightMeasureSpec)-->setMeasuredDimension(int measuredWidth, int measuredHeight)-->setMeasuredDimensionRaw(int measuredWidth, int measuredHeight)可以看到setMeasuredDimensionRaw()方法:
private void setMeasuredDimensionRaw(int measuredWidth, int measuredHeight) { // 存储测量结果 mMeasuredWidth = measuredWidth; mMeasuredHeight = measuredHeight; // 设置测量完成的标志位 mPrivateFlags |= PFLAG_MEASURED_DIMENSION_SET;}View不需要考虑子View的大小,根据内容测量得出自身大小即可。
另外,View中的onMeasure方法中调用到getDefaultSize方法:
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { setMeasuredDimension(getDefaultSize(getSuggestedMinimumWidth(), widthMeasureSpec), getDefaultSize(getSuggestedMinimumHeight(), heightMeasureSpec));}public static int getDefaultSize(int size, int measureSpec) { int result = size; int specMode = MeasureSpec.getMode(measureSpec); int specSize = MeasureSpec.getSize(measureSpec); switch (specMode) { case MeasureSpec.UNSPECIFIED: result = size; break; case MeasureSpec.AT_MOST: case MeasureSpec.EXACTLY: // 最终测量的结果都是父容器的大小 result = specSize; break; } return result;}这里看到精确模式和最大模式,最终测量的结果都是父容器的大小,即布局中的wrap_content、match_parent以及数值大小效果都一样,这也就是自定义View一定要重写onMeasure方法的原因。
2、layout
布局相对测量而言要简单许多,从ViewRootImpl的performLayout方法出发,可以看到其中调用了DecorView的layout方法:
// 实则为DecorView的left, top, right, bottom四个信息host.layout(0, 0, host.getMeasuredWidth(), host.getMeasuredHeight());进入layout方法,发现l、t、r、b被传递到了setFrame方法中,并设置给了成员变量:
mLeft = left;mTop = top;mRight = right;mBottom = bottom;所以,布局实际为调用View的layout方法,设置自身的l、t、r、b值。另外,layout方法中往下走,可以看到调用了onLayout方法,进入后发现为空方法。因而查看FrameLayout的onLayout方法:
@Overrideprotected void onLayout(boolean changed, int left, int top, int right, int bottom) { layoutChildren(left, top, right, bottom, false );}void layoutChildren(int left, int top, int right, int bottom, boolean forceLeftGravity) { final int count = getChildCount(); // 省略 for (int i = 0; i < count; i++) { final View child = getChildAt(i); if (child.getVisibility() != GONE) { final LayoutParams lp = (LayoutParams) child.getLayoutParams(); // 省略 child.layout(childLeft, childTop, childLeft + width, childTop + height); } }}可以看到,进行一系列计算后,调用了child的layout方法,对子控件进行布局,同时子控件又会继续往下对自己的子控件布局,从而实现遍历。
综上,布局实际为调用layout方法设置View位置,ViewGroup则需要另外实现onLayout方法摆放子控件。
3、draw
(1)绘制过程入口
ViewRootImpl.performDraw()-->ViewRootImpl.draw()-->ViewRootImpl.drawSoftware()-->View.draw()(2)绘制步骤
进入到View的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) */结合draw方法的源码,绘制过程的关键步骤如下:
感谢大家的阅读和对的支持。
声明:本页内容来源网络,仅供用户参考;我单位不保证亦不表示资料全面及准确无误,也不保证亦不表示这些资料为最新信息,如因任何原因,本网内容或者用户因倚赖本网内容造成任何损失或损害,我单位将不会负任何法律责任。如涉及版权问题,请提交至online#300.cn邮箱联系删除。
前言:前面几篇讲了自定义控件绘制原理Android自定义控件基本原理详解(一),Android自定义控件之自定义属性(二),Android自定义控件之自定义组合
java仿Servlet生成验证码实例详解实现原理:使用BufferedImage对象的Graphics来进行绘制,然后输出成一张图片进行保存实现代码及详解:p
Android注解相关文章:AndroidAOP注解Annotation详解(一)AndroidAOP之注解处理解释器详解(二)AndroidAOP注解详解及简
Android详解ThreadLocal及InheritableThreadLocal概要:因为在android中经常用到handler来处理异步任务,通常用于
前言在学习android中图形图像处理技术这部分内容时,对绘制圆弧函数canvas.drawArc()的用法、参数含义及画图原理很是不理解,在网上搜索了一些,加