带有滑动手势的运动布局+滑动刷新布局+循环查看错误行为向上滚动



我使用MotionLayout构建由两部分组成的UI——顶部有一些视图,底部有SwipeRefresh和RecyclerView。此外,我对MotionLayout有一个手势-向上滑动时,SwipeRefresh会在顶部视图上方向上移动。问题是,当我将RecyclerView滚动到底部(顶部视图"折叠"(,然后滚动到顶部时-MotionLayout开始立即反转我的转换("展开"(-当RecyclerView没有完全滚动到顶部而不是首先滚动RecyclerView。当我的SwipeRefresh正在更新或刷新时,它可以正常工作。禁用它会导致刷新布局进度条在没有动画的情况下消失——这不是一个好的解决方案。有解决办法吗?

布局xml要点

布局场景要点

我在浏览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:constraintllayout: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;
});

然后覆盖RecyclerViewonScrolled方法,如下所示:

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>

如果RecyclerViewListView不是SwipeRefreshLayout的直接子级,则会出现此问题。

最简单的解决方案是提供OnChildScrollUpCallback实现并适当地返回结果。在下面的Kotlin代码中,refreshLayoutSwipeRefreshLayoutrecyclerViewRecyclerView,也可以在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>

这里也回答了这个问题。

最新更新