Posted at

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>