2
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 1 year has passed since last update.

Android 9(API 28)以下で同一VectorDrawableをCanvasに描画すると全部同じ色になる件

Last updated at Posted at 2023-02-22

同一VectorDrawableをCanvasに描画すると全部同じ色になる場合があるとの噂を聞いたので調べてみました。
適当にカスタムViewを作って以下のように赤、緑、青で描画してみます。

private val drawable = AppCompatResources.getDrawable(context, R.drawable.ic_android)!!.mutate()
private val rect = Rect(0, 0, drawable.intrinsicWidth, drawable.intrinsicHeight)

override fun onDraw(canvas: Canvas) {
    rect.offsetTo(50, 50)
    drawable.bounds = rect
    drawable.setTint(Color.RED)
    drawable.draw(canvas)

    rect.offsetTo(150, 50)
    drawable.bounds = rect
    drawable.setTint(Color.GREEN)
    drawable.draw(canvas)

    rect.offsetTo(250, 50)
    drawable.bounds = rect
    drawable.setTint(Color.BLUE)
    drawable.draw(canvas)
}
API28(Android 9) API29(Android 10)

どうもAPI28以下とAPI29以上で違いがあるようですね。
setTintを使っていますが、setColorFilterでも、また、DrawableCompat.wrap()したDrawableでも同じでした

Drawableの扱いでよくある問題としてはmutate()をコールしていなくて全部同じ色になってしまうってのがありますが、それとも様子が違いますね。順序的には赤で描画、緑で描画、青で描画、という順序で処理を書いているのに、最後の青で全部が描画されてしまっています。
なんとなく描画処理の最適化が問題な感じがしますね。

解決方法がないか検証してみましょう。

検証1:LayerTypeを変更してみる

とりあえずハードウェアアクセラレーションが関係してるのでは?
カスタムViewのコンストラクタでsetLayerTypeをコールしてみます。

init {
    setLayerType(LAYER_TYPE_SOFTWARE, null)
}
LAYER_TYPE_SOFTWARE LAYER_TYPE_HARDWARE

LAYER_TYPE_SOFTWAREを指定するとちゃんと色が反映された。
反映されたのはいいけど、ハードウェアアクセラレーション切らないといけないってのは、おいそれととれる手段じゃない気がする

検証2:mutateのコールタイミングを変更してみる

初回にmutate()をコールするのではなく、

private val drawable = AppCompatResources.getDrawable(context, R.drawable.ic_android)!!
private val rect = Rect(0, 0, drawable.intrinsicWidth, drawable.intrinsicHeight)

override fun onDraw(canvas: Canvas) {
    rect.offsetTo(50, 50)
    drawable.bounds = rect
    drawable.setTint(Color.RED)
    drawable.draw(canvas)

    drawable.mutate()

    rect.offsetTo(150, 50)
    drawable.bounds = rect
    drawable.setTint(Color.GREEN)
    drawable.draw(canvas)

    drawable.mutate()

    rect.offsetTo(250, 50)
    drawable.bounds = rect
    drawable.setTint(Color.BLUE)
    drawable.draw(canvas)
}

mutate()をコールすることで、DrawableStateがアプリ内で共有されたものから切り離されるため、その前に設定したものとその後の2パターンができて、2色で描画されました。mutate()をコールする度に別のインスタンスになる訳じゃないので複数回コールしても解消はしません。

検証3:色ごとにDrawableを用意する

色ごとに別のDrawableインスタンスを持たせるようにすればいいのでは?ということでやってみます。

private val redDrawable = AppCompatResources.getDrawable(context, R.drawable.ic_android)!!.mutate().also {
    it.setTint(Color.RED)
}
private val greenDrawable = AppCompatResources.getDrawable(context, R.drawable.ic_android)!!.mutate().also {
    it.setTint(Color.GREEN)
}
private val blueDrawable = AppCompatResources.getDrawable(context, R.drawable.ic_android)!!.mutate().also {
    it.setTint(Color.BLUE)
}
private val rect = Rect(0, 0, redDrawable.intrinsicWidth, redDrawable.intrinsicHeight)

override fun onDraw(canvas: Canvas) {
    rect.offsetTo(50, 50)
    redDrawable.bounds = rect
    redDrawable.draw(canvas)
    redDrawable.invalidateSelf()

    rect.offsetTo(150, 50)
    greenDrawable.bounds = rect
    greenDrawable.draw(canvas)
    greenDrawable.invalidateSelf()

    rect.offsetTo(250, 50)
    blueDrawable.bounds = rect
    blueDrawable.draw(canvas)
    blueDrawable.invalidateSelf()
}

うまくいきました、しかし、色を変えて描画するってことは、だいたいの場合それなりのバリエーションの色があるってことでしょうから、その数のdrawableインスタンスを用意しておかないといけないってのも微妙ですね。

検証4:VectorDrawable以外を使ってみる

これまではVectorDrawableを使っていましたが、別のDrawableだったらどうでしょう?

BitmapDrawable

png画像のDrawableです。

問題なし

GradientDrawable

shapeタグで作るDrawableです。

<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android"
    android:shape="rectangle">
    <corners android:radius="30dp" />
    <solid android:color="@color/black" />
    <size android:width="24dp" android:height="24dp" />
</shape>

7.png

問題なし

AdaptiveIconDrawable

Android 8以上でランチャーアイコンに使われる奴ですね

8.png

全部同じ色になってしまいました。
中身はVectorDrawableなので想定の範囲内ではあります。


まとめ

VectorDrawableに着色しCanvasに描画しようとすると、Android 9(API 28)以下ではすべての色が同じになってしまうようです。

解決策としては

  • LAYER_TYPE_SOFTWAREに変更する
  • 色ごとにDrawableのインスタンスを利用する
  • BitmapDrawableやGradientDrawableを利用する

などがありそうです。
しかし、いずれも本質的には解決していない、もやもやした感じが残ります。

2
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
2
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?