Android source code analysis Scroller

As we all know, the scroller can slide, but its use process is different from that of general classes. For a better understanding, we need to study the source code of Scroller. Do you still remember how to use Scroller? Let's recap:

The system will call the computeScroller method in the draw() method when drawing the View. We do this by calling the scrollTo method of the viewGroup's view.getParent. to get the current sliding value. But how to get the current sliding finger? We create a scroller variable. Then by calling the scroller.startScroller method. to make it happen. Let's recall the code:

  private val mScroller = Scroller(context)

     fun smoothScrollTo(destX:Int, destY:Int){
        val scrollX = scrollX
        val delta  = destX - scrollX
        mScroller.startScroll(scrollX,0,delta,2,2000)
        invalidate()
    }

    override fun computeScroll() {
        super.computeScroll()
        if (mScroller.computeScrollOffset()) {
            (parent as View).scrollTo(mScroller.currX,mScroller.currY)
            invalidate()
        }
    }

This is the method in our custom View, where smoothScrollerTo is the method provided to the outside world.

Let's first look at the Scroller (context) construction method:

 public Scroller(Context context) {
        this(context, null);
    }

    /**
     * Create a Scroller with the specified interpolator. If the interpolator is
     * null, the default (viscous) interpolator will be used. "Flywheel" behavior will
     * be in effect for apps targeting Honeycomb or newer.
     */
    public Scroller(Context context, Interpolator interpolator) {
        this(context, interpolator,
                context.getApplicationInfo().targetSdkVersion >= Build.VERSION_CODES.HONEYCOMB);
    }

    /**
     * Create a Scroller with the specified interpolator. If the interpolator is
     * null, the default (viscous) interpolator will be used. Specify whether or
     * not to support progressive "flywheel" behavior in flinging.
     */
    public Scroller(Context context, Interpolator interpolator, boolean flywheel) {
        mFinished = true;
        if (interpolator == null) {
            mInterpolator = new ViscousFluidInterpolator();
        } else {
            mInterpolator = interpolator;
        }
        mPpi = context.getResources().getDisplayMetrics().density * 160.0f;
        mDeceleration = computeDeceleration(ViewConfiguration.getScrollFriction());
        mFlywheel = flywheel;

        mPhysicalCoeff = computeDeceleration(0.84f); // look and feel tuning
    }

In the end, we will call the third construction method, and the latter two parameters, one of which we pass by default is null, and the other is false.

Next we look at the scroller.startScroll method. code show as below:


    /**
     * Start scrolling by providing a starting point, the distance to travel,
     * and the duration of the scroll.
     * 
     * @param startX Starting horizontal scroll offset in pixels. Positive
     *        numbers will scroll the content to the left.
     * @param startY Starting vertical scroll offset in pixels. Positive numbers
     *        will scroll the content up.
     * @param dx Horizontal distance to travel. Positive numbers will scroll the
     *        content to the left.
     * @param dy Vertical distance to travel. Positive numbers will scroll the
     *        content up.
     * @param duration Duration of the scroll in milliseconds.
     */
    public void startScroll(int startX, int startY, int dx, int dy, int duration) {
        mMode = SCROLL_MODE;
        mFinished = false;
        mDuration = duration;
        mStartTime = AnimationUtils.currentAnimationTimeMillis();
        mStartX = startX;
        mStartY = startY;
        mFinalX = startX + dx;
        mFinalY = startY + dy;
        mDeltaX = dx;
        mDeltaY = dy;
        mDurationReciprocal = 1.0f / (float) mDuration;
    }

As you can see, in this method, there is no method to start sliding, but the various parameters passed in are saved. So the startScroller method is only used for preliminary preparations. It does not make the View slide. Looking back at the smoothScroll method we provided to the outside, the key is that after calling startScroll, we call the invalidate method, which will cause the view to be redrawn. The redrawing of the View will call the draw method of the View, and the draw method will call the computeScroll method of the View. How to get the scrollx and scrollY of the current position in the scroller? Because when we call the scrollTo method, we will call the scroller's computeScrollOffset method.

  public boolean computeScrollOffset() {
        if (mFinished) {
            return false;
        }

        int timePassed = (int)(AnimationUtils.currentAnimationTimeMillis() - mStartTime);
    
        if (timePassed < mDuration) {
            switch (mMode) {
            case SCROLL_MODE:
                final float x = mInterpolator.getInterpolation(timePassed * mDurationReciprocal);
                mCurrX = mStartX + Math.round(x * mDeltaX);
                mCurrY = mStartY + Math.round(x * mDeltaY);
                break;
            case FLING_MODE:
                final float t = (float) timePassed / mDuration;
                final int index = (int) (NB_SAMPLES * t);
                float distanceCoef = 1.f;
                float velocityCoef = 0.f;
                if (index < NB_SAMPLES) {
                    final float t_inf = (float) index / NB_SAMPLES;
                    final float t_sup = (float) (index + 1) / NB_SAMPLES;
                    final float d_inf = SPLINE_POSITION[index];
                    final float d_sup = SPLINE_POSITION[index + 1];
                    velocityCoef = (d_sup - d_inf) / (t_sup - t_inf);
                    distanceCoef = d_inf + (t - t_inf) * velocityCoef;
                }

                mCurrVelocity = velocityCoef * mDistance / mDuration * 1000.0f;
                
                mCurrX = mStartX + Math.round(distanceCoef * (mFinalX - mStartX));
                // Pin to mMinX <= mCurrX <= mMaxX
                mCurrX = Math.min(mCurrX, mMaxX);
                mCurrX = Math.max(mCurrX, mMinX);
                
                mCurrY = mStartY + Math.round(distanceCoef * (mFinalY - mStartY));
                // Pin to mMinY <= mCurrY <= mMaxY
                mCurrY = Math.min(mCurrY, mMaxY);
                mCurrY = Math.max(mCurrY, mMinY);

                if (mCurrX == mFinalX && mCurrY == mFinalY) {
                    mFinished = true;
                }

                break;
            }
        }
        else {
            mCurrX = mFinalX;
            mCurrY = mFinalY;
            mFinished = true;
        }
        return true;
    }

Let's analyze this method, first calculate the animation duration timePassed. If the animation duration is less than the sliding duration mDuration we set, then execute the switch statement. Because the mMode value in the startScroll method is SCROLL_MODE, the branch statement SCROLL_MODE is executed, and then the distance moved within the time period is calculated according to the interpolator. Assign values ​​to mCurX and mCurrY, so that we can get the current srollX and scrollY through Scroller. In addition, if the return value of computeScrollOffset is true, it means that the sliding is not over. If false, it means the slide is over. Therefore, if the sliding is not over, we have to continue to call the scrollTo method and the invalidate method to slide the View.

So, let's summarize the principle of Scoller:

Scroller cannot directly realize the sliding of View, it needs to cooperate with View's computeScroll method. In the computeScroll method, the view is continuously redrawn. Each redraw will calculate the sliding duration, and the sliding position of the View can be calculated according to this duration. We call the scrollTo method to swipe according to the position of each swipe, so that the elastic swipe is achieved by continuously repeating the last process.

Guess you like

Origin blog.csdn.net/howlaa/article/details/128475038