Foreword: Scroller actually mentioned in the above blog, roughly described the process of use, no in-depth detailed research, this blog will take you from the source code point of view combined with the case to understand it in depth.
Scroller is a class used in Android development to assist in dealing with elastic sliding. What is elastic sliding? Let your View slide smoothly rather than rigidly. Increase the user experience effect, this is Scroller. Scroller is not meaningful in itself and can not be used alone. It needs to cooperate with the relevant methods of View to play its role. In Android, Scroller is used in the bottom layer, such as ListView, GridView, ViewPager, etc. It can be seen that Scroller still plays a very important role. Understanding the underlying principle of Scroller is necessary for a senior Android development engineer to master. To understand the underlying principles, we can also write a simple version of the ViewPager, saying that sliding everyone should understand that all Views in Android can slide, because all Views have scrollTo and scrollBy two methods for sliding, the use and difference of these two methods here is not much to say, can refer to the previous blog, scrollTo is relative to the object. Absolute sliding of the label position, scrollBy is relative sliding relative to the current position, the scrollBy bottom layer actually calls the scrollTo method.
Let's start with a code snippet that uses elastic sliding to slide a View to a specified location.
private Scroller mScroller = new Scroller(mContext);
//Slowly scroll a View to the specified location
private void smoothScrollTo(int x , int y){
int scrollX = getScrollX();
int deltaX = x - scrollX;
//Slide View over deltaX pixels in a second
mScroller.startScroll(scrollX, 0, deltaX, 0,1000);
invalidate();
}
@Override
public void computeScroll() {
if(mScroller.computeScrollOffset()){
scrollTo(mScroller.getCurrX(), mScroller.getCurrY());
postInvalidate();
}
}
Is it found that it is very simple, in fact, the use of Scroller process is very fixed, the specific analysis will be carried out below, the above is a simple operation, then we start to analyze the work process.
First, when we instantiate a Scroller, it does nothing inside, but completes the initialization of some variables. The source code is as follows:
/**
* Create a Scroller with the default duration and interpolator.
*/
public Scroller(Context context) {
this(context, null);
}
Then when we call the startScroll method, the code is as follows:
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;
}
Looking at the code, you can see that you haven't done anything, just record the variables. The implications of these variables need to be introduced to you.
startX and startY represent the coordinates of X axis and Y axis at the beginning of sliding, that is, the starting point of sliding. dX and dY represent the distance between the X axis and the Y axis to slide (note that if you slide to the right, to the left, to the left, to the up, to the down, to the negative).
Duration represents the duration of sliding in milliseconds. Here you should understand that simply calling startScroll does not allow View to slide, so how does View slide? The answer is the invalidate method below. Yes, that's it. How does it slide View? We know that calling invalidate method will make View redraw. You already know that when View redraw, it will call View draw method. Draw method will eventually go to dispatchDraw method. The source code is as follows:
protected void dispatchDraw(Canvas canvas) {
.
.
.
.
if ((flags & FLAG_USE_CHILD_DRAWING_ORDER) == 0) {
for (int i = 0; i < count; i++) {
final View child = children[i];
if ((child.mViewFlags & VISIBILITY_MASK) == VISIBLE || child.getAnimation() != null) {
more |= drawChild(canvas, child, drawingTime);
}
}
} else {
for (int i = 0; i < count; i++) {
final View child = children[getChildDrawingOrder(count, i)];
if ((child.mViewFlags & VISIBILITY_MASK) == VISIBLE || child.getAnimation() != null) {
more |= drawChild(canvas, child, drawingTime);
}
}
}
}
The drawChild method is as follows:
protected boolean drawChild(Canvas canvas, View child, long drawingTime) {
.
.
.
.
if (!concatMatrix && canvas.quickReject(cl, ct, cr, cb, Canvas.EdgeType.BW) &&
(child.mPrivateFlags & DRAW_ANIMATION) == 0) {
return more;
}
child.computeScroll();
final int sx = child.mScrollX;
final int sy = child.mScrollY;
}
You can find that as long as we redraw the invalidate of View, the method will eventually go to the computeScroll method of View. Remember the computeScroll method that we overwrote when we processed the View sliding in the code above. Here you should understand that it is because the computeScroll method View can achieve flexible sliding. The main process is like this. I will describe it completely again. When we call invalidate of View to redraw the view, we call the draw method of View. At the bottom of the draw method, we call dispatchDraw method. At the bottom of the dispatchDraw method, we finally call computeScroll method. In this method, we get the current scrollX and scrollY from the Scroller object, then slide through scrollTo method, and then call post-Scroll method. The Invalidate method is redrawn twice, and then continues to obtain the current scrollX and scrollY from the Scroller object, and slides to the specified position by the scrollTo method, so repeatedly until the entire smoothing process ends.
Finally, let's look at the source code of the 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;
}
There's not much code. Let's start with the second sentence.
if (mFinished) {
return false;
}
If mFinished is true, the direct return false ends and is not executed below. If mFinished is false, skip executing the following code and find that mFinished is false by default. Remember the startScroll method we called at the beginning of the slide.
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;
}
Obviously, you should understand that mFinished defaults to false, and the code continues to execute. In line 6, we can see that a time difference is calculated in milliseconds. Animation Utils. current Animation Time Millis () takes the current time (note that the time here refers to the time from boot to the present), and mStartTime just calls tScroll for the program. Then look at the eighth line of code. Here you should understand that when you continue to redraw the View view, the time Passed here will gradually increase with the passage of time until the duration of our animation. Then down, mMode is actually SCROLL_MODE, and then the following code roughly means to calculate according to the passage of time. Let's take a percentage, and then the following two sentences of code:
mCurrX = mStartX + Math.round(x * mDeltaX);
mCurrY = mStartY + Math.round(x * mDeltaY);
Keep assigning values to mCurrX and mCurrY. Are you enlightened here? MCurrX and mCurrY are just the getmCurrX() and getmCurrY () obtained by Scroller. mStartX and mStartY are the starting positions of our first time. Each time we add a small distance of movement, we finally slide to this position through scrollTo. The small slip of n finally forms a smooth effect, that is, elastic slip.
It can be found that as long as the animation does not end, it will always return to true and will be executed at the end of the animation:
mCurrX = mFinalX;
mCurrY = mFinalY;
mFinished = true;
That is to say, mFinished = true; finally, false is returned directly on it, representing the end of the animation. Scroller.isFinished() can also be used to return true to determine the end of the animation slide.
So far, all you know about Scroller is over. If you have any questions, please leave a message below.