Posted at

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


やりたいこと

ここ数年、ユーザへの簡単なフィードバックにToastでなく、SnackBarが良く使われるようになりました。

標準のままでも特に問題はないのですが、MATERIAL DESIGN - Snackbarsにはこんな感じのSnackbarが表示されています。

_人人人人人人人人人人人人人人人人人人人人人人_

> 自分のアプリにもこの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にも、具体的な実装方法は載っていません。

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


解決策 : 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


これでMATERIAL DESIGN - Snackbarsに似た感じのSnackbarが実装できました。

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

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