Toolbar and bottom nav scroll snap (#5915)

This commit is contained in:
Ivan Iskandar 2021-09-19 03:41:23 +07:00 committed by GitHub
parent 774f818bbb
commit a2d007f2a9
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
3 changed files with 173 additions and 2 deletions

View file

@ -0,0 +1,102 @@
package com.google.android.material.appbar
import android.animation.ValueAnimator
import android.view.View
import android.view.animation.DecelerateInterpolator
import androidx.appcompat.widget.Toolbar
import androidx.coordinatorlayout.widget.CoordinatorLayout
import androidx.core.animation.doOnEnd
import androidx.core.view.ViewCompat
import androidx.core.view.marginTop
import eu.kanade.tachiyomi.util.system.animatorDurationScale
import eu.kanade.tachiyomi.util.view.findChild
import eu.kanade.tachiyomi.widget.ElevationAppBarLayout
import kotlin.math.roundToLong
/**
* Hide toolbar on scroll behavior for [AppBarLayout].
*
* Inside this package to access some package-private methods.
*/
class HideToolbarOnScrollBehavior : AppBarLayout.Behavior() {
@ViewCompat.NestedScrollType
private var lastStartedType: Int = 0
private var offsetAnimator: ValueAnimator? = null
private var toolbarHeight: Int = 0
override fun onStartNestedScroll(
parent: CoordinatorLayout,
child: AppBarLayout,
directTargetChild: View,
target: View,
nestedScrollAxes: Int,
type: Int
): Boolean {
lastStartedType = type
offsetAnimator?.cancel()
return super.onStartNestedScroll(parent, child, directTargetChild, target, nestedScrollAxes, type)
}
override fun onStopNestedScroll(
parent: CoordinatorLayout,
layout: AppBarLayout,
target: View,
type: Int
) {
super.onStopNestedScroll(parent, layout, target, type)
if (toolbarHeight == 0) {
toolbarHeight = layout.findChild<Toolbar>()?.height ?: 0
}
if (lastStartedType == ViewCompat.TYPE_TOUCH || type == ViewCompat.TYPE_NON_TOUCH) {
animateToolbarVisibility(
parent,
layout,
getTopBottomOffsetForScrollingSibling(layout) > -toolbarHeight / 2
)
}
}
override fun onFlingFinished(parent: CoordinatorLayout, layout: AppBarLayout) {
super.onFlingFinished(parent, layout)
animateToolbarVisibility(
parent,
layout,
getTopBottomOffsetForScrollingSibling(layout) > -toolbarHeight / 2
)
}
private fun getTopBottomOffsetForScrollingSibling(abl: AppBarLayout): Int {
return topBottomOffsetForScrollingSibling - abl.marginTop
}
private fun animateToolbarVisibility(
coordinatorLayout: CoordinatorLayout,
child: AppBarLayout,
isVisible: Boolean
) {
offsetAnimator?.cancel()
offsetAnimator = ValueAnimator().apply {
interpolator = DecelerateInterpolator()
duration = (150 * child.context.animatorDurationScale).roundToLong()
addUpdateListener {
setHeaderTopBottomOffset(coordinatorLayout, child, it.animatedValue as Int)
}
doOnEnd {
if (!isVisible &&
!child.isLifted &&
(child as? ElevationAppBarLayout)?.isTransparentWhenNotLifted == true
) {
child.isLifted = true
}
}
}
offsetAnimator?.setIntValues(
getTopBottomOffsetForScrollingSibling(child),
if (isVisible) 0 else -toolbarHeight
)
offsetAnimator?.start()
}
}

View file

@ -5,10 +5,12 @@ import android.content.Context
import android.util.AttributeSet import android.util.AttributeSet
import android.widget.TextView import android.widget.TextView
import androidx.annotation.FloatRange import androidx.annotation.FloatRange
import androidx.coordinatorlayout.widget.CoordinatorLayout
import androidx.lifecycle.coroutineScope import androidx.lifecycle.coroutineScope
import androidx.lifecycle.findViewTreeLifecycleOwner import androidx.lifecycle.findViewTreeLifecycleOwner
import com.google.android.material.animation.AnimationUtils import com.google.android.material.animation.AnimationUtils
import com.google.android.material.appbar.AppBarLayout import com.google.android.material.appbar.AppBarLayout
import com.google.android.material.appbar.HideToolbarOnScrollBehavior
import com.google.android.material.appbar.MaterialToolbar import com.google.android.material.appbar.MaterialToolbar
import eu.kanade.tachiyomi.R import eu.kanade.tachiyomi.R
import eu.kanade.tachiyomi.util.view.findChild import eu.kanade.tachiyomi.util.view.findChild
@ -51,6 +53,8 @@ class ElevationAppBarLayout @JvmOverloads constructor(
} }
} }
override fun getBehavior(): CoordinatorLayout.Behavior<AppBarLayout> = HideToolbarOnScrollBehavior()
/** /**
* Disabled. Lift on scroll is handled manually with [TachiyomiCoordinatorLayout] * Disabled. Lift on scroll is handled manually with [TachiyomiCoordinatorLayout]
*/ */

View file

@ -1,11 +1,19 @@
package eu.kanade.tachiyomi.widget package eu.kanade.tachiyomi.widget
import android.animation.ValueAnimator
import android.content.Context import android.content.Context
import android.util.AttributeSet import android.util.AttributeSet
import android.view.View import android.view.View
import android.view.ViewGroup
import android.view.animation.DecelerateInterpolator
import androidx.appcompat.widget.Toolbar
import androidx.coordinatorlayout.widget.CoordinatorLayout import androidx.coordinatorlayout.widget.CoordinatorLayout
import androidx.core.view.ViewCompat import androidx.core.view.ViewCompat
import com.google.android.material.appbar.AppBarLayout
import com.google.android.material.bottomnavigation.BottomNavigationView import com.google.android.material.bottomnavigation.BottomNavigationView
import eu.kanade.tachiyomi.util.system.animatorDurationScale
import eu.kanade.tachiyomi.util.view.findChild
import kotlin.math.roundToLong
/** /**
* Hide behavior similar to app bar for [BottomNavigationView] * Hide behavior similar to app bar for [BottomNavigationView]
@ -15,6 +23,31 @@ class HideBottomNavigationOnScrollBehavior @JvmOverloads constructor(
attrs: AttributeSet? = null attrs: AttributeSet? = null
) : CoordinatorLayout.Behavior<BottomNavigationView>(context, attrs) { ) : CoordinatorLayout.Behavior<BottomNavigationView>(context, attrs) {
@ViewCompat.NestedScrollType
private var lastStartedType: Int = 0
private var offsetAnimator: ValueAnimator? = null
private var dyRatio = 1F
override fun layoutDependsOn(parent: CoordinatorLayout, child: BottomNavigationView, dependency: View): Boolean {
return dependency is AppBarLayout
}
override fun onDependentViewChanged(
parent: CoordinatorLayout,
child: BottomNavigationView,
dependency: View
): Boolean {
val toolbarSize = (dependency as ViewGroup).findChild<Toolbar>()?.height ?: 0
dyRatio = if (toolbarSize > 0) {
child.height.toFloat() / toolbarSize
} else {
1F
}
return false
}
override fun onStartNestedScroll( override fun onStartNestedScroll(
coordinatorLayout: CoordinatorLayout, coordinatorLayout: CoordinatorLayout,
child: BottomNavigationView, child: BottomNavigationView,
@ -23,7 +56,12 @@ class HideBottomNavigationOnScrollBehavior @JvmOverloads constructor(
axes: Int, axes: Int,
type: Int type: Int
): Boolean { ): Boolean {
return axes == ViewCompat.SCROLL_AXIS_VERTICAL if (axes != ViewCompat.SCROLL_AXIS_VERTICAL) {
return false
}
lastStartedType = type
offsetAnimator?.cancel()
return true
} }
override fun onNestedPreScroll( override fun onNestedPreScroll(
@ -36,6 +74,33 @@ class HideBottomNavigationOnScrollBehavior @JvmOverloads constructor(
type: Int type: Int
) { ) {
super.onNestedPreScroll(coordinatorLayout, child, target, dx, dy, consumed, type) super.onNestedPreScroll(coordinatorLayout, child, target, dx, dy, consumed, type)
child.translationY = (child.translationY + dy).coerceIn(0F, child.height.toFloat()) child.translationY = (child.translationY + (dy * dyRatio)).coerceIn(0F, child.height.toFloat())
}
override fun onStopNestedScroll(
coordinatorLayout: CoordinatorLayout,
child: BottomNavigationView,
target: View,
type: Int
) {
if (lastStartedType == ViewCompat.TYPE_TOUCH || type == ViewCompat.TYPE_NON_TOUCH) {
animateBottomNavigationVisibility(child, child.translationY < child.height / 2)
}
}
private fun animateBottomNavigationVisibility(child: BottomNavigationView, isVisible: Boolean) {
offsetAnimator?.cancel()
offsetAnimator = ValueAnimator().apply {
interpolator = DecelerateInterpolator()
duration = (150 * child.context.animatorDurationScale).roundToLong()
addUpdateListener {
child.translationY = it.animatedValue as Float
}
}
offsetAnimator?.setFloatValues(
child.translationY,
if (isVisible) 0F else child.height.toFloat()
)
offsetAnimator?.start()
} }
} }