Android自定义控件 —— 三大流程

来源:互联网 发布:visualbasic2010 mac版 编辑:程序博客网 时间:2024/05/09 17:48

摘要

自定义控件在工作中时常有所接触,但始终是只得其形,不得其神。最近抽空系统学习了下这方面的知识,准备用“Android自定义控件”作为一个博客系列来记录我的学习心得。系列文中不乏引用到Android SDK源码,以API 25为准。最后,如有雷同,没错,就是我去抄的。

Android自定义控件系列目录

  • Android自定义控件 —— 事件分发

  • Android自定义控件 —— 三大流程

View的三大流程分为:测量(measure)、布局(layout)、绘制(draw),下面就一一进行探索吧。

measure

布局绘画涉及两个流程:测量流程和布局流程,测量流程通过measure(int, int)实现,是View树自顶向下的遍历,每个View在循环过程中将尺寸细节往下传递,当测量过程完成之后,所有的View都存储了自己的尺寸。第二个过程则是通过方法layout(int, int, int, int)来实现的,也是自顶向下的。在这个过程中,每个父容器ViewGroup负责通过计算好的尺寸放置它的子View。

前面讲过,onMeasure(int, int)是用来测量当前控件大小的,给onLayout(boolean, int, int, int, int)提供数值参考,需要特别注意的是,测量完成以后通过setMeasuredDimension(int,int)设置给系统。

MeasureSpec

这是一个View的内部类,介绍之前,先来看看源码

package android.view;...public class View implements Drawable.Callback, KeyEvent.Callback,        AccessibilityEventSource {    ...    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 {}        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 << MeasureSpec.MODE_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, "MeasureSpec.adjust: new size would be negative! (" + size +                        ") spec: " + toString(measureSpec) + " delta: " + delta);                size = 0;            }            return makeMeasureSpec(size, mode);        }        public static String toString(int measureSpec) {            int mode = getMode(measureSpec);            int size = getSize(measureSpec);            StringBuilder sb = new StringBuilder("MeasureSpec: ");            if (mode == UNSPECIFIED)                sb.append("UNSPECIFIED ");            else if (mode == EXACTLY)                sb.append("EXACTLY ");            else if (mode == AT_MOST)                sb.append("AT_MOST ");            else                sb.append(mode).append(" ");            sb.append(size);            return sb.toString();        }    }    ...}

作用

MeasureSpec是用来规范测量数值的。什么意思呢?MeasureSpec规定了,一个规范的测量数值(int型,32位)应该由mode和size两部分组成,即应该由一个32位的二进制数“mode(前两位) + size(后30位,即原始测量数值)”的形式组成。

mode分类

mode是用两位二进制表示的数,其分类如下

  • UPSPECIFIED:父元素不对子元素施加任何束缚,子元素可以得到任意想要的大小

  • EXACTLY:父元素决定子元素的确切大小,子元素将被限定在给定的边界里而忽略子元素本身的大小

  • AT_MOST:子元素至多达到指定大小的值

它们对应的二进制值分别是

  • UNSPECIFIED = 00

  • EXACTLY = 01

  • AT_MOST = 10

应用

在XML布局时,通过android:layout_widthandroid:layout_height来设置控件的宽高值,宽高值分为三种类型:wrap_content、match_parent、具体值,三种值类型所对应的mode为

  • wrap_content ——> MeasureSpec.AT_MOST

  • match_parent ——> MeasureSpec.EXACTLY

  • 具体值 ——> MeasureSpec.EXACTLY

举个例子,测量的宽度值为50,那么其所对应的MeasureSpec规范数值就应该为:01 000000000000000000000000110010,其中,前两位(01)代表mode位,50对应的二进制数为110010,不足30位补位即可。

MeasureSpec和LayoutParams

上面了解了MeasureSpec这个类,那么该类是怎么创建来的呢?根据什么创建来的呢?现在就来深入了解一下。

对于普通View,其对应的MeasureSpec是由父容器的MeasureSpec和自身的LayoutParams来共同决定的。

普通View的measure过程由ViewGroup传递而来,那么来看看源码了解下MeasureSpec是怎么被创建出来的

package android.view;...public abstract class ViewGroup extends View implements ViewParent, ViewManager {    ...    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);    }    ...}

上述方法,首先获取到了子元素的LayoutParams,然后根据当前子元素的LayoutParams和父容器的MeasureSpec来获取子元素的 MeasureSpec,最后调用了子元素的measure(int, int)

来看看getChildMeasureSpec(int, int, int)的具体实现

package android.view;...public abstract class ViewGroup extends View implements ViewParent, ViewManager {    ...    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);    }    ...}

正如先前所说,上述方法主要是根据父容器的MeasureSpec同时结合子元素的LayoutParams来确定子元素的MeasureSpec。

由于UNSPECIFIED这个mode主要用于系统内部measure,一般来说,我们无须关注,所以排除掉UNSPECIFIED来总结一下就是

  • 当子View采用固定宽高的时候,不管父容器的MeasureSpec是什么,子View的mode都是EXACTLY,并且其大小遵循LayoutParams中的大小

  • 当子View的宽高设为match_parent的时候,如果父容器的mode为EXACTLY,那么子View的mode也是EXACTLY,并且大小是父容器的剩余空间。如果父容器的mode为AT_MOST,那么子View的mode也是AT_MOST,并且大小不会超过父容器的剩余空间

  • 当子View的宽高设为wrap_content的时候,不管父容器的mode是EXACTLY还是AT_MOST,子View的mode都是AT_MOST,并且大小不能够超过父容器的剩余空间

View的measure流程

View的测量流程是由measure(int, int)来完成的

package android.view;...public class View implements Drawable.Callback, KeyEvent.Callback,        AccessibilityEventSource {    ...    public final void measure(int widthMeasureSpec, int heightMeasureSpec) {        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);        }        // Suppress sign extension for the low bytes        long key = (long) widthMeasureSpec << 32 | (long) heightMeasureSpec & 0xffffffffL;        if (mMeasureCache == null) mMeasureCache = new LongSparseLongArray(2);        final boolean forceLayout = (mPrivateFlags & PFLAG_FORCE_LAYOUT) == PFLAG_FORCE_LAYOUT;        // Optimize layout by avoiding an extra EXACTLY pass when the view is        // already measured as the correct size. In API 23 and below, this        // extra pass is required to make LinearLayout re-distribute weight.        final boolean specChanged = widthMeasureSpec != mOldWidthMeasureSpec                || heightMeasureSpec != mOldHeightMeasureSpec;        final boolean isSpecExactly = MeasureSpec.getMode(widthMeasureSpec) == MeasureSpec.EXACTLY                && MeasureSpec.getMode(heightMeasureSpec) == MeasureSpec.EXACTLY;        final boolean matchesSpecSize = getMeasuredWidth() == MeasureSpec.getSize(widthMeasureSpec)                && getMeasuredHeight() == MeasureSpec.getSize(heightMeasureSpec);        final boolean needsLayout = specChanged                && (sAlwaysRemeasureExactly || !isSpecExactly || !matchesSpecSize);        if (forceLayout || needsLayout) {            // first clears the measured dimension flag            mPrivateFlags &= ~PFLAG_MEASURED_DIMENSION_SET;            resolveRtlPropertiesIfNeeded();            int cacheIndex = forceLayout ? -1 : mMeasureCache.indexOfKey(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 = mMeasureCache.valueAt(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;        mMeasureCache.put(key, ((long) mMeasuredWidth) << 32 |                (long) mMeasuredHeight & 0xffffffffL); // suppress sign extension    }    ...}

发现该方法是一个final型方法,所以不能被重写。不过在其中回调了onMeasure(int, int),因此我们只需在View中重写onMeasure(int, int)方法来完成View的测量即可。那么View默认的onMeasure(int, int)实现是怎样的呢?

package android.view;...public class View implements Drawable.Callback, KeyEvent.Callback,        AccessibilityEventSource {    ...    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {        setMeasuredDimension(getDefaultSize(getSuggestedMinimumWidth(), widthMeasureSpec),                getDefaultSize(getSuggestedMinimumHeight(), heightMeasureSpec));    }    ...}

可以看到,该方法的实现很简单,直接调用了setMeasuredDimension(int, int)来设置测量的尺寸。也就是说,关键就在于getDefaultSize(int, int)方法里

package android.view;...public class View implements Drawable.Callback, KeyEvent.Callback,        AccessibilityEventSource {    ...    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;    }    ...}

很显然,getDefaultSize(int, int)方法返回的就是MeasureSpec的specSize(源码里的变量,即实际测量值)部分,而这个MeasureSpec是ViewGroup传递过来的。

到这里也就理解了,为什么当我们在布局中设置子View的宽高值为wrap_content的时候,如果不重写其onMeasure(int, int)方法,则默认大小就是父容器的可用大小了。

当我们在布局中设置子View的宽高值为wrap_content时,那么测量模式(即mode)就是AT_MOST,在该模式下,它的宽高值等于specSize。而specSize由ViewGroup传递过来时就是parentSize(自定义的变量名称),也就是父控件的可用大小。

当我们在布局中设置子View的宽高值为match_parent时,那么不用多说,宽高值当然也是parentSize。这时候,我们只需对AT_MOST测量模式进行处理

protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {    int widthMode = MeasureSpec.getMode(widthMeasureSpec);    int heightMode = MeasureSpec.getMode(heightMeasureSpec);    int widthSize = MeasureSpec.getSize(widthMeasureSpec);    int heightSize = MeasureSpec.getSize(heightMeasureSpec);    int width = 0;    int height = 0;    if(widthMode == MeasureSpec.AT_MOST){        width = ...    }    if(heightMode == MeasureSpec.AT_MOST){        height = ...    }    setMeasuredDimension(widthMode != MeasureSpec.AT_MOST ? widthSize : width,            heightMode != MeasureSpec.AT_MOST? heightSize : height);}

上述代码,判断当测量模式是AT_MOST时,自己计算View的宽高。其他情况,直接使用specSize。

至于UNSPECIFIED这种情况,则是使用的是getDefaultSize(int, int)的第一个参数的值,也就是getSuggestedMinimumWidth()getSuggestedMinimumHeight()所返回的值,一般用于系统内部的测量过程,这两个方法的源码如下

package android.view;...public class View implements Drawable.Callback, KeyEvent.Callback,        AccessibilityEventSource {    ...    protected int getSuggestedMinimumHeight() {        return (mBackground == null) ? mMinHeight : max(mMinHeight, mBackground.getMinimumHeight());    }    protected int getSuggestedMinimumWidth() {        return (mBackground == null) ? mMinWidth : max(mMinWidth, mBackground.getMinimumWidth());    }    ...}

从源码看出,这是判断View有没有背景,没有背景的话,那么值就是View最小的宽度或高度,也就是对应XML布局中的android:minWidthandroid:minHeight属性,如果属性没有指定的话,默认值为0;有背景的话,那么值就是View最小的宽度或高度和背景的最小宽度或高度,取两者中最大的一个值,这个值就是当测量模式是UNSPECIFIED时View的测量宽/高。

到这里就完成了整个View的measure过程,完成之后我们就可以通过getMeasuredWidth()getMeasuredHeight()方法获取View正确的测量宽/高了。但是需要注意的是,在某些极端情况下,系统可能需要在多次measure过程后才能确定最终的测量宽/高,在这种情况下,直接在onMeasure(int, int)中获取的测量宽/高可能是不准确的,保险的做法是在onLayout(boolean, int, int, int, int)方法中去获取。

ViewGroup的measure流程

ViewGroup的measure流程和View不同,不仅需要完成自身的measure流程,还需要去遍历其所有子View,各个子元素再递归这个流程。ViewGroup提供了一个叫measureChildren(int, int)的方法

package android.view;...public abstract class ViewGroup extends View implements ViewParent, ViewManager {    ...    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 ((child.mViewFlags & VISIBILITY_MASK) != GONE) {                measureChild(child, widthMeasureSpec, heightMeasureSpec);            }        }    }    ...}

该方法遍历了所有的子View,判断如果子View没有GONE掉的时候,就继续执行measureChild(View, int, int)方法

package android.view;...public abstract class ViewGroup extends View implements ViewParent, ViewManager {    ...    protected void measureChild(View child, int parentWidthMeasureSpec,            int parentHeightMeasureSpec) {        final LayoutParams lp = child.getLayoutParams();        final int childWidthMeasureSpec = getChildMeasureSpec(parentWidthMeasureSpec,                mPaddingLeft + mPaddingRight, lp.width);        final int childHeightMeasureSpec = getChildMeasureSpec(parentHeightMeasureSpec,                mPaddingTop + mPaddingBottom, lp.height);        child.measure(childWidthMeasureSpec, childHeightMeasureSpec);    }    ...}

该方法获取了子View的LayoutParams,然后通过getChildMeasureSpec(int, int, int)创建子View的MeasureSpec,创建好后,将MeasureSpec传给子VIew进行其measure 流程。

解读源码发现,ViewGroup并没有定义其具体的测量流程(即实现onMeasure(int, int)),这是因为ViewGroup是一个抽象类,它的测量流程需要其子类去实现不同的布局特性,没办法做统一实现,比如说像LinearLayout、RelativeLayout等。

layout

通过源码发现,layout流程的关键在于onLayout(boolean, int, int, int, int),实现View的layout流程,就是实现该View的布局,而如果换成是ViewGroup,那么就实现其所有子控件的布局。

package android.view;...public class View implements Drawable.Callback, KeyEvent.Callback,        AccessibilityEventSource {    ...    protected void onLayout(boolean changed, int left, int top, int right, int bottom) {    }    ...}
package android.view;...public abstract class ViewGroup extends View implements ViewParent, ViewManager {    ...    @Override    protected abstract void onLayout(boolean changed,            int l, int t, int r, int b);    ...}

draw

下面直接通过查看draw(Canvas)的源码,来分析下其draw流程

package android.view;...public class View implements Drawable.Callback, KeyEvent.Callback,        AccessibilityEventSource {    ...    public void draw(Canvas canvas) {        ...        /*         * 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        ...        if (!dirtyOpaque) {            drawBackground(canvas);        }        // skip step 2 & 5 if possible (common case)        ...        // Step 2, save the canvas' layers        ...        int solidColor = getSolidColor();        if (solidColor == 0) {            final int flags = Canvas.HAS_ALPHA_LAYER_SAVE_FLAG;            if (drawTop) {                canvas.saveLayer(left, top, right, top + length, null, flags);            }            if (drawBottom) {                canvas.saveLayer(left, bottom - length, right, bottom, null, flags);            }            if (drawLeft) {                canvas.saveLayer(left, top, left + length, bottom, null, flags);            }            if (drawRight) {                canvas.saveLayer(right - length, top, right, bottom, null, flags);            }        } else {            scrollabilityCache.setFadeColor(solidColor);        }        // Step 3, draw the content        if (!dirtyOpaque) onDraw(canvas);        // Step 4, draw the children        dispatchDraw(canvas);        // Step 5, draw the fade effect and restore layers        ...        if (drawTop) {            ...            canvas.drawRect(left, top, right, top + length, p);        }        if (drawBottom) {            ...            canvas.drawRect(left, bottom - length, right, bottom, p);        }        if (drawLeft) {            ...            canvas.drawRect(left, top, left + length, bottom, p);        }        if (drawRight) {            ...            canvas.drawRect(right - length, top, right, bottom, p);        }        ...        // Step 6, draw decorations (foreground, scrollbars)        onDrawForeground(canvas);    }    ...}

上面的源码注释写的很清晰,通过查看后我们了解到View的绘制共分为如下六步

  • 绘制背景

  • 如果需要,保存图层信息

  • 绘制View的内容

  • 如果View有子View,绘制View的子View

  • 如果需要,绘制View的边缘(如阴影等)

  • 绘制View的装饰(如滚动条等)

其中以上六步,第二步和第五步并不是必须的,所以我们只需重点分析其他四步即可。

绘制背景

绘制背景调用了drawBackground(Canvas)方法

package android.view;...public class View implements Drawable.Callback, KeyEvent.Callback,        AccessibilityEventSource {    ...    private void drawBackground(Canvas canvas) {        // 获取背景 drawable        final Drawable background = mBackground;        if (background == null) {            return;        }        // 根据在 layout 过程中获取的 View 的位置参数,来设置背景的边界        setBackgroundBounds();        ...        // 获取 mScrollX 和 mScrollY值         final int scrollX = mScrollX;        final int scrollY = mScrollY;        if ((scrollX | scrollY) == 0) {            background.draw(canvas);        } else {            // 如果 mScrollX 和 mScrollY 有值,则对 canvas 的坐标进行偏移            canvas.translate(scrollX, scrollY);            // 调用 Drawable 的 draw 方法绘制背景            background.draw(canvas);            canvas.translate(-scrollX, -scrollY);        }    }    ...}

绘制内容

绘制内容调用了View#onDraw(Canvas)方法,由于View的内容各不相同,所以该方法是一个空实现,需要由子类去实现

package android.view;...public class View implements Drawable.Callback, KeyEvent.Callback,        AccessibilityEventSource {    ...    protected void onDraw(Canvas canvas) {    }    ...}

绘制子View

绘制子View调用了View#dispatchDraw(Canvas)方法,该方法同样是一个空实现

package android.view;...public class View implements Drawable.Callback, KeyEvent.Callback,        AccessibilityEventSource {    ...    protected void dispatchDraw(Canvas canvas) {    }    ...}

当只有包含子View的时候,才会去重写它,ViewGroup不正好符合条件吗? 来看下ViewGroup对该方法的实现吧

package android.view;...public abstract class ViewGroup extends View implements ViewParent, ViewManager {    ...    protected void dispatchDraw(Canvas canvas) {        ...        final int childrenCount = mChildrenCount;        ...        for (int i = 0; i < childrenCount; i++) {            ...            if ((transientChild.mViewFlags & VISIBILITY_MASK) == VISIBLE ||                    transientChild.getAnimation() != null) {                more |= drawChild(canvas, transientChild, drawingTime);            }            ...        }    }    ...}

ViewGroup#dispatchDraw(Canvas)方法的代码比较多,只分析重点,遍历了所有的子View并调用了ViewGroup#drawChild(Canvas, View, long)方法

package android.view;...public abstract class ViewGroup extends View implements ViewParent, ViewManager {    ...    protected boolean drawChild(Canvas canvas, View child, long drawingTime) {        return child.draw(canvas, this, drawingTime);    }    ...}

该方法最终还是调用了子View的draw(Canvas)方法。

由于ViewGroup已经为我们实现了该方法,所以我们一般都不需要重写该方法。

绘制装饰

绘制装饰调用了View#onDrawForeground(Canvas)方法,源码如下

package android.view;...public class View implements Drawable.Callback, KeyEvent.Callback,        AccessibilityEventSource {    ...    public void onDrawForeground(Canvas canvas) {        onDrawScrollIndicators(canvas);        onDrawScrollBars(canvas);        final Drawable foreground = mForegroundInfo != null ? mForegroundInfo.mDrawable : null;        if (foreground != null) {            if (mForegroundInfo.mBoundsChanged) {                mForegroundInfo.mBoundsChanged = false;                final Rect selfBounds = mForegroundInfo.mSelfBounds;                final Rect overlayBounds = mForegroundInfo.mOverlayBounds;                if (mForegroundInfo.mInsidePadding) {                    selfBounds.set(0, 0, getWidth(), getHeight());                } else {                    selfBounds.set(getPaddingLeft(), getPaddingTop(),                        getWidth() - getPaddingRight(), getHeight() - getPaddingBottom());                }                final int ld = getLayoutDirection();                Gravity.apply(mForegroundInfo.mGravity, foreground.getIntrinsicWidth(),                    foreground.getIntrinsicHeight(), selfBounds, overlayBounds, ld);                foreground.setBounds(overlayBounds);            }            foreground.draw(canvas);        }    }    ...}

该方法默认实现是绘制了滚动指示器、滚动条、和前景。

参考链接:
1. Android View 的绘制流程
2. 自定义控件三部曲视图篇(一)——测量与布局
3. Android 从0开始自定义控件之深入理解 MeasureSpec (六)
4. Android 从0开始自定义控件之 View 的 measure 过程(七)
5. Andriod 从0开始自定义控件之 View 的 layout 过程 (八)
6. Android 从0开始自定义控件之 View 的 draw 过程 (九)

0 0
原创粉丝点击