こんにちは。
最近気になっているパンは一本堂の食パン、mukaiyachiです。
SnackbarはToastのようにメッセージを表示する機能です。
画面下からせり出し、一定時間経過すると非表示になります。
使用にはSupport Design Libraryの追加が必要です。
1 |
implementation 'com.android.support:design:28.0.0' |
基本的な使い方は以下の様に、第1引数にSnackbarを表示する際に親となるView、第2引数に表示する内容、第3引数に表示する長さを渡して、show()メソッドで画面に表示します。
1 |
Snackbar.make(v, "タピオカミルクティー飲みたい", Snackbar.LENGTH_LONG).show() |
この基本的な使い方をいつも行うでも良いですが、毎回Viewや表示する長さを渡したり、show()メソッドをコールしたりする必要があります。
特に第1引数のViewを用意するのが地味に面倒くさそうです。
これをExtension(拡張関数)とLiveDataを使うようにします。
LiveDataをsubscribeすればViewや長さを渡したり、show()メソッドをコールする処理を毎回しなくても、表示したい内容だけを渡せば随時Snackbarが表示できるようになります。
※Activityクラスとレイアウト、Fragmentクラスとレイアウト、ViewModelクラスがあることが前提です。無い場合は別途用意が必要です。
Fragmentは以下のようにするとViewModelと一緒に作成されるのでおすすめです。
・Android Studioの左メニューで、パッケージ名右クリック。
・選択メニュー内のNew -> Fragment -> Fragment(with ViewModel) を選択。
まずはDataBindingを使うようにします。
レイアウトXMLを<layout>タグで囲むようにし、バインディング変数(<data>タグ内に設定)にViewModelを定義します。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 |
<layout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:app="http://schemas.android.com/apk/res-auto" xmlns:tools="http://schemas.android.com/tools" tools:context=".snackbar.SnackbarFragment"> <data> <variable name="snackbarViewModel" type="info.mukaiyachi.demoapp.snackbar.SnackbarViewModel"/> </data> <androidx.constraintlayout.widget.ConstraintLayout android:layout_width="match_parent" android:layout_height="match_parent"> <Button android:text="Snackbar表示" android:textAllCaps="false" android:layout_width="wrap_content" android:layout_height="wrap_content" android:id="@+id/button" android:textSize="30sp" android:onClick="@{(view) -> snackbarViewModel.onCLickShowSnackbar(view)}" app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toTopOf="parent" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintBottom_toBottomOf="parent"/> </androidx.constraintlayout.widget.ConstraintLayout> </layout> |
次にViewクラスのExtensionを作成してsetupSnackbar()メソッドとshowSnackbar()メソッドを追加します。
setupSnackbar()メソッドの第2引数が表示する内容を含むLiveDataです。
この値が変更された事をトリガーとしてshowSnackbar()メソッドが呼び出され、Snackbarが表示されます。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 |
/** * ViewおよびViewのサブクラスの拡張機能 */ /** * 第2引数が表示する内容を含むLiveData。この値が変更された事をトリガーとしてSnackbarを表示する。 */ fun View.setupSnackbar(lifecycleOwner: LifecycleOwner, snackbarEvent: LiveData<Event<String>>, timeLength: Int) { snackbarEvent.observe(lifecycleOwner, Observer { event -> event.getContentIfNotHandled()?.let { showSnackbar(it, timeLength) } }) } fun View.showSnackbar(snackbarText: String, timeLength: Int) { Snackbar.make(this, snackbarText, timeLength).run { addCallback(object : Snackbar.Callback() { override fun onDismissed(transientBottomBar: Snackbar?, event: Int) { super.onDismissed(transientBottomBar, event) // Snackbarが非表示になったとき何かしたいときはここに書くなど。 } }).show() } } |
あとsetupSnackbar()メソッドで使用していますが、以下のようなEventクラスが無い場合は追加します。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 |
open class Event<out T>(private val content: T) { // 読み取りを許可するが書き込みは許可しない var hasBeenHadled = false private set /** * Event内のコンテンツが処理されていない場合にのみ呼び出される */ fun getContentIfNotHandled(): T? { return if (hasBeenHadled) { null } else { hasBeenHadled = true content } } /** * 既に処理されている場合でもコンテンツを返却 */ fun peekContent(): T = content } |
これはsubscribeしているLiveDataが複数呼び出されないように制御するためのものです。
次にViewModelに表示したい内容を入れるLiveDataと、他のクラスやメソッドから入れるために使うメソッドを用意します。
1 2 3 4 5 6 7 8 9 |
class SnackbarViewModel : ViewModel() { private val _snackbarText = MutableLiveData<Event<String>>() val snackbarMessage: LiveData<Event<String>> get() = _snackbarText fun showSnackBar(messege: String) { _snackbarText.value = Event(messege) } } |
次にレイアウトにBindingしたViewModelに紐付けるかたちで、追加したsetupSnackbar()メソッドを呼ぶようにします。
第2引数にはViewModelで用意したLiveDataを入れます。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 |
class SnackbarFragment : Fragment() { companion object { fun newInstance() = SnackbarFragment() } private lateinit var viewDataBinding: SnackbarFragmentBinding private lateinit var viewModel: SnackbarViewModel override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? { viewDataBinding = SnackbarFragmentBinding.inflate(inflater, container, false).apply { viewModel = (activity as SnackbarActivity).obtainViewModel() } viewDataBinding.snackbarViewModel = viewModel return viewDataBinding.root } override fun onActivityCreated(savedInstanceState: Bundle?) { super.onActivityCreated(savedInstanceState) viewDataBinding.snackbarViewModel?.let { view?.setupSnackbar(this, it.snackbarMessage, Snackbar.LENGTH_SHORT) } } } |
最後に任意のタイミングでViewModelクラスに用意したshowSnackBar()メソッドにLiveDataに表示したい内容を渡してあげればSnackbarが表示されるようになります。
1 2 3 4 5 |
override fun onResume() { super.onResume() snackbarViewModel.showSnackBar("高級食パン食べたい") } |
1 2 3 |
fun onCLickShowSnackbar() { showSnackBar("高級食パン食べたい") } |