Android

StateListAnimatorを使ってスクロール時にToolbarのelevationを変化させる

Overview

test.gif

Toolbarの下のScrollViewやRecyclerViewをスクロールした時にToolbarの影をつけたい時の実装です。
ToolbarのStateListAnimatorと、ScrollViewのcanScrollVertically()を使って実現します。

1. ObjectAnimatorを作る

状態がselectedになった時にelevationを変化させるObjectAnimatorを作ります。

res/animator/toolbar_elevation.xml
<?xml version="1.0" encoding="utf-8"?>
<selector xmlns:android="http://schemas.android.com/apk/res/android">

    <item android:state_selected="true">
        <objectAnimator
            android:duration="200"
            android:propertyName="elevation"
            android:valueTo="4dp"
            android:valueType="floatType" />
    </item>

    <item>
        <objectAnimator
            android:duration="200"
            android:propertyName="elevation"
            android:valueTo="0dp"
            android:valueType="floatType" />
    </item>

</selector>

2. ToolbarにObjectAnimatorをセットする

作成したObjectAnimatorをToolbarにセットします。

res/layout/{layout_name}.xml
<androidx.appcompat.widget.Toolbar
    ...
    android:stateListAnimator="@animator/toolbar_elevation" />

3. Toolbarの状態を変更する

ScrollView/RecyclerViewのcanScrollVertically()の値でToolbarの状態を変更します。

ActivityorFragment.kt
binding.scrollView.viewTreeObserver.addOnScrollChangedListener {
    // -1をセットすると、スクロール位置が一番上の時のみtrueになる
    isSelected = binding.scrollView.canScrollVertically(-1)
}

これでスクロール時にToolbarの選択状態が変わり、elevationが変化するようになります。


DataBinding Tips

styleとDataBindingのCustomBindingAdapterを使って共通化することもできます。

res/values/styles.xml
<style name="Toolbar.NoShadow" parent="Widget.AppCompat.Toolbar">
    <item name="android:elevation">0dp</item>
    <item name="android:stateListAnimator">@animator/toolbar_elevation</item>
</style>
ToolbarBindingAdapters.kt
@BindingAdapter("shadowAnimationScrollViewId")
fun Toolbar.setShadowAnimationScrollViewId(@IdRes scrollViewId: Int) {
    val scrollView = this.rootView.findViewById<View>(scrollViewId)
    if (scrollView is ScrollView) {
        scrollView.viewTreeObserver.addOnScrollChangedListener {
            isSelected = scrollView.canScrollVertically(-1)
        }
    }
}
layout/{layout}.xml
<android.support.v7.widget.Toolbar
    ...
    style="@style/Toolbar.NoShadow"
    app:shadowAnimationScrollViewId="@{R.id.scroll}" />

<ScrollView
    android:id="@+id/scroll"
    android:layout_width="match_parent"
    android:layout_height="match_parent">
    ...
</ScrollView>