Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Motion Layout with swipe gesture + SwipeRefreshLayout + RecyclerView bug wrong behavior scrolling up

I'm using MotionLayout to build UI with 2 parts - top one with some view and bottom one with SwipeRefresh and RecyclerView inside. Also I have a single gesture for MotionLayout - SwipeRefresh moves up above top view on swipe up. The problem is when I scroll RecyclerView to the bottom (top view "collapses") and then to the top - MotionLayout starts to reverse my transition at once ("expand") - when RecyclerView is not fully scrolled to the top instead of scrolling RecyclerView first. While my SwipeRefresh is updating or refreshing it works as should. Disabling it causes refresh layout progressbar disappearing without animation - it's not a good solution. Any workarounds?

Layout xml gist

Layout scene gist

like image 907
Andriy Shpek Avatar asked Mar 29 '20 23:03

Andriy Shpek


3 Answers

I had the same issue and came up with a solution while browsing the official bugfix history of MotionLayout. You have to override the onNestedPreScroll method of MotionLayout like this:

/**
 * 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)
    }
}

Using this MotionLayout in conjunction with SwipeRefreshLayout works nicely for me. I also posted this here, in case you want to keep track of the bugfix by Google.

like image 155
muetzenflo Avatar answered Sep 23 '22 09:09

muetzenflo


I can't post comment to @muetzenflo answer due to lack of reputation, but I was struggling for several hours trying to disable animation in my MotionLayout. I set isInteractionEnabled to "false", but it didn't work. Finally I realized that I use custom MotionLayout and probably should check it. Only when I added

if (!isInteractionEnabled) {
    return
}

as a first check in onNestedPreScroll() disabling animation work as intended.

like image 26
Vyacheslav Trushin Avatar answered Sep 25 '22 09:09

Vyacheslav Trushin


After I set SwipeRefreshLayout as touchAnchorId the bug was gone

<OnSwipe
motion:dragDirection="dragDown"
motion:touchAnchorId="@+id/swipeContainer"
motion:touchAnchorSide="top" />

androidx.constraintlayout:constraintlayout:2.0.2

like image 41
Vlkam Avatar answered Sep 21 '22 09:09

Vlkam