Drawable.invalidateSelf()とはなにものなのか


AnimatableなDrawable自作したときにハマったので。

Drawable

ViewonDraw()にあたる処理だけを切り出した抽象クラスです。
Viewの場合は主にonMeasure()onLayout()onDraw()の3つを担いますが、DrawableonDraw()のみになります。
サイズや配置などは、使う側(View)に委ねられています。

Drawable.invalidateSelf()

View.invalidate()同様、再描画を促すメソッドです。invalidate()Drawable版と思ってかまわないと思います。

とはいえ、DrawableViewと違い、描画しかサポートしていないため、本来再描画を促すことはできません。
ではinvalidateSelf()はどのように再描画可能にしているのか?

これにはDrawable.setCalllback()メソッドが関係しています。

Drawable.setCallback()

setCallback()とはなんなのか?(名前雑すぎて役割が何もつかめないんだけど…)

setCallback()の引数Drawable.Callbackインターフェースは以下のようになっています。

Drawable.Callback
public interface Callback {

    void invalidateDrawable(Drawable who);

    void scheduleDrawable(Drawable who, Runnable what, long when);

    void unscheduleDrawable(Drawable who, Runnable what);
}

Drawable.Callbackの役割は2つinvalidateDrawable()scheduleDrawable()です。

invalidateDrawable()who(SelfにあたるDrawable)がView.invalidate()を要求するコールバックです。
scheduleDrawable()whoが特定の時刻にスケジューリングされたRunnableの実行を要求するコールバックです。

このDrawable.Callbackですが、基本的に実装する必要はありません。というのも、既にViewが実装してくれているからです。1
CustomViewなどでinvalidateSelf()を有効にする場合は、drawable.setCallback(this)するだけで構いません。

ここまでで、invalidateSelf()Drawable.Callbackinvalidate()を要求するというメソッドであることがわかります。
invalidateSelf()をするためには準備が必要なんですね。(ハマり)

View.verifyDrawable()

先ほど、ViewDrawable.Callbackを実装しているので、drawable.setCallback(this)するだけでinvalidateSelf()は動くと書きましたが、これだけでは動かないケースが存在します

ViewinvalidateDrawable()の実装では、invalidate()を呼び出せるDrawableを制限しています。
この条件を実装しているのがView.verifyDrawable()です。

デフォルトでは以下のように実装されています。

View.java
@CallSuper
protected boolean verifyDrawable(@NonNull Drawable who) {
    return who == mBackground || (mForegroundInfo != null && mForegroundInfo.mDrawable == who);
}

background及びforegroundに設定されているdrawable以外のinvalidateDrawable()は承認されません。
ImageViewの場合はこれに加えて、srcにあたるDrawableを追加しています。

つまり場合によっては、setCallback(this)をしてもverifyDrawable()をoverrideし忘れるとinvalidate()されないということです。
気をつけましょう。(2度目のハマり)

注釈


  1. supportライブラリのように、Drawableのラッパーをつくる場合は自前で実装したりするようです