AndroidView的工作流程和原理

AndroidView的工作流程和原理

2023年6月27日发(作者:)

AndroidView的⼯作流程和原理前⾔在⽇常开发中,我们每天都在和各种 View 打交道,⽐如TextView,Button等,我们直接拿过来就可以使⽤,那么 Android 是怎么把View 绘制到屏幕上呢,接下来我们结合源码来具体分析。在具体结合源码分析前,先了解⼀个⽐较重要的概念 ViewRootViewRoot先看⼀张图 Android 窗⼝构成图解Android View 的⼯作流程和原理ViewRoot 对应于 ViewRootImpl 类,它是连接 WindowManager 和 根布局 DecorView(看上图) 的纽带, View 的三⼤流程均是通过 ViewRoot 来完成的。在 ActivityThread 中,当 Activity 对象被创建完毕后,会将 DecorView 添加到 Window 中,同时会创建ViewRootImpl 对象,并将 ViewRootImpl 对象和 DecorView 建⽴关联。View 的绘制流程是从 ViewRoot 的 performTraversals ⽅法开始的,它经过 measure、layout 和 draw 三个过程才能最终将⼀个 View绘制出来,其中 measure ⽤来测量 View 的宽和⾼,layout ⽤来确定 View 在⽗容器中的放置位置,⽽ draw 则负责将 View 绘制在屏幕上。针对 performTraversals的⼤致流程如下:Android View 的⼯作流程和原理performTraversals 会依次调⽤ performMeasure、performLayout 和 performDraw 三个⽅法,这三个⽅法分别完成顶级 View 的measure、layout 和 draw 这三⼤流程,其中在 performMeasure 中会调⽤ measure ⽅法,在 measure ⽅法中⼜会调⽤ onMeasure⽅法,在 onMeasure ⽅法中则会对所有的⼦元素进⾏ measure 过程,这个时候 measure 流程就从⽗容器传递到⼦元素中了,这样就完成了⼀次 measure 过程。接着⼦元素会重复⽗容器的 measure 过程,如此反复就完成了整个 View 树的遍历。同理,performLayout和 performDraw 的传递流程和 performMeasure 是类似的,唯⼀不同的是,performDraw 的传递过程是在 draw ⽅法中通过dispatchDraw 来实现的,不过这并没有本质区别。接下来结合源码来分析这三个过程。Measure 测量过程这⾥分两种情况,View 的测量过程和 ViewGroup 的测量过程。View 的测量过程View 的 测量过程由其 measure ⽅法来完成,源码如下:public final void measure(int widthMeasureSpec, int heightMeasureSpec) {//省略代码… if (forceLayout || needsLayout) { // first clears the measured dimension flag mPrivateFlags &= ~PFLAG_MEASURED_DIMENSION_SET; resolveRtlPropertiesIfNeeded(); int cacheIndex = forceLayout ? -1 : fKey(key); if (cacheIndex < 0 || sIgnoreMeasureCache) { // measure ourselves, this should set the measured dimension flag back onMeasure(widthMeasureSpec, heightMeasureSpec); mPrivateFlags3 &= ~PFLAG3_MEASURE_NEEDED_BEFORE_LAYOUT; } else { long value = t(cacheIndex); // Casting a long to int drops the high 32 bits, no mask needed setMeasuredDimensionRaw((int) (value >> 32), (int) value); mPrivateFlags3 |= PFLAG3_MEASURE_NEEDED_BEFORE_LAYOUT; } // flag not set, setMeasuredDimension() was not invoked, we raise // an exception to warn the developer if ((mPrivateFlags & PFLAG_MEASURED_DIMENSION_SET) != PFLAG_MEASURED_DIMENSION_SET) { throw new IllegalStateException("View with id " + getId() + ": " + getClass().getName() + "#onMeasure() did not set the" + " measured dimension by calling" + " setMeasuredDimension()"); } mPrivateFlags |= PFLAG_LAYOUT_REQUIRED; } mOldWidthMeasureSpec = widthMeasureSpec; mOldHeightMeasureSpec = heightMeasureSpec; (key, ((long) mMeasuredWidth) << 32 | (long) mMeasuredHeight & 0xffffffffL); // suppress sign extension}可以看到 measure ⽅法是⼀个 final 类型的⽅法,这意味着⼦类不能重写此⽅法。在 13 ⾏ measure 中会调⽤ onMeasure ⽅法,这个⽅法是测量的主要⽅法,继续看 onMeasure 的实现protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {setMeasuredDimension(getDefaultSize(getSuggestedMinimumWidth(), widthMeasureSpec),getDefaultSize(getSuggestedMinimumHeight(), heightMeasureSpec));}setMeasuredDimension ⽅法的作⽤是设置 View 宽和⾼的测量值,我们主要看 getDefaultSize ⽅法是如何⽣成测量的尺⼨。public static int getDefaultSize(int size, int measureSpec) {int result = size;int specMode = e(measureSpec);int specSize = e(measureSpec); switch (specMode) { case IFIED: result = size; break; case _MOST: case Y: result = specSize; break; } return result;}可以看到要得到测量的尺⼨需要⽤到 MeasureSpec,MeasureSpec 是什么⿁呢,敲⿊板了,重点来了。MeasureSpec 决定了 View 的测量过程。确切来说,MeasureSpec 在很⼤程度上决定了⼀个 View 的尺⼨规格。来看 MeasureSpec 类的实现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() public @interface MeasureSpecMode {} 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; public static int makeMeasureSpec(@IntRange(from = 0, to = (1 << _SHIFT) - 1) int size, @MeasureSpecMode int mode) { if (sUseBrokenMakeMeasureSpec) { return size + mode; } else { return (size & ~MODE_MASK) | (mode & MODE_MASK); } } public static int makeSafeMeasureSpec(int size, int mode) { if (sUseZeroUnspecifiedMeasureSpec && mode == UNSPECIFIED) { return 0; } return makeMeasureSpec(size, mode); } @MeasureSpecMode public static int getMode(int measureSpec) { //noinspection ResourceType return (measureSpec & MODE_MASK); } public static int getSize(int measureSpec) { return (measureSpec & ~MODE_MASK); } static int adjust(int measureSpec, int delta) { final int mode = getMode(measureSpec); int size = getSize(measureSpec); if (mode == UNSPECIFIED) { // No need to adjust size for UNSPECIFIED mode. return makeMeasureSpec(size, UNSPECIFIED); } size += delta; if (size < 0) { Log.e(VIEW_LOG_TAG, ": new size would be negative! (" + size + ") spec: " + toString(measureSpec) + " delta: " + delta); size = 0; } return makeMeasureSpec(size, mode); } public static String toString(int measureSpec) { //省略... }}可以看出 MeasureSpec 中有两个主要的值,SpecMode 和 SpecSize, SpecMode 是指测量模式,⽽ SpecSize 是指在某种测量模式下的规格⼤⼩。SpecMode 有三种模式:UNSPECIFIED不限制:⽗容器不对 View 有任何限制,要多⼤给多⼤,这种情况⽐较少见,⼀般不会⽤到。EXACTLY限制固定值:⽗容器已经检测出 View 所需要的精确⼤⼩,这个时候 View 的最终⼤⼩就是 SpecSize 所指定的值。它对应于LayoutParams 中的 match_parent 和具体的数值这两种模式。AT_MOST限制上限:⽗容器指定了⼀个可⽤⼤⼩即 SpecSize,View 的⼤⼩不能⼤于这个值,具体是什么值要看不同 View 的具体实现。它对应于LayoutParams 中的 wrap_content。MeasureSpec 中三个主要的⽅法来处理 SpecMode 和 SpecSizemakeMeasureSpec 打包 SpecMode 和 SpecSizegetMode 解析出 SpecModegetSize 解析出 SpecSize不知道童鞋们之前有没有注意到 onMeasure 有两个参数 widthMeasureSpec 和 heightMeasureSpec,那这两个值从哪来的呢,这两个值都是由⽗视图经过计算后传递给⼦视图的,说明⽗视图会在⼀定程度上决定⼦视图的⼤⼩,但是最外层的根视图 也就是 DecorView ,它的 widthMeasureSpec 和 heightMeasureSpec ⼜是从哪⾥得到的呢?这就需要去分析 ViewRoot 中的源码了,在 performTraversals⽅法中调了 measureHierarchy ⽅法来创建 MeasureSpec 源码如下:private boolean measureHierarchy(final View host, final Params lp,final Resources res, final int desiredWindowWidth, final int desiredWindowHeight) {int childWidthMeasureSpec;int childHeightMeasureSpec; childWidthMeasureSpec = getRootMeasureSpec(baseSize, ); childHeightMeasureSpec = getRootMeasureSpec(desiredWindowHeight, ); performMeasure(childWidthMeasureSpec, childHeightMeasureSpec); //省略代码...

}⾥⾯调⽤了 getRootMeasureSpec ⽅法⽣成 MeasureSpec,继续查看 getRootMeasureSpec 源码private static int getRootMeasureSpec(int windowSize, int rootDimension) {int measureSpec;switch (rootDimension) { case _PARENT: // Window can't resize. Force root view to be windowSize. measureSpec = asureSpec(windowSize, Y); break; case _CONTENT: // Window can resize. Set max size for root view. measureSpec = asureSpec(windowSize, _MOST); break; default: // Window wants to be an exact size. Force root view to be that size. measureSpec = asureSpec(rootDimension, Y); break; } return measureSpec;}通过上述代码,DecorView 的 MeasureSpec 的产⽣过程就很明确了,具体来说其遵守如下规则,根据它的 LayoutParams 中的宽和⾼的参数来划分。_PARENT:限制固定值,⼤⼩就是窗⼝的⼤⼩ _CONTENT:限制上限,⼤⼩不定,但是不能超过窗⼝的⼤⼩ windowSize固定⼤⼩:限制固定值,⼤⼩为 LayoutParams 中指定的⼤⼩ rootDimension对于 DecorView ⽽⾔, rootDimension 的值为 和 也就是屏幕的宽和⾼,所以说 根视图 DecorView 的⼤⼩默认总是会充满全屏的。那么我们使⽤的 View 也就是 ViewGroup 中 View 的 MeasureSpec 产⽣过程⼜是怎么样的呢,在 ViewGroup 的测量过程中会具体介绍。先回头看 getDefaultSize ⽅法:public static int getDefaultSize(int size, int measureSpec) {int result = size;int specMode = e(measureSpec);int specSize = e(measureSpec); switch (specMode) { case IFIED: result = size; break; case _MOST: case Y: result = specSize; break; } return result;}现在理解起来是不是很简单呢,如果 specMode 是 AT_MOST 或 EXACTLY 就返回 specSize,这也是系统默认的⾏为。之后会在onMeasure ⽅法中调⽤ setMeasuredDimension ⽅法来设定测量出的⼤⼩,这样 View 的 measure 过程就结束了,接下来看ViewGroup 的 measure 过程。ViewGroup 的测量过程ViewGroup中定义了⼀个 measureChildren ⽅法来去测量⼦视图的⼤⼩,如下所⽰protected 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];if ((lags & VISIBILITY_MASK) != GONE) {measureChild(child, widthMeasureSpec, heightMeasureSpec);}}}从上述代码来看,除了完成⾃⼰的 measure 过程以外,还会遍历去所有在页⾯显⽰的⼦元素,然后逐个调⽤ measureChild ⽅法来测量相应⼦视图的⼤⼩measureChild 的实现如下protected void measureChild(View child, int parentWidthMeasureSpec,int parentHeightMeasureSpec) {final LayoutParams lp = outParams(); final int childWidthMeasureSpec = getChildMeasureSpec(parentWidthMeasureSpec, mPaddingLeft + mPaddingRight, ); final int childHeightMeasureSpec = getChildMeasureSpec(parentHeightMeasureSpec, mPaddingTop + mPaddingBottom, ); e(childWidthMeasureSpec, childHeightMeasureSpec);}measureChild 的思想就是取出⼦元素的 LayoutParams,然后再通过 getChildMeasureSpec 来创建⼦元素的 MeasureSpec,接着将MeasureSpec 直接传递给 View 的 measure ⽅法来进⾏测量。那么 ViewGroup 是如何创建来创建⼦元素的 MeasureSpec 呢,我们继续看 getChildMeasureSpec ⽅法源码:public static int getChildMeasureSpec(int spec, int padding, int childDimension) {int specMode = e(spec);int specSize = e(spec); int size = (0, specSize - padding); int resultSize = 0; int resultMode = 0; switch (specMode) { // Parent has imposed an exact size on us case Y: if (childDimension >= 0) { resultSize = childDimension; resultMode = Y; } else if (childDimension == _PARENT) { // Child wants to be our size. So be it. resultSize = size; resultMode = Y; } else if (childDimension == _CONTENT) { // Child wants to determine its own size. It can't be // bigger than us. resultSize = size; resultMode = _MOST; } break; // Parent has imposed a maximum size on us case _MOST: if (childDimension >= 0) { // Child wants a so be it resultSize = childDimension; resultMode = Y; } else if (childDimension == _PARENT) { // Child wants to be our size, but our size is not fixed. // Constrain child to not be bigger than us. resultSize = size; resultMode = _MOST; } else if (childDimension == _CONTENT) { // Child wants to determine its own size. It can't be // bigger than us. resultSize = size; resultMode = _MOST; } break; // Parent asked to see how big we want to be case IFIED: if (childDimension >= 0) { // Child wants a let him have it resultSize = childDimension; resultMode = Y; } else if (childDimension == _PARENT) { // Child wants to be find out how big it should // be resultSize = roUnspecifiedMeasureSpec ? 0 : size; resultMode = IFIED; } else if (childDimension == _CONTENT) { // Child wants to determine its find out how // big it should be resultSize = roUnspecifiedMeasureSpec ? 0 : size; resultMode = IFIED; } break; } //noinspection ResourceType return asureSpec(resultSize, resultMode);}上⾯的代码理解起来很简单,为了更清晰地理解 getChildMeasureSpec 的逻辑,这⾥提供⼀个表,表中对 getChildMeasureSpec 的⼯作原理进⾏了梳理,表中的 parentSize 是指⽗容器中⽬前可使⽤的⼤⼩,childSize 是⼦ View 的 LayoutParams 获取的值,从measureChild ⽅法中可看出final MarginLayoutParams lp = (MarginLayoutParams) outParams();final int childWidthMeasureSpec = getChildMeasureSpec(parentWidthMeasureSpec,mPaddingLeft + mPaddingRight + rgin + argin+ widthUsed, );final int childHeightMeasureSpec = getChildMeasureSpec(parentHeightMeasureSpec,mPaddingTop + mPaddingBottom + gin + Margin+ heightUsed, );表如下:普通 View 的 MeasureSpec 的创建规则通过上表可以看出,只要提供⽗容器的 MeasureSpec 和⼦元素的 LayoutParams,就可以快速地确定出⼦元素的 MeasureSpec 了,有了 MeasureSpec 就可以进⼀步确定出⼦元素测量后的⼤⼩了。⾄此,View 和 ViewGroup 的测量过程就告⼀段落了。来个⼩结。MeasureSpec 的模式和⽣成规则MeasureSpec 中 specMode 有三种模式:UNSPECIFIED不限制:⽗容器不对 View 有任何限制,要多⼤给多⼤,这种情况⽐较少见,⼀般不会⽤到。EXACTLY限制固定值:⽗容器已经检测出 View 所需要的精确⼤⼩,这个时候 View 的最终⼤⼩就是 SpecSize 所指定的值。它对应于LayoutParams 中的 match_parent 和具体的数值这两种模式。AT_MOST限制上限:⽗容器指定了⼀个可⽤⼤⼩即 SpecSize,View 的⼤⼩不能⼤于这个值,具体是什么值要看不同 View 的具体实现。它对应于LayoutParams 中的 wrap_content。⽣成规则:对于普通 View,其 MeasureSpec 由⽗容器的 MeasureSpec 和⾃⾝的 LayoutParams 来共同决定。对于不同 ViewGroup 中的不同 View ⽣成规则参照上表。MeasureSpec 测量过程:measure 过程主要就是从顶层⽗ View 向⼦ View 递归调⽤ e ⽅法,measure 中调 onMeasure ⽅法的过程。说⼈话呢就是,视图⼤⼩的控制是由⽗视图、布局⽂件、以及视图本⾝共同完成的,⽗视图会提供给⼦视图参考的⼤⼩,⽽开发⼈员可以在XML ⽂件中指定视图的⼤⼩,然后视图本⾝会对最终的⼤⼩进⾏拍板。那么测量过后,怎么获取 View 的测量结果呢⼀般情况下 View 测量⼤⼩和最终⼤⼩是⼀样的,我们可以使⽤ getMeasuredWidth ⽅法和 getMeasuredHeight ⽅法来获取视图测量出的宽⾼,但是必须在 setMeasuredDimension 之后调⽤,否则调⽤这两个⽅法得到的值都会是0。为什么要说是⼀般情况下是⼀样的呢,在下⽂介绍 Layout 中会具体介绍。Layout 布局过程测量结束后,视图的⼤⼩就已经测量好了,接下来就是 Layout 布局的过程。上⽂说过 ViewRoot 的 performTraversals ⽅法会在measure 结束后,执⾏ performLayout ⽅法,performLayout ⽅法则会调⽤ layout ⽅法开始布局,代码如下private void performLayout(Params lp, int desiredWindowWidth,int desiredWindowHeight) {mLayoutRequested = false;mScrollMayChange = true;mInLayout = true; final View host = mView; if (host == null) { return; } if (DEBUG_ORIENTATION || DEBUG_LAYOUT) { Log.v(mTag, "Laying out " + host + " to (" + suredWidth() + ", " + suredHeight() + ")"); }try {(0, 0, suredWidth(), suredHeight());//…省略代码} finally {nd(_TAG_VIEW);}mInLayout = false;View 类中 layout ⽅法实现如下:public void layout(int l, int t, int r, int b) {if ((mPrivateFlags3 & PFLAG3_MEASURE_NEEDED_BEFORE_LAYOUT) != 0) {onMeasure(mOldWidthMeasureSpec, mOldHeightMeasureSpec);mPrivateFlags3 &= ~PFLAG3_MEASURE_NEEDED_BEFORE_LAYOUT;} int oldL = mLeft; int oldT = mTop; int oldB = mBottom; int oldR = mRight; boolean changed = isLayoutModeOptical(mParent) ? setOpticalFrame(l, t, r, b) : setFrame(l, t, r, b); if (changed || (mPrivateFlags & PFLAG_LAYOUT_REQUIRED) == PFLAG_LAYOUT_REQUIRED) { onLayout(changed, l, t, r, b); if (shouldDrawRoundScrollbar()) { if(mRoundScrollbarRenderer == null) { mRoundScrollbarRenderer = new RoundScrollbarRenderer(this); } } else { mRoundScrollbarRenderer = null; } mPrivateFlags &= ~PFLAG_LAYOUT_REQUIRED; ListenerInfo li = mListenerInfo; if (li != null && outChangeListeners != null) { ArrayList listenersCopy = (ArrayList)(); int numListeners = (); for (int i = 0; i < numListeners; ++i) { (i).onLayoutChange(this, l, t, r, b, oldL, oldT, oldR, oldB); } } } mPrivateFlags &= ~PFLAG_FORCE_LAYOUT; mPrivateFlags3 |= PFLAG3_IS_LAID_OUT; if ((mPrivateFlags3 & PFLAG3_NOTIFY_AUTOFILL_ENTER_ON_LAYOUT) != 0) { mPrivateFlags3 &= ~PFLAG3_NOTIFY_AUTOFILL_ENTER_ON_LAYOUT; notifyEnterOrExitForAutoFillIfNeeded(true); }}layout ⽅法接收四个参数,分别代表着左、上、右、下的坐标,当然这个坐标是相对于当前视图的⽗视图⽽⾔的,然后会调⽤ setFrame⽅法来设定 View 的四个顶点的位置,即初始化 mLeft、mRight、mTop、mBottom 这四个值,View 的四个顶点⼀旦确定,那么 View在⽗容器中的位置也就确定了,接着会调⽤ onLayout ⽅法,这个⽅法的⽤途是⽗容器确定⼦元素的位置,和 onMeasure ⽅法类似onLayout 源码如下:protected void onLayout(boolean changed, int left, int top, int right, int bottom) {}纳尼,怎么是个空⽅法,没错,就是⼀个空⽅法,因为 onLayout 过程是为了确定视图在布局中所在的位置,⽽这个操作应该是由布局来完成的,即⽗视图决定⼦视图的显⽰位置,我们继续看 ViewGroup 中的 onLayout ⽅法@Overrideprotected abstract void onLayout(boolean changed, int l, int t, int r, int b);可以看到,ViewGroup 中的 onLayout ⽅法竟然是⼀个抽象⽅法,这就意味着所有 ViewGroup 的⼦类都必须重写这个⽅法。像LinearLayout、RelativeLayout 等布局,都是重写了这个⽅法,然后在内部按照各⾃的规则对⼦视图进⾏布局的。所以呢我们如果要⾃定义 ViewGroup 那么就要重写 onLayout ⽅法。Android View 的⼯作流程和原理public class TestViewGroup extends ViewGroup {public TestViewGroup(Context context, AttributeSet attrs) { super(context, attrs);}@Overridepublic Params generateLayoutParams(AttributeSet attrs) { return new MarginLayoutParams(getContext(), attrs);}@Overrideprotected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { ure(widthMeasureSpec, heightMeasureSpec); if (getChildCount() > 0) { View childView = getChildAt(0); measureChild(childView, widthMeasureSpec, heightMeasureSpec); }}@Overrideprotected void onLayout(boolean changed, int l, int t, int r, int b) { if (getChildCount() > 0) { View childView = getChildAt(0); (0, 0, suredWidth(), suredHeight()); }}}xml 中使⽤ 显⽰效果如下:Android View 的⼯作流程和原理不知道童鞋们发现了没,我给⾃定义的 ViewGroup 设置了背景⾊,看效果貌似占满全屏了,可是我在 xml 中设置的 wrap_content 啊,这是什么情况,我们回头看看 ViewGroup 中 View 的 MeasureSpec 的创建规则普通 View 的 MeasureSpec 的创建规则从表中可看出因为 ViewGroup 的⽗布局设置的 match_parent 也就是限制固定值模式,⽽ ViewGroup 设置的 wrap_content,那么最后 ViewGroup 使⽤的是 ⽗布局的⼤⼩,也就是窗⼝⼤⼩ parentSize,那么如果我们给 ViewGroup 设置固定值就会使⽤ 我们设置的值,来改下代码。 效果如下:Android View 的⼯作流程和原理表中的其他情况,建议童鞋们⾃⼰写下代码,会理解的更好。之前说过,⼀般情况下 View 测量⼤⼩和最终⼤⼩是⼀样的,为什么呢,因为最终⼤⼩在 onLayout 中确定,我们来改下代码:@Overrideprotected void onLayout(boolean changed, int l, int t, int r, int b) {if (getChildCount() > 0) {View childView = getChildAt(0);(0, 0, suredWidth()+100, suredHeight()+200);}}显⽰效果Android View 的⼯作流程和原理没错,onLayout 就是这么任性,所以要获取 View 的真实⼤⼩最好在 onLayout 之后获取。那么如何来获取 view 的真实⼤⼩呢,可以通过下⾯的代码来获取tv_(Runnable {log(" getMeasuredWidth() = ${tv_edWidth}")log(" getWidth() = ${tv_}")}打印如下:01-18 23:33:20.947 2836-2836/mo I/debugLog: getMeasuredWidth() = 23901-18 23:33:20.947 2836-2836/mo I/debugLog: getWidth() = 339可以看到实际⾼度和测试的⾼度是不⼀样的,因为我们在 onLayout 中做了修改。因为 View 的绘制过程和 Activity 的⽣命周期是不同步的,所以我们可能在 onCreate 中获取不到值。这⾥提供⼏种⽅法来获取ty 的 onWindowFocusChanged ⽅法override fun onWindowFocusChanged(hasFocus: Boolean) {owFocusChanged(hasFocus)if (hasFocus){//获取 view 的⼤⼩}}(runnable) 也就是我上⾯使⽤的⽅法eeObserver 这⾥童鞋们搜索下就可以找到使⽤⽅法,篇幅较长就不举例⼦了Draw 绘制过程确定了 View 的⼤⼩和位置后,那就要开始绘制了,Draw 过程就⽐较简单,它的作⽤是将 View 绘制到屏幕上⾯。View 的绘制过程遵循如下⼏步:绘制背景 (canvas)绘制⾃⼰(onDraw)绘制 children(dispatchDraw)绘制装饰(onDrawScrollBars)public void draw(Canvas canvas) {final int privateFlags = mPrivateFlags;final boolean dirtyOpaque = (privateFlags & PFLAG_DIRTY_MASK) == PFLAG_DIRTY_OPAQUE &&(mAttachInfo == null || !eDirtyState);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); drawAutofilledHighlight(canvas); // Overlay is part of the content and draws beneath Foreground if (mOverlay != null && !y()) { rlayView().dispatchDraw(canvas); } // Step 6, draw decorations (foreground, scrollbars) onDrawForeground(canvas); // Step 7, draw the default focus highlight drawDefaultFocusHighlight(canvas); if (debugDraw()) { debugDrawFocus(canvas); } // we' return; } //省略代码..}View 的绘制过程的传递是通过 dispatchDraw 实现的,dispatchdraw 会遍历调⽤所有⼦元素的 draw ⽅法,如此 draw 事件就⼀层⼀层的传递下去。和 Layout ⼀样 View 是不会帮我们绘制内容部分的,因此需要每个视图根据想要展⽰的内容来⾃⾏绘制,重写 onDraw ⽅法。具体可参考 TextView 或者 ImageView 的源码。最后View 的⼯作流程和原理到这就分析完了,难点主要是 MeasureSpec 测量过程,需要童鞋们认真揣摩。

发布者:admin,转转请注明出处:http://www.yc00.com/news/1687842218a50006.html

相关推荐

发表回复

评论列表(0条)

  • 暂无评论

联系我们

400-800-8888

在线咨询: QQ交谈

邮件:admin@example.com

工作时间:周一至周五,9:30-18:30,节假日休息

关注微信