美文网首页
View的工作流程(三个重要方法)

View的工作流程(三个重要方法)

作者: Noblel | 来源:发表于2017-12-13 09:59 被阅读0次

measure过程

onMeasure()

官方翻译:
测量视图及其内容,以确定测量宽度和测量高度。这个方法是由measure(int,int)调用的,它应该被子类覆盖,以提供准确和有效的内容度量。当重写该方法时,你必须调用setMeasuredDimension(int, int)来存储该视图的测量宽度和高度。会由measure(int,int)抛出IllegalStateException。对背景大小的默认值的基本类实现,除非测量值允许较大的尺寸。子类应该覆盖onMeasure(int,int),以便更好地度量的内容。如果覆盖该方法,它是子类的责任确保测量高度和宽度至少视图的最小高度和宽度getSuggestedMinimumHeight()和getSuggestedMinimumWidth()。

measure(int,int)方法时final的所以子类不能重写

如果需要支持wrap_content属性,必须重写onMeasure()方法

源码

protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
    setMeasuredDimension(getDefaultSize(getSuggestedMinimumWidth(), widthMeasureSpec),
            getDefaultSize(getSuggestedMinimumHeight(), heightMeasureSpec));
}

//来到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:
        //UNSPECIFIED模式返回第一个值
        result = size;
        break;
    case MeasureSpec.AT_MOST:
    case MeasureSpec.EXACTLY:
        //EXACTLY模式返回指定值
        result = specSize;
        break;
    }
    return result;
}

//获取模式
public static int getMode(int measureSpec) {
    //MODE_MASK:位运算0x11左移30位 也就是11后面30个0
    //再按位与获取头两位为模式  
    return (measureSpec & MODE_MASK);
}

//获取大小
public static int getSize(int measureSpec) {
    //再按位与获取头两位为大小  
    return (measureSpec & ~MODE_MASK);
}

测量模式

    /**
     * Measure specification mode: The parent has not imposed any constraint
     * on the child. It can be whatever size it wants.
     */
    public static final int UNSPECIFIED = 0 << MODE_SHIFT;

    /**
     * Measure specification mode: The parent has determined an exact size
     * for the child. The child is going to be given those bounds regardless
     * of how big it wants to be.
     */
    public static final int EXACTLY     = 1 << MODE_SHIFT;

    /**
     * Measure specification mode: The child can be as large as it wants up
     * to the specified size.
     */
    public static final int AT_MOST     = 2 << MODE_SHIFT;

UNSPECIFIED

任意大小,要多大就有多大,一般用于系统内部的测量过程,在这种情况下View的大小为getDefaultSize第一个参数,宽/高分别为getSuggestedMinimumWidth(),getSuggestedMinimumHeight()返回值

protected int getSuggestedMinimumWidth() {
    //没有设置背景就为android:minWidth属性指定值否则为0
    //如果有背景那么为就是mBackground(Drawable)的getMinimumWidth()返回值
    return (mBackground == null) ? mMinWidth : max(mMinWidth,mBackground.getMinimumWidth());
}

EXACTLY

精确值模式,比如 android:layout_width="100dp",或者指定为match_parent属性,使用的是EXACTLY模式,View类默认的onMeasure()方法只支持EXACTLY模式。

AT_MOST

最大值模式,父容器指定了一个大小,View的大小不能大于这个值,在布局中使用wrap_content相当于match_parent时候的模式就是AT_MOST,此时的specSize就是parentSize,而parentSize是父容器中目前可以使用的大小,所以就相当于使用了match_parent。

解决办法:

@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
    super.onMeasure(widthMeasureSpec, heightMeasureSpec);
    int widthSpecMode = MeasureSpec.getMode(widthMeasureSpec);
    int widthSpecSize = MeasureSpec.getSize(widthMeasureSpec);
    int heightSpecMode = MeasureSpec.getMode(heightMeasureSpec);
    int heightSpecSize = MeasureSpec.getSize(heightMeasureSpec);
    if (widthSpecMode == MeasureSpec.AT_MOST && heightSpecMode == MeasureSpec.AT_MOST) {
        //设置默认的宽高            
        setMeasuredDimension(mWidth, mHeight);
    } else if (widthSpecMode == MeasureSpec.AT_MOST) {
        //设置默认的宽度
        setMeasuredDimension(mWidth, heightSpecSize);
    } else if (heightSpecMode == MeasureSpec.AT_MOST) {
        //设置默认的高度
        setMeasuredDimension(widthSpecSize, mHeight);
    }
}

layout过程

layout的作用是ViewGroup用来确定子元素的位置的,layout方法是确定View本身,当ViewGroup的位置被确定了,onLayout会遍历所有子元素执行layout方法。

layout()

    /**
 * Assign a size and position to a view and all of its
 * descendants
 *
 * <p>This is the second phase of the layout mechanism.
 * (The first is measuring). In this phase, each parent calls
 * layout on all of its children to position them.
 * This is typically done using the child measurements
 * that were stored in the measure pass().</p>
 *
 * <p>Derived classes should not override this method.
 * Derived classes with children should override
 * onLayout. In that method, they should
 * call layout on each of their children.</p>
 *
 * @param l Left position, relative to parent
 * @param t Top position, relative to parent
 * @param r Right position, relative to parent
 * @param b Bottom position, relative to parent
 */
@SuppressWarnings({"unchecked"})
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;
    }
    
    //setFrame()中设定的值
    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 && 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;
}

大致流程: 先通过setFrame方法设定View的四个顶点初始化四个值接着调用onLayout(),可以查看一下LinearLayout的onLayout()方法。

getMeasureWidth和getWidth方法区别

/**
* 测量宽/高形成与View的measure过程
*/
public final int getMeasuredWidth() {
    return mMeasuredWidth & MEASURED_SIZE_MASK;
}

public final int getMeasuredHeight() {
    return mMeasuredHeight & MEASURED_SIZE_MASK;
}

/**
* 最终宽/高形成与View的layout过程
*/
public final int getWidth() {
    return mRight - mLeft;
}

public final int getHeight() {
    return mBottom - mTop;
}

一般认为这俩个方法返回的值是相等的,除非特殊情况,重写layout方法重新赋值就不一定了

draw过程:

绘制流程其实是一套模板设计模式

源码

public void draw(Canvas canvas) {
    final int privateFlags = mPrivateFlags;
    //决定onDraw()方法是否调用,是否是不透明
    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 对View的背景进行绘制 
     *      2. If necessary, save the canvas' layers to prepare for fading 保存当前的图层信息(可跳过) 
     *      3. Draw view's content 绘制View的内容 
     *      4. Draw children 对View的子View进行绘制(如果有子View) 
     *      5. If necessary, draw the fading edges and restore layers 绘制View的褪色的边缘,类似于阴影效果(可跳过) 
     *      6. Draw decorations (scrollbars for instance) 绘制View的装饰(例如:滚动条) 
     *
     */

    // 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
        //如果ViewGroup没有指定背景色不会调用此方法,绘制自己
        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;
    }

    .....

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

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

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

    ....

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

由此看来执行过程:drawBackground(canvas) --> onDraw() --> dispatchDraw(canvas)
--> onDrawForeground(canvas)

ViewGroup是继承View的抽象类,它的dispatchDraw(canvas)在View中的draw()方法中直接调用,ViewGroup的dispatchDraw()方法由dirtyOpaque决定是否调用,也就是child是可见或者上面有动画,那么就一定会调用child的draw()。

View中的特殊方法setWillNotDraw

一个View如果不需要绘制任何内容就可以这个标志位为true系统会进行优化,当我们的自定义控件继承与ViewGroup并且不需要绘制任何功能,就可以开启这个标志位,当需要onDraw来绘制内容需要显示关闭这个标志位。

    /**
 * If this view doesn't do any drawing on its own, set this flag to
 * allow further optimizations. By default, this flag is not set on
 * View, but could be set on some View subclasses such as ViewGroup.
 *
 * Typically, if you override {@link #onDraw(android.graphics.Canvas)}
 * you should clear this flag.
 *
 * @param willNotDraw whether or not this View draw on its own
 */
public void setWillNotDraw(boolean willNotDraw) {
    setFlags(willNotDraw ? WILL_NOT_DRAW : 0, DRAW_MASK);
}

相关文章

网友评论

      本文标题:View的工作流程(三个重要方法)

      本文链接:https://www.haomeiwen.com/subject/hhhoixtx.html