LayerDrawableの落とし穴

ytRino2012-01-28

LayerDrawable

こんにちはこんにちは!
みなさん、突然ですがLayerDrawable使ってますか?

   △  ¥ ▲
  ( ? 皿 ?)  がしゃーん
LayerDrawableだよ
自動でDrawableを重ねてくれるすごいやつだよ

LayerList - Drawable Resources | Android Developers

使ってみる

写真を添付するようなアプリをイメージして、
取り消し用の×印と添付する画像を2つほど用意して画像を切り替えてみました。
n1.png
n2.png
x.png

test_layer.xml 上に書いたものから置かれるので下にあるものが上に来ます。

<?xml version="1.0" encoding="utf-8"?>
<layer-list xmlns:android="http://schemas.android.com/apk/res/android" >
    <item android:id="@+id/number">
        <bitmap
            android:src="@drawable/n1"
            android:gravity="center" />
    </item>
    <item>
        <bitmap
            android:src="@drawable/x"
            android:gravity="bottom|right" />
    </item>
</layer-list>

これをImageViewに設定するだけ。

    <ImageView
        android:id="@+id/image"
        android:layout_width="fill_parent"
        android:layout_height="wrap_content"

        android:src="@drawable/test_layer"
        />

楽ちんですね。
LayerDrawable#setDrawableByLayerIdで先ほどののIDと新しくセットするDrawableを指定して入れ替えます。

       LayerDrawable mLayer = (LayerDrawable)((ImageView)findViewById(R.id.image)).getDrawable();
       ...
       // 画像URIから適当にDrawable生成したりして
        Drawable drawable = getNewDrawable();
       // 入れ替え
        mLayer.setDrawableByLayerId(R.id.number, drawable);
       // 再描画
        mLayer.invalidateSelf();

単純に添付取り消しを表現するなら mLayer.setVisible(View.INVISIBLE)でいい気がする。

問題発生

こんな感じで順調に行くかとおもいきや、問題が。
Galaxy Nexusだとちゃんと切り替わるのに、他のいくつかの端末では切り替えて再描画の要求をだすと消えます。×印を残して。
結論から言うとICSでDrawbleがちょいちょい変わっていて、#setDrawableByLayerIdが(少なくとも自分の)思うような動作に修正されていました。
2.3の該当箇所
http://tools.oesf.biz/android-2.3_r1.0/xref/frameworks/base/graphics/java/android/graphics/drawable/LayerDrawable.java

    266     public boolean setDrawableByLayerId(int id, Drawable drawable) {
    267         final ChildDrawable[] layers = mLayerState.mChildren;
    268 
    269         for (int i = mLayerState.mNum - 1; i >= 0; i--) {
    270             if (layers[i].mId == id) {
    271                 layers[i].mDrawable = drawable;
    272                 return true;
    273             }
    274         }
    275 
    276         return false;
    277     }

シンプルですね。つづいでICS
http://tools.oesf.biz/android-4.0.1_r1.0/xref/frameworks/base/graphics/java/android/graphics/drawable/LayerDrawable.java

    278     public boolean setDrawableByLayerId(int id, Drawable drawable) {
    279         final ChildDrawable[] layers = mLayerState.mChildren;
    280 
    281         for (int i = mLayerState.mNum - 1; i >= 0; i--) {
    282             if (layers[i].mId == id) {
    283                 if (layers[i].mDrawable != null) {
    284                     if (drawable != null) {
    285                         Rect bounds = layers[i].mDrawable.getBounds();
    286                         drawable.setBounds(bounds);
    287                     }
    288                     layers[i].mDrawable.setCallback(null);
    289                 }
    290                 if (drawable != null) {
    291                     drawable.setCallback(this);
    292                 }
    293                 layers[i].mDrawable = drawable;
    294                 return true;
    295             }
    296         }
    297 
    298         return false;
    299     }

2倍ぐらいに増えてる… 285,286、怪しさ満点です。さっきのコードのsetDrawableByLayerIdの前に付け加えてみます。

       // 画像URIから適当にDrawable生成したりして
        Drawable drawable = getNewDrawable();
       // 入れ替える前rのDrawableからbounds取ってきてセット
        Rect bounds = mLayer.findDrawableByLayerId(R.id.number).getBounds();
        drawable.setBounds(bounds);
       // 入れ替え
        mLayer.setDrawableByLayerId(R.id.number, drawable);
       // 再描画
        mLayer.invalidateSelf();

無事

動きました。
コード貼る所が無いので出来上がったAPKを置いておきます。
とりあえず見てみるかという方はどうぞ。apk
上のDrawableがそのままの処理で、下のDrawableがRectを設定するようにしたものです。GNだとどっちも同じように動き、2.3以前だと上は最初にボタンを押したときに消えてそのまま。IS01で動かしたらImageViewの大きさがばらばらになりました。

追記

あ、そういえばせっかくなのでgithubで公開してみました。
使い方全然わかりません\(^o^)/
https://github.com/ytRino/LayerDrawableSample