RecyclerView.ItemDecorationの表示、非表示をスクロール状態によって切り替える回
はじめに
RecyclerView.ItemDecorationを表示するかどうか、スクロールの状態によって切り替えたかったというお話です。
僕がDroidKaigi 2019の公式アプリのissueと、それに対して僕が提出したプルリクに基づく投稿です。*1 *2
やりたかったこと
具体的なissueの内容については上のリンクを見てもらえればと思います。
ざっくり言うと、あるRecyclerView上のItemDecorationについて、RecyclerViewのコンテンツを見やすくするために、スクロール中のみItemDecorationを表示しスクロールを止めたらそれを非表示にしたかった、というお話しです。
どうやったか
RecyclerView.OnScrollListener
を設定してスクロール状態に応じaddItemDecoration
、removeItemDecoration
を行いました。
まずコードの例を出してそこから少し説明をしようと思います。
//rvはRecyclerViewです。 rv.addOnScrollListener( object : RecyclerView.OnScrollListener() { override fun onScrollStateChanged(recyclerView: RecyclerView, newState: Int) { rv.removeItemDecoration(itemDecoration) if (newState != RecyclerView.SCROLL_STATE_IDLE) { rv.addItemDecoration(itemDecoration) } } } )
onScrollStateChanged
内にてitemDecorationを付けたり外したりすることによってその表示、非表示を切り替えています。
具体的には、rvの状態が変化したら一旦itemDecorationを外し、その状態が止まった状態でなければもう一度addItemDecoration
しています。
newStateについて
これだけだとあまりにも説明に味がないのでonScrollStateChanged
の引数であるnewStateについて少し説明します。
newStateの値 | どのような状態か |
---|---|
SCROLL_STATE_IDLE |
スクロールしていない状態 |
SCROLL_STATE_DRAGGING |
外からの入力によってドラッグされている状態 |
SCROLL_STATE_SETTLING |
外からの入力はないが動いている状態 |
外からの入力という点がちょっと?ですが、Android Developersの説明文によればoutside input such as user touch input
とのことなので、基本的にはユーザー操作と考えて良さそうです。
SCROLL_STATE_SETTLING
に関しては、スワイプした後の慣性で動いてるような状態でしょう。
おまけ
onScrollStateChanged
内での処理は以下のようにすることも考えました。
override fun onScrollStateChanged(recyclerView: RecyclerView, newState: Int) { if (newState == RecyclerView.SCROLL_STATE_IDLE) { rv.removeItemDecoration(itemDecoration) } else { rv.addItemDecoration(itemDecoration) } }
考えたのですが、
- onScrollStateChanged自体が一秒間に何回も呼ばれるようなものではないので、一旦
removeItemDecoration
してからaddItemDecoration
してもそこまで負荷が高くはならないのではないか - もうすでに追加されているItemDecorationを再度
addItemDecoration
するのが嫌だ
といった理由から先の書き方にしました。
少し待ってから非表示にする
冒頭であげたissueでは上のコードとは異なり、「スクロールが止まってから少しの間を空けてItemDecorationを非表示にする」ことに取り組みました。
ここでも上と同じようにまずコードを出して軽く説明したいと思います。
rv.addOnScrollListener( object : RecyclerView.OnScrollListener() { override fun onScrollStateChanged(recyclerView: RecyclerView, newState: Int) { val job = GlobalScope.launch(Dispatchers.Main) { delay(500) rv.removeItemDecoration(itemDecoration) } super.onScrollStateChanged(recyclerView, newState) if (newState == RecyclerView.SCROLL_STATE_IDLE) { job.start() } else { if (job.isActive) { job.cancel() } rv.removeItemDecoration(itemDecoration) rv.addItemDecoration(itemDecoration) } } } )
ここではスクロールが止まってから500ミリ秒後にremoveItemDecoration
をしています。
「500ミリ秒待ってremoveItemDecoration
」をcoroutinesのJobにしています。
この主な理由は、待ち時間のうちに再びスクロールされたときにその操作をキャンセルしたかったというものです。
if (job.isActive) { job.cancel() }
ですね。
あとはそんな変わったことはしていないと思います。
最後に
DroidKaigi楽しみです。