[英]Motion Layout with swipe gesture + SwipeRefreshLayout + RecyclerView bug wrong behavior scrolling up
我正在使用 MotionLayout 构建包含 2 个部分的 UI - 顶部带有一些视图,底部带有 SwipeRefresh 和 RecyclerView。 此外,我对 MotionLayout 有一个手势 - SwipeRefresh 在向上滑动时向上移动到顶视图上方。 问题是当我将 RecyclerView 滚动到底部(顶视图“折叠”)然后到顶部时 - MotionLayout 立即开始反转我的转换(“展开”) - 当 RecyclerView 没有完全滚动到顶部而不是滚动 RecyclerView第一的。 当我的 SwipeRefresh 正在更新或刷新时,它可以正常工作。 禁用它会导致刷新布局进度条在没有动画的情况下消失 - 这不是一个好的解决方案。 任何解决方法?
我在浏览 MotionLayout 的官方错误修复历史时遇到了同样的问题并想出了一个解决方案。 您必须像这样覆盖 MotionLayout 的onNestedPreScroll
方法:
/**
* The current version of motionLayout (2.0.0-beta04) does not honor the position
* of the RecyclerView, if it is wrapped in a SwipeRefreshLayout.
* This is the case for the PullRequest screen: When scrolling back to top, the motionLayout transition
* would be triggered immediately instead of only as soon as the RecyclerView scrolled back to top.
*
* This workaround checks if the SwipeRefresh layout can still scroll back up. If so, it does not trigger the motionLayout transition.
*/
class SwipeRefreshMotionLayout : MotionLayout {
constructor(context: Context) : super(context)
constructor(context: Context, attrs: AttributeSet?) : super(context, attrs)
constructor(context: Context, attrs: AttributeSet?, defStyleAttr: Int) : super(context, attrs, defStyleAttr)
override fun onNestedPreScroll(target: View, dx: Int, dy: Int, consumed: IntArray, type: Int) {
if (!isInteractionEnabled) {
return
}
if (target !is SwipeRefreshLayout) {
return super.onNestedPreScroll(target, dx, dy, consumed, type)
}
val recyclerView = target.getChildAt(0)
if (recyclerView !is RecyclerView) {
return super.onNestedPreScroll(target, dx, dy, consumed, type)
}
val canScrollVertically = recyclerView.canScrollVertically(-1)
if (dy < 0 && canScrollVertically) {
// don't start motionLayout transition
return;
}
super.onNestedPreScroll(target, dx, dy, consumed, type)
}
}
将此 MotionLayout 与 SwipeRefreshLayout 结合使用对我来说效果很好。 我也在这里发布了这个,以防你想跟踪谷歌的错误修复。
由于缺乏声誉,我无法对@muetzenflo 答案发表评论,但我挣扎了几个小时试图禁用 MotionLayout 中的动画。 我将 isInteractionEnabled 设置为“false”,但它不起作用。 最后我意识到我使用自定义 MotionLayout 并且可能应该检查它。 只有当我添加
if (!isInteractionEnabled) {
return
}
作为第一次检查 onNestedPreScroll() 禁用动画按预期工作。
在我将 SwipeRefreshLayout 设置为 touchAnchorId 后,错误消失了
<OnSwipe
motion:dragDirection="dragDown"
motion:touchAnchorId="@+id/swipeContainer"
motion:touchAnchorSide="top" />
androidx.constraintlayout:constraintlayout:2.0.2
在@muetzenflo 的答案中添加更多内容(因为我也没有足够的声誉来发表评论):
target
中的 SwipeRefreshLayout 也将包含一个CircleImageView
(我假设它是下拉时显示的刷新图标)。 这有时会是布局的第一个子元素(如果布局所在的片段已被删除然后稍后添加回来,这似乎会发生),因此target.getChildAt(0)
将返回 CircleImageView 而不是 RecyclerView。 遍历target
的所有孩子并检查其中一个是否是 RecyclerView 提供了预期的结果。
代码(Java):
SwipeRefreshLayout swipeLayout = (SwipeRefreshLayout) target;
// Check that the SwipeRefreshLayout has a RecyclerView child
View recyclerView = null;
for (int i = 0; i < swipeLayout.getChildCount(); i++) {
View child = swipeLayout.getChildAt(i);
if (child instanceof RecyclerView) {
recyclerView = child;
break;
}
}
if (recyclerView == null) {
super.onNestedPreScroll(target, dx, dy, consumed, type);
return;
}
这个解决方案对我有用。 这是基于java中的这两个答案:
https://stackoverflow.com/a/59827320/11962518
https://stackoverflow.com/a/65202877/11962518
binding.motionLayout.setTransitionListener(new MotionLayout.TransitionListener() {
@Override
public void onTransitionStarted(MotionLayout motionLayout, int startId, int endId) {
binding.swipeLayout.setEnabled(false);
}
@Override
public void onTransitionChange(MotionLayout motionLayout, int startId, int endId, float progress) {
if (progress != 0f) {
binding.swipeLayout.setEnabled(false);
binding.swipeLayout.setRefreshing(false);
}
}
@Override
public void onTransitionCompleted(MotionLayout motionLayout, int currentId) {
}
@Override
public void onTransitionTrigger(MotionLayout motionLayout, int triggerId, boolean positive, float progress) {
}
});
binding.recyclerView.setOnTouchListener((view, motionEvent) -> {
binding.motionLayout.onTouchEvent(motionEvent);
return false;
});
然后像这样覆盖RecyclerView
的onScrolled
方法:
gridLayoutManager = new RtlGridLayoutManager(context, 1);
adapterRowList = new RecyclerAdapterRowItemCommodity(rowList.getRowItemHome(), context);
binding.recyclerView.setAdapter(adapterRowList);
binding.recyclerView.setLayoutManager(gridLayoutManager);
binding.recyclerView.addOnScrollListener(new RecyclerView.OnScrollListener() {
@Override
public void onScrolled(@NotNull RecyclerView recyclerView, int dx, int dy) {
if (!recyclerView.canScrollVertically(-1)) { //Detects start of RecyclerView
binding.swipeLayout.setEnabled(true); // now enable swipe refresh layout
}
});
XML:
注意:您应该将您的recyclerView
放在另一个layout
中,如下所示:
<androidx.constraintlayout.widget.ConstraintLayout
android:id="@+id/main_lay"
android:layout_width="match_parent"
android:layout_height="0dp"
app:layout_constraintBottom_toTopOf="@id/view_bottom"
app:layout_constraintTop_toBottomOf="@id/top_buttons">
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/recyclerView"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:paddingStart="@dimen/_8cdp"
android:paddingEnd="@dimen/_8cdp"
/>
</androidx.constraintlayout.widget.ConstraintLayout>
运动场景:
像这样定义Transaction
:
<Transition
motion:constraintSetStart="@id/start"
motion:constraintSetEnd="@+id/end"
motion:duration="1000">
<OnSwipe motion:touchAnchorId="@id/main_lay"
motion:touchAnchorSide="top"
motion:dragDirection="dragUp"/>
</Transition>
如果RecyclerView
或ListView
不是SwipeRefreshLayout
的直接子级,则会出现此问题。
最简单的解决方案是提供OnChildScrollUpCallback
实现并适当地返回结果。 在下面的 Kotlin 代码中, refreshLayout
是SwipeRefreshLayout
而recyclerView
是RecyclerView
,这在xml
布局代码中也可以看到。
refreshLayout.setOnChildScrollUpCallback(object : SwipeRefreshLayout.OnChildScrollUpCallback {
override fun canChildScrollUp(parent: SwipeRefreshLayout, child: View?): Boolean {
if (recyclerView != null) {
return recyclerView.canScrollVertically(-1)
}
return false
}
})
虽然xml
布局是这样的,
<androidx.swiperefreshlayout.widget.SwipeRefreshLayout
android:id="@+id/swipeRefresh".../>
...
lots of other views i.e TextView, ImageView, MotionLayout
...
...
...
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/recyclerView".../>
...
...
...
</androidx.swiperefreshlayout.widget.SwipeRefreshLayout>
这也在这里得到解答。
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.