RecyclerViewのスクロールにあわせてToolbarやBottomNavigationView, FloatingActionButtonを隠そうとしたら、いろいろとハマったのでメモ。
何をしたいのか
こんな感じにRecyclerViewのスクロールにあわせて各要素を隠したい。

動かす要素は次のとおり。
- Toolbar
- AdView
- BottomNavigationView
- FloatingActionButton
レイアウト
Data Bindingを利用するためルートは<layout>になっていますが、各要素がCoordinatorLayoutの直接の子になっていればOK。
<layout ...> <data> ... </data> <androidx.coordinatorlayout.widget.CoordinatorLayout android:layout_width="match_parent" android:layout_height="match_parent"> <com.google.android.material.appbar.AppBarLayout android:id="@+id/appBarLayout" ...> <androidx.appcompat.widget.Toolbar android:id="@+id/toolbar" ... app:layout_scrollFlags="scroll|enterAlways" /> </com.google.android.material.appbar.AppBarLayout> <FrameLayout android:id="@+id/mainFrag" ... app:layout_behavior="@string/appbar_scrolling_view_behavior" /> <com.google.android.gms.ads.AdView android:id="@+id/adView" ... app:layout_behavior=".list.behavior.BottomWidgetBehavior" /> <com.google.android.material.floatingactionbutton.FloatingActionButton android:id="@+id/floatingActionButton" ... app:layout_behavior=".list.behavior.FabBehavior" /> <com.google.android.material.bottomnavigation.BottomNavigationView android:id="@+id/bottomNavigation" ... app:layout_behavior=".list.behavior.BottomWidgetBehavior" /> </androidx.coordinatorlayout.widget.CoordinatorLayout> </layout>
構造だけ抜き出すとこんな感じ。
<layout> <data></data> <androidx.coordinatorlayout.widget.CoordinatorLayout> <com.google.android.material.appbar.AppBarLayout> <androidx.appcompat.widget.Toolbar/> </com.google.android.material.appbar.AppBarLayout> <FrameLayout/> <com.google.android.gms.ads.AdView/> <com.google.android.material.floatingactionbutton.FloatingActionButton/> <com.google.android.material.bottomnavigation.BottomNavigationView/> </androidx.coordinatorlayout.widget.CoordinatorLayout> </layout>
真ん中あたりのFrameLayoutにRecyclerViewを含むFragmentをInflateし、そのスクロールをトリガーとして他のViewを動作させます。
CoordinatorLayout
Viewのスクロールにあわせて他のViewを動かす場合、前提としてCoordinatorLayoutの直下にないとだめ。
また、スクロールするViewはNestedScrollViewかRecyclerViewでないとだめ。
Toolbarの場合
AppBarLayoutを親要素とし、その中に配置する。
スクロールにあわせてどんな動作をさせるかは、app:layout_scrollFlagsにて定義する。
パラメータについては次の記事が参考になりそう。
AdView, BottomNavigationViewの場合
自前でカスタムBehaviorを定義し、app:layout_behaviorに設定する必要がある。
CoordinatorLayout.Behaviorを継承し、onStartNestedScroll及びonNestedPreScrollをオーバーライドする。
class BottomWidgetBehavior<V : View>(context: Context, attrs: AttributeSet) : CoordinatorLayout.Behavior<V>(context, attrs) { override fun onStartNestedScroll(...): Boolean { return axes == ViewCompat.SCROLL_AXIS_VERTICAL } override fun onNestedPreScroll(...) { super.onNestedPreScroll(coordinatorLayout, child, target, dx, dy, consumed, type) child.translationY = max(0f, min(child.height.toFloat() + child.marginBottom, child.translationY + dy)) } }
child.translationYにて、ViewのY座標を逐次書き直すことで隠す動作を表現する。
child.marginBottomを加算することで、marginBottomを設定しているAdViewにも対応可能。
FABの場合
FABはFloatingActionButton.Behaviorを継承し、同じくonStartNestedScroll及びonNestedPreScrollをオーバーライドする。
なお、FABはスクロール時にshowとhideで表示非表示を切り替えてるが、hideはリスナーをセットしないとshowが呼ばれないので要注意。
class FabBehavior(context: Context, attrs: AttributeSet) : FloatingActionButton.Behavior(context, attrs) { override fun onStartNestedScroll(...): Boolean { return axes == ViewCompat.SCROLL_AXIS_VERTICAL } override fun onNestedPreScroll(...) { super.onNestedPreScroll(coordinatorLayout, child, target, dx, dy, consumed, type) if (dy > 0 && child.visibility == View.VISIBLE) { // User scrolled down and the FAB is currently visible -> hide the FAB child.hide(object : FloatingActionButton.OnVisibilityChangedListener() { override fun onHidden(fab: FloatingActionButton?) { super.onHidden(fab) fab?.visibility = View.INVISIBLE } }) } else if (dy < 0 && child.visibility != View.VISIBLE) { // User scrolled up and the FAB is currently not visible -> show the FAB child.show() } } }
それぞれ、作成したBehaviorをapp:layout_behaviorに設定すればおk。