RecyclerView rollback and drop-down refresh in the most elegant way

Posted by john1704 on Fri, 22 Oct 2021 08:51:55 +0200

SatisOverScroll

Foreword: A big gap between Android and IOS development is the inertial roll-back effect. The effect is really more "human nature", which is why many Android developers are competing to follow, of course I am no exception.

As a five-year-old ape, he has long been dissatisfied with the various open source frameworks available on the market for a number of reasons:

  • No exception requires more configuration to complete the inertial rebound effect
    • By nesting a Layout with OverScroll event handling mechanism
    • Event interception, scrolling animation addition, etc. of the original ScrollView by customizing RecyclerView, etc.
    • Where this effect is needed, the layout layout level needs to be modified
  • For me, I don't want to destroy the existing layout hierarchy, I don't want to do too many configurations and use more "steps" in places.
    What I want more is that Native Control comes with this feature.

It sounds like a bit of a fool, but it's not. Let's think about it from a different perspective. Can native components be compiled without the functionality we reference?

Here's the point

APT (Annotation Processing Tool)

AOP (Aspect Oriented Programming)

The above framework uses and principles are not described much, if you are not familiar with it, you can click to see the introduction.

Today's hero is AspectJ , use this framework to insert byte codes during the compile-to-class phase to achieve the functions we want

Where to insert?

Events in andorid development already have an event distribution mechanism for gesture sliding, which means we don't need to override the logic on the gesture (onTouchEvent). We only listen for events that are distributed for processing, mainly involving two interfaces, NestedScrollParent and NestedScrollChild3 (extended NestedScrollChild2 extended NestedScrollChild) How specific interfaces are distributed is not covered here. For principles, please refer to Learn more about Android NesteScroll principles

1. First slice the Nested implementation

Direct Up Code

@Aspect
class AOPOverScroll {
    @Around(Constants.STARTNESTEDSCROLL)
    @Throws(Throwable::class)
    fun startNestedScroll(joinPoint: ProceedingJoinPoint): Any? {
        return NestedScrollAOP.startNestedScroll(joinPoint)
    }

    @Around(Constants.DISPATCHNESTEDPRESCROLL)
    @Throws(Throwable::class)
    fun dispatchNestedPreScroll(joinPoint: ProceedingJoinPoint): Any? {
        return NestedScrollAOP.dispatchNestedPreScroll(joinPoint)
    }

    @Around(Constants.DISPATCHNESTEDSCROLL)
    @Throws(Throwable::class)
    fun dispatchNestedScroll(joinPoint: ProceedingJoinPoint): Any? {
        return NestedScrollAOP.dispatchNestedScroll(joinPoint)
    }

    @Around(Constants.DISPATCHNESTEDPREFLING)
    @Throws(Throwable::class)
    fun dispatchNestedPreFling(joinPoint: ProceedingJoinPoint): Any? {
        return NestedScrollAOP.dispatchNestedPreFling(joinPoint)
    }

    @Around(Constants.STOPNESTEDSCROLL)
    @Throws(Throwable::class)
    fun stopNestedScroll(joinPoint: ProceedingJoinPoint): Any? {
        return NestedScrollAOP.stopNestedScroll(joinPoint)
    }
}

NestedScrollAOP is the concrete logical part of the stake insertion
object NestedScrollAOP {
    /**
     *  With tag_overscroll_delegate id as identity store proxy class [OverScrollDelegate]
     *  @param target Implementation class integrated from the NestedScrollChild3 interface (RecyclerView, NestedScrollView currently only stakes these two native controls for consumer event handling exclusion custom controls)
     */
    private fun getOverScrollDelegate(target: Any): OverScrollDelegate? {
        if (target is RecyclerView || target is NestedScrollView) {
            var tag = (target as View).getTag(R.id.tag_overscroll_delegate)
            if (tag == null) {
                tag = createByTarget(target)
                target.setTag(R.id.tag_overscroll_delegate, tag)
            }
            return tag as OverScrollDelegate
        }
        return null
    }

    /**
     *  Methods for handling the two parameters [androidx.core.view.NestedScrollingChild2.startNestedScroll]
     */

    @Throws(Throwable::class)
    fun startNestedScroll(joinPoint: ProceedingJoinPoint): Any? {
        val args = joinPoint.args
        val target = joinPoint.target
        val overScrollDelegate = getOverScrollDelegate(target)
        if (overScrollDelegate != null) {
            if (overScrollDelegate.onStartNestedScroll(args[0] as Int, args[1] as Int)) {
                overScrollDelegate.onNestedScrollAccepted(
                    (target as View),
                    args[0] as Int,
                    args[1] as Int
                )
                return true
            }
        }
        return joinPoint.proceed()
    }

    /**
     *  Correspondence Processing [androidx.core.view.NestedScrollingChild2.dispatchNestedPreScroll]
     */
    @Throws(Throwable::class)
    fun dispatchNestedPreScroll(joinPoint: ProceedingJoinPoint): Any? {
        val args = joinPoint.args
        val target = joinPoint.target
        val overScrollDelegate = getOverScrollDelegate(target)
        if (overScrollDelegate != null) {
            overScrollDelegate.onNestedPreScroll(
                target as View,
                args[0] as Int,
                args[1] as Int,
                (args[2] as IntArray),
                args[4] as Int
            )
            if ((args[2] as IntArray)[1] != 0) {
                return true
            }
        }
        return joinPoint.proceed()
    }

    /**
     *  Correspondence Processing [androidx.core.view.NestedScrollingChild2.dispatchNestedScroll]
     */
    @Throws(Throwable::class)
    fun dispatchNestedScroll(joinPoint: ProceedingJoinPoint): Any? {
        val args = joinPoint.args
        val target = joinPoint.target
        val overScrollDelegate = getOverScrollDelegate(target)
        overScrollDelegate?.onNestedScroll(
            target as View,
            args[0] as Int,
            args[1] as Int,
            args[2] as Int,
            args[3] as Int,
            args[5] as Int
        )
        return joinPoint.proceed()
    }


    /**
     *  Correspondence Processing [androidx.core.view.NestedScrollingChild2.dispatchNestedPreFling] 
     */
    @Throws(Throwable::class)
    fun dispatchNestedPreFling(joinPoint: ProceedingJoinPoint): Any? {
        val args = joinPoint.args
        val target = joinPoint.target
        val overScrollDelegate = getOverScrollDelegate(target)
        overScrollDelegate?.onNestedPreFling(
            target as View,
            (args[0] as Float),
            (args[1] as Float)
        )
        return joinPoint.proceed()
    }

    /**
     *  Correspondence Processing [androidx.core.view.NestedScrollingChild2.stopNestedScroll]
     */
    @Throws(Throwable::class)
    fun stopNestedScroll(joinPoint: ProceedingJoinPoint): Any? {
        val target = joinPoint.target
        val args = joinPoint.args
        val overScrollDelegate = getOverScrollDelegate(target)
        if (overScrollDelegate != null && args.isNotEmpty()) {
            overScrollDelegate.onStopNestedScroll((target as View), args[0] as Int)
        }
        return joinPoint.proceed()
    }
}

Stake insertion code is more fixed

2. Agent class OverScrollDelegate adds "OverScroll" effect when handling specific sliding distribution events

I won't stick to the code. It's important to handle every method to achieve an effect. I don't know the NestedScroll principle yet. First add some knowledge and then look at the code

Demo

The RecyclerView Sliver processor (adapter-free, the fastest and most efficient way to use recyclerview) is also included in the Satis open source framework and is strongly recommended.

RecyclerView's correct posture - Sliver

Topics: Java Android Apache IDE