一、官方文檔
先看下官方給出的解釋:
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();
}
分分鐘搞定,看下效果:

竟然沒有顯示出來指針,看下 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的繪制,影響了我們的指針:

第三次嘗試
@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 遮擋了。