Android CoordinatorLayout 与 AppbarLayout Double Fling Bug
Android CoordinatorLayout with AppbarLayout Double Fling Bug
AppbarLayout 有一个很大的child,所以我们可以拖拽它。
当我先用 child 抛出 appbar 并在之前 抛出 scrollview 时出错,之前的抛出动画是 finished。
当 appbar 和 nestedscrollview 都发生双重滚动时,滚动变得一团糟
这里是layout.xml
<?xml version="1.0" encoding="utf-8"?>
<androidx.coordinatorlayout.widget.CoordinatorLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
xmlns:app="http://schemas.android.com/apk/res-auto">
<com.google.android.material.appbar.AppBarLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:layout_behavior="com.google.android.material.appbar.AppBarLayout$Behavior"
>
<View
android:layout_width="match_parent"
android:layout_height="800dp"
android:background="#8f8"
app:layout_scrollFlags="scroll"
/>
</com.google.android.material.appbar.AppBarLayout>
<androidx.core.widget.NestedScrollView
android:layout_width="match_parent"
android:layout_height="match_parent"
app:layout_behavior="com.google.android.material.appbar.AppBarLayout$ScrollingViewBehavior">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content">
<View
android:layout_width="match_parent"
android:layout_height="800dp"
android:background="#88f" />
</LinearLayout>
</androidx.core.widget.NestedScrollView>
</androidx.coordinatorlayout.widget.CoordinatorLayout>
我和RecyclerView
有同样的问题
见HeaderViewBehavior.setHeaderTopBottomOffset
,newOffset
参数调用方向不同。
当 onStartNestedScroll
被调用时,你应该取消 AppBarLayout.Behavior
fling。
如何修复:
覆盖AppBarLayout.Behavior
package com.google.android.material.appbar // original package
import android.content.Context
import android.util.AttributeSet
import android.view.View
import android.widget.OverScroller
import androidx.coordinatorlayout.widget.CoordinatorLayout
class FixFlingBehavior @JvmOverloads constructor(
context: Context,
attributeSet: AttributeSet? = null
) : AppBarLayout.Behavior(context, attributeSet) {
private var isAppbarFlinging: Boolean = false
private var originalScroller: OverScroller? = null
private val fakeScroller: OverScroller = object : OverScroller(context) {
override fun computeScrollOffset(): Boolean {
scroller = originalScroller
return false // it cancels HeaderBehavior FlingRunnable
}
}
override fun setHeaderTopBottomOffset(coordinatorLayout: CoordinatorLayout, appBarLayout: AppBarLayout, newOffset: Int, minOffset: Int, maxOffset: Int): Int {
isAppbarFlinging = minOffset == Int.MIN_VALUE && maxOffset == Int.MAX_VALUE
if (scroller === fakeScroller) {
scroller = originalScroller
}
return super.setHeaderTopBottomOffset(coordinatorLayout, appBarLayout, newOffset, minOffset, maxOffset)
}
override fun onFlingFinished(parent: CoordinatorLayout, layout: AppBarLayout) {
super.onFlingFinished(parent, layout)
isAppbarFlinging = false
}
override fun onStartNestedScroll(parent: CoordinatorLayout, child: AppBarLayout, directTargetChild: View, target: View, nestedScrollAxes: Int, type: Int): Boolean {
if (isAppbarFlinging && scroller !== fakeScroller) {
originalScroller = scroller
scroller = fakeScroller
}
return super.onStartNestedScroll(parent, child, directTargetChild, target, nestedScrollAxes, type)
}
}
并在您的布局中使用它
...
<com.google.android.material.appbar.AppBarLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:layout_behavior="com.google.android.material.appbar.FixFlingBehavior">
...
AppbarLayout 有一个很大的child,所以我们可以拖拽它。
当我先用 child 抛出 appbar 并在之前 抛出 scrollview 时出错,之前的抛出动画是 finished。
当 appbar 和 nestedscrollview 都发生双重滚动时,滚动变得一团糟
这里是layout.xml
<?xml version="1.0" encoding="utf-8"?>
<androidx.coordinatorlayout.widget.CoordinatorLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
xmlns:app="http://schemas.android.com/apk/res-auto">
<com.google.android.material.appbar.AppBarLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:layout_behavior="com.google.android.material.appbar.AppBarLayout$Behavior"
>
<View
android:layout_width="match_parent"
android:layout_height="800dp"
android:background="#8f8"
app:layout_scrollFlags="scroll"
/>
</com.google.android.material.appbar.AppBarLayout>
<androidx.core.widget.NestedScrollView
android:layout_width="match_parent"
android:layout_height="match_parent"
app:layout_behavior="com.google.android.material.appbar.AppBarLayout$ScrollingViewBehavior">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content">
<View
android:layout_width="match_parent"
android:layout_height="800dp"
android:background="#88f" />
</LinearLayout>
</androidx.core.widget.NestedScrollView>
</androidx.coordinatorlayout.widget.CoordinatorLayout>
我和RecyclerView
见HeaderViewBehavior.setHeaderTopBottomOffset
,newOffset
参数调用方向不同。
当 onStartNestedScroll
被调用时,你应该取消 AppBarLayout.Behavior
fling。
如何修复:
覆盖AppBarLayout.Behavior
package com.google.android.material.appbar // original package
import android.content.Context
import android.util.AttributeSet
import android.view.View
import android.widget.OverScroller
import androidx.coordinatorlayout.widget.CoordinatorLayout
class FixFlingBehavior @JvmOverloads constructor(
context: Context,
attributeSet: AttributeSet? = null
) : AppBarLayout.Behavior(context, attributeSet) {
private var isAppbarFlinging: Boolean = false
private var originalScroller: OverScroller? = null
private val fakeScroller: OverScroller = object : OverScroller(context) {
override fun computeScrollOffset(): Boolean {
scroller = originalScroller
return false // it cancels HeaderBehavior FlingRunnable
}
}
override fun setHeaderTopBottomOffset(coordinatorLayout: CoordinatorLayout, appBarLayout: AppBarLayout, newOffset: Int, minOffset: Int, maxOffset: Int): Int {
isAppbarFlinging = minOffset == Int.MIN_VALUE && maxOffset == Int.MAX_VALUE
if (scroller === fakeScroller) {
scroller = originalScroller
}
return super.setHeaderTopBottomOffset(coordinatorLayout, appBarLayout, newOffset, minOffset, maxOffset)
}
override fun onFlingFinished(parent: CoordinatorLayout, layout: AppBarLayout) {
super.onFlingFinished(parent, layout)
isAppbarFlinging = false
}
override fun onStartNestedScroll(parent: CoordinatorLayout, child: AppBarLayout, directTargetChild: View, target: View, nestedScrollAxes: Int, type: Int): Boolean {
if (isAppbarFlinging && scroller !== fakeScroller) {
originalScroller = scroller
scroller = fakeScroller
}
return super.onStartNestedScroll(parent, child, directTargetChild, target, nestedScrollAxes, type)
}
}
并在您的布局中使用它
...
<com.google.android.material.appbar.AppBarLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:layout_behavior="com.google.android.material.appbar.FixFlingBehavior">
...