view的绘制流程
来源:互联网 发布:邓肯为什么不笑 知乎 编辑:程序博客网 时间:2024/06/05 15:27
Measure
当我们启动一个应用时,会启动一个主Activity,Android会根据我们的Activity布局进行绘制,绘制从ViewRootImpl类中看到performTraversals方法开始
private void performTraversals(){ int childWidthMeasureSpec = getRootMeasureSpec(mWidth,lp.width); int childHeightMeasureSpec = getRootMeasureSpec(mHeight,lp.height);.... //执行测量流程 performMeasure(childWidthMeasureSpec,childHeightMeasureSpec); ... //执行布局流程 performLayout(childWidthMeasureSpec,childHeightMeasureSpec); ... //执行绘制流程 performDraw(childWidthMeasureSpec,childHeightMeasureSpec);}
自定义view我们都知道重写onMeasure
,onLayout
,onDraw
,但我们总是在写onMeasure
时分类讨论,其实都是一些固定的格式,但身为程序员,我们总是想要知道为什么这么写。
首先我们先介绍一下MeasureSpec
,它表示的是一个32位的整形值,最高两位表示测量的模式SpecMode
,低三十位表示某种测量模式下的规格大小SpecSize
.
public static class MeasureSpec { private static final int MODE_SHIFT = 30; private static final int MODE_MASK = 0x3 << MODE_SHIFT; /** @hide */ @IntDef({UNSPECIFIED, EXACTLY, AT_MOST}) @Retention(RetentionPolicy.SOURCE) public @interface MeasureSpecMode {} /** * Measure specification mode: The parent has not imposed any constraint * on the child. It can be whatever size it wants. */ public static final int UNSPECIFIED = 0 << MODE_SHIFT; /** * Measure specification mode: The parent has determined an exact size * for the child. The child is going to be given those bounds regardless * of how big it wants to be. */ public static final int EXACTLY = 1 << MODE_SHIFT; /** * Measure specification mode: The child can be as large as it wants up * to the specified size. */ public static final int AT_MOST = 2 << MODE_SHIFT; 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); } } .........
MeasureSpec
是View的一个静态内部类。
我们需要重点关注代码中的以下三种测量模式:
- UNSPECIFIED:不指定测量模式,父视图没有限制子视图的大小,子视图可以是想要的任何尺寸,通常用于系统内部,应用开发中很少使用到。
- EXACTLY:精确测量模式,当视图的layout_width或者layout_height指定为具体数值,或者match_parent时生效,表示父视图已经决定了子视图的精确大小,这种情况下View的测量值就是Specsize的值。
- AT_MOST:最大值模式,当该视图的layout_width或者layout_height指定为wrap_content时生效,此时子视图的尺寸可以是不超过父视图允许的最大尺寸的任何尺寸
对于DecorView而言,他的MeasureSpec由窗口尺寸和其自身的LayoutParams共同决定;对于普通的View,他的MeasureSpec由父类的MeasureSpec和其自身的LayoutParams共同决定,我们自定义view在重写onMeasure方法时传入的两个参数就是由父类的MeasureSpec和我们自定义view的LayoutParams合成的。
下面我们可以看ViewGroup的一段代码,看完我们就能理解onMeasure的两个参数如何使用了。
protected void measureChildren(int widthMeasureSpec, int heightMeasureSpec) { final int size = mChildrenCount; final View[] children = mChildren; //循环遍历测量子view的大小 for (int i = 0; i < size; ++i) { final View child = children[i]; //当view的状态为GONE时不执行测量 if ((child.mViewFlags & VISIBILITY_MASK) != GONE) { measureChild(child, widthMeasureSpec, heightMeasureSpec); } } }
protected void measureChild(View child, int parentWidthMeasureSpec, int parentHeightMeasureSpec) { final LayoutParams lp = child.getLayoutParams(); //下面就是根据父类的MeasureSpec组成子view的MeasureSpec final int childWidthMeasureSpec = getChildMeasureSpec(parentWidthMeasureSpec, mPaddingLeft + mPaddingRight, lp.width); final int childHeightMeasureSpec = getChildMeasureSpec(parentHeightMeasureSpec, mPaddingTop + mPaddingBottom, lp.height); child.measure(childWidthMeasureSpec, childHeightMeasureSpec); }
下面我们就看看getChildMeasureSpec是如何组成子类的MeasureSpec
public static int getChildMeasureSpec(int spec, int padding, int childDimension) { int specMode = MeasureSpec.getMode(spec); int specSize = MeasureSpec.getSize(spec); int size = Math.max(0, specSize - padding); int resultSize = 0; int resultMode = 0; switch (specMode) { // Parent has imposed an exact size on us case MeasureSpec.EXACTLY: if (childDimension >= 0) { resultSize = childDimension; resultMode = MeasureSpec.EXACTLY; } else if (childDimension == LayoutParams.MATCH_PARENT) { // Child wants to be our size. So be it. resultSize = size; resultMode = MeasureSpec.EXACTLY; } else if (childDimension == LayoutParams.WRAP_CONTENT) { // Child wants to determine its own size. It can't be // bigger than us. resultSize = size; resultMode = MeasureSpec.AT_MOST; } break; // Parent has imposed a maximum size on us case MeasureSpec.AT_MOST: if (childDimension >= 0) { // Child wants a specific size... so be it resultSize = childDimension; resultMode = MeasureSpec.EXACTLY; } else if (childDimension == LayoutParams.MATCH_PARENT) { // Child wants to be our size, but our size is not fixed. // Constrain child to not be bigger than us. resultSize = size; resultMode = MeasureSpec.AT_MOST; } else if (childDimension == LayoutParams.WRAP_CONTENT) { // Child wants to determine its own size. It can't be // bigger than us. resultSize = size; resultMode = MeasureSpec.AT_MOST; } break; // Parent asked to see how big we want to be case MeasureSpec.UNSPECIFIED: if (childDimension >= 0) { // Child wants a specific size... let him have it resultSize = childDimension; resultMode = MeasureSpec.EXACTLY; } else if (childDimension == LayoutParams.MATCH_PARENT) { // Child wants to be our size... find out how big it should // be resultSize = View.sUseZeroUnspecifiedMeasureSpec ? 0 : size; resultMode = MeasureSpec.UNSPECIFIED; } else if (childDimension == LayoutParams.WRAP_CONTENT) { // Child wants to determine its own size.... find out how // big it should be resultSize = View.sUseZeroUnspecifiedMeasureSpec ? 0 : size; resultMode = MeasureSpec.UNSPECIFIED; } break; } //noinspection ResourceType return MeasureSpec.makeMeasureSpec(resultSize, resultMode); }
从上表可以得出当我们在重写子view的onMeasure时,当传过来的Mode为EXACTLY时,此时传过来的size就是我们想要的size,其他两种情况我们需要自己手动测量大小,此时传过来的size并不是我们想要的大小
而我们在写onMeasure时需要保存测量的值
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { setMeasuredDimension(getDefaultSize(getSuggestedMinimumWidth(), widthMeasureSpec), getDefaultSize(getSuggestedMinimumHeight(), heightMeasureSpec)); }
我们可以在重写的onMeasure方法中调用setMeasuredDimension方法,传入我们想要设置的size或者直接执行super.onMeasure传入我们设置的size大小
Layout
Layout过程用来确定View在父容器的布局位置,它是由父容器获取子view的位置参数后,调用View的layout方法并将位置参数传入实现。ViewRootImpl的performLayout代码如下:
//ViewRootImpl.java private void performLayout(WindowManager.LayoutParams lp, int desiredWindowWidth, int desiredWindowHeight) { ... host.layout(0, 0, host.getMeasuredWidth(), host.getMeasuredHeight()); ... }
//View.java public void layout(int l, int t, int r, int b) { ... onLayout(changed,l,t,r,b); ...
//View.java//这是个空方法,子类如果是ViewGroup类型,则重写这个方法,实现子View的布局流程protected void onLayout(boolean changed, int left, int top, int right, int bottom) { }
Draw
Draw操作用来将控件绘制出来,绘制的流程从performDraw方法开始
//ViewRootImpl.javaprivate void performDraw() { ... draw(fullRedrawNeeded); ...}private void draw(boolean fullRedrawNeeded) {........if (!drawSoftware(surface, mAttachInfo, xOffset, yOffset, scalingRequired, dirty)) { return; } .....} private boolean drawSoftware(Surface surface, AttachInfo attachInfo, int xoff, int yoff, boolean scalingRequired, Rect dirty) { ... mView.draw(canvas); ... }
可以看到最终调用到每个View的draw方法绘制每个具体的View,绘制基本可以分为六个步骤
//View.java public void draw(Canvas canvas) { ... //步骤一:绘制View背景 drawBackground(canvas); ...//步骤二:如果需要的话,保存canvas的图层,为fading做准备saveCount = canvas.getSaveCount();....canvas.saveLayer(left,top,right,top+length,null,flags);....//步骤三:绘制View的内容onDraw();...//步骤四:绘制View的子ViewdispatchDraw();...//步骤五:如果需要的话,绘制View的fading边缘并恢复图层canvas.drawRect(left,top,right,top+length,p);...canvas.restoreToCount();..//步骤六:绘制view的装饰(例如滚动条)onDrawScrollBars(canvas);
- view的绘制流程
- View 的绘制流程
- View的绘制流程
- View的绘制流程
- View的绘制流程
- View的绘制流程
- View的绘制流程
- View的绘制流程
- view的绘制流程
- View的绘制流程
- View 的绘制流程
- View的绘制流程
- View 的绘制流程
- View的绘制流程
- View的绘制流程
- View的绘制流程
- View的绘制流程
- View的绘制流程
- Android 运行时权限处理封装在 BaseActivity 中,方便业务申请时只需要简单的 1,2 行代码即可成功处理权限申请
- 那些强悍的PHP一句话后门
- 【android实用工具 二】 自定义圆形文字头像
- bzoj 1677: [Usaco2005 Jan]Sumsets 求和(DP)
- Android Tv 中的按键事件 KeyEvent 分发处理流程
- view的绘制流程
- bzoj3562 瞎搞+并查集+dfs
- eclipse gradle Path for project must have only one segment的问题
- 【Junit】之 仿 junit 3.8.1 写 lite-junit
- 修改PHP上传SIZE限制
- JSON Web Token
- 利用jenkins+maven部署java应用到远程服务器
- 【JSOI2008】星球大战 (并查集)
- Kotlin快速入门(1) -- 与Java的对比学习