Post please indicate this article from xiaanming blog (http://blog.csdn.net/xiaanming/article/details/17483273), please respect others' hard work results, thank you!
Today, I want to explain the Scroller class's rolling implementation principle. Many friends may not know what this class is for. But those who study Launcher should be familiar with it, scroller class is a rolling encapsulation class that can realize the smooth rolling effect of the View. What is the smooth rolling effect of the View? For example, A View is scrolled from one position to another within the specified time. We can use the scroroller class to achieve constant speed scrolling. It can be accelerated first and then slowed down, you can first slow down, speed up, and so on, instead of moving instantly, so Scroller can help us achieve a lot of sliding effects.
Before introducing the Scroller class, let's first understand the differences between View's scrollBy () and scrollTo () methods. Before distinguishing the two methods, first, we need to understand the two member variables mScrollX, mScrollY in the View, the offset in the X axis direction and the offset in the Y axis direction. This is a relative distance, not the origin of the screen, it is the left edge of the View. For example, if a train passes through Ganzhou from Ji 'an to Shenzhen, the origin is Ganzhou, And the offset is the distance from Ji 'an to Ganzhou, you can see the answer from the comments in the getScrollX () method.
/** * Return the scrolled left position of this view. This is the left edge of * the displayed part of your view. You do not need to draw any pixels * farther left, since those are outside of the frame of your view on * screen. * * @return The left edge of the displayed part of your view, in pixels. */ public final int getScrollX() { return mScrollX; }Now we know that sliding mScrollX to the right is a negative number and sliding mScrollX to the left is a positive number. Next let's take a look at the source code of the scrollTo () method.
/** * Set the scrolled position of your view. This will cause a call to * {@link #onScrollChanged(int, int, int, int)} and the view will be * invalidated. * @param x the x position to scroll to * @param y the y position to scroll to */ public void scrollTo(int x, int y) { if (mScrollX != x || mScrollY != y) { int oldX = mScrollX; int oldY = mScrollY; mScrollX = x; mScrollY = y; onScrollChanged(mScrollX, mScrollY, oldX, oldY); if (!awakenScrollBars()) { invalidate(); } } }From this method, we can see that we first determine whether the passed (x, y) value is equal to the X and y offsets of the View. If not, we call onScrollChanged () method to notify the interface to change, and then re-paint the interface, so this achieves the moving effect. Now we know that the scrollTo () method is to scroll to (x, y) the offset point, which is scrolled relative to the start position of the View. Look at the code for scrollBy ().
/** * Move the scrolled position of your view. This will cause a call to * {@link #onScrollChanged(int, int, int, int)} and the view will be * invalidated. * @param x the amount of pixels to scroll by horizontally * @param y the amount of pixels to scroll by vertically */ public void scrollBy(int x, int y) { scrollTo(mScrollX + x, mScrollY + y); }
It turns out that the scrollTo () method is called in it, so it is easy to do. It is based on (x, y) to scroll relative to the position on the View, the two methods may be vague in your mind. Let's take a common example to help you understand it. If a View calls scrollTo (-10, 0) twice ), the first time we scroll to the right 10, the second time we do not scroll, because the mScrollX and x are equal, when we call two scrollBy (-10, 0), the first time we scroll to the right 10, for the second time, scroll 10 to the right, which is relative to the previous position of the View.
It is important to note the scrollTo () and scrollBy () methods. If you call the scrollTo () method for a LinearLayout method, it is not LinearLayout scrolling, instead, you can scroll through the content in LinearLayout. For example, if you want to scroll a Button, directly calling scrollTo () using a Button will not meet your requirements. You can try it, to perform scrollTo () Scrolling on a Button, we can wrap a layer of Layout outside the Button and call the scrollTo () method on Layout.
After learning about the scrollTo () and scrollBy () methods, we will understand the Scroller class. Let's first look at its constructor method.
/** * Create a Scroller with the default duration and interpolator. */ 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. */ public Scroller(Context context, Interpolator interpolator) { mFinished = true; mInterpolator = interpolator; float ppi = context.getResources().getDisplayMetrics().density * 160.0f; mDeceleration = SensorManager.GRAVITY_EARTH // g (m/s^2) * 39.37f // inch/meter * ppi // pixels per inch * ViewConfiguration.getScrollFriction(); }There are only two constructor methods. The first constructor has only one Context parameter, and the second constructor specifies Interpolator. What about Interpolator? Plug-ins, familiar with Android animation.
Interpolator, which specifies the animation change rate, such as constant speed change, acceleration first, deceleration, and sine change. Different Interpolator can make different effects, first, use the default Interpolator (viscous)
Next, we need to find the rolling method in the Scroller class. We can see from the name above that startScroll () should be a rolling method. Let's take a look at its source code.
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; // This controls the viscous fluid effect (how much of it) mViscousFluidScale = 8.0f; // must be set to 1.0 (used in viscousFluid()) mViscousFluidNormalize = 1.0f; mViscousFluidNormalize = 1.0f / viscousFluid(1.0f); }In this method, we only see some basic settings for scrolling, such as setting the scroll mode, start time, and duration. There is no scrolling operation on The View, maybe you are wondering why it's not the rolling method or startScroll (). You should be a little cautious. Since it's called rolling, it's the basic setting before rolling.
/** * Call this when you want to know the new location. If it returns true, * the animation is not yet finished. loc will be altered to provide the * new location. */ public boolean computeScrollOffset() { if (mFinished) { return false; } int timePassed = (int)(AnimationUtils.currentAnimationTimeMillis() - mStartTime); if (timePassed < mDuration) { switch (mMode) { case SCROLL_MODE: float x = (float)timePassed * mDurationReciprocal; if (mInterpolator == null) x = viscousFluid(x); else x = mInterpolator.getInterpolation(x); mCurrX = mStartX + Math.round(x * mDeltaX); mCurrY = mStartY + Math.round(x * mDeltaY); break; case FLING_MODE: float timePassedSeconds = timePassed / 1000.0f; float distance = (mVelocity * timePassedSeconds) - (mDeceleration * timePassedSeconds * timePassedSeconds / 2.0f); mCurrX = mStartX + Math.round(distance * mCoeffX); // Pin to mMinX <= mCurrX <= mMaxX mCurrX = Math.min(mCurrX, mMaxX); mCurrX = Math.max(mCurrX, mMinX); mCurrY = mStartY + Math.round(distance * mCoeffY); // Pin to mMinY <= mCurrY <= mMaxY mCurrY = Math.min(mCurrY, mMaxY); mCurrY = Math.max(mCurrY, mMinY); break; } } else { mCurrX = mFinalX; mCurrY = mFinalY; mFinished = true; } return true; }When startScroll () method is used, the current animation millisecond value is assigned to mStartTime, and AnimationUtils. currentAnimationTimeMillis () is called again in computescroloffset () to obtain the animation.
Millisecond minus mStartTime is the duration, and then go into if to judge. if the animation duration is smaller than the scroll duration mDuration we set, go to the switch SCROLL_MODE, then, based on Interpolator, calculate the moving distance in the time period and assign the value to mCurrX and mCurrY. Therefore, the function of this method is to calculate the offset of the scrolling from 0 to mDuration, and judge whether the rolling is over. "true" indicates that the rolling is not over. "false" indicates that the rolling is introduced. I will not mention other methods of the Scroller class. Most of them are get (), set () method.
After reading so much, you must have a lot of questions about how to trigger rolling. before rolling, I need to introduce another method computeScroll (), this method is a sliding control method. This method is called during the draw () process when a View is drawn. Let's take a look at the source code of computeScroll ().
/** * Called by a parent to request that a child update its values for mScrollX * and mScrollY if necessary. This will typically be done if the child is * animating a scroll using a {@link android.widget.Scroller Scroller} * object. */ public void computeScroll() { }Yes, he is an empty method. The subclass needs to override this method to implement logic. Where is the method triggered. Let's continue to look at the drawing method of View draw ()
public void draw(Canvas canvas) { final int privateFlags = mPrivateFlags; final boolean dirtyOpaque = (privateFlags & PFLAG_DIRTY_MASK) == PFLAG_DIRTY_OPAQUE && (mAttachInfo == null || !mAttachInfo.mIgnoreDirtyState); mPrivateFlags = (privateFlags & ~PFLAG_DIRTY_MASK) | PFLAG_DRAWN; /* * Draw traversal performs several drawing steps which must be executed * in the appropriate order: * * 1. Draw the background * 2. If necessary, save the canvas' layers to prepare for fading * 3. Draw view's content * 4. Draw children * 5. If necessary, draw the fading edges and restore layers * 6. Draw decorations (scrollbars for instance) */ // Step 1, draw the background, if needed int saveCount; if (!dirtyOpaque) { final Drawable background = mBackground; if (background != null) { final int scrollX = mScrollX; final int scrollY = mScrollY; if (mBackgroundSizeChanged) { background.setBounds(0, 0, mRight - mLeft, mBottom - mTop); mBackgroundSizeChanged = false; } if ((scrollX | scrollY) == 0) { background.draw(canvas); } else { canvas.translate(scrollX, scrollY); background.draw(canvas); canvas.translate(-scrollX, -scrollY); } } } // skip step 2 & 5 if possible (common case) final int viewFlags = mViewFlags; boolean horizontalEdges = (viewFlags & FADING_EDGE_HORIZONTAL) != 0; boolean verticalEdges = (viewFlags & FADING_EDGE_VERTICAL) != 0; if (!verticalEdges && !horizontalEdges) { // Step 3, draw the content if (!dirtyOpaque) onDraw(canvas); // Step 4, draw the children dispatchDraw(canvas); // Step 6, draw decorations (scrollbars) onDrawScrollBars(canvas); // we're done... return; } ...... ...... ......
We only intercepted some of the code of draw (). In the above Code, we wrote several steps to draw a View. Let's take a look at the steps that will trigger dispatchDraw () when drawing a child in step 4 () this method is used to see what the source code is.
/** * Called by draw to draw the child views. This may be overridden * by derived classes to gain control just before its children are drawn * (but after its own view has been drawn). * @param canvas the canvas on which to draw the view */ protected void dispatchDraw(Canvas canvas) { }Well, it is also an empty method defined to rewrite the method for the subclass, so we find the ViewGroup subclass of the View to see the specific implementation logic of this method.
@Override protected void dispatchDraw(Canvas canvas) { final int count = mChildrenCount; final View[] children = mChildren; int flags = mGroupFlags; if ((flags & FLAG_RUN_ANIMATION) != 0 && canAnimate()) { final boolean cache = (mGroupFlags & FLAG_ANIMATION_CACHE) == FLAG_ANIMATION_CACHE; final boolean buildCache = !isHardwareAccelerated(); for (int i = 0; i < count; i++) { final View child = children[i]; if ((child.mViewFlags & VISIBILITY_MASK) == VISIBLE) { final LayoutParams params = child.getLayoutParams(); attachLayoutAnimationParameters(child, params, i, count); bindLayoutAnimation(child); if (cache) { child.setDrawingCacheEnabled(true); if (buildCache) { child.buildDrawingCache(true); } } } } final LayoutAnimationController controller = mLayoutAnimationController; if (controller.willOverlap()) { mGroupFlags |= FLAG_OPTIMIZE_INVALIDATE; } controller.start(); mGroupFlags &= ~FLAG_RUN_ANIMATION; mGroupFlags &= ~FLAG_ANIMATION_DONE; if (cache) { mGroupFlags |= FLAG_CHILDREN_DRAWN_WITH_CACHE; } if (mAnimationListener != null) { mAnimationListener.onAnimationStart(controller.getAnimation()); } } int saveCount = 0; final boolean clipToPadding = (flags & CLIP_TO_PADDING_MASK) == CLIP_TO_PADDING_MASK; if (clipToPadding) { saveCount = canvas.save(); canvas.clipRect(mScrollX + mPaddingLeft, mScrollY + mPaddingTop, mScrollX + mRight - mLeft - mPaddingRight, mScrollY + mBottom - mTop - mPaddingBottom); } // We will draw our child's animation, let's reset the flag mPrivateFlags &= ~PFLAG_DRAW_ANIMATION; mGroupFlags &= ~FLAG_INVALIDATE_REQUIRED; boolean more = false; final long drawingTime = getDrawingTime(); 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); } } } // Draw any disappearing views that have animations if (mDisappearingChildren != null) { final ArrayList
disappearingChildren = mDisappearingChildren; final int disappearingCount = disappearingChildren.size() - 1; // Go backwards -- we may delete as animations finish for (int i = disappearingCount; i >= 0; i--) { final View child = disappearingChildren.get(i); more |= drawChild(canvas, child, drawingTime); } } if (debugDraw()) { onDebugDraw(canvas); } if (clipToPadding) { canvas.restoreToCount(saveCount); } // mGroupFlags might have been updated by drawChild() flags = mGroupFlags; if ((flags & FLAG_INVALIDATE_REQUIRED) == FLAG_INVALIDATE_REQUIRED) { invalidate(true); } if ((flags & FLAG_ANIMATION_DONE) == 0 && (flags & FLAG_NOTIFY_ANIMATION_LISTENER) == 0 && mLayoutAnimationController.isDone() && !more) { // We want to erase the drawing cache and notify the listener after the // next frame is drawn because one extra invalidate() is caused by // drawChild() after the animation is over mGroupFlags |= FLAG_NOTIFY_ANIMATION_LISTENER; final Runnable end = new Runnable() { public void run() { notifyAnimationListener(); } }; post(end); } }
This method has a lot of code, but let's take a look at it. Lines 5-79 show that in dispatchDraw (), drawChild () is called to draw the child View in ViewGroup, next let's take a look at the code of the drawChild () method.
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; boolean scalingRequired = false; Bitmap cache = null;............}Only part of the code is intercepted and child is displayed. computeScroll (): You probably understand what it is. After a long time, you finally find that the computeScroll () method is triggered, that is, when ViewGroup distributes and draws its own children, the computeScroll () method is called for its child View.
Sort out the ideas and take a look at the implementation principle of View scrolling. We first call the startScroll () method of Scroller to perform some rolling initialization settings, and then force the View to be drawn, you can call invalidate () or postInvalidate () of the View to re-draw the View. The computeScroll () method is triggered when the View is drawn. We rewrite computeScroll () in computeScroll () in this example, we first call Scroller's computescroloffset () method to determine whether the rolling has ended. If the rolling has not ended, we call the scrollTo () method to scroll. The scrollTo () although the method will re-draw the View, we still need to manually call invalidate () or postInvalidate () to trigger the UI re-painting, re-draw the View and trigger computeScroll (), so we enter a cycle stage, which achieves a smooth rolling effect of rolling a certain distance in a certain period of time.
Some may ask why we need to call the scrollTo () method and finally call the scrollTo () method to implement scrolling. In fact, it is okay to call the scrollTo () method directly, however, scrollTo () is an instant scroll that does not provide a good user experience. Therefore, Android provides the scroroller class for smooth scrolling. For your understanding, I drew a simple call
Now, we have finished the Scroller class rolling implementation principle. I don't know if you understand it. Scroller can achieve a lot of rolling effects. Considering the length of this article, therefore, this article will not lead you to use the scroler class. I will introduce the scroler class in the next article. I hope you will pay attention to it later. If you have any questions, please leave a message below, I will answer your questions!
I am honored to beCSDN 2013 blog Star SelectionHope to continue to get everyone's support and encouragement. If you see a friend, please vote for me!
Voting address: Http://vote.blog.csdn.net/blogstaritem/blogstar2013/xiaanming