言いたいことはそれだけか

KotlinとかAndroidとかが好きです。調べたことをメモします。٩( 'ω' )و

SnackbarでCustom Content を表示する

Support library revision25.1.0からSnackBarにCustom Contentを表示できるようになったらしいので試してみました٩( 'ω' )و

今回の変更概要

さて、release noteをよく読んでみましょう。

Snackbar has been refactored to allow apps to display custom content. BaseTransientBottomBar is the new base class that exposes the general sliding and animations behavior.

custom contentを表示するためにリファクタしたよって言ってますね。BaseTransientBottomBarが新しい基底クラスになるよって言ってます。誰よそれって感じなのでリファレンスやコードを読んでみましょう。

BaseTransientBottomBarって何よ

まずはSnackbarのクラス定義。確かにBaseTransientBottomBarをextendsしています。

public final class Snackbar extends BaseTransientBottomBar<Snackbar> {

このBaseTransientBottomBarが何をしてくれるクラスなのか、リファレンスを読んでみましょう。 主にやってくれそうなのは、この二つ。

  • 画面下部から現れるViewの表示制御
  • 表示・非表示のタイミングを取得するためのCallbackの提供

もともとSnackbarがになっていた機能の一部ですね。

Snackbarがどう変わったか

これらを踏まえて今度はSnackbarのコードを見てみましょう*1。Viewの表示制御をBaseTransientBottomBarへ移したからか、300行ほどの随分とスッキリしたクラスになりました。(以前は確か850行ほどありました。)
中身を読むと分かりますが、このクラスでやっていることは二つ。

  • Snackbarのコンテンツ制御
  • 表示・非表示のタイミングを取得するためのCallbackの提供

この通り、Viewの表示制御やアニメーションなどはやっていません。(Snackbar#make()の第3引数でdurationをとるので、ある意味ここだけアニメーションと言えなくはない。)

このようにコンテンツ制御というかSnackbar独自のviewの制御に特化したクラスになっています。 さらに言えばSnackbarのカスタムレイアウトはSnackbarContentLayout としてhideな別クラスに切り出されています。 なのでSnackbar#setText()の中身も結構無理矢理な感じ。

    /**
     * Update the text in this {@link Snackbar}.
     *
     * @param message The new text for this {@link BaseTransientBottomBar}.
     */
    @NonNull
    public Snackbar setText(@NonNull CharSequence message) {
        final SnackbarContentLayout contentLayout = (SnackbarContentLayout) mView.getChildAt(0);
        final TextView tv = contentLayout.getMessageView();
        tv.setText(message);
        return this;
    }

このように、VIewの表示制御とコンテンツ制御を切り離して実装することができるようになりました。 ということは、独自デザインのViewをSnackbarの用に表示・非表示するのが簡単にできるようになったってことです。

試してみた

今回は独自デザインの簡単なサンプルとして背景色が黒じゃないCustomSnackbarを作っていきます٩( 'ω' )و

まずはsupport libraryのrevisionを25.0.1にあげます。対象のlibraryをinstallしてbuild.gradleをアップデートします。

    compile 'com.android.support:design:25.1.0'

それではCustomSnackbarクラスを作っていきます。 BaseTransientBottomBarをextendsしてやって、

public class CustomSnackBar extends BaseTransientBottomBar<CustomSnackBar>  {

Snackbarを参考に make() メソッドを実装します。findSuitableParent() の中身はSnackbarのそれのコピペです。 ちなみに、BaseTransientBottomBarのコンストラクタはprotectedなので、自前でコンストラクタを作ってやらないと"There is no default constructor available"って怒られます。

  @NonNull
  public static CustomSnackBar make(@NonNull View view, @NonNull CharSequence text,
                              int duration) {
    LayoutInflater inflater = LayoutInflater.from(view.getContext());
    View content = inflater.inflate(R.layout.custom_snackbar, findSuitableParent(view), false);
    ((TextView) content.findViewById(R.id.custom_snackbar_textview)).setText(text);
    CustomSnackBar customSnackBar = new CustomSnackBar(findSuitableParent(view), content, new ViewCallBack());
    return customSnackBar;
  }

  private CustomSnackBar(ViewGroup parent, View content, ContentViewCallback contentViewCallback) {
    super(parent, content, contentViewCallback);
  }

ここでinflateするレイアウトですが、下記のような適当な背景色をつけたTextViewを持つLinearLayoutを読み込みます。

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
              android:orientation="horizontal"
              android:layout_width="match_parent"
              android:layout_height="wrap_content"
              android:layout_margin="10dp">
    <TextView android:layout_width="match_parent" android:layout_height="wrap_content"
              android:id="@+id/custom_snackbar_textview"
              android:background="@color/colorAccent"
              android:textColor="@android:color/white"/>

</LinearLayout>

show()メソッドはBaseTransientBottomBarクラスの方ですでに実装されているので、たったこれだけで使うときは普通のSnackbarの様に使えます。

CustomSnackBar.make(view, "This is custom snackbar sample", Snackbar.LENGTH_LONG)
        .show();

こんな感じで表示されます٩( 'ω' )و

f:id:muumuumuumuu:20161225174903p:plain

*1:Web上にソースが見つからずリンク貼れませんでした…。手元のlibraryをデコンパイルしてみています。