diff --git a/app/src/main/res/layout/activity_scrollable_coordinator_layout.xml b/app/src/main/res/layout/activity_scrollable_coordinator_layout.xml index da03f8a..09927ad 100644 --- a/app/src/main/res/layout/activity_scrollable_coordinator_layout.xml +++ b/app/src/main/res/layout/activity_scrollable_coordinator_layout.xml @@ -1,32 +1,30 @@ - + + android:id="@+id/recycler_view" + android:layout_width="match_parent" + android:layout_height="match_parent" /> + android:id="@+id/fab" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_gravity="bottom|end" + android:layout_margin="16dp" + app:srcCompat="@drawable/ic_meow" /> + android:id="@+id/expandable_bottom_bar" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:layout_gravity="bottom" + app:exb_items="@menu/bottom_bar" + app:layout_behavior="github.com.st235.lib_expandablebottombar.behavior.ExpandableBottomBarScrollableBehavior" /> diff --git a/build.gradle b/build.gradle index d93315a..294a462 100644 --- a/build.gradle +++ b/build.gradle @@ -1,7 +1,7 @@ ext { vers = [ - versionCode: 35, - versionName: "1.2.2" + versionCode: 36, + versionName: "1.2.3" ] info = [ name: 'expandablebottombar', diff --git a/lib-expandablebottombar/src/main/java/github/com/st235/lib_expandablebottombar/behavior/ExpandableBottomBarScrollableBehavior.kt b/lib-expandablebottombar/src/main/java/github/com/st235/lib_expandablebottombar/behavior/ExpandableBottomBarScrollableBehavior.kt index 5bcc0d8..80c2bd0 100644 --- a/lib-expandablebottombar/src/main/java/github/com/st235/lib_expandablebottombar/behavior/ExpandableBottomBarScrollableBehavior.kt +++ b/lib-expandablebottombar/src/main/java/github/com/st235/lib_expandablebottombar/behavior/ExpandableBottomBarScrollableBehavior.kt @@ -1,15 +1,27 @@ package github.com.st235.lib_expandablebottombar.behavior +import android.animation.ValueAnimator import android.content.Context +import android.os.Handler +import android.os.Looper import android.util.AttributeSet import android.view.View +import android.view.animation.DecelerateInterpolator import androidx.coordinatorlayout.widget.CoordinatorLayout import androidx.core.view.ViewCompat +import androidx.core.view.marginBottom import github.com.st235.lib_expandablebottombar.utils.clamp +import kotlin.math.abs class ExpandableBottomBarScrollableBehavior: ExpandableBottomBarBehavior { + private val handler = Handler(Looper.getMainLooper()) + private var lastKnownRunnable: Runnable? = null + + private var animator: ValueAnimator? = null + private var lastKnownDirection: Int? = null + constructor(): super() constructor(context: Context, attributeSet: AttributeSet): super(context, attributeSet) @@ -20,10 +32,77 @@ class ExpandableBottomBarScrollableBehavior: return axes == ViewCompat.SCROLL_AXIS_VERTICAL } - override fun onNestedPreScroll( - coordinatorLayout: CoordinatorLayout, child: V, target: View, dx: Int, dy: Int, consumed: IntArray, type: Int - ) { - super.onNestedPreScroll(coordinatorLayout, child, target, dx, dy, consumed, type) - child.translationY = clamp(child.translationY + dy, 0f, child.height.toFloat()) + override fun onNestedScroll(coordinatorLayout: CoordinatorLayout, child: V, target: View, dxConsumed: Int, dyConsumed: Int, dxUnconsumed: Int, dyUnconsumed: Int, type: Int, consumed: IntArray) { + super.onNestedScroll(coordinatorLayout, child, target, dxConsumed, dyConsumed, dxUnconsumed, dyUnconsumed, type, consumed) + + if (lastKnownRunnable != null) { + handler.removeCallbacks(lastKnownRunnable) + lastKnownRunnable = null + } + + cancelAnimation() + + lastKnownDirection = dyConsumed + child.translationY = getScrollRange(child, dyConsumed) + } + + override fun onStopNestedScroll(coordinatorLayout: CoordinatorLayout, child: V, target: View, type: Int) { + super.onStopNestedScroll(coordinatorLayout, child, target, type) + + if (lastKnownRunnable != null) { + handler.removeCallbacks(lastKnownRunnable) + } + + lastKnownRunnable = Runnable { + animateWithDirection(child) + } + handler.postDelayed(lastKnownRunnable, 500L) + } + + private fun getScrollRange(child: V, dy: Int): Float { + return clamp(child.translationY + dy, 0f, getMaxScrollDistance(child)) + } + + private fun getMaxScrollDistance(child: V): Float { + val childHeight = if (ViewCompat.isLaidOut(child)) child.height else child.measuredHeight + return childHeight.toFloat() + child.marginBottom + } + + private fun animateWithDirection(child: V) { + val dy = lastKnownDirection ?: return + val halfOfScrollDistance = getMaxScrollDistance(child) / 2F + + val overHalfUp = dy < 0 /* up */ && abs(child.translationY) < halfOfScrollDistance + val lessThanHalfDown = dy > 0 /* down */ && abs(child.translationY) < halfOfScrollDistance + + if (overHalfUp || lessThanHalfDown) { + animateTo(child, 0F) + } else { + animateTo(child, getMaxScrollDistance(child)) + } + } + + private fun animateTo(child: V, translation: Float) { + if (child.translationY == 0F || child.translationY == getMaxScrollDistance(child)) { + return + } + + cancelAnimation() + + animator = ValueAnimator.ofFloat(child.translationY, translation) + animator?.interpolator = DecelerateInterpolator() + animator?.addUpdateListener { animator -> + val animatedValue = this.animator?.animatedValue as Float + child.translationY = animatedValue + } + + animator?.start() + } + + private fun cancelAnimation() { + if (animator != null && animator?.isRunning == true) { + animator?.cancel() + animator = null + } } }