Fab(FloatingActionButton)のアイコンをtintする

地味にハマったので。

# API Level 21 (Android 5.0) 以上

普通に書けば終わりです。

```xml
<FloatingActionButton
    android:src="@drawable/icon"
    android:tint="@color/fab_color" />
```

# API Level 21未満

FloatingActionButtonは`AppCompatImageButton`ではないです。[^1]
そのため、`android:tint`はおろか`app:tint`も使えません。[^2]

## Drawableをtintする

Viewがtintをサポートしていないのであれば、Drawableがサポートすれば対応できます。

```java
DrawableCompat.setTintList(icon, colorStateList);
```

しかし、`DrawableCompat.setTintList()`は、21未満の場合`TintAwareDrawable`のサブクラスである必要があります。
そのため、`TintAwareDrawable`でwrapする必要があります。

```java
icon = DrawableCompat.wrap(icon);
```

あとは、FloatingActionButtonに上のDrawableを与えるだけです。

## BindingAdapterで属性として実装する

以上の実装をBindingAdapterで実装します。

```java
@BindingAdapter("tintCompat")
public static void setTintCompat(FloatingActionButton fab, ColorStateList colorStateList) {
    Drawable icon = fab.getDrawable();
    if (icon != null) {
        icon = DrawableCompat.wrap(icon);
        DrawableCompat.setTintList(icon, colorStateList);
        fab.setImageDrawable(icon);
    }
}
```

## つかってみる

activateステートを使って、アイコンと色を変更してみます。

```drawable/state_icon.xml
<selector
    xmlns:android="http://schemas.android.com/apk/res/android">

    <item
        android:drawable="@drawable/ic_star_black_24dp"
        android:state_activated="true" />

    <item android:drawable="@drawable/ic_star_outline_black_24dp" />
</selector>
```

```color/state_icon_color.xml
<selector
    xmlns:android="http://schemas.android.com/apk/res/android">

    <item
        android:color="@color/red"
        android:state_activated="true" />

    <item android:color="@color/gray" />
</selector>
```

```activity_sample.xml
<layout
    xmlns:android="http://schemas.android.com/apk/res/android">

    <data>

        <variable
            name="favorite"
            type="boolean" />
    </data>

    <android.support.design.widget.CoordinatorLayout
        android:layout_width="match_parent"
        android:layout_height="match_parent" >

        <android.support.design.widget.FloatingActionButton
            tintCompat="@{@colorStateList/state_icon_color}"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_gravity="bottom|end"
            android:layout_margin="16dp"
            app:activated="@{favorite}"
            app:backgroundTint="@android:color/white"
            app:srcCompat="@drawable/state_icon" />

    </<android.support.design.widget.CoordinatorLayout>

</layout>
```

selectorで状態別に色を定義する場合は、`@colorStateList`式で指定する必要があります。
`@color`式で参照すると、default色をColorStateListにするという動きをします。

# 参考

* [DrawableCompat | Android Developers](https://developer.android.com/reference/android/support/v4/graphics/drawable/DrawableCompat.html)
* [Data Binding Library | Android Developers](https://developer.android.com/topic/libraries/data-binding/index.html#expression_language)


# 番外編：なぜFloatingActionButtonの`app:tint`が21未満で効かないのか

注：support-design内部の実装のため、修正がかかる可能性があります。

`app:tint`属性は、実際は`AppCompatImageView`ではなく、`AppCompatImageHelper`が読み取ります。
FloatingActionButtonは、`AppCompatImageView`のサブクラスではありませんが、`AppCompatImageHelper`を使っているため、以下の属性が有効です。

* app:srcCompat
* app:tint
* app:tintMode

ではなぜ、`app:tint`をサポートしているにもかかわらず、FloatingActionButtonには効果が得られないのでしょうか…？

AppCompatImageHelperのtint属性の実装は以下のようになっています。([AOSP](https://github.com/aosp-mirror/platform_frameworks_support/blob/support-library-27.0.2/v7/appcompat/src/main/java/android/support/v7/widget/AppCompatImageHelper.java#L69-L77))

```java
if (a.hasValue(R.styleable.AppCompatImageView_tint)) {
    ImageViewCompat.setImageTintList(mView,
            a.getColorStateList(R.styleable.AppCompatImageView_tint));
}
```

`ImageViewCompat.setImageTintList()`は21未満の場合、`TintableImageSourceView`を実装している場合のみ、tintを有効にする実装となっています。

ここで、FloatingActionButtonの継承関係を見ると

```
android.widget.ImageView
    │
    └ android.widget.ImageButton
        │
        └ android.support.design.widget.VisibilityAwareImageButton
            │
            └ FloatingActionButton
```

となっています。
このうちsupport-designのクラスである`VisibitilyAwareImageButton`、`FloatingActionButton`のいずれも、`TintableImageSourceView`を実装していないため、21未満では`app:tint`が効かないということになります。(対応漏れでは…)


[^1]: support-design v27.0.2現在
[^2]: 正確には`android:tint`は使えますが、ColorStateListに対応していません([参照](https://qiita.com/ushi3_jp/items/fc055e7654654f58fcf8))