读书笔记(4) View的工作原理

这些读书笔记是根据《Android开发艺术探索》和《Android群英传》这两本书,然后将书上的一些知识点做一下记录。方便学习和理解,如果有存在侵犯版权的地方,还麻烦告知。个人强烈建议购买这两本书。真心不错。

本节是和《Android开发艺术探索》中的第4章 “View的工作原理” 有关系,建议先买书查看这一章。

[]初始ViewRoot和DecorView

ViewRoot:ViewRoot对应于ViewRootImpl类,它是连接WindowManager和DecorView的纽带,View的三大流程都是通过ViewRoot来完成的,在ActivityThread中,当Activity对象被创建完毕后,会将DecorView添加到Window中,同时会创建ViewRootImpl对象,并将ViewRootImpl和DecorView建立关联。这个过程可参考如下源码:

root = new ViewRootImpl(view.getContext(), display);
root.setView(view, wparams, panelParentView);

View的绘制流程是从ViewRoot的performTraversals()开始,它经过measure(),layout(),draw()三个过程才能最终将一个View绘制出来,其中measure()用来测量View的宽和高,layout()用来确定View在父容器中的放置位置,draw()负责将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的宽/高。measure完成后,可以通过getMeasuredWidth()和getMeasuredHeight()来获取到View测量后的宽/高,在几乎所有的情况下它都等同于View的最终的宽/高,但是在特殊情况下除外。layout过程决定View的四个顶点的坐标和实际的View的宽/高。完成以后,可以通过getTop(),getBottom(),getLeft(),getRight()来获取View的四个顶点的位置,并可以通过getWidth()和getHeight()来获取View的最终宽/高,draw过程决定View的显示,只有draw()完成以后View的内容才能呈现在屏幕上。

DecorView:DecorView作为顶级View,一般情况下它内部会包含一个竖直方向的LinearLayout,在这个LinearLayout里面有上下两个部分,上面是标题栏,下面是内容栏。在Activity中通过setContentView()设置的布局文件其实就是被加到内容栏之中的,而内容栏的id是content,因此可以理解为Activity指定布局的方法不叫setView()而叫setContentView(),因为我们的布局的确加到id为content的FrameLayout中。DecorView其实是一个FrameLayout,View层的事件都先通过DecorView,然后才传递给我们的View。

[]理解MeasureSpec

{}MeasureSpec

MeasureSpec在很大程度上决定一个View的尺寸规格,因为这个过程还受到父容器的影响,因为父容器影响View的MeasureSpec的创建过程。在测量过程中,系统会将View的LayoutParams根据父容器所施加的规则转换成对应的MeasureSpec,然后在根据这个MeasureSpec来测量出View的宽/高,这里的宽/高是测量宽/高,不一定等于View的最终宽/高。

MeasureSpec代表一个32位int值。高2位代表SpecMode,低30位代表SpecSize,SpecMode是指测量模式,SpecSize是指在某种测量模式下的规格大小。

SpecMode有三类:

UNSPECIFIED
父容器不对View有任何限制,要多大给多大,这种情况一般用于系统内部,表示一种测量的状态。

EXACTLY
父容器已经检测出View所需要的精确大小,这个时候View的最终大小就是SpecSize所指定的值,它对应于LayoutParams中的math_parent和具体的数值这两种模式。

AT_MOST
父容器指定一个可用大小即SpecSize,View的大小不能大于这个值,具体是什么要看View的具体实现,它对应于LayoutParams中的wrap_content。

{}MeasureSpec和LayoutParams的对应关系

在View测量的时候,系统会将LayoutParams在父容器的约束下转换成对应的MeasureSpec,然后再根据这个MeasureSpec来确定View测量后的宽/高。需要注意的是,MeasureSpec不是唯一由LayoutParams决定的,LayoutParams需要和父容器一起才能决定View的MeasureSpec,从而进一步确定view的宽高。对于顶级View(DecorView),其MeasureSpec由窗口的尺寸和其自身的LayoutParams来决定。对于普通View,其MeasureSpec由父容器的MeasureSpec和自身的LayoutParams来共同决定。MeasureSpec一旦确定后,onMeasure()中就可以确定View的测量宽/高。

对于DecorView来说,在ViewRootImpl中的measureHierarchy()中有如下的一段代码,它展示了DecorView的MeasureSpec的创建过程。

childWidthMeasureSpec = getRootMeasureSpec(baseSize, lp.width);
childHeightMeasureSpec = getRootMeasureSpec(desiredWindowHeight, lp.height);
performMeasure(childWidthMeasureSpec, childHeightMeasureSpec);

private static int getRootMeasureSpec(int windowSize, int rootDimension) {
    int measureSpec;
    switch (rootDimension) {

    case ViewGroup.LayoutParams.MATCH_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;
}

通过上述代码, 对于顶级View(DecorView)来说MeasureSpec产生遵守如下规则,根据它的LayoutParams的宽/高参数来划分。

LayoutParams.MATH_PARENT:精确模式,大小就是窗口大小。

LayoutParams.WRAP_CONTENT:最大模式,大小不定。但是不能超过窗口的大小。

固定大小:精确模式,大小为LayoutParams中指定的大小。

对于普通View来说,这里是指我们布局中的View,View的measure过程由ViewGroup传递而来,先来看一下ViewGroup的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);
}

上述方法会对子元素进行measure,在调用子元素的measure()之前会先通过getChildMeasureSpec()来得到子元素的MeasureSpec,从代码来看,子元素的MeasureSpec创建与父容器的MeasureSpec和子元素本身的LayoutParams有关,此外还和View的margin及padding有关。

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;
    }
    return MeasureSpec.makeMeasureSpec(resultSize, resultMode);
}

getChildMeasureSpec()的主要作用是根据父容器的MeasureSpec同时结合View本身的LayoutParams来确定子元素的MeasureSpec,参数中的padding是指父容器中已占用的空间大小。

对于普通View,针对不同的父容器和View本身不同的LayoutParams,View就可以有多种MeasureSpec。View采用固定宽/高的时候,不管父容器的MeasureSpec是什么,View的MeasureSpec都是精确模式并且大小是LayoutParams中的大小,当View的宽/高是math_parent时,如果父容器的模式是精准模式,那么View也是精准模式并且其大小是父容器的剩余空间,如果父容器是最大模式,那么View也是最大模式并且其大小不会超过父容器的剩余空间,当View的宽/高是wrap_content,不管父容器是精准还是最大,View的模式总是最大化并且大小不能超过父容器的剩余空间。UNSPECIFIED 模式主要用于系统内部多次MeasureSpec的情形,一般来说,我们不需要关注此模式。

[]View的工作流程

View的工作流程主要是指measure,layout,draw三大流程,即测量,布局,绘制。其中measure确定View的测量宽/高,layout确定View的最终宽/高和四个顶点的位置,draw将View绘制到屏幕上。

{}measure过程

measure过程要分情况来看,如果只是一个原始的View,那么通过measure()就完成了其测量过程,如果是一个ViewGroup,除了完成自己的测量过程外,还会遍历去调用所有子元素的measure(),各个子元素在递归去执行这个流程。

1,View的measure过程

View的measure过程由其measure()来完成,measure()是一个final类型的方法。这意味着子类不能重写此方法,在View的measure()会去调用onMeasure(),因此只需要看onMeasure()的实现即可。

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);

    if ((mPrivateFlags & PFLAG_FORCE_LAYOUT) == PFLAG_FORCE_LAYOUT ||
            widthMeasureSpec != mOldWidthMeasureSpec ||
            heightMeasureSpec != mOldHeightMeasureSpec) {

        // first clears the measured dimension flag
        mPrivateFlags &= ~PFLAG_MEASURED_DIMENSION_SET;

        resolveRtlPropertiesIfNeeded();

        int cacheIndex = (mPrivateFlags & PFLAG_FORCE_LAYOUT) == PFLAG_FORCE_LAYOUT ? -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
}



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 = 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()中我们只需要看AT_MOST和EXACTLY这两种情况,简单的理解,其实getDefaultSize()返回的大小就是measureSpec中的specSize。而这个specSize就是View测量后的大小,

至于UNSPECIFIED这种情况,一般用于系统内部的测量过程,在这种情况下,View的大小为getDefaultSize()的第一个参数size,即宽/高分别是getSuggestedMinimumWidth()和getSuggestedMinimumHeight()的返回值。

protected int getSuggestedMinimumWidth() {
    return (mBackground == null) ? mMinWidth : max(mMinWidth, mBackground.getMinimumWidth());
}

getSuggestedMinimumWidth():
如果View没有设置背景,那么View的宽度为mMinWidth,而mMinWidth对应android:minWidth这个属性指定的值,因此View的宽度即为android:minWidth属性指定的值,如果这个属性没有指定,那么mMinWidth则默认为0,如果View指定了背景,则View的宽度为max(mMinWidth,mBackground.getMinimunWidth())

mBackground.getMinimunWidth()返回的就是Drawable的原始宽度,前提是这个Drawable有原始宽度,否则就返回0。

从getDefaultSize()的实现来看,View的宽/高由specSize决定,所以我们得出如下结论:直接继承View的自定义控件需要重写onMeasure()并设置wrap_content时的自身大小,否则在布局中使用wrap_content就相当于match_parent。因为如果View在布局中使用wrap_content,那么它的specMode是AT_MOST模式,这这种模式下,它的宽/高等于specSize,这种情况下View的specSize是parentSize,而parentSize是父容器中目前可以使用的大小。也就是父容器当前剩余的空间大小,很显然,View的宽/高就等于父容器当前剩余的空间大小,这种效果和布局中使用match_parent完全一致。如何解决:只需要高View指定一个默认的内部宽/高(mWidth和mHeight),并在wrap_content时设置此宽/高。对于非wrap_content情况,我们沿用系统的测量值即可,至于这个默认的内部宽/高的大小则根据业务指定。

2,ViewGroup的measure过程

ViewGroup除了完成自己的measure过程以外,还会遍历去调用所有子元素的measure(),各个子元素再递归去执行这个过程,和View不同的是
ViewGroup是一个抽象类,因此它没有重写View的onMeasure(),但是它提供了一个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 ((child.mViewFlags & VISIBILITY_MASK) != GONE) {
            measureChild(child, widthMeasureSpec, heightMeasureSpec);
        }
    }
}

从上述代码来看,ViewGroup在measure时,会对每个子元素进行measure。

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);
}

measureChild()的思想就是取出子元素的LayoutParams,然后再通过getChildMeasureSpec()来创建子元素的MeasureSpec,接着将MeasureSpec直接传递给View的measure()进行测量。

为啥ViewGroup并没有定义其测量的具体过程,这是因为ViewGroup是一个抽象类,其测量过程的onMeasure()需要各个子类去实现,为啥不统一,这是因为不同的ViewGroup子类有不同的布局特性。以下分析LinearLayout的onMeasure()。

protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
    if (mOrientation == VERTICAL) {
        measureVertical(widthMeasureSpec, heightMeasureSpec);
    } else {
        measureHorizontal(widthMeasureSpec, heightMeasureSpec);
    }
}

选择竖直布局的LinearLayout的测量过程,即measureVertical()。

void measureVertical(int widthMeasureSpec, int heightMeasureSpec) {
    mTotalLength = 0;
    int maxWidth = 0;
    int childState = 0;
    int alternativeMaxWidth = 0;
    int weightedMaxWidth = 0;
    boolean allFillParent = true;
    float totalWeight = 0;

    final int count = getVirtualChildCount();

    final int widthMode = MeasureSpec.getMode(widthMeasureSpec);
    final int heightMode = MeasureSpec.getMode(heightMeasureSpec);

    boolean matchWidth = false;
    boolean skippedMeasure = false;

    final int baselineChildIndex = mBaselineAlignedChildIndex;        
    final boolean useLargestChild = mUseLargestChild;

    int largestChildHeight = Integer.MIN_VALUE;

    // See how tall everyone is. Also remember max width.
    for (int i = 0; i < count; ++i) {
        final View child = getVirtualChildAt(i);

        if (child == null) {
            mTotalLength += measureNullChild(i);
            continue;
        }

        if (child.getVisibility() == View.GONE) {
           i += getChildrenSkipCount(child, i);
           continue;
        }

        if (hasDividerBeforeChildAt(i)) {
            mTotalLength += mDividerHeight;
        }

        LinearLayout.LayoutParams lp = (LinearLayout.LayoutParams) child.getLayoutParams();

        totalWeight += lp.weight;

        if (heightMode == MeasureSpec.EXACTLY && lp.height == 0 && lp.weight > 0) {
            // Optimization: don't bother measuring children who are going to use
            // leftover space. These views will get measured again down below if
            // there is any leftover space.
            final int totalLength = mTotalLength;
            mTotalLength = Math.max(totalLength, totalLength + lp.topMargin + lp.bottomMargin);
            skippedMeasure = true;
        } else {
            int oldHeight = Integer.MIN_VALUE;

            if (lp.height == 0 && lp.weight > 0) {
                // heightMode is either UNSPECIFIED or AT_MOST, and this
                // child wanted to stretch to fill available space.
                // Translate that to WRAP_CONTENT so that it does not end up
                // with a height of 0
                oldHeight = 0;
                lp.height = LayoutParams.WRAP_CONTENT;
            }

            // Determine 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 needed).
            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));

            if (useLargestChild) {
                largestChildHeight = Math.max(childHeight, largestChildHeight);
            }
        }

        /**
         * If applicable, compute the additional offset to the child's baseline
         * we'll need later when asked {@link #getBaseline}.
         */
        if ((baselineChildIndex >= 0) && (baselineChildIndex == i + 1)) {
           mBaselineChildTop = mTotalLength;
        }

        // if we are trying to use a child index for our baseline, the above
        // book keeping only works if there are no children above it with
        // weight.  fail fast to aid the developer.
        if (i < baselineChildIndex && lp.weight > 0) {
            throw new RuntimeException("A child of LinearLayout with index "
                    + "less than mBaselineAlignedChildIndex has weight > 0, which "
                    + "won't work.  Either remove the weight, or don't set "
                    + "mBaselineAlignedChildIndex.");
        }

        boolean matchWidthLocally = false;
        if (widthMode != MeasureSpec.EXACTLY && lp.width == LayoutParams.MATCH_PARENT) {
            // The width of the linear layout will scale, and at least one
            // child said it wanted to match our width. Set a flag
            // indicating that we need to remeasure at least that view when
            // we know our width.
            matchWidth = true;
            matchWidthLocally = true;
        }

        final int margin = lp.leftMargin + lp.rightMargin;
        final int measuredWidth = child.getMeasuredWidth() + margin;
        maxWidth = Math.max(maxWidth, measuredWidth);
        childState = combineMeasuredStates(childState, child.getMeasuredState());

        allFillParent = allFillParent && lp.width == LayoutParams.MATCH_PARENT;
        if (lp.weight > 0) {
            /*
             * Widths of weighted Views are bogus if we end up
             * remeasuring, so keep them separate.
             */
            weightedMaxWidth = Math.max(weightedMaxWidth,
                    matchWidthLocally ? margin : measuredWidth);
        } else {
            alternativeMaxWidth = Math.max(alternativeMaxWidth,
                    matchWidthLocally ? margin : measuredWidth);
        }

        i += getChildrenSkipCount(child, i);
    }

    if (mTotalLength > 0 && hasDividerBeforeChildAt(count)) {
        mTotalLength += mDividerHeight;
    }

    if (useLargestChild &&
            (heightMode == MeasureSpec.AT_MOST || heightMode == MeasureSpec.UNSPECIFIED)) {
        mTotalLength = 0;

        for (int i = 0; i < count; ++i) {
            final View child = getVirtualChildAt(i);

            if (child == null) {
                mTotalLength += measureNullChild(i);
                continue;
            }

            if (child.getVisibility() == GONE) {
                i += getChildrenSkipCount(child, i);
                continue;
            }

            final LinearLayout.LayoutParams lp = (LinearLayout.LayoutParams)
                    child.getLayoutParams();
            // Account for negative margins
            final int totalLength = mTotalLength;
            mTotalLength = Math.max(totalLength, totalLength + largestChildHeight +
                    lp.topMargin + lp.bottomMargin + getNextLocationOffset(child));
        }
    }

    // Add in our padding
    mTotalLength += mPaddingTop + mPaddingBottom;

    int heightSize = mTotalLength;

    // Check against our minimum height
    heightSize = Math.max(heightSize, getSuggestedMinimumHeight());

    // Reconcile our calculated size with the heightMeasureSpec
    int heightSizeAndState = resolveSizeAndState(heightSize, heightMeasureSpec, 0);
    heightSize = heightSizeAndState & MEASURED_SIZE_MASK;

    // Either expand children with weight to take up available space or
    // shrink them if they extend beyond our current bounds. If we skipped
    // measurement on any children, we need to measure them now.
    int delta = heightSize - mTotalLength;
    if (skippedMeasure || delta != 0 && totalWeight > 0.0f) {
        float weightSum = mWeightSum > 0.0f ? mWeightSum : totalWeight;

        mTotalLength = 0;

        for (int i = 0; i < count; ++i) {
            final View child = getVirtualChildAt(i);

            if (child.getVisibility() == View.GONE) {
                continue;
            }

            LinearLayout.LayoutParams lp = (LinearLayout.LayoutParams) child.getLayoutParams();

            float childExtra = lp.weight;
            if (childExtra > 0) {
                // Child said it could absorb extra space -- give him his share
                int share = (int) (childExtra * delta / weightSum);
                weightSum -= childExtra;
                delta -= share;

                final int childWidthMeasureSpec = getChildMeasureSpec(widthMeasureSpec,
                        mPaddingLeft + mPaddingRight +
                                lp.leftMargin + lp.rightMargin, lp.width);

                // TODO: Use a field like lp.isMeasured to figure out if this
                // child has been previously measured
                if ((lp.height != 0) || (heightMode != MeasureSpec.EXACTLY)) {
                    // child was measured once already above...
                    // base new measurement on stored values
                    int childHeight = child.getMeasuredHeight() + share;
                    if (childHeight < 0) {
                        childHeight = 0;
                    }

                    child.measure(childWidthMeasureSpec,
                            MeasureSpec.makeMeasureSpec(childHeight, MeasureSpec.EXACTLY));
                } else {
                    // child was skipped in the loop above.
                    // Measure for this first time here      
                    child.measure(childWidthMeasureSpec,
                            MeasureSpec.makeMeasureSpec(share > 0 ? share : 0,
                                    MeasureSpec.EXACTLY));
                }

                // Child may now not fit in vertical dimension.
                childState = combineMeasuredStates(childState, child.getMeasuredState()
                        & (MEASURED_STATE_MASK>>MEASURED_HEIGHT_STATE_SHIFT));
            }

            final int margin =  lp.leftMargin + lp.rightMargin;
            final int measuredWidth = child.getMeasuredWidth() + margin;
            maxWidth = Math.max(maxWidth, measuredWidth);

            boolean matchWidthLocally = widthMode != MeasureSpec.EXACTLY &&
                    lp.width == LayoutParams.MATCH_PARENT;

            alternativeMaxWidth = Math.max(alternativeMaxWidth,
                    matchWidthLocally ? margin : measuredWidth);

            allFillParent = allFillParent && lp.width == LayoutParams.MATCH_PARENT;

            final int totalLength = mTotalLength;
            mTotalLength = Math.max(totalLength, totalLength + child.getMeasuredHeight() +
                    lp.topMargin + lp.bottomMargin + getNextLocationOffset(child));
        }

        // Add in our padding
        mTotalLength += mPaddingTop + mPaddingBottom;
        // TODO: Should we recompute the heightSpec based on the new total length?
    } else {
        alternativeMaxWidth = Math.max(alternativeMaxWidth,
                                       weightedMaxWidth);


        // We have no limit, so make all weighted views as tall as the largest child.
        // Children will have already been measured once.
        if (useLargestChild && heightMode != MeasureSpec.EXACTLY) {
            for (int i = 0; i < count; i++) {
                final View child = getVirtualChildAt(i);

                if (child == null || child.getVisibility() == View.GONE) {
                    continue;
                }

                final LinearLayout.LayoutParams lp =
                        (LinearLayout.LayoutParams) child.getLayoutParams();

                float childExtra = lp.weight;
                if (childExtra > 0) {
                    child.measure(
                            MeasureSpec.makeMeasureSpec(child.getMeasuredWidth(),
                                    MeasureSpec.EXACTLY),
                            MeasureSpec.makeMeasureSpec(largestChildHeight,
                                    MeasureSpec.EXACTLY));
                }
            }
        }
    }

    if (!allFillParent && widthMode != MeasureSpec.EXACTLY) {
        maxWidth = alternativeMaxWidth;
    }

    maxWidth += mPaddingLeft + mPaddingRight;

    // Check against our minimum width
    maxWidth = Math.max(maxWidth, getSuggestedMinimumWidth());

    setMeasuredDimension(resolveSizeAndState(maxWidth, widthMeasureSpec, childState),
            heightSizeAndState);

    if (matchWidth) {
        forceUniformWidth(count, heightMeasureSpec);
    }
}

measureVertical():系统会遍历子元素并对每个子元素执行measureChildBeforeLayout(),这个方法会调用子元素的measure(),这样子元素就开始依次进入measure过程,并且系统会通过mTotalLength这个变量来存储LinearLayout在竖直方向的初步高度,每测量一个子元素,mTotalLength就会增加,增加的部分主要包括子元素的高度以及子元素在竖直方向上的margin等,当子元素测量完毕后,LinearLayout会测量自己的大小。

当子元素测量完毕后,LinearLayout会根据子元素的情况来测量自己的大小,针对竖直的LinearLayout来说,它在水平方向的测量过程遵循View的测量过程,在竖直方向的测量过程则和View有所不同,具体来说是指,如果它的布局中高度采用match_parent或者具体数值,那么它的测量过程和View一致,即高度为specSize。如果它的布局中高度采用wrap_
content,那么它的高度是所有子元素占用的高度总和,但是不能超过它的父容器的剩余空间,当然它的最终高度还需要考虑其在竖直方向的padding。

measure完成后,通过getMeasuredWidth()和getMeasureHeight()就可以正确的获取到View的测量宽/高,需要注意的是。在某些极端情况下,系统可能需要多次measure才能最终确定最终的测量宽/高,在这种情况下,onMeasure()拿到的测量宽/高很可能是不准确的吗,一个比较好的习惯是在onLayout()中去获取View的测量宽/高或者最终宽/高。

在Activity已启动的时候就做一件任务,这个任务需要获取某个View的宽/高。在onCreate(),onStart(),onResume()中均无法正确得到某个View的宽/高,这是因为View的measure过程和Activity什么周期不是同步执行的,因此无法保证Activity执行了onCreate(),onStart(),onResume()时某个View已经测量完毕,如果View还没有测量完毕,那么获取的宽/高就是0。下面给出四种方法来解决这个问题:

1,Activity/View#onWindowFocusChanged

onWindowFocusChanged这个方法的含义是:View已经初始化完毕了,宽高已经准备好了,这时去获取宽/高是没有问题的。需要注意:它会被调用多次,当Activity的窗口得到焦点和失去焦点均会被调用。具体来说,当Activity继续执行和暂停执行时,它均会被调用。如果频繁的进行onResume()和onPause(),那么它就会被频繁的调用。

2,view.post(runnable)

通过post将一个runnable投递到消息队列的尾部,当Looper调用此runnable的时候,View也初始化好了。

3,ViewTreeObserver

使用ViewTreeObserver的众多回调可以完成这个功能,比如OnGlobalLayoutListener这个接口,当View树的状态发送改变或View树内部的View的可见性发生改变时,onGlobalLayout方法会被回调。因此这时获取View的宽/高一个很好的时机。需要注意的是,伴随着View树状态的改变,onGlobalLayout会被回调多次。

4,view.measure(int widthMeasureSpec,int heightMeasureSpec)

通过手动对View进行measure来得到View的宽/高。这种方法比较复杂,这里要分情况处理,根据View的LayoutParams来分:

1,match_parent:

直接放弃,无法measure出具体的宽高,原因很简单,根据View的measure过程,构造此种MeasureSpec需要知道parentSize,即父容器的剩余空间。而这个时候我们无法知道parentSize的大小,所以理论上无法测量出View的大小。

2,具体的数值(dp/px)

3,wrap_content

{}layout过程

layout过程是ViewGroup用来确定子元素的位置,当ViewGroup的位置被确定后,它在onLayout()中会遍历所有的子元素并调用其layout()。在layout()中onLayout()又会被调用,layout()确定View本身的位置,onLayout()则会确定所有子元素的位置。

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);
        mPrivateFlags &= ~PFLAG_LAYOUT_REQUIRED;

        ListenerInfo li = mListenerInfo;
        if (li != null && li.mOnLayoutChangeListeners != null) {
            ArrayList<OnLayoutChangeListener> listenersCopy =
                    (ArrayList<OnLayoutChangeListener>)li.mOnLayoutChangeListeners.clone();
            int numListeners = listenersCopy.size();
            for (int i = 0; i < numListeners; ++i) {
                listenersCopy.get(i).onLayoutChange(this, l, t, r, b, oldL, oldT, oldR, oldB);
            }
        }
    }

    mPrivateFlags &= ~PFLAG_FORCE_LAYOUT;
    mPrivateFlags3 |= PFLAG3_IS_LAID_OUT;
}

layout()的流程:首先会通过setFrame()来设定View的四个顶点的位置,即初始化mLeft,mRight,mTop,mBottom这四个值,View的四个顶点一旦确定,那么View在父容器中的位置也就确定了,接着会调用onLayout(),这个方法是父容器确定子元素的位置。onLayout()的具体实现和具体的布局有关。所以View和ViewGroup都没有真正实现onLayout()。

以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, t, r, b);
    }
}


void layoutVertical(int left, int top, int right, int bottom) {
    final int paddingLeft = mPaddingLeft;

    int childTop;
    int childLeft;

    // Where right end of child should go
    final int width = right - left;
    int childRight = width - mPaddingRight;

    // Space available for child
    int childSpace = width - paddingLeft - mPaddingRight;

    final int count = getVirtualChildCount();

    final int majorGravity = mGravity & Gravity.VERTICAL_GRAVITY_MASK;
    final int minorGravity = mGravity & Gravity.RELATIVE_HORIZONTAL_GRAVITY_MASK;

    switch (majorGravity) {
       case Gravity.BOTTOM:
           // mTotalLength contains the padding already
           childTop = mPaddingTop + bottom - top - mTotalLength;
           break;

           // mTotalLength contains the padding already
       case Gravity.CENTER_VERTICAL:
           childTop = mPaddingTop + (bottom - top - mTotalLength) / 2;
           break;

       case Gravity.TOP:
       default:
           childTop = mPaddingTop;
           break;
    }

    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.getMeasuredWidth();
            final int childHeight = child.getMeasuredHeight();

            final LinearLayout.LayoutParams lp =
                    (LinearLayout.LayoutParams) child.getLayoutParams();

            int gravity = lp.gravity;
            if (gravity < 0) {
                gravity = minorGravity;
            }
            final int layoutDirection = getLayoutDirection();
            final int absoluteGravity = Gravity.getAbsoluteGravity(gravity, layoutDirection);
            switch (absoluteGravity & Gravity.HORIZONTAL_GRAVITY_MASK) {
                case Gravity.CENTER_HORIZONTAL:
                    childLeft = paddingLeft + ((childSpace - childWidth) / 2)
                            + lp.leftMargin - lp.rightMargin;
                    break;

                case Gravity.RIGHT:
                    childLeft = childRight - childWidth - lp.rightMargin;
                    break;

                case Gravity.LEFT:
                default:
                    childLeft = paddingLeft + lp.leftMargin;
                    break;
            }

            if (hasDividerBeforeChildAt(i)) {
                childTop += mDividerHeight;
            }

            childTop += lp.topMargin;
            setChildFrame(child, childLeft, childTop + getLocationOffset(child),
                    childWidth, childHeight);
            childTop += childHeight + lp.bottomMargin + getNextLocationOffset(child);

            i += getChildrenSkipCount(child, i);
        }
    }
}

以layoutVertical()来讲解:首先会遍历所有子元素并调用setChildFrame()来为子元素指定对应的位置,其中childTop会逐渐增大,这就意味着后面的子元素会被放置在靠下的位置,这刚好符合竖直方向的LinearLayout特性。setChildFrame()它仅仅是调用子元素的layout()而已,这样父元素在layout()中完成自己的定位以后,就通过onLayout()去调用子元素的layout(),子元素又会通过自己的layout()来确定自己的位置,这样一层一层地传递下去完成整个View树的layout过程。

private void setChildFrame(View child, int left, int top, int width, int height) {        
    child.layout(left, top, left + width, top + height);
}

setChildFrame()中的width和height实际上就是子元素的测量宽/高。

View的测量宽/高和最终宽/高有什么区别,也就是View的getMeasuredWidth()和getWidth()的区别。View的默认实现中,View的测量宽/高和最终宽/高是相等的,只不过测量宽/高形成与View的measured过程,而最终宽/高形成于View的layout过程,即两者的赋值时机不同,测量宽/高的赋值时机稍微早一些。因此,在日常开发中,我们认为View的测量宽/高等于最终宽/高。但是的确存在某些特殊情况会导致两者不一致。比如重写View的layout(),代码如下:

public void layout(int l, int t, int r, int b) {
    super.layout(l, t, r+100, b+100);
}

这样会导致View的最终宽/高总是比测量宽/高大100,虽然这样会导致View显示不正常并且没有实际意义。但是这说明了测量宽/高和最终宽/高可以不相等。
另外一种情况时在某些情况下,View需要多次measure才能确定自己的测量宽/高,在前几次的测量过程中,其得出的测量宽/高有可能和最终宽/高不一致,但是最终来说,测量宽/高和最终宽/高是相等的。

{}draw过程

draw过程的作用是将View绘制到屏幕上,View的绘制过程遵守下面几步:
1,绘制背景:drawBackground(canvas)。
2,绘制自己:onDraw()。
3,绘制children:dispatchDraw()。
4,绘制装饰:onDrawForeground()。

    public void draw(Canvas canvas) {
    final int privateFlags = mPrivateFlags;
    final boolean dirtyOpaque = (privateFlags & PFLAG_DIRTY_MASK) == PFLAG_DIRTY_OPAQUE &&
            (mAttachInfo == null || !mAttachInfo.mIgnoreDirtyState);
    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);

        // Overlay is part of the content and draws beneath Foreground
        if (mOverlay != null && !mOverlay.isEmpty()) {
            mOverlay.getOverlayView().dispatchDraw(canvas);
        }

        // Step 6, draw decorations (foreground, scrollbars)
        onDrawForeground(canvas);

        // we're done...
        return;
    }

    /*
     * Here we do the full fledged routine...
     * (this is an uncommon case where speed matters less,
     * this is why we repeat some of the tests that have been
     * done above)
     */

    boolean drawTop = false;
    boolean drawBottom = false;
    boolean drawLeft = false;
    boolean drawRight = false;

    float topFadeStrength = 0.0f;
    float bottomFadeStrength = 0.0f;
    float leftFadeStrength = 0.0f;
    float rightFadeStrength = 0.0f;

    // Step 2, save the canvas' layers
    int paddingLeft = mPaddingLeft;

    final boolean offsetRequired = isPaddingOffsetRequired();
    if (offsetRequired) {
        paddingLeft += getLeftPaddingOffset();
    }

    int left = mScrollX + paddingLeft;
    int right = left + mRight - mLeft - mPaddingRight - paddingLeft;
    int top = mScrollY + getFadeTop(offsetRequired);
    int bottom = top + getFadeHeight(offsetRequired);

    if (offsetRequired) {
        right += getRightPaddingOffset();
        bottom += getBottomPaddingOffset();
    }

    final ScrollabilityCache scrollabilityCache = mScrollCache;
    final float fadeHeight = scrollabilityCache.fadingEdgeLength;
    int length = (int) fadeHeight;

    // clip the fade length if top and bottom fades overlap
    // overlapping fades produce odd-looking artifacts
    if (verticalEdges && (top + length > bottom - length)) {
        length = (bottom - top) / 2;
    }

    // also clip horizontal fades if necessary
    if (horizontalEdges && (left + length > right - length)) {
        length = (right - left) / 2;
    }

    if (verticalEdges) {
        topFadeStrength = Math.max(0.0f, Math.min(1.0f, getTopFadingEdgeStrength()));
        drawTop = topFadeStrength * fadeHeight > 1.0f;
        bottomFadeStrength = Math.max(0.0f, Math.min(1.0f, getBottomFadingEdgeStrength()));
        drawBottom = bottomFadeStrength * fadeHeight > 1.0f;
    }

    if (horizontalEdges) {
        leftFadeStrength = Math.max(0.0f, Math.min(1.0f, getLeftFadingEdgeStrength()));
        drawLeft = leftFadeStrength * fadeHeight > 1.0f;
        rightFadeStrength = Math.max(0.0f, Math.min(1.0f, getRightFadingEdgeStrength()));
        drawRight = rightFadeStrength * fadeHeight > 1.0f;
    }

    saveCount = canvas.getSaveCount();

    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
    final Paint p = scrollabilityCache.paint;
    final Matrix matrix = scrollabilityCache.matrix;
    final Shader fade = scrollabilityCache.shader;

    if (drawTop) {
        matrix.setScale(1, fadeHeight * topFadeStrength);
        matrix.postTranslate(left, top);
        fade.setLocalMatrix(matrix);
        p.setShader(fade);
        canvas.drawRect(left, top, right, top + length, p);
    }

    if (drawBottom) {
        matrix.setScale(1, fadeHeight * bottomFadeStrength);
        matrix.postRotate(180);
        matrix.postTranslate(left, bottom);
        fade.setLocalMatrix(matrix);
        p.setShader(fade);
        canvas.drawRect(left, bottom - length, right, bottom, p);
    }

    if (drawLeft) {
        matrix.setScale(1, fadeHeight * leftFadeStrength);
        matrix.postRotate(-90);
        matrix.postTranslate(left, top);
        fade.setLocalMatrix(matrix);
        p.setShader(fade);
        canvas.drawRect(left, top, left + length, bottom, p);
    }

    if (drawRight) {
        matrix.setScale(1, fadeHeight * rightFadeStrength);
        matrix.postRotate(90);
        matrix.postTranslate(right, top);
        fade.setLocalMatrix(matrix);
        p.setShader(fade);
        canvas.drawRect(right - length, top, right, bottom, p);
    }

    canvas.restoreToCount(saveCount);

    // Overlay is part of the content and draws beneath Foreground
    if (mOverlay != null && !mOverlay.isEmpty()) {
        mOverlay.getOverlayView().dispatchDraw(canvas);
    }

    // Step 6, draw decorations (foreground, scrollbars)
    onDrawForeground(canvas);
}

View绘制过程的传递是通过dispatchDraw()来实现的,它会遍历调用所有子元素的draw(),如此draw事件就一层层地传递下去。

注:
()drawBackground()是private的,不能被重写,可以通过android:background或者View.setBackgroundXxx()来设置。

()onDrawForeground()绘制装饰包括:滑动边缘渐变,滑动条,前景,都是在这个方法中重写。

()滑动边缘渐变和滑动条可以通过android:scrollbarXXX或者View.setXXXScrollbarXXX()来设置,前景可以通过android:foreground或者View.setForeground()来设置。

setWillNotDraw(boolean willNotDraw)

从注释中可以看出,如果一个View不需要绘制任何内容,那么设置这个标记位为true后,系统会进行相应的优化(不调用draw(),直接执行dispatchDraw())。默认情况下,View没有启用这个优化标记位,但是ViewGroup会默认启用这个优化标记位。这个标记位对实际开发的意义:当我们的自定义控件继承于ViewGroup并且本身不具备绘制功能时,就可以开启这个标记位从而便于系统进行后续的优化,当明确知道一个ViewGroup需要通过onDraw来绘制内容的时候,需要显式的关闭标记位。

[]自定义View

{}自定义View的分类

1,继承View重写onDraw()

这种方式主要用于实现一些不规则的效果,即这种效果不方便通过布局的组合方式来达到,往往需要静态或者动态地显示一些不规则的图形。很显然这需要通过绘制的方式来实现,即重写onDraw()。采用这种方式需要自己支持wrap_content和padding。

2,继承ViewGroup派生特殊的Layout

这种方式主要用于实现自定义的布局,即除了系统定义的布局,我们重新定义一种新布局。当某种效果看起来很像几种View组合在一起时,可以采用这种方式来实现。需要合适地处理ViewGroup的测量和布局这两个过程,并同时处理子元素的测量和布局过程。

3,继承特定的View(比如TextView)

这种方式一般是用于扩展某种已有的View的功能,比如TextView,这种方式不需要自己支持wrap_content和padding。

4,继承特定的ViewGroup(比如LinearLayout)

当某种效果看起来很像几种View组合在一起时,可以采用这种方式来实现。不需要自己处理ViewGroup的测量和布局这两个过程,方式2能实现的方式4都能实现,主要的差别是方式2更接近View的底层。

{}自定义View须知

1,让View支持wrap_content

因为直接继承View或者ViewGroup,如果不在onMeasure()中对wrap_content做特殊处理,那么当外界在布局中使用wrap_content就无法达到预期的效果。

2,如果有必要,让你的View支持padding

因为直接继承View,如果不在draw()中处理padding,那么padding属性是无法起作用的,直接继承ViewGroup需要在onMeasure()和onLayout()中考虑padding和子元素的margin对其的影响。不然将导致padding和子元素的margin失效。

3,尽量不要在View中使用Handler,没必要

因为View内部本身就提供了post系列的方法,完全可以替代Handler的作用,当然除非你明确地要使用Handler来发送消息。

4,View中如果有线程或者动画,需要在View的onDetachedFromWindow中停止

如果有线程或者动画需要停止,如果不及时处理,有可能会造成内存泄漏,如果要处理,那么onDetachedFromWindow()是一个很好的时机,当包含此View的Activity退出或者当前View被remove()时,View的onDetachedFromWindow()会被调用。当包含此View的Activity启动时,View的onAttachedToWindow()会被调用。

5,View带有滑动嵌套情况时,需要处理好滑动冲突

如果有滑动冲突,那么要合适的处理滑动冲突,否则将会严重影响View效果。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值