ViewGroup为什么不会调用onDraw

本文深入分析了当LinearLayout没有背景时,onDraw方法为何不会被调用的原因,并提供了两种解决方案:给LinearLayout设置背景色或在构造函数中调用setWillNotDraw(false)。通过理解View的源码,可以有效解决此类问题。

转自 http://blog.youkuaiyun.com/leehong2005/article/details/7299471

正常情况下,我们重写LinearLayout的onDraw方法,它是不会被调用的,这篇文章就来分析一下原因和解决方法。

一,现象

<com.test.demo.MyLinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:id="@+id/ll_absolute"
    android:orientation="vertical"
    android:layout_width="fill_parent"
    android:layout_height="fill_parent"
    android:background="#FF000000">
</com.test.demo.MyLinearLayout> 


大概的架构是,MyLinearLayout从LinearLayout派生出来,然后在程序中重载OnDraw(Canvas canvas)。但是,onDraw不会被调用。我们可能会遇到这个问题:如果不给LinearLayout设置一个背景,系统是不会调用onDraw时,也就是说,我们重写的onDraw是不会调用的。当设置一个背景后,onDraw就会被调用。

二,原因

造成这种现象的原因是继承自LinearLayout,而LinearLayout这是一个容器,ViewGroup嘛,它本身并没有任何可画的东西,它是一个透明的控件,因些并不会触发onDraw,但是你现在给LinearLayout设置一个背景色,其实这个背景色不管你设置成什么颜色,系统会认为,这个LinearLayout上面有东西可画了,因此会调用onDraw方法。 

我们可以仔细分析View的源码,它有一个方法View#draw(Canvas)方法,这里面有两个地方调用onDraw,它的条件都是:

if (!dirtyOpaque) onDraw(canvas);

也就是说,如果dirtyOpaque是true的话,onDraw就不会调用,而dirtyOpaque的值的计算代码如下:

final boolean dirtyOpaque = (privateFlags & DIRTY_MASK) == DIRTY_OPAQUE &&
                (mAttachInfo == null || !mAttachInfo.mIgnoreDirtyState); 

当View初始化他时,它会调用一个私有方法:computeOpaqueFlags,这里面列出了不透明的三个条件:

        // Opaque if:

        //   - Has a background

        //   - Background is opaque

        //   - Doesn't have scrollbars or scrollbars are inside overlay

View还提供了一个重要的方法:setWillNotDraw,我们看一看它的实现:

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

    }
  1. 从这个方法的注释,我们可以看出,如果你想重写onDraw的话,你应该调用这个方法来清除flag,所以如果我们想要重写LinearLayout的onDraw的话,我们也可以在其构造方法中调用setWillNotDraw方法。 在ViewGroup初始他时,它调用了一个私有方法:initViewGroup,它里面会有一句setFlags(WILL_NOT_DRAW, DRAW_MASK); 相当于调用了setWillNotDraw(true),所以说,对于ViewGroup,它就认为是透明的了。如果我们想要重写onDraw,就需要调用setWillNotDraw(false)

三,总结

1)ViewGroup默认情况下,会被设置成WILL_NOT_DRAW,这是从性能考虑,这样一来,onDraw就不会被调用了。
2)如果我们要重写一个ViweGroup的onDraw方法,有两种方法:
        1,在构造函数里面,给其设置一个颜色,如#00000000。
        2,在构造函数里面,调用setWillNotDraw(false),去掉其WILL_NOT_DRAW flag。
 

### 父 View调用 onLayout 的情况 - **手动调用**:开发者可在代码中手动调用View 的 onLayout 方法,而不触发 onMeasure 和 onDraw 方法。例如: ```java // 假设 parentView 是一个 ViewGroup 的实例 ViewGroup parentView = findViewById(R.id.parent_view); int left = 0; int top = 0; int right = 100; int bottom = 100; parentView.onLayout(true, left, top, right, bottom); ``` - **异常情况**:若在代码中出现异常,导致测量等流程中断,而刚好满足了调用 onLayout 的条件,可能出现只调用 onLayout 的情况。比如在 onMeasure 方法中抛出异常,后续流程无法正常执行,但由于之前的某些操作触发了 onLayout 的调用条件,就可能只执行 onLayout 方法。 - **布局参数未改变**:当 View 的布局参数没有改变时,系统可能不会重新调用 onMeasure 方法,但可能会因为某些原因调用 onLayout 方法来调整子 View 的位置。例如,父 View 内部的子 View 进行了重新排序,但整体的布局参数没有变化,此时可能只调用 onLayout 方法来重新布局子 View。 - **自定义 ViewGroup 逻辑**:在自定义 ViewGroup 时,如果开发者在代码中手动控制方法的调用,也可能只调用 onLayout 方法。例如,在某些特定的业务逻辑下,开发者认为不需要重新测量,只需要重新布局子 View,就可以直接调用 onLayout 方法。 ### onMeasure 和 onLayout 都调用onDraw调用的情况 - **View 不可见**:当 View 的 `setVisibility` 方法被设置为 `View.GONE` 时,View 不会进行绘制,因为 `GONE` 表示该 View 完全不可见,且不占用布局空间。此时,虽然会调用 onMeasure 和 onLayout 方法来确定 View 的大小和位置,但不会调用 onDraw 方法。示例代码如下: ```java View parentView = findViewById(R.id.parent_view); parentView.setVisibility(View.GONE); ``` - **缓存机制**:如果开启了 View 的缓存机制,并且缓存的内容仍然有效,系统可能不会再次调用 onDraw 方法。例如,通过 `setDrawingCacheEnabled(true)` 开启了绘制缓存,当布局发生变化时,系统会先检查缓存是否可用,如果可用则直接使用缓存内容,而不会重新调用 onDraw 方法。 ```java View parentView = findViewById(R.id.parent_view); parentView.setDrawingCacheEnabled(true); ``` - **自定义逻辑跳过绘制**:在自定义 View 时,如果开发者在代码中手动控制,在某些条件下跳过 onDraw 方法的执行,也会出现 onMeasure 和 onLayout 都调用onDraw调用的情况。例如: ```java @Override protected void onDraw(Canvas canvas) { if (shouldSkipDrawing()) { return; } // 正常的绘制逻辑 } private boolean shouldSkipDrawing() { // 根据具体条件判断是否跳过绘制 return false; } ```
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值