帶你一步步了解 onDraw() 和 dispatchDraw() 的區(qū)別

一、官方文檔

先看下官方給出的解釋:

onDraw: Implement this to do your drawing.

繪制 View 自身內(nèi)容時(shí),會(huì)調(diào)用 onDraw(Canvas canvas) ,可實(shí)現(xiàn)該方法完成繪制。

dispatchDraw: Called by draw to draw the child views. This may be overridden by derived classes to gain control just before its children are drawn (but after its own view has been drawn).

繪制 ViewGroup 中的子 View 時(shí),會(huì)調(diào)用 dispatchDraw(Canvas canvas),需要注意的是,是在繪制 ViewGroup 自己之后,也就是在 onDraw(Canvas canvas) 之后。

說的很清晰,看上去也平平無奇嘛!可稍一不小心,就會(huì)讓你分分鐘懷疑人生,這 API 有問題吧?

在下面在 Demo 中,會(huì)逐步講解這里面的使用,功能很簡(jiǎn)單:繼承 LinearLayout 繪制一個(gè)指針。

二、Demo

先看下最終效果


最終效果

布局:

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:background="@android:color/darker_gray"
    tools:context=".MainActivity">

    <com.ff.canvas.Indicator
        android:layout_width="match_parent"
        android:layout_height="50dp"
        android:orientation="horizontal">

        <TextView
            android:layout_width="0dp"
            android:layout_height="50dp"
            android:layout_weight="1"
            android:background="@android:color/holo_red_dark"
            android:gravity="center"
            android:text="標(biāo)題1"
            android:textColor="@android:color/white" />

        <TextView
            android:layout_width="0dp"
            android:layout_height="50dp"
            android:layout_weight="1"
            android:gravity="center"
            android:text="標(biāo)題2"
            android:textColor="@android:color/white" />

        <TextView
            android:layout_width="0dp"
            android:layout_height="50dp"
            android:layout_weight="1"
            android:gravity="center"
            android:text="標(biāo)題3"
            android:textColor="@android:color/white" />
    </com.ff.canvas.Indicator>

</LinearLayout>

自定義控件:

public class Indicator extends LinearLayout {

    private static final String TAG = "Indicator";

    private Paint mPaint;

    private Path mPath;// 繪制三角的路徑

    public Indicator(Context context) {
        this(context, null);
    }

    public Indicator(Context context, @Nullable AttributeSet attrs) {
        this(context, attrs, 0);
    }

    public Indicator(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        init();
    }

    private void init() {
        mPaint = new Paint();
        mPaint.setColor(Color.WHITE);
        mPaint.setStyle(Paint.Style.FILL);

        mPath = new Path();// 三角形
        mPath.moveTo(0, -8);
        mPath.lineTo(40, -8);
        mPath.lineTo(20, -30);
        mPath.close();// 形成閉環(huán)
    }

    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);
        Log.d(TAG, "onDraw: ");
    }

    @Override
    protected void dispatchDraw(Canvas canvas) {
        super.dispatchDraw(canvas);
        Log.d(TAG, "dispatchDraw: ");
    }
}

第一次嘗試

準(zhǔn)備工作都做好了,開始在onDraw中開始擼吧!

@Override
protected void onDraw(Canvas canvas) {
    super.onDraw(canvas);
    Log.i(TAG, "onDraw: ");
    canvas.drawRect(0, getHeight() - 8, getWidth() / 3f, getHeight(), mPaint);
    // 移動(dòng)Canvas,繪制指針
    canvas.save();
    canvas.translate(getWidth() / 6f - 20, getHeight());
    canvas.drawPath(mPath, mPaint);
    canvas.restore();
}

分分鐘搞定,看下效果:


onDraw 沒有執(zhí)行

竟然沒有顯示出來指針,看下 Log,onDraw() 都沒有執(zhí)行

4246-424/com.ff.canvas D/ViewPagerIndicator: dispatchDraw: 

原因是,ViewGroup 一般不會(huì)繪制自身,只會(huì)繪制子 View,除非存在背景,所以不會(huì)回調(diào) onDraw(),這也是處于性能和效率的考慮。

第二次嘗試

那么我們給 Indicator 一個(gè)背景不就搞定了么。

<com.ff.canvas.Indicator
    android:layout_width="match_parent"
    android:layout_height="50dp"
    android:background="@android:color/holo_blue_dark"
    android:orientation="horizontal">
    ...
</com.ff.canvas.Indicator>

還是沒有出現(xiàn):


增加背景

onDraw()也執(zhí)行了:

8525-8525/com.ff.canvas D/Indicator: onDraw: 
8525-8525/com.ff.canvas D/Indicator: dispatchDraw: 

這時(shí)大膽的猜測(cè)很可能是繪制子 View 的時(shí)候,將指針遮擋了,畢竟咱的代碼不會(huì)出錯(cuò)!把dispatchDraw()注釋掉:

@Override
protected void dispatchDraw(Canvas canvas) {
//    super.dispatchDraw(canvas);
    Log.d(TAG, "dispatchDraw: ");
}

果不其然,就是子View的繪制,影響了我們的指針:


注釋掉 dispatchDraw

第三次嘗試

@Override
protected void onDraw(Canvas canvas) {
    super.onDraw(canvas);
    Log.d(TAG, "onDraw: ");
}

@Override
protected void dispatchDraw(Canvas canvas) {
    super.dispatchDraw(canvas);
    Log.d(TAG, "dispatchDraw: ");
    canvas.drawRect(0, getHeight() - 8, getWidth() / 3f, getHeight(), mPaint);
    // 移動(dòng)Canvas,繪制指針
    canvas.save();
    canvas.translate(getWidth() / 6f - 20, getHeight());
    canvas.drawPath(mPath, mPaint);
    canvas.restore();
}

完美實(shí)現(xiàn):


最終效果

一定要注意,繪制指針要在 dispatchDraw 的 super 之后,不然子 View 還沒有完成繪制,依然會(huì)遮擋我們的指針。

三、結(jié)論

  • ViewGroup 一般情況下不會(huì)回調(diào) onDraw(),除非有背景或者其他情況,具體可以看下UI繪制流程中有關(guān) draw 的描述。
  • ViewGroup 存在背景的情況下,在 onDraw() 中繪制,的確可以繪制成功,但會(huì)被子控件遮擋,所以這里并不是是一個(gè)好的時(shí)機(jī)。
  • ViewGroup 的繪制,應(yīng)該在 dispatchDraw() 中,并且放在 super 之后,目的是確保在子 View 繪制完成后,再進(jìn)行繪制,這樣就不會(huì)被子 View 遮擋了。
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容