源码解析ViewGroup的onDraw为什么不一定会调用

网上有不少文章都说自定义ViewGroup的时候,如果需要自定义绘图,不能重写onDraw方法,而是要重写dispatchDraw方法,因为ViewGroup绘制的时候不一定走onDraw方法,但是dispatchDraw方法是一定会走的。至于何时会走onDraw,何时不会走onDraw,有人说当ViewGroup设置背景的时候,会走onDraw方法,而没有背景的时候,不会走onDraw方法,这个说法也是有道理的。

切到本文的问题,为什么ViewGroup会不一定走onDraw方法呢,而非容器类的View为什么都会调用到onDraw方法呢?针对这个问题,下面把本人通过阅读源码从源码角度说一下自己的理解。

一、我们看一下View.updateDisplayListIfDirty(),不管是走dispatchDraw还是onDraw,总入口是在这个方法里面。

@NonNull
    public RenderNode updateDisplayListIfDirty() {
        。。。。。。。。。
                    computeScroll();

                    canvas.translate(-mScrollX, -mScrollY);
                    mPrivateFlags |= PFLAG_DRAWN | PFLAG_DRAWING_CACHE_VALID;
                    mPrivateFlags &= ~PFLAG_DIRTY_MASK;

                    // Fast path for layouts with no backgrounds
                    if ((mPrivateFlags & PFLAG_SKIP_DRAW) == PFLAG_SKIP_DRAW) {
                        dispatchDraw(canvas);
                        。。。。。。
                    } else {
                        draw(canvas);
                    }
                }
            } 。。。。。。
        return renderNode;
    }

看代码可知,是通过判断mPrivateFlags变量有没有设置过PFLAG_SKIP_DRAW标识,来判断是该调用dispatchDraw方法,还是draw方法,而draw方法里面就会调用onDraw方法了。如果设置过PFLAG_SKIP_DRAW,那么就会直接走dispatchDraw方法了,否则走draw方法。

即mPrivateFlags有PFLAG_SKIP_DRAW标识,那么会走dispatchDraw方法,如果没有PFLAG_SKIP_DRAW标识,那么就会走onDraw方法。

那么mPrivateFlags有没有设置过PFLAG_SKIP_DRAW呢,如果设置过的话又是在何时设置的呢,请看下面。

二、PFLAG_SKIP_DRAW的设置或者清除

1、关于PFLAG_SKIP_DRAW这个标识的设置或者清除,一共有三处涉及到。

(1)View.setForeground(Drawable foreground) 方法

public void setForeground(Drawable foreground) {
        if (mForegroundInfo == null) {
            if (foreground == null) {
                // Nothing to do.
                return;
            }
            mForegroundInfo = new ForegroundInfo();
        }

        ......

        if (foreground != null) {
            if ((mPrivateFlags & PFLAG_SKIP_DRAW) != 0) {
                mPrivateFlags &= ~PFLAG_SKIP_DRAW;
            }
            ........
        } else if ((mViewFlags & WILL_NOT_DRAW) != 0 && mBackground == null) {
            mPrivateFlags |= PFLAG_SKIP_DRAW;
        }
        ......
    }

看这段代码可知,如果设置过foreground,就会清除mPrivateFlags的PFLAG_SKIP_DRAW,而当没有设置foreground并且mViewFlags设置过WILL_NOT_DRAW时,会给mPrivateFlags设置PFLAG_SKIP_DRAW。至于mViewFlags有没有过WILL_NOT_DRAW,请看下面第2小节。

(2)View.setBackgroundDrawable(Drawable background)方法

public void setBackgroundDrawable(Drawable background) {
        。。。。。。

        if (background != null) {
            。。。。。。

            if ((mPrivateFlags & PFLAG_SKIP_DRAW) != 0) {
                mPrivateFlags &= ~PFLAG_SKIP_DRAW;
                requestLayout = true;
            }
        } else {
            /* Remove the background */
            mBackground = null;
            if ((mViewFlags & WILL_NOT_DRAW) != 0
                    && (mForegroundInfo == null || mForegroundInfo.mDrawable == null)) {
                mPrivateFlags |= PFLAG_SKIP_DRAW;
            }

            。。。。。。
        }

        。。。。。。
    }

看这段代码可知,如果设置过background,那么会清除mPrivateFlags的PFLAG_SKIP_DRAW,否则当没有设置过background并且mViewFlags设置过WILL_NOT_DRAW时,会给mPrivateFlags设置PFLAG_SKIP_DRAW。至于mViewFlags有没有过WILL_NOT_DRAW,请看下面第2小节。

(3)View.setFlag(int flags, int mask)方法

void setFlags(int flags, int mask) {
    。。。。。。
    int old = mViewFlags;
    mViewFlags = (mViewFlags & ~mask) | (flags & mask);

    int changed = mViewFlags ^ old;
    。。。。。。
    if ((changed & DRAW_MASK) != 0) {
            if ((mViewFlags & WILL_NOT_DRAW) != 0) {
                if (mBackground != null
                        || (mForegroundInfo != null && mForegroundInfo.mDrawable != 
                    null)) 
            {
                    mPrivateFlags &= ~PFLAG_SKIP_DRAW;
                } else {
                    mPrivateFlags |= PFLAG_SKIP_DRAW;
                }
            } else {
                mPrivateFlags &= ~PFLAG_SKIP_DRAW;
            }
            。。。。。。
   }
    。。。。。。
}

看这个方法可知,当mViewFlags设置过WILL_NOT_DRAW,当设置过background或者foreground的时候,那么会清除mPrivateFlags的PFLAG_SKIP_DRAW,如果没有设置过background或者foreground的时候,会给mPrivateFlags设置PFLAG_SKIP_DRAW;当mViewFlags没有设置过WILL_NOT_DRAW的时候,也会清除mPrivateFlags的PFLAG_SKIP_DRAW。

2、这三个方法的调用时机

setForeground和setBackgroundDrawable是在View的构造方法里面调用的

public View(Context context, @Nullable AttributeSet attrs, int defStyleAttr, int defStyleRes) {
    。。。。。。
    case R.styleable.View_foreground:
                    if (targetSdkVersion >= VERSION_CODES.M || this instanceof FrameLayout) 
                    {
                        setForeground(a.getDrawable(attr));
                    }
                    break;
    。。。。。。
    if (background != null) {
        setBackground(background);
    }
    。。。。。。
}

看这段代码可知,如果设置过foreground或者background二者之一,都会显示清除掉mViewFlags设置过PFLAG_SKIP_DRAW,那么foreground和background都没有设置过呢,setForeground和setBackground方法是没有被调用的,那么默认情况下mViewFlags是没有PFLAG_SKIP_DRAW标识的。

View的构造方法,对容器类View和非容器类View都会调用,那么为什么ViewGroup会表现的和非容器类View不一样呢,它一定是对PFLAG_SKIP_DRAW有过特殊的处理,果然,我们来看一下ViewGroup的构造方法

public ViewGroup(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
        super(context, attrs, defStyleAttr, defStyleRes);
        initViewGroup();
        initFromAttributes(context, attrs, defStyleAttr, defStyleRes);
}

private void initViewGroup() {
        // ViewGroup doesn't draw by default
        if (!debugDraw()) {
            setFlags(WILL_NOT_DRAW, DRAW_MASK);
        }
        mGroupFlags |= FLAG_CLIP_CHILDREN;
        mGroupFlags |= FLAG_CLIP_TO_PADDING;
        mGroupFlags |= FLAG_ANIMATION_DONE;
        mGroupFlags |= FLAG_ANIMATION_CACHE;
        mGroupFlags |= FLAG_ALWAYS_DRAWN_WITH_CACHE;

        。。。。。。
    }

可见,ViewGroup的构造方法里面调用了setFlag方法,传的参数是WILL_NOT_DRAW和DRAW_MASK,由第1小节的(3)可知,会显示地根据有无foreground或者background来对PFLAG_SKIP_DRAW进行显示地设置或者清除。

三、总结性分析

由一和二可知,ViewGroup会在构造方法里面根据是否设置过foreground或者background来对PFLAG_SKIP_DRAW进行显示的设置或者清除,如果设置过二者任何一个,就会对mViewFlags清除PFLAG_SKIP_DRAW,否则设置PFLAG_SKIP_DRAW。

而非容器类的View只有在设置过foreground或者background二者任何一个的时候,会对mViewFlags清除PFLAG_SKIP_DRAW,否则是没有处理的,而默认情况下mViewFlags是没有PFLAG_SKIP_DRAW的,所以非容器的View可以认为其mViewFlags是没有PFLAG_SKIP_DRAW标识的。

综上,ViewGroup在设置过foreground或者background二者之一的时候,会每次走onDraw方法,而二者都没设置的时候,会走dispatchDraw方法。而View因为其mViewFlags一般情况下是没有PFLAG_SKIP_DRAW的,所以总会走onDraw方法。ViewGroup和非容器类View的区别就是在没有设置背景和前景的时候,ViewGroup显示地设置了PFLAG_SKIP_DRAW,而非容器类View没有设置PFLAG_SKIP_DRAW,这样就导致了绘制流程出现了差异。

  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Android海纳百川

打赏加微信,送跑车加管理

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值