LoginSignup
15

More than 5 years have passed since last update.

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

Last updated at Posted at 2016-12-09

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

ListのTop paddingとBottom padding

ガイドラインのListsを読むと、「Add 8dp of padding at the top and bottom of a list」という表現が12回も出てきます。
これをシンプルにRecyclerViewのpaddingToppaddingBottomで解決しようとすると、
スクロール限界のエフェクトがpaddingの下からになってしまったりスクロール時にアイテムが切れてしまったりと美しくありません。
ここは独自のItemDecorationを作って解決しましょう!

    @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

これで完璧ですね!

文字サイズ(Dense)

ガイドラインのTypographyを読むと、日本語は「Dense」に分類されると書いてあります。
しかしながら、Support LibraryにDenseのTextAppearanceはありません。
作りましょう。

<style name="TextAppearance.AppCompat.Body1.Dense" parent="TextAppearance.AppCompat.Body1">
    <item name="android:textSize">15sp</item>
</style>

xmlの全体はgithubをご覧下さい。
TextViewにはこのように適用します

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

NavigationDrawerのitemのBackground

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

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});
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を実装したくなりますが、RecyclerViewだと地味に面倒ではないでしょうか。
Adapter側で実装しようとするとListViewのHeaderViewListAdapterのようにAdapter in Adapterな実装になるかと思いますが、僕はあまりしっくり来ませんでした。
ここも独自のItemDecorationを作って解決しましょう!
どちらが正しい、ではなくItemDecorationの限界に挑戦したいのです。
実装を要約すると、

  • ItemDecoration+Drawableのアニメーションで解決
  • 独自のItemDecorationにDrawable.Callbackを実装する
  • HandlerにはRecyclerViewを使う

ということをやっています。細かな実装はgithubをご覧下さい。
ちなみにRecyclerView#removeItemDecorationを適切に呼ばないとメモリリークします!

最後に

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

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
15