LayerDrawable#setTintListが機能しない件


Lollipop以降に追加されたLayerDrawable#setTintList()ですが呼び出しても機能しません。

## サンプル

https://github.com/tsuyosh/LayerDrawableTintDemo/

<img src="https://raw.githubusercontent.com/tsuyosh/LayerDrawableTintDemo/master/assets/demo.gif" style="width:360px; height:640px;" >

## 原因

AOSPのソースを追ってみた所、LayerDrawable#isStateful()が古い結果をキャッシュし続けていてViewがDrawable#setState()を呼ばないのが原因のようです。
LayerDrawable#setTintList()の実装にキャッシュを無効化するコードが入ってないのでStatefulなはずなのにisStateful()がfalseを返してしまいます。


```java:LayerDrawable.java
    public void setTintList(ColorStateList tint) {
        final ChildDrawable[] array = mLayerState.mChildren;
        final int N = mLayerState.mNum;
        for (int i = 0; i < N; i++) {
            final Drawable dr = array[i].mDrawable;
            if (dr != null) {
                dr.setTintList(tint);
            }
        }
        // ← Statefulキャッシュを無効化してない
    }

    public boolean isStateful() {
        return mLayerState.isStateful();
    }

	static class LayerState extends ConstantState {
        public final boolean isStateful() {
            if (mHaveIsStateful) { // ← こいつがキャッシュを保持し続ける
                return mIsStateful;
            }

            final ChildDrawable[] array = mChildren;
            final int N = mNum;
            boolean isStateful = false;
            for (int i = 0; i < N; i++) {
                final Drawable dr = array[i].mDrawable;
                if (dr != null && dr.isStateful()) {
                    isStateful = true;
                    break;
                }
            }

            mIsStateful = isStateful;
            mHaveIsStateful = true;
            return isStateful;
        }
    }

```

```java:View.java
                if (mBackground.isStateful()) {
                    mBackground.setState(getDrawableState()); // ← isStateful()がtrueじゃない限りDrawable#setState()を呼んでくれない
                }

```

## 回避方法

statefulキャッシュを無効化するにはLayerDrawable.LayerState#invalidateCache()を呼べばいいのですがそれを呼んでいるはLayerDrawable#setDrawable()とLayerDrawable#setDrawableByLayerId()です。

なのでsetTintList()を呼ぶ直前に上記のAPIを呼べばisStateful()は正しい結果を返して問題は解決します。

いちいち書くのは面倒なので簡単なUtilityクラスを用意しました。ソースにも書いてますがlayerの最初のitemにandroid:idの設定が必要になります。

```java:LayerDrawableUtil.java
public class LayerDrawableUtil {
	public static void invalidateCache(Drawable d) {
		if (Build.VERSION.SDK_INT < Build.VERSION_CODES.LOLLIPOP) return;
		if (!(d instanceof LayerDrawable)) return;

		LayerDrawable ld = (LayerDrawable) d;
		int count = ld.getNumberOfLayers();
		if (count == 0) return;

		if (Build.VERSION.SDK_INT < Build.VERSION_CODES.M) {
			// TODO first layer item needs to be set android:id
			int id = ld.getId(0);
			Drawable d0 = ld.findDrawableByLayerId(id);
			ld.setDrawableByLayerId(id, d0);
		} else {
			Drawable d0 = ld.getDrawable(0);
			ld.setDrawable(0, d0);
		}
	}
}

```

これを下のように使います。

```java:MainActivity.java
	@Override
	protected void onCreate(Bundle savedInstanceState) {
		super.onCreate(savedInstanceState);
		setContentView(R.layout.activity_main);

		// Not Worked
		Button button1 = (Button) findViewById(R.id.button1);
		ViewCompat.setBackgroundTintList(button1, ContextCompat.getColorStateList(this, R.color.tint));

		// Worked
		Button button2 = (Button) findViewById(R.id.button2);
		LayerDrawableUtil.invalidateCache(button2.getBackground());
		ViewCompat.setBackgroundTintList(button2, ContextCompat.getColorStateList(this, R.color.tint));
	}

```