View的工作原理(三)layout和draw过程

layout方法确定view的位置,onLayout方法用来确定子view的位置,接下来看view的layout源码。

一、Layout过程
1、 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;
    // 判断 isLayoutModeOptical 方法的返回值, true 则执行 setOpticalFrame 方法,否则执行 setFrame 
    // setOpticalFrame 方法最终也走了 setFrame 方法,所以最终都会执行 setFrame 方法
    // setFrame 方法会判断 View 位置是否发生改变, 如果发生改变, 会将 View 新的 left、top、right、bottom 值赋值给成员变量,并返回一个 boolean 值,表示位置是否改变
    // 当 setFrame 完成后,表示 View 本身的位置已经确定

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

1、layout 方法首先通过调用 setFrame 方法判断 View 的位置是否发生改变,如果发生改变,则将新位置的值 left、top、right、bottom 赋值给 mLeft、mTop、mRight、mBottom 这几个成员变量,这四个值保存着 View 的位置信息,我们可以通过 getLeft、getTop、getRight、getBottom 方法获取到。
2、我们如果想要得到 View 的位置信息,那么必须在 setFrame 方法执行完毕后获取,比如说在 onLayout 方法中获取,因为通过看源码我们已经知道,onLayout 方法是在 setFrame 方法之后执行的。
3、当 setFrame 方法执行完成后,会返回一个 View 位置是否发生改变的 boolean 值,如果发生改变,那么就会走 onLayout 方法,该方法为空实现,在 ViewGroup 中调用,用于确定子 View 的位置。
4、由于具体每种布局的实现效果都不同,所以 onLayout 的默认实现,和 measure 测量过程中 ViewGroup 的 onMeasure 方法一样,是空实现,具体怎么确定子 View 的位置,由 ViewGroup 的具体实现类去作不同实现。view和iewGroup都没实现(参看LinearLayout的onLayout实现)

和Measure对比总结:
1、自定义viewGroup就重写onLayout摆放布局,在onLayout中遍历子view调用子view的layout摆放子元素
2、自定义viewGroup就重写onMeasure,onMeasure中遍历子元素,measureChild即可。
3、自定义view就重写onMeasure,setMeasuredDimension即可。
4、自定义view就重写layout。

2、getMeasuredWidth 和 getWidth 的区别

1、getMeasuredWidth / getMeasuredHeight 的值是在 onMeausre 方法结束后可以获取到的,getWidth / getHeight 的值是在 onLayout 方法结束后可以获取到的。
2、在view的默认实现中view测量的宽高和最终宽高相等,只不过二者形成时间不同一个是onMeausre ,一个是onLayout 。日常开法中我们可以认为二者一致,
3、特殊情况下不同(重写layout如下)

@Override
    public void layout(int l, int t, int r, int b) {
        super.layout(l, t, r+100, b+100);
    }
二、draw过程

1:绘制背景。
2:如果需要,保存图层信息。
3:绘制 View 的内容。
4:如果 View 有子 View,绘制 View 的子 View。
5:如果需要,绘制 View 的边缘(如阴影等)。
6:绘制 View 的装饰(如滚动条等)。

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

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

            // Step 7, draw the default focus highlight
            drawDefaultFocusHighlight(canvas);

            if (debugDraw()) {
                debugDrawFocus(canvas);
            }

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


setWillNotDraw

View 一个特殊的方法,该方法是用于设置 WILL_NOT_DRAW 标记位的。默认情况下, View 没有启用这个优化标记位的,但是 ViewGroup 会默认启用。
如果当我们自定义的控件继承自 ViewGroup 并且本身并不具备任何绘制时,那么可以设置 setWillNotDraw 方法为 true,设置为 true 后,系统会进行相应的优化。
如果当我们知道我们自定义继承自 ViewGroup 的控件需要绘制内容时,那么需要设置 setWillNotDraw 方法为 false,来关闭 WILL_NOT_DRAW 这个标记位。

三、小结

至此三大流程结束

The end

本文来自<安卓开发艺术探索>笔记总结

猜你喜欢

转载自blog.csdn.net/qq_38350635/article/details/89290121