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

本文深入探讨了Android中View的layout和draw流程,包括layout方法如何确定View位置,onLayout方法如何确定子View位置,以及View的绘制过程,从背景到内容再到装饰的绘制顺序。同时,文章对比了getMeasuredWidth与getWidth的区别,并介绍了setWillNotDraw方法的使用场景。
摘要由CSDN通过智能技术生成

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

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

view知识点终篇:自定义view

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值