上一篇中我们讲到了Android的触摸事件传递机制,除此之外,关于AndroID VIEw的绘制流程这一块也是VIEw相关的核心知识点。我们都知道,PhoneWindow是AndroID系统中最基本的窗口系统,每个Activity会创建一个。同时,PhoneWindow也是Activity和VIEw系统交互的接口。DecorVIEw本质上是一个FrameLayout,是Activity中所有VIEw的祖先。
一、开始:DecorVIEw被加载到Window中从Activity的startActivity开始,最终调用到ActivityThread的handleLaunchActivity方法来创建Activity,相关核心代码如下:
private voID handleLaunchActivity(ActivityClIEntRecord r, Intent customIntent) { .... // 创建Activity,会调用Activity的onCreate方法 // 从而完成DecorVIEw的创建 Activity a = performlaunchActivity(r, customIntent); if (a != null) { r.createdConfig = new Configuration(mConfiguration); Bundle oldState = r.state; handleResumeActivity(r.tolen, false, r.isForward, !r.activity..mFinished && !r.startsNotResumed); }}final voID handleResumeActivity(IBinder token, boolean clearHIDe, boolean isForward, boolean reallyResume) { unscheduleGcIDler(); mSomeActivitIEsChanged = true; // 调用Activity的onResume方法 ActivityClIEntRecord r = performResumeActivity(token, clearHIDe); if (r != null) { final Activity a = r.activity; ... if (r.window == null &&& !a.mFinished && willBeVisible) { r.window = r.activity.getwindow(); // 得到DecorVIEw VIEw decor = r.window.getDecorVIEw(); decor.setVisibility(VIEw.INVISIBLE); // 得到了WindowManager,WindowManager是一个接口 // 并且继承了接口VIEwManager VIEwManager wm = a.getwindowManager(); WindowManager.LayoutParams l = r.window.getAttributes(); a.mDecor = decor; l.type = WindowManager.LayoutParams.TYPE_BASE_APPliCATION; l.softinputMode |= forwardBit; if (a.mVisibleFromClIEnt) { a.mWindowAdded = true; // WindowManager的实现类是WindowManagerImpl, // 所以实际调用的是WindowManagerImpl的addVIEw方法 wm.addVIEw(decor, l); } } }}public final class WindowManagerImpl implements WindowManager { private final WindowManagerGlobal mGlobal = WindowManagerGlobal.getInstance(); ... @OverrIDe public voID addVIEw(@NonNull VIEw vIEw, @NonNull VIEwGroup.LayoutParams params) { applyDefaultToken(params); mGlobal.addVIEw(vIEw, params, mdisplay, mParentwindow); } ...}
在了解VIEw绘制的整体流程之前,我们必须先了解下VIEwRoot和DecorVIEw的概念。VIEwRoot对应于VIEwRootImpl类,它是连接WindowManager和DecorVIEw的纽带,VIEw的三大流程均是通过VIEwRoot来完成的。在ActivityThread中,当Activity对象被创建完毕后,会将DecorVIEw添加到Window中,同时会创建VIEwRootImpl对象,并将VIEwRootImpl对象和DecorVIEw建立关联,相关源码如下所示:
// WindowManagerGlobal的addVIEw方法public voID addVIEw(VIEw vIEw, VIEwGroup.LayoutParams params, display display, Window parentwindow) { ... VIEwRootImpl root; VIEw pannelParentVIEw = null; synchronized (mlock) { ... // 创建VIEwRootImpl实例 root = new VIEwRootImpl(vIEw..getContext(), display); vIEw.setLayoutParams(wparams); mVIEws.add(vIEw); mRoots.add(root); mParams.add(wparams); } try { // 把DecorVIEw加载到Window中 root.setVIEw(vIEw, wparams, panelParentVIEw); } catch (RuntimeException e) { synchronized (mlock) { final int index = findVIEwLocked(vIEw, false); if (index >= 0) { removeVIEwLocked(index, true); } } throw e; }}
二、了解绘制的整体流程绘制会从根视图VIEwRoot的performTraversals()方法开始,从上到下遍历整个视图树,每个VIEw控件负责绘制自己,而VIEwGroup还需要负责通知自己的子VIEw进行绘制 *** 作。performTraversals()的核心代码如下。
private voID performTraversals() { ... int chilDWIDthMeasureSpec = getRootMeasureSpec(mWIDth, lp.wIDth); int childHeightmeasureSpec = getRootMeasureSpec(mHeight, lp.height); ... //执行测量流程 performMeasure(chilDWIDthMeasureSpec, childHeightmeasureSpec); ... //执行布局流程 performlayout(lp, desireDWindowWIDth, desireDWindowHeight); ... //执行绘制流程 performDraw();}
performTraversals的大致工作流程图如下所示:
显示不出来的可点击这里查看
注意:
preformlayout和performDraw的传递流程和performMeasure是类似的,唯一不同的是,performDraw的传递过程是在draw方法中通过dispatchDraw来实现的,不过这并没有本质区别。获取content:
VIEwGroup content = (VIEwGroup)findVIEwByID(androID.R.ID.content);
获取设置的VIEw:
content.getChildAt(0);
三、理解MeasureSpec1.MeasureSpec源码解析MeasureSpec表示的是一个32位的int值,它的高2位表示测量模式SpecMode,低30位表示某种测量模式下的规格大小Specsize。MeasureSpec是VIEw类的一个静态内部类,用来说明应该如何测量这个VIEw。MeasureSpec的核心代码如下。
public static class MeasureSpec { private static final int MODE_SHIFT = 30; private static final int MODE_MASK = 0X3 << MODE_SHIFT; // 不指定测量模式, 父视图没有限制子视图的大小,子视图可以是想要 // 的任何尺寸,通常用于系统内部,应用开发中很少用到。 public static final int UnspecIFIED = 0 << MODE_SHIFT; // 精确测量模式,视图宽高指定为match_parent或具体数值时生效, // 表示父视图已经决定了子视图的精确大小,这种模式下VIEw的测量 // 值就是Specsize的值。 public static final int EXACTLY = 1 << MODE_SHIFT; // 最大值测量模式,当视图的宽高指定为wrap_content时生效,此时 // 子视图的尺寸可以是不超过父视图允许的最大尺寸的任何尺寸。 public static final int AT_MOST = 2 << MODE_SHIFT; // 根据指定的大小和模式创建一个MeasureSpec public static int makeMeasureSpec(int size, int mode) { if (sUsebrokenMakeMeasureSpec) { return size + mode; } else { return (size & ~MODE_MASK) | (mode & MODE_MASK); } } // 微调某个MeasureSpec的大小 static int adjust(int measureSpec, int delta) { final int mode = getMode(measureSpec); if (mode == UnspecIFIED) { // No need to adjust size for UnspecIFIED mode. return make MeasureSpec(0, UnspecIFIED); } int size = getSize(measureSpec) + delta; if (size < 0) { size = 0; } return makeMeasureSpec(size, mode); }}
MeasureSpec通过将SpecMode和Specsize打包成一个int值来避免过多的对象内存分配,为了方便 *** 作,其提供了打包和解包的方法,打包方法为上述源码中的makeMeasureSpec,解包方法源码如下:
public static int getMode(int measureSpec) { return (measureSpec & MODE_MASK);}public static int getSize(int measureSpec) { return (measureSpec & ~MODE_MASK);}
2.DecorVIEw的MeasureSpec的创建过程://desireDWindowWIDth和desireDWindowHeight是屏幕的尺寸chilDWIDthMeasureSpec = getRootMeasureSpec(desireDWindowWIDth, lp.wIDth);childHeightmeasureSpec = getRootMeasureSpec(desireDWindowHeight, lp.height);performMeasure(chilDWIDthMeasureSpec, childHeightmeasureSpec);private static int getRootMeaureSpec(int windowsize, int rootDimension) { int measureSpec; switch (rootDimension) { case VIEwGroup.LayoutParams.MATRCH_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;}
3.子元素的MeasureSpec的创建过程// VIEwGroup的measureChilDWithmargins方法protected voID measureChilDWithmargins(VIEw child,int parentWIDthMeasureSpec, int wIDthUsed,int parentHeightmeasureSpec, int heightUsed) { final marginLayoutParams lp = (marginLayoutParams) child.getLayoutParams(); // 子元素的MeasureSpec的创建与父容器的MeasureSpec和子元素本身 // 的LayoutParams有关,此外还和VIEw的margin及padding有关 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);}public static int getChildMeasureSpec(int spec, int padding, int childDimesion) { int specMode = MeasureSpec.getMode(spec); int specsize = MeasureSpec.getSize(spec); // padding是指父容器中已占用的空间大小,因此子元素可用的 // 大小为父容器的尺寸减去padding int size = Math.max(0, specsize - padding); int resultSize = 0; int resultMode = 0; switch (sepcMode) { // 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 (childDimesion == LayoutParams.WRAP_CONTENT) { // Child wants to deter@R_301_6386@ 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 deter@R_301_6386@ 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 = 0; resultMode = MeasureSpec.UnspecIFIED; } else if (childDimension == LayoutParams.WRAP_CONTENT) { // Child wants to deter@R_301_6386@ its own size.... // find out how big it should be resultSize = 0; resultMode == MeasureSpec.UnspecIFIED; } break; } return MeasureSpec.makeMeasureSpec(resultSize, resultMode);}
普通VIEw的MeasureSpec的创建规则如下:
注意:UnspecIFIED模式主要用于系统内部多次Measure的情形,一般不需关注。
结论:对于DecorVIEw而言,它的MeasureSpec由窗口尺寸和其自身的LayoutParams共同决定;对于普通的VIEw,它的MeasureSpec由父视图的MeasureSpec和其自身的LayoutParams共同决定。
四、VIEw绘制流程之Measure1.Measure的基本流程由前面的分析可知,页面的测量流程是从performMeasure方法开始的,相关的核心代码流程如下。
private voID perormMeasure(int chilDWIDthMeasureSpec, int childHeightmeasureSpec) { ... // 具体的测量 *** 作分发给VIEwGroup mVIEw.measure(chilDWIDthMeasureSpec, childHeightmeasureSpec); ...}// 在VIEwGroup中的measureChildren()方法中遍历测量VIEwGroup中所有的VIEwprotected voID measureChildren(int wIDthMeasureSpec, int heightmeasureSpec) { final int size = mChildrenCount; final VIEw[] children = mChildren; for (int i = 0; i < size; ++i) { final VIEw child = children[i]; // 当VIEw的可见性处于GONE状态时,不对其进行测量 if ((child.mVIEwFlags & VISIBIliTY_MASK) != GONE) { measureChild(child, wIDthMeasureSpec, heightmeasureSpec); } }}// 测量某个指定的VIEwprotected voID measureChild(VIEw child, int parentWIDthMeasureSpec, int parentHeightmeasureSpec) { final LayoutParams lp = child.getLayoutParams(); // 根据父容器的MeasureSpec和子VIEw的LayoutParams等信息计算 // 子VIEw的MeasureSpec final int chilDWIDthMeasureSpec = getChildMeasureSpec(parentWIDthMeasureSpec, mpaddingleft + mpaddingRight, lp.wIDth); final int childHeightmeasureSpec = getChildMeasureSpec(parentHeightmeasureSpec, mpaddingtop + mpaddingBottom, lp.height); child.measure(chilDWIDthMeasureSpec, childHeightmeasureSpec);}// VIEw的measure方法public final voID measure(int wIDthMeasureSpec, int heightmeasureSpec) { ... // VIEwGroup没有定义测量的具体过程,因为VIEwGroup是一个 // 抽象类,其测量过程的onMeasure方法需要各个子类去实现 onMeasure(wIDthMeasureSpec, heightmeasureSpec); ...}// 不同的VIEwGroup子类有不同的布局特性,这导致它们的测量细节各不相同,如果需要自定义测量过程,则子类可以重写这个方法protected voID onMeasure(int wIDthMeasureSpec, int heightmeasureSpec) { // setMeasureDimension方法用于设置VIEw的测量宽高 setMeasureDimension(getDefaultSize(getSuggestedMinimumWIDth(), wIDthMeasureSpec), getDefaultSize(getSuggestedMinimumHeight(), heightmeasureSpec));}// 如果VIEw没有重写onMeasure方法,则会默认调用getDefaultSize来获得VIEw的宽高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 = sepcSize; break; } return result;}
2.对getSuggestMinimumWIDth的分析protected int getSuggestedMinimumWIDth() { return (mBackground == null) ? mMinWIDth : max(mMinWIDth, mBackground.getMinmumWIDth());}protected int getSuggestedMinimumHeight() { return (mBackground == null) ? mMinHeight : max(mMinHeight, mBackground.getMinimumHeight());}public int getMinimumWIDth() { final int intrinsicWIDth = getIntrinsicWIDth(); return intrinsicWIDth > 0 ? intrinsicWIDth : 0;}
如果VIEw没有设置背景,那么返回androID:minWIDth这个属性所指定的值,这个值可以为0;如果VIEw设置了背景,则返回androID:minWIDth和背景的最小宽度这两者中的最大值。
3.自定义view时手动处理wrap_content时的情形直接继承VIEw的控件需要重写onMeasure方法并设置wrap_content时的自身大小,否则在布局中使用wrap_content就相当于使用match_parent。解决方式如下:
protected voID onMeasure(int wIDthMeasureSpec, int height MeasureSpec) { super.onMeasure(wIDthMeasureSpec, heightmeasureSpec); int wIDthSpecMode = MeasureSpec.getMode(wIDthMeasureSpec); int wIDtuhSpecsize = MeasureSpec.getSize(wIDthMeasureSpec); int heightSpecMode = MeasureSpec.getMode(heightmeasureSpec); // 在wrap_content的情况下指定内部宽/高(mWIDth和mHeight) int heightSpecsize = MeasureSpec.AT_MOST && heightSpecMode == MeasureSpec.AT_MOST) { setMeasuredDimension(mWIDth, mHeight); } else if (wIDthSpecMode == MeasureSpec.AT_MOST) { setMeasureDimension(mWIDth, heightSpecsize); } else if (heightSpecMode == MeasureSpec.AT_MOST) { setMeasureDimension(wIDthSpecsize, mHeight); }}
4.linearLayout的onMeasure方法实现解析protected voID onMeasure(int wIDthMeasureSpec, int hegithMeasureSpec) { if (mOrIEntation == VERTICAL) { measureVertical(wIDthMeasureSpec, heightmeasureSpec); } else { measureHorizontal(wIDthMeasureSpec, heightmeasureSpec); }}// measureVertical核心源码// See how tall everyone is. Also remember max wIDth.for (int i = 0; i < count; ++i) { final VIEw child = getVirtualChildAt(i); ... // Deter@R_301_6386@ how big this child would like to be. If this or // prevIoUs children have given a weight, then we allow it to // use all available space (and we will shrink things later // if need) measureChildBeforeLayout( child, i, wIDthMeasureSpec, 0, heightmeasureSpec, totalWeight == 0 ? mTotalLength : 0); if (oldHeight != Integer.MIN_VALUE) { lp.height = oldHeight; } final int childHeight = child.getMeasuredHeight(); final int totalLength = mTotalLength; mTotalLength = Math.max(totalLength, totalLength + childHeight + lp.topmargin + lp.bottommargin + getNextLocationOffset(child));}
系统会遍历子元素并对每个子元素执行measureChildBeforeLayout方法,这个方法内部会调用子元素的measure方法,这样各个子元素就开始依次进入measure过程,并且系统会通过mTotalLength这个变量来存储linearLayout在竖直方向的初步高度。每测量一个子元素,mTotalLength就会增加,增加的部分主要包括了子元素的高度以及子元素在竖直方向上的margin等。
// linearLayout测量自己大小的核心源码// Add in our paddingmTotalLength += mpaddingtop + mpaddingBottom;int heightSize = mTotalLength;// Check against our minimum heightheightSize = Math.max(heightSize, getSuggestedMinimumHeight());// Reconcile our calculated size with the heightmeasureSpecint heightSizeAndState = resolveSizeAndState(heightSize, heightmeasureSpec, 0);heightSize = heightSizeAndState & MEASURED_SIZE_MASK;...setMeasuredDimension(resolveSizeAndSize(maxWIDth, wIDthMeasureSpec, childState),heightSizeAndState);public static int resolveSizeAndState(int size, int measureSpec, int childMeasuredState) { 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: // 高度不能超过父容器的剩余空间 if (specsize < size) { result = specsize | MEASURED_STATE_TOO_SMALL; } else { result = size; } break; case MeasureSpec.EXACTLY: result = specsize; break; } return result | (childMeasuredState & MEASURED_STATE_MASK);}
5.在Activity中获取某个VIEw的宽高由于VIEw的measure过程和Activity的生命周期方法不是同步执行的,如果VIEw还没有测量完毕,那么获得的宽/高就是0。所以在onCreate、onStart、onResume中均无法正确得到某个VIEw的宽高信息。解决方式如下:
Activity/VIEw#onWindowFocusChanged
// 此时VIEw已经初始化完毕
// 当Activity的窗口得到焦点和失去焦点时均会被调用一次
// 如果频繁地进行onResume和onPause,那么onWindowFocusChanged也会被频繁地调用
public voID onWindowFocusChanged(boolean hasFocus) {
super.onWindowFocusChanged(hasFocus);
if (hasFocus) {
int wIDth = vIEw.getMeasureWIDth();
int height = vIEw.getMeasuredHeight();
}
}
vIEw.post(runnable)
// 通过post可以将一个runnable投递到消息队列的尾部,// 然后等待Looper调用次runnable的时候,VIEw也已经初
// 始化好了
protected voID onStart() {
super.onStart();
vIEw.post(new Runnable() {
@OverrIDe public voID run() { int wIDth = vIEw.getMeasureDWIDth(); int height = vIEw.getMeasuredHeight(); }});
}
VIEwTreeObserver
// 当VIEw树的状态发生改变或者VIEw树内部的VIEw的可见// 性发生改变时,onGlobalLayout方法将被回调
protected voID onStart() {
super.onStart();
VIEwTreeObserver observer = vIEw.getVIEwTreeObserver();observer.addOnGlobalLayoutListener(new OnGlobalLayoutListener() { @SuppressWarnings("deprecation") @OverrIDe public voID onGlobalLayout() { vIEw.getVIEwTreeObserver().removeGlobalOnLayoutListener(this); int wIDth = vIEw.getMeasureDWIDth(); int height = vIEw.getMeasuredHeight(); }});
}
VIEw.measure(int wIDthMeasureSpec, int heightmeasureSpec)五、VIEw的绘制流程之Layout1.Layout的基本流程// VIEwRootImpl.javaprivate voID performlayout(WindowManager.LayoutParams lp, int desireDWindowWIDth, int desireDWindowHeight) { ... host.layout(0, 0, host.getMeasureDWIDth(), host.getMeasuredHeight()); ...}// VIEw.javapublic voID layout(int l, int t, int r, int b) { ... // 通过setFrame方法来设定VIEw的四个顶点的位置,即VIEw在父容器中的位置 boolean changed = isLayoutModeOptical(mParent) ? set OpticalFrame(l, t, r, b) : setFrame(l, t, r, b); ... onLayout(changed, l, t, r, b); ...}// 空方法,子类如果是VIEwGroup类型,则重写这个方法,实现VIEwGroup// 中所有VIEw控件布局流程protected voID onLayout(boolean changed, int left, int top, int right, int bottom) {}
2.linearLayout的onLayout方法实现解析protected voID onlayout(boolean changed, int l, int t, int r, int b) { if (mOrIEntation == VERTICAL) { layoutVertical(l, t, r, b); } else { layoutHorizontal(l,) }}// layoutVertical核心源码voID layoutVertical(int left, int top, int right, int bottom) { ... final int count = getVirtualChildCount(); for (int i = 0; i < count; i++) { final VIEw child = getVirtualChildAt(i); if (child == null) { childtop += measureNullChild(i); } else if (child.getVisibility() != GONE) { final int chilDWIDth = child.getMeasureWIDth(); final int childHeight = child.getMeasuredHeight(); final linearLayout.LayoutParams lp = (linearLayout.LayoutParams) child.getLayoutParams(); ... if (hasdivIDerBeforeChildAt(i)) { childtop += mdivIDerHeight; } childtop += lp.topmargin; // 为子元素确定对应的位置 setChildFrame(child, childleft, childtop + getLocationOffset(child), chilDWIDth, childHeight); // childtop会逐渐增大,意味着后面的子元素会被 // 放置在靠下的位置 childtop += childHeight + lp.bottommargin + getNextLocationOffset(child); i += getChildrenSkipCount(child,i) } }}private voID setChildFrame(VIEw child, int left, int top, int wIDth, int height) { child.layout(left, top, left + wIDth, top + height);}
注意:在VIEw的默认实现中,VIEw的测量宽/高和最终宽/高是相等的,只不过测量宽/高形成于VIEw的measure过程,而最终宽/高形成于VIEw的layout过程,即两者的赋值时机不同,测量宽/高的赋值时机稍微早一些。在一些特殊的情况下则两者不相等:
重写VIEw的layout方法,使最终宽度总是比测量宽/高大100px
public voID layout(int l, int t, int r, int b) {
super.layout(l, t, r + 100, b + 100);
}
private voID performDraw() { ... draw(fullRefrawNeeded); ...}private voID draw(boolean fullRedrawNeeded) { ... if (!drawSoftware(surface, mAttachInfo, xOffest, yOffset, scalingrequired, dirty)) { return; } ...}private boolean drawSoftware(Surface surface, AttachInfo attachInfo, int xoff, int yoff, boolean scallingrequired, Rect dirty) { ... mVIEw.draw(canvas); ...}// 绘制基本上可以分为六个步骤public voID draw(Canvas canvas) { ... // 步骤一:绘制VIEw的背景 drawBackground(canvas); ... // 步骤二:如果需要的话,保持canvas的图层,为fading做准备 saveCount = canvas.getSaveCount(); ... canvas.saveLayer(left, top, right, top + length, null, flags); ... // 步骤三:绘制VIEw的内容 onDraw(canvas); ... // 步骤四:绘制VIEw的子VIEw dispatchDraw(canvas); ... // 步骤五:如果需要的话,绘制VIEw的fading边缘并恢复图层 canvas.drawRect(left, top, right, top + length, p); ... canvas.restoretoCount(saveCount); ... // 步骤六:绘制VIEw的装饰(例如滚动条等等) onDrawForeground(canvas)}
2.setwillNotDraw的作用// 如果一个VIEw不需要绘制任何内容,那么设置这个标记位为true以后,// 系统会进行相应的优化。public voID setwillNotDraw(boolean willNotDraw) { setFlags(willNotDraw ? WILL_NOT_DRAW : 0, DRAW_MASK);}
默认情况下,VIEw没有启用这个优化标记位,但是VIEwGroup会默认启用这个优化标记位。当我们的自定义控件继承于VIEwGroup并且本身不具备绘制功能时,就可以开启这个标记位从而便于系统进行后续的优化。当明确知道一个VIEwGroup需要通过onDraw来绘制内容时,我们需要显示地关闭WILL_NOT_DRAW这个标记位。七、总结VIEw的绘制流程和事件分发机制都是AndroID开发中的核心知识点,也是自定义view高手的内功心法。对于一名优秀的AndroID开发来说,主流三方源码分析和AndroID核心源码分析可以说是必修课,下一篇,将会带领大家更进一步深入AndroID。
参考链接:1、AndroID开发艺术探索
2、AndroID进阶之光
3、AndroID高级进阶
4、Android应用层View绘制流程与源码分析
5、Android中View绘制流程浅析
赞赏如果这个库对您有很大帮助,您愿意支持这个项目的进一步开发和这个项目的持续维护。你可以扫描下面的二维码,让我喝一杯咖啡或啤酒。非常感谢您的捐赠。谢谢!
<div align="center">
<img src="https://user-gold-cdn.xitu.io/2020/1/7/16f7dc32595031fa?w=1080&h=1457&f=jpeg&s=93345" wIDth=20%><img src="https://user-gold-cdn.xitu.io/2020/1/7/16f7dc3259518ecd?w=990&h=1540&f=jpeg&s=110691" wIDth=20%>
</div>
Contanct Me● 微信:
● 微信群:欢迎关注我的微信:
bcce5360
微信群如果不能扫码加入,麻烦大家想进微信群的朋友们,加我微信拉你进群。
<div align="center">
<img src="https://user-gold-cdn.xitu.io/2020/1/7/16f7dc352011e1fe?w=1013&h=1920&f=jpeg&s=86819" wIDth=35%>
</div>
About meEmail: [chao.qu521@gmail.com]()Blog: https://jsonchao.github.io/掘金: https://juejin.im/user/5a3ba9375188252bca050ade很感谢您阅读这篇文章,希望您能将它分享给您的朋友或技术群,这对我意义重大。希望我们能成为朋友,在 Github、掘金上一起分享知识。 总结2千人QQ群,Awesome-AndroID学习交流群,QQ群号:959936182, 欢迎大家加入~
以上是内存溢出为你收集整理的一文彻底搞懂Android View的绘制流程全部内容,希望文章能够帮你解决一文彻底搞懂Android View的绘制流程所遇到的程序开发问题。
如果觉得内存溢出网站内容还不错,欢迎将内存溢出网站推荐给程序员好友。
欢迎分享,转载请注明来源:内存溢出
评论列表(0条)