AnimatableなDrawable自作したときにハマったので。
Drawable
ViewのonDraw()にあたる処理だけを切り出した抽象クラスです。
Viewの場合は主にonMeasure()、onLayout()、onDraw()の3つを担いますが、DrawableはonDraw()のみになります。
サイズや配置などは、使う側(View)に委ねられています。
Drawable.invalidateSelf()
View.invalidate()同様、再描画を促すメソッドです。invalidate()のDrawable版と思ってかまわないと思います。
とはいえ、DrawableはViewと違い、描画しかサポートしていないため、本来再描画を促すことはできません。
ではinvalidateSelf()はどのように再描画可能にしているのか?
これにはDrawable.setCalllback()メソッドが関係しています。
Drawable.setCallback()
setCallback()とはなんなのか?(名前雑すぎて役割が何もつかめないんだけど…)
setCallback()の引数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.Callbackにinvalidate()を要求するというメソッドであることがわかります。
invalidateSelf()をするためには準備が必要なんですね。(ハマり)
View.verifyDrawable()
先ほど、ViewはDrawable.Callbackを実装しているので、drawable.setCallback(this)するだけでinvalidateSelf()は動くと書きましたが、これだけでは動かないケースが存在します。
ViewのinvalidateDrawable()の実装では、invalidate()を呼び出せるDrawableを制限しています。
この条件を実装しているのがView.verifyDrawable()です。
デフォルトでは以下のように実装されています。
@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度目のハマり)
注釈
-
supportライブラリのように、
Drawableのラッパーをつくる場合は自前で実装したりするようです ↩