为什么ViewGroup的onDraw()方法不执行

问题

ViewGroup onDraw不执行的原因?
怎么让ViewGroup onDraw执行?

android代码一直在优化,我看了几个版本的源码,目前,我用的是API30的源码,再去看ViewGroup为什么不走onDraw()的时候,已经不是一句 if (!dirtyOpaque) 就能决定是否执行onDraw()的事了。

原因详解

在API27中,还是我们熟悉的那个 if 判断决定 onDraw()的执行

在API27以后,你会发现在draw()方法里找不到 上面这个 if 语句,那么问题来了:他是如何控制 ViewGroup 不执行 onDraw() 的呢?

这个时候,我们的目光该放在这两个片段上了,还是在 View 这个类里面,

片段一:

view.java 
/**
     * This method is called by ViewGroup.drawChild() to have each child view draw itself.
     *
     * This is where the View specializes rendering behavior based on layer type,
     * and hardware acceleration.
     */
      boolean draw(x1,x2,x3)方法
                ...
                // Fast path for layouts with no backgrounds
                if ((mPrivateFlags & PFLAG_SKIP_DRAW) == PFLAG_SKIP_DRAW) {
                    mPrivateFlags &= ~PFLAG_DIRTY_MASK;
                    dispatchDraw(canvas);
                } else {
                    draw(canvas);
                }
                ...

从这一段我们能获取两个信息:

  1. 注释: ViewGroup.drawChild()调用此方法,使每个子视图都绘制自己。这是视图根据图层类型专门处理渲染行为的地方,硬件加速
  2. 是否走draw()方法由两个标志决定 mPrivateFlags & PFLAG_SKIP_DRAW

片段二 :

public RenderNode updateDisplayListIfDirty() {
       // Fast path for layouts with no backgrounds
                    if ((mPrivateFlags & PFLAG_SKIP_DRAW) == PFLAG_SKIP_DRAW) {
                        dispatchDraw(canvas);
                        drawAutofilledHighlight(canvas);
                        if (mOverlay != null && !mOverlay.isEmpty()) {
                            mOverlay.getOverlayView().draw(canvas);
                        }
                        if (isShowingLayoutBounds()) {
                            debugDrawFocus(canvas);
                        }
                    } else {
                        draw(canvas);
                    }
}

从这一段我们能获取这么个信息:是否走draw()方法由两个标志决定 mPrivateFlags & PFLAG_SKIP_DRAW

在片段一中,官方注释到 硬件加速

现在Android默认开启硬件加速,什么是硬件加速呢?为了加快Android绘制速度,适当解放cpu资源,Android将一部分绘制放到gpu执行。而对应的Android里面的canvas,也分为是否支持硬件加速,因此绘制流程也有所差异,流程图简示如下:

从上图可以看出,不管是否开启硬件加速,都会经历“跳过绘制”的逻辑判断,而该判断的分支就决定了viewGroup的ondraw()方法是否执行。如果“跳过绘制”成立,那么调用dispatchDraw()方法,继而调用子view进行绘制(如果有子view)。如果“跳过绘制”不成立,那么调用draw(x1),该方法会调用dispatchDraw()和ondraw()方法。也就是说无论怎样 dispatchDraw() 是一定会调的

讲解到这里,我们大家应该清楚,硬件加速这个知识点只是一个小插曲,最重要的是 mPrivateFlags & PFLAG_SKIP_DRAW 是在哪里被赋值的?从而影响到draw()方法的调用?

其实很简单,我一说大家就能明白,这种变量的初始化肯定在构造函数里写啊!!!

对,你们已经明白了,就是在ViewGroup里调用了setFlag(参数1,参数2),对 mPrivateFlags & PFLAG_SKIP_DRAW 这两个参数进行了设置

其实官方的注释 // ViewGroup doesn't draw by default 已经说明了一切,不过,我们还是来看看 setFlag 的源码

vew.java setFlags方法
        //省略
        if ((changed & DRAW_MASK) != 0) {
            if ((mViewFlags & WILL_NOT_DRAW) != 0) {
                if (mBackground != null
                        || mDefaultFocusHighlight != null
                        || (mForegroundInfo != null && mForegroundInfo.mDrawable != null)) {
                    mPrivateFlags &= ~PFLAG_SKIP_DRAW;
                } else {
                    mPrivateFlags |= PFLAG_SKIP_DRAW;
                }
            } else {
                mPrivateFlags &= ~PFLAG_SKIP_DRAW;
            }
            requestLayout();
            invalidate(true);
        }
        //省略

对上面这段代码的理解:

1、如果设置了WILL_NOT_DRAW标记,那么继续检查background、foreground(mDrawable字段)、focusHighLight是否有值,如果三者任意一个设置了,那么将PFLAG_SKIP_DRAW标记清除,否则将该标记加上。
2、如果没有设置WILL_NOT_DRAW标记,那么将PFLAG_SKIP_DRAW标记清除。

至此,我们知道了ViewGroup onDraw()方法没有执行的原因:viewGroup默认设置了WILL_NOT_DRAW标记,进而设置了PFLAG_SKIP_DRAW标记,而在绘制的时候通过判断PFLAG_SKIP_DRAW标记来决定是否调用ViewGroup draw(x)方法,最终决定是否调用onDraw()方法。而view默认没有设置WILL_NOT_DRAW标记,也就没有后面的事了。

如何让viewGroup onDraw()执行

既然知道了ViewGroup没有绘制的原因,那么就有方法让它执行绘制流程。先来看看WILL_NOT_DRAW

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

    /**
     * Returns whether or not this View draws on its own.
     *
     * @return true if this view has nothing to draw, false otherwise
     */
    @ViewDebug.ExportedProperty(category = "drawing")
    public boolean willNotDraw() {
        return (mViewFlags & DRAW_MASK) == WILL_NOT_DRAW;
    }

View类里暴露了设置WILL_NOT_DRAW标记的接口:setWillNotDraw(boolean willNotDraw),可以在  继承ViewGroup的自定义View  里使用setWillNotDraw(false)。不想设置该标记也是可行的,前面说过即使设置了WILL_NOT_DRAW,后面还是有判断background、foreground、focusHighLight是否有值。

background:view背景

foreground(mDrawable字段):view前景

focusHighLight:view获得焦点时高亮

我们只要设置了其中一个值,PFLAG_SKIP_DRAW标记将会被清空。来看看这三个值如何影响PFLAG_SKIP_DRAW标记

view.java
public void setBackgroundDrawable(Drawable background) {
if (background != null) {
   if ((mPrivateFlags & PFLAG_SKIP_DRAW) != 0) {
                mPrivateFlags &= ~PFLAG_SKIP_DRAW;
                requestLayout = true;
            }
     }
}

public void setForeground(Drawable foreground) {
        if (foreground != null) {
            if ((mPrivateFlags & PFLAG_SKIP_DRAW) != 0) {
                mPrivateFlags &= ~PFLAG_SKIP_DRAW;
            }
    }
}

private void setDefaultFocusHighlight(Drawable highlight) {
        mDefaultFocusHighlight = highlight;
        mDefaultFocusHighlightSizeChanged = true;
        if (highlight != null) {
            if ((mPrivateFlags & PFLAG_SKIP_DRAW) != 0) {
                mPrivateFlags &= ~PFLAG_SKIP_DRAW;
            }
      }
    }

总结

  1. 若要ViewGroup onDraw()执行,只需要setWillNotDraw(false)、设置背景、设置前景、设置焦点高亮,4个选项其中一项满足即可。
  2. 当然如果不想在 继承ViewGroup的自定义View onDraw里绘制,也可以重写ViewGroup dispatchDraw()方法,在该方法里绘制 自定义View 内容。

 

好的,以下是一个简单的示例,演示如何使用 `FocusableOnTouchView` 类来设计一个广告展示的界面: 1. 创建一个 `FocusableOnTouchView` 类的子类 `AdvertisementView`,并在其构造函数中设置 `setClickable(true)`。 ```java public class AdvertisementView extends FocusableOnTouchView { public AdvertisementView(Context context) { super(context); setClickable(true); } // Override onTouchEvent() and onFocusChanged() methods as needed } ``` 2. 在 `AdvertisementView` 类中实现 `onDraw()` 方法,绘制广告的图片和文字信息。 ```java @Override protected void onDraw(Canvas canvas) { super.onDraw(canvas); // Draw advertisement image Drawable image = getResources().getDrawable(R.drawable.advertisement_image); image.setBounds(0, 0, getWidth(), getHeight()); image.draw(canvas); // Draw advertisement text Paint textPaint = new Paint(); textPaint.setTextSize(20); textPaint.setColor(Color.WHITE); String text = "This is an advertisement"; canvas.drawText(text, getWidth() / 2, getHeight() / 2, textPaint); } ``` 3. 在布局文件中添加 `AdvertisementView` 控件,并使用 `ViewGroup` 类(如 `LinearLayout` 或 `RelativeLayout`)将多个 `AdvertisementView` 控件组合在一起。 ```xml <LinearLayout android:layout_width="match_parent" android:layout_height="wrap_content" android:orientation="vertical"> <com.example.AdvertisementView android:layout_width="match_parent" android:layout_height="100dp" android:focusable="true" android:focusableInTouchMode="true" /> <com.example.AdvertisementView android:layout_width="match_parent" android:layout_height="100dp" android:focusable="true" android:focusableInTouchMode="true" /> <!-- Add more AdvertisementView controls as needed --> </LinearLayout> ``` 4. 在 `Activity` 类中实现 `View.OnFocusChangeListener` 接口,并将其注册到 `AdvertisementView` 控件中。 ```java public class MainActivity extends AppCompatActivity implements View.OnFocusChangeListener { @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); // Register onFocusChangeListener to all AdvertisementView controls ViewGroup advertisementContainer = findViewById(R.id.advertisement_container); for (int i = 0; i < advertisementContainer.getChildCount(); i++) { View childView = advertisementContainer.getChildAt(i); if (childView instanceof AdvertisementView) { childView.setOnFocusChangeListener(this); } } } // Implement onFocusChange() method to handle focus change events @Override public void onFocusChange(View v, boolean hasFocus) { if (hasFocus) { // Handle advertisement focus change event } } } ``` 这样,您就可以使用 `AdvertisementView` 类来创建一个简单的广告展示界面,并在 `Activity` 类中处理焦点变化事件。当用户聚焦到某个广告控件上时,您可以根据需要执行一些操作,例如打开一个链接或显示更多的广告信息。
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

super码王

你的鼓励将是我创作的最大动力

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

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

打赏作者

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

抵扣说明:

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

余额充值