preface

Recent several work is quite busy, quite long time did not update nuggets, today spare some time to update a (good habits can not lose 😂). Before in the introduction of View sliding related posture, the end of the article briefly talked about Scroller, there is no details of the source code, today we go through the details of the source code of Scroller.

Routine routines and codes

We often see Scroller used on the Internet like this:

class CustomView constructor(context: Context, attributeSet: AttributeSet?) :
    View(context, attributeSet) {
  		...
      / / create a Scroller
      private val scroller = Scroller(context)

      ...
      // Define a method for silky scrolling
      fun smoothScroll(startX: Int, startY: Int, deltaX: Int, deltaY: Int, duration: Int = 2000) {
          / / 1
          scroller.startScroll(startX, startY, deltaX, deltaY, duration)
          // Redrawing causes a computeScroll() method callback
          postInvalidateOnAnimation()
      }

      ...
      / / the view in the method
      override fun computeScroll(a) {
              / / 2
              if (scroller.computeScrollOffset()) {
                  // Get the current x of scroller
                  val currX = scroller.currX
                  // Update left Right
                  left = currX
                  right = left + measuredWidth

                  // Get the current y of scroller
                  val currY = scroller.currY
                  // Update top bottom
                  top = currY
                  bottom = top + measuredHeight
                  // Redrawing causes a computeScroll() method callback
                  postInvalidateOnAnimation()
              }
      }
}


Copy the code

From the above custom View code we define a relatively common method smoothScroll, the parameters of this method are as follows:

  • StartX The initial value of x in the horizontal direction
  • StartY the initial value of y in the vertical direction
  • DeltaX horizontal x sliding distance
  • DeltaY vertical y slip distance
  • Duration Duration of the entire slide execution

What would happen if we called the fragment inside the fragment like the following:

.// Define related parameters
private val startX = 20.dp
private val startY = 20.dp
private val deltaX = 200.dp
private val deltaY = 200.dp
...
// Set the click event for ButTom
binding.btnStartSmoothScroll.setOnClickListener {
      // Call our custom View's smoothScroll method
      binding.svTest.smoothScroll(startX, startY, deltaX, deltaY)
}
...

Copy the code

The phenomenon is shown as follows:

At first glance, it feels like an animation, and visually it does. So let’s start with the smoothScroll method of a custom view.

The source code to explain

The smoothScroll method of the custom View actually calls the following two lines of code:

fun smoothScroll(startX: Int, startY: Int, deltaX: Int, deltaY: Int, duration: Int = 2000) {
        //1 Call the startScroll method of scroller
        scroller.startScroll(startX, startY, deltaX, deltaY, duration)
  	//2 redraw
        postInvalidateOnAnimation()
}
Copy the code

What does the startScroll method of scroller at 1 do:

/** * Method description: * Start a slide by providing a start slide point, horizontal and vertical slide distance, and slide time * start slide point determined by startX startY * slide distance determined by dx dy * slide time determined by duration ** /
public void startScroll(int startX, int startY, int dx, int dy, int duration) {
    // Current mode Scroll mode
    mMode = SCROLL_MODE;
    // Whether end is set to false
    mFinished = false;
    // Copy the slide time
    mDuration = duration;
    // Record the sliding start time
    mStartTime = AnimationUtils.currentAnimationTimeMillis();
    mStartX = startX;
    mStartY = startY;
    // Final horizontal position
    mFinalX = startX + dx;
    // Final vertical position
    mFinalY = startY + dy;
    mDeltaX = dx;
    mDeltaY = dy;
    mDurationReciprocal = 1.0 f / (float) mDuration;
}
Copy the code

We can see from the above method: Scroller’s startScroll method only assigns values to each member variable and does not start sliding. So the question is, how do we get the view to slide? Then we look at the mark 2 place postInvalidateOnAnimation (), this method will lead to the view re-paint, also indirectly led to the view of computeScroll method call:

override fun computeScroll(a) {
    if(scroller.computeScrollOffset()) { ... }}Copy the code

ComputeScrollOffset () : computeScrollOffset() : computeScrollOffset() : computeScrollOffset() : computeScrollOffset();

/** * Call this when you want to know the new location. If it returns true, * The animation is not yet finished. * This method returns true when you want to know the current position of the animation 
public boolean computeScrollOffset(a) {
    // Return false if end
    if (mFinished) {
        return false;
    }
    
    // The time elapsed since startScroll was called
    int timePassed = (int)(AnimationUtils.currentAnimationTimeMillis() - mStartTime);
    
    // If the past time is less than the total animation time, the slide animation is not finished
    if (timePassed < mDuration) {
        switch (mMode) {
        // Scroll mode
        case SCROLL_MODE:
            Interpolator Interpolates the current animation progression based on the current past time
            final float x = mInterpolator.getInterpolation(timePassed * mDurationReciprocal);
            // Get the current horizontal coordinate based on the initial value and animation progress
            mCurrX = mStartX + Math.round(x * mDeltaX);
            mCurrY = mStartY + Math.round(x * mDeltaY);
            break; . }}else {
         // If the past time is greater than or equal to the total animation time, slide animation ends
         // Directly set mCurrX and mCurrY to the final coordinates and mFinished to true
        mCurrX = mFinalX;
        mCurrY = mFinalY;
        mFinished = true;
    }
    return true;
}
Copy the code

Call this method to determine whether the animation is over. If not, calculate the current values of mCurrX and mCurrY, which are the current sliding coordinates, and return true to indicate that the animation is not over. So we can know that calling this computeScrollOffset method is used to update the coordinate points that should be sliding to and determine whether the sliding process is over.

At this point, let’s look at this code at the beginning of the article:

override fun computeScroll(a) {
    // If the animation does not end, go to the code block execution logic
    if (scroller.computeScrollOffset()) {
        // Get the current level coordinate of scroller
        val currX = scroller.currX
        // Update left Right
        left = currX
        right = left + measuredWidth
        
        // Get the current vertical coordinate of scroller
        val currY = scroller.currY
        // Update top bottom
        top = currY
        bottom = top + measuredHeight
        / / redraw
        postInvalidateOnAnimation()
    }
}
Copy the code

You can see that the logic of the above code is to keep polling for the coordinates calculated by scroller, and then update the rectangular area of the view until Scroller tells us that the animation is over.

conclusion

Starting with a small demo above and some source code explanations, we can get a general idea of Scroller:

  • StartScroll is used to assign the initial value (start point, distance, time)
  • ComputeScrollOffset computeScrollOffset computeScrollOffset computeScrollOffset computeScrollOffset computeScrollOffset computeScrollOffset
  • Scroller is just a calculation tool, which provides us with many methods related to sliding. We just need to get the values calculated by Scroller to dynamically update the position of the view

When doing sliding related things, we should flexibly use Scroller/OverScroller, combined with the [View series] several ways to make the View slide, we can make a cool sliding View.

See Github for the above source code

Finally, I would like to recommend an open source library I maintain called the Excellent Android Highlight Boot Library. The Github address is github.com/hyy920109/H…

expand

Some of Scroller’s other popular methods are as follows:

/** * forces the animation to stop at the coordinate point calculated for the current frame */
public final void forceFinished(boolean finished) {
    mFinished = finished;
}

/** * interrupts the animation and slides directly to the final position */
public void abortAnimation(a) {
    mCurrX = mFinalX;
    mCurrY = mFinalY;
    mFinished = true;
}
Copy the code

View series recommended reading:

  1. 【View series 】View event distribution source code exploration
  2. [View series] View measure process source code full analysis
  3. 【View Series 】 UNSPECIFIED, MeasureSpec is still available.
  4. 【View series 】 Handle your ViewPager2 slide conflicts
  5. 【View series 】 Several ways to make a View slide