細かすぎて伝わらないMaterial Designの実装

お世話になっております。[@seto_hi](https://twitter.com/seto_hi)です。
Androidエンジニアの皆様におかれましては本日もMaterial Designの実装で消耗されていることと心中お察し申し上げます。
本日はMaterial Designのガイドラインの重箱の隅をつついた実装をご紹介しますので、何かの間違いでご参考になれば幸いです。
コードは全て[こちらのリポジトリ](https://github.com/hiroyuki-seto/AndroidAdventCalendar2016)に上げてあります。

# ListのTop paddingとBottom padding
[ガイドラインのLists](https://material.google.com/components/lists.html)を読むと、「Add 8dp of padding at the top and bottom of a list」という表現が12回も出てきます。
これをシンプルにRecyclerViewの`paddingTop`と`paddingBottom`で解決しようとすると、
スクロール限界のエフェクトがpaddingの下からになってしまったりスクロール時にアイテムが切れてしまったりと美しくありません。
ここは独自のItemDecorationを作って解決しましょう！

```java
    @Override
    public void getItemOffsets(Rect outRect, View view, RecyclerView parent, RecyclerView.State state) {
        super.getItemOffsets(outRect, view, parent, state);
        if (parent.getChildAdapterPosition(view) == 0) {
            outRect.top += mPadding;
        } else if (parent.getChildAdapterPosition(view) == parent.getAdapter().getItemCount() - 1) {
            outRect.bottom += mPadding;
        }
    }
```

![diff.png](https://qiita-image-store.s3.amazonaws.com/0/103039/00a7a588-62e9-a718-e274-f2171a42329a.png)

これで完璧ですね！

# 文字サイズ(Dense)
[ガイドラインのTypography](https://material.google.com/style/typography.html)を読むと、日本語は「Dense」に分類されると書いてあります。
しかしながら、Support LibraryにDenseのTextAppearanceはありません。
作りましょう。

```xml
<style name="TextAppearance.AppCompat.Body1.Dense" parent="TextAppearance.AppCompat.Body1">
    <item name="android:textSize">15sp</item>
</style>
``` 
xmlの全体は[github](https://github.com/hiroyuki-seto/AndroidAdventCalendar2016/blob/master/app/src/main/res/values/text_appearances_dense.xml)をご覧下さい。
TextViewにはこのように適用します

```xml
<TextView
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:textAppearance="@style/TextAppearance.AppCompat.Body1.Dense" />
```

# NavigationDrawerのitemのBackground
[ガイドラインのNavigation Drawer](https://material.google.com/patterns/navigation-drawer.html)を読むと、未選択の項目に触れるとRipple Effectが出て、選択中の項目は背景色が灰色になるようなイラストがあります。
Design Support Libraryの[NavigationView](https://developer.android.com/reference/android/support/design/widget/NavigationView.html)を使えば何もしなくても実現できるのですが、諸事情によってRecyclerViewを使ってカスタマイズしたくなった際にはやや手間です。
ItemのViewで`android:background="?attr/selectableItemBackground"`としてみても選択時の背景色が出ませんし、
かといってRippleEffectと選択色を合わせたStateListDrawable自作するのも頭痛が痛いです。
そこで、LayerDrawableを使います。

```java
TypedValue value = new TypedValue();
getContext().getTheme().resolveAttribute(R.attr.selectableItemBackground, value, true);
Drawable top = ContextCompat.getDrawable(getContext(), value.resourceId);
Drawable back = ContextCompat.getDrawable(getContext(), R.drawable.bg_checkable_list_item);
Drawable itemBackground = new LayerDrawable(new Drawable[]{back, top});
```

```xml:R.drawable.bg_checkable_list_item
<?xml version="1.0" encoding="utf-8"?>
<selector xmlns:android="http://schemas.android.com/apk/res/android">
    <item android:drawable="@color/bgListItemChecked" android:state_checked="true" />
    <item android:drawable="@android:color/transparent" />
</selector>
```

これならば選択色部分のStateListDrawableを作るだけでいいですね。

ちなみにNavigationViewはForegroundDrawableに対応したLinearLayoutを実装し、foregroundには`?attr/selectableItemBackground`、backgroundには上記のようなStateListDrawableを設定していました。しかもStateListDrawableは`?attr/colorControlHighlight`を参照するためにJava上でゴリゴリ生成しています。なるほどね！

# Auto Loading
[ガイドラインのProgress & activityにあるTwo-phased loadsみたいなloading](https://material.google.com/components/progress-activity.html#progress-activity-behavior)を実装したくなりますが、RecyclerViewだと地味に面倒ではないでしょうか。
Adapter側で実装しようとするとListViewの[HeaderViewListAdapter](https://developer.android.com/reference/android/widget/HeaderViewListAdapter.html)のようにAdapter in Adapterな実装になるかと思いますが、僕はあまりしっくり来ませんでした。
ここも独自のItemDecorationを作って解決しましょう！
どちらが正しい、ではなくItemDecorationの限界に挑戦したいのです。
実装を要約すると、

- ItemDecoration+Drawableのアニメーションで解決
- 独自のItemDecorationに[Drawable.Callback](https://developer.android.com/reference/android/graphics/drawable/Drawable.Callback.html)を実装する
- HandlerにはRecyclerViewを使う

ということをやっています。細かな実装は[github](https://github.com/hiroyuki-seto/AndroidAdventCalendar2016)をご覧下さい。
ちなみに`RecyclerView#removeItemDecoration`を適切に呼ばないとメモリリークします！

# 最後に

ItemDecoration最高！Drawable最高！
Material Designの実装にはまだまだつらみが多いですが、身を粉にして実装してユーザーを幸せにしていきましょう！