Snackbarのデザインをカスタマイズする

# やりたいこと
ここ数年、ユーザへの簡単なフィードバックにToastでなく、SnackBarが良く使われるようになりました。
標準のままでも特に問題はないのですが、[MATERIAL DESIGN - Snackbars](https://material.io/design/components/snackbars.html#)にはこんな感じのSnackbarが表示されています。
<img width="363" alt="スクリーンショット 2018-10-03 19.58.12.png" src="https://qiita-image-store.s3.amazonaws.com/0/20559/ca425467-d0f4-7409-e150-463a773c5fe8.png">

＿人人人人人人人人人人人人人人人人人人人人人人＿
＞　自分のアプリにもこのSnackbarを表示したい　＜
￣Y^Y^Y^Y^Y^Y^Y^Y^Y^Y^Y^Y^Y^Y^Y^Y^Y^Y^Y^Y^Y￣

ですが、いろいろ調べても背景色を変えたり、テキストの色を変更するぐらいしか見つかりませんでした。
[MATERIAL DESIGN - Snackbars](https://material.io/design/components/snackbars.html#)にも、具体的な実装方法は載っていません。

どうやって実装するの？といろいろ試行錯誤して解決策が見つかったのでここで共有します。

# 解決策 : Snackbarが参照しているLayoutResourceを自分のアプリに用意し、Resource参照を上書きする

### Snackbarが参照しているLayoutResource
Snackbarは内部で次の2つのLayoutResourceを読み込んでいます。
**R.layout.design_layout_snackbar.xml (Snackbarの背景)**
**R.layout.design_layout_snackbar_include.xml (Snackbarのコンテンツ部分)**

調べて出てくる情報にはSnackbarのコンテンツ部分を動的に変更するものが多く、Snackbarの背景の変更までは出来ていませんでした。
上記の2つのLayoutResouceを自分のアプリに用意して、Resource参照を上書きすることでSnackbarのデザインを自由に変更できます。

### 2つのLayoutResourceを自分のアプリに用意する。

Snackbarが参照しているLayoutResourceのxmlコードは次のとおりなので、自分のアプリのres.layoutフォルダに作成します。

**R.layout.design_layout_snackbar.xml**

```
<?xml version="1.0" encoding="utf-8"?>
<view xmlns:android="http://schemas.android.com/apk/res/android"
    style="@style/Widget.Design.Snackbar"
    class="android.support.design.widget.Snackbar$SnackbarLayout"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:layout_gravity="bottom"
    android:theme="@style/ThemeOverlay.AppCompat.Dark"/>

```

**R.layout.design_layout_snackbar_include.xml**

```
<?xml version="1.0" encoding="utf-8"?>
<view
    xmlns:android="http://schemas.android.com/apk/res/android"
    class="android.support.design.widget.SnackbarContentLayout"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:layout_gravity="bottom"
    android:theme="@style/ThemeOverlay.AppCompat.Dark">

  <TextView
      android:id="@+id/snackbar_text"
      android:layout_width="wrap_content"
      android:layout_height="wrap_content"
      android:layout_weight="1"
      android:layout_gravity="center_vertical|left|start"
      android:paddingTop="@dimen/design_snackbar_padding_vertical"
      android:paddingBottom="@dimen/design_snackbar_padding_vertical"
      android:paddingLeft="@dimen/design_snackbar_padding_horizontal"
      android:paddingRight="@dimen/design_snackbar_padding_horizontal"
      android:ellipsize="end"
      android:maxLines="@integer/design_snackbar_text_max_lines"
      android:textAlignment="viewStart"
      android:textAppearance="@style/TextAppearance.Design.Snackbar.Message"/>

  <Button
      android:id="@+id/snackbar_action"
      style="?attr/borderlessButtonStyle"
      android:layout_width="wrap_content"
      android:layout_height="wrap_content"
      android:layout_marginStart="@dimen/design_snackbar_extra_spacing_horizontal"
      android:layout_marginLeft="@dimen/design_snackbar_extra_spacing_horizontal"
      android:layout_gravity="center_vertical|right|end"
      android:minWidth="48dp"
      android:textColor="?attr/colorAccent"
      android:visibility="gone"/>

</view>

```

### Snackbarの背景を透過する

Snackbarの背景は`R.layout.design_layout_snackbar.xml`の次の箇所で定義されています。

```
style="@style/Widget.Design.Snackbar"
```

**Widget.Design.Snackbar**

```
  <style name="Widget.Design.Snackbar" parent="android:Widget">
    <item name="android:minWidth">@dimen/design_snackbar_min_width</item>
    <item name="android:maxWidth">@dimen/design_snackbar_max_width</item>
    <item name="android:background">@drawable/design_snackbar_background</item>
    <item name="android:paddingLeft">@dimen/design_snackbar_padding_horizontal</item>
    <item name="android:paddingRight">@dimen/design_snackbar_padding_horizontal</item>
    <item name="elevation">@dimen/design_snackbar_elevation</item>
    <item name="maxActionInlineWidth">@dimen/design_snackbar_action_inline_max_width</item>
  </style>
```

android:background属性で指定されている部分を上書きすることで透過が実現できます。
res.values/styles.xmlに上書きしたstyleを追記します。

```
  <style name="CustomSnackbar" parent="Widget.Design.Snackbar">
    <item name="android:background">@android:color/transparent</item>
  </style>
```

そして、R.layout.design_layout_snackbar.xmlを修正します。

```
<?xml version="1.0" encoding="utf-8"?>
<view xmlns:android="http://schemas.android.com/apk/res/android"
    style="@style/CustomSnackbar" <-- カスタムStyleを指定
    class="android.support.design.widget.Snackbar$SnackbarLayout"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:layout_gravity="bottom"
    android:theme="@style/AppTheme" /> <-- アプリのThemeに変更する
```

### Snackbarのコンテンツ部分を変更する
Snackbarのコンテンツ部分はR.layout.design_layout_snackbar_include.xmlを編集することで自由に変更出来ますが、守らなければいけない条件があります。

##### idを変更しない
R.layout.design_layout_snackbar_include.xmlには、TextViewとButtonがあり、それぞれにid(snackbar_text、snackbar_action)が振られていますが、これはSnackbarクラスから呼び出されるため、変更してはいけません。

##### 要素を増やさない
LayoutResourceを編集することで、Viewの要素を増やしたり、画像を表示したりといったことが可能ですが、Snackbarクラスからは2つのid(snackbar_text、snackbar_action)以外の要素にはアクセス出来ないのと、ガイドラインでSnackbarへのアイコン表示などは推奨されていないため、やめておいたほうがいいでしょう。

##### @dimens/design_snackbar~のリソースはそのまま使う
R.layout.design_layout_snackbar_include.xmlでサポートライブラリのdimensリソースを参照しています。
変更してもいいのですが、サポートライブラリで様々な画面サイズに対応しているので、このまま利用するのがおすすめです。

#### 背景リソースを用意する
Snackbarは現時点で透過のままなので角丸の背景リソースを用意します。

**R.drawable.snackbar_background.xml**

```
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android">
    <solid android:color="#4c4c4c" />
    <corners android:radius="4dp" />
</shape>
```

#### Message部分のStyleを定義する

SnackbarのMessage部分のStyleは次のように定義されています。

**TextAppearance.Design.Snackbar.Message**

```
  <style name="TextAppearance.Design.Snackbar.Message" parent="android:TextAppearance">
    <item name="android:textSize">@dimen/design_snackbar_text_size</item>
    <item name="android:textColor">?android:textColorPrimary</item>
  </style>
```

文字色だけ変えたいので、R.values.styles.xmlに次のように追記します。

```
  <style name="CustomSnackbarMessage" parent="TextAppearance.Design.Snackbar.Message">
    <item name="android:textColor">#f0f0f0</item>
  </style>
```

#### 最後にR.layout.design_layout_snackbar_include.xmlを編集する

次のように、コンテンツ部分のxmlを編集します。

```
<?xml version="1.0" encoding="utf-8"?>
<view xmlns:android="http://schemas.android.com/apk/res/android"
    class="android.support.design.widget.SnackbarContentLayout"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:layout_gravity="bottom"
    android:layout_marginBottom="8dp" <-- 下端から少し離す
    android:background="@drawable/snackbar_background" <-- 背景適用
    android:elevation="2dp" <-- 影を表現する
    android:theme="@style/AppTheme"> <-- アプリのThemeを適用する

    <TextView
        android:id="@+id/snackbar_text"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_gravity="center_vertical|left|start"
        android:layout_weight="1"
        android:ellipsize="end"
        android:maxLines="@integer/design_snackbar_text_max_lines"
        android:paddingBottom="@dimen/design_snackbar_padding_vertical"
        android:paddingLeft="@dimen/design_snackbar_padding_horizontal"
        android:paddingRight="@dimen/design_snackbar_padding_horizontal"
        android:paddingTop="@dimen/design_snackbar_padding_vertical"
        android:textAlignment="viewStart"
        android:textAppearance="@style/CustomSnackbarMessage" /> <-- 定義したStyleを適用する

    <Button
        android:id="@+id/snackbar_action"
        style="?attr/borderlessButtonStyle"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_gravity="center_vertical|right|end"
        android:layout_marginLeft="@dimen/design_snackbar_extra_spacing_horizontal"
        android:layout_marginStart="@dimen/design_snackbar_extra_spacing_horizontal"
        android:minWidth="48dp"
        android:textColor="@color/colorAccent" <-- アプリのアクセントカラーを適用
        android:visibility="gone" />

</view>
```

### 結果

次のようになったかと思います。

|before|after|
|:-:|:-:|
|![device-2018-10-03-202555.png](https://qiita-image-store.s3.amazonaws.com/0/20559/5e8bb86b-7e28-d08d-d498-a82c38b65a9f.png)|![device-2018-10-03-215004.png](https://qiita-image-store.s3.amazonaws.com/0/20559/7b2819ce-22a4-684d-7de4-a30c2a7300c6.png)|

これで[MATERIAL DESIGN - Snackbars](https://material.io/design/components/snackbars.html#)に似た感じのSnackbarが実装できました。

LayoutResourceの参照を上書きする方法を使っているので、今後のサポートライブラリのアップデートでSnackbarの実装が大きく変わった場合は使えなくなるので注意してください。(多分ないとは思いますが...)

以上です。Snackbarのカスタマイズに困っていた方の参考になれば幸いです。