Material Design度が高まるRipple Effect対応

## 概要
Material Design対応を行っているアプリが増えてきたと感じる今日この頃ですが、みなさんRipple Effectの対応は行っていますか。
Ripple EffectはMaterial Designの中でもキーになる表現効果ですが、APIレベル21（Lollipop）以上のOSでないと標準の仕組みでは扱えないため後回しにしているアプリも多いのではないかと思います。
今回は、私が開発に携わっている女性向けファッションアプリ [iQON](https://play.google.com/store/apps/details?id=jp.vasily.iqon&hl=ja) で、Ripple Effect対応を行った際の話を書きたいと思います。

![iqon_anim.gif](https://qiita-image-store.s3.amazonaws.com/0/40539/5d3901e4-5cb9-c835-a228-b3052e7a26bc.gif)


## 対応方針
Ripple Effect対応を行う上で、まずは以下のような対応方針を定めました。

### 1. 標準の仕組みを使って対応させる
Ripple Effectはバックポートされたサードパーティ製のLibraryがいくつか存在しますが、今回はそのようなLibraryは使わないで、OSが標準で提供している仕組みを使って対応を行うことにしました。
そのため、実際にRipple Effectが現れているのはAPIレベル21以上のOSのみになります。ただし、APIレベル21未満の場合でもPressed時のタッチフィードバックをRipple Effectと同じ領域で表示されるようにします。

サードパーティ製のLibraryを使わない理由としては、Ripple Effectの部分をサードパーティ製のLibraryに依存してしまうとほぼすべての画面が依存することになってしまい、何かあった際の改修が困難になる可能性があると考えたからです。

### 2. できるだけdrawable-v21以下にファイルを作らない
長期的に運用を続けるアプリの場合、バージョン別に同名ファイルを作りすぎてしまうと改修に弱い仕組みになる可能性があるため、できるだけdrawable-v21以下にファイルを作らないようにしました。
ただしUIの表現上、rippleタグを使う必要がある場合や ```?android:attr/selectableItemBackgroundBorderless``` を使う必要がある場合は致し方無いと判断しています。

### 3. ImageViewなどのパーツを設置しているViewGroupをタップ領域にする場合は、パーツをRipple Effectでオーバーレイさせる
アプリによっては、設置したパーツをオーバーレイするアプリ、しないアプリがありますが、今回はパーツを設置する場合にはオーバーレイするように統一することにしました。
理由としては、オーバーレイさせた方がRipple Effectの見栄えが良いためです。

__オーバーレイしていない場合__

![background.gif](https://qiita-image-store.s3.amazonaws.com/0/40539/46bfcfe1-b5b8-5d4a-3858-f241d6b7cab9.gif)

__オーバーレイしている場合__

![foreground.gif](https://qiita-image-store.s3.amazonaws.com/0/40539/94e5c294-d1a7-d361-47c7-425b96fbd67c.gif)


### 4. Ripple Effectの色は指定した色にする
この項目に関してはアプリ特有の問題になりますが、アプリ内でのデザイン統一のためにRipple Effectの色はデフォルトのグレー以外の指定した色になるようにしました。
APIレベル21未満のPressed時のタッチフィードバックも同じ色になるようにしました。

## 実装
上記の対応方針を踏まえて、実際にやったことを紹介していきます。

### OnClickListenerを実装するViewのbackground/foregroundにはselectableItemBackgroundを設定
例外もありますが、OnClickListenerを実装するほとんどのViewのbackground/foregroundには ```?android:attr/selectableItemBackground``` を設定しました。
```?android:attr/selectableItemBackground``` を設定することで、そのViewにはPressed時にタッチフィードバックが表示されるようになります。
もちろんAPIレベル21以上ではRipple Effectが表示されるようになります。

他のDrawableを設定する場合には、Drawable内で ```?android:attr/selectableItemBackground``` を設定するようにすれば対応可能です。

### パーツをオーバーレイさせる場合にはforegroundにselectableItemBackgroundを設定
ImageViewなどのパーツをオーバーレイさせたい場合には、OnClickListenerを実装するViewGroupのforegroundに ```?android:attr/selectableItemBackground``` を設定します。
ただし、foregroundのattributeを持つViewGroupは ```FrameLayout``` やFrameLayoutを継承した ```CardView``` などに限られるため注意が必要です。

場合によってはViewの階層が深くなってしまう場合もあると思いますが、どうしても気になる場合はGoogle I/Oアプリのコードである [ForegroundLinearLayout.java](https://gist.github.com/chrisbanes/9091754#file-foregroundlinearlayout-java) などを参考にCustomViewを作ってしまえば回避できると思います。

### Ripple Effectの色を変えるためにcolorControlHighlightに色を設定
```?android:attr/selectableItemBackground``` を設定時のRipple Effect（APIレベル21以上）の色と ```colorControlHighlight``` の色は連動しているため、ベースで使用しているstyleに ```colorControlHighlight``` のitemを追加します。

```styles.xml
<style name="AppTheme" parent="Theme.AppCompat.Light.DarkActionBar">
    <item name="colorControlHighlight">@color/color_control_highlight</item>
</style>
```

### APIレベル21未満のPressed時のためにstyles.xmlをバージョン別に分ける
APIレベル21未満のPressed時のタッチフィードバックをRipple Effectと同じ色にするためにstyleに ```android:selectableItemBackground``` のitemを追加します。
ただし、これだとAPIレベル21以上も影響を受けてしまいRipple Effectが表示されなくなるので ```android:selectableItemBackground``` のitemを追加しない ```v21/styles.xml``` を作成してあげる必要があります（もっと良いやり方ないものか...）。

```styles.xml
<style name="AppTheme" parent="Theme.AppCompat.Light.DarkActionBar">
    <item name="colorPrimary">@color/color_primary</item>
    <item name="colorPrimaryDark">@color/color_primary_dark</item>
    <item name="colorAccent">@color/color_accent</item>
    <item name="colorControlHighlight">@color/color_control_highlight</item>
    <item name="android:selectableItemBackground">@drawable/pre_lollipop_selectable_item</item>
</style>
```

```pre_lollipop_selectable_item.xml
<selector xmlns:android="http://schemas.android.com/apk/res/android">
    <item android:drawable="@color/color_control_highlight" android:state_pressed="true" />
    <item android:drawable="@android:color/transparent" />
</selector>
```

```v21/styles.xml
<style name="AppTheme" parent="Theme.AppCompat.Light.DarkActionBar">
    <item name="colorPrimary">@color/color_primary</item>
    <item name="colorPrimaryDark">@color/color_primary_dark</item>
    <item name="colorAccent">@color/color_accent</item>
    <item name="colorControlHighlight">@color/color_control_highlight</item>
</style>
```

## サンプルコード
今回の内容のサンプルコードはこちらになります。
https://github.com/nissiy/RippleSample

興味がある方は、APIレベル21以上と、APIレベル21未満の端末にビルドして見比べてみてください。

## まとめ
APIレベル21以上でしか有効にならないですが ```?android:attr/selectableItemBackground``` の使用に絞れば比較的楽にRipple Effect対応が行えます。
今回紹介した方法を用いれば、APIレベル21未満のPressed時のタッチフィードバックも一緒に最適化できるのでオススメです。
一気にMaterial Design度が高まるのでRipple Effect対応を行えていないアプリは対応を検討してみてはいかがでしょうか。