[Android] in-depth analysis of scroler and Android scroler from the perspective of source code

Source: Internet
Author: User

[Android] in-depth analysis of scroler and Android scroler from the perspective of source code

The source must be indicated for reprinting. Thank you! Http://blog.csdn.net/chdjj


In this article, I will analyze the Scroller class from the source code perspective. When reading this article, we suggest you check the source code. Otherwise, you may be confused.

I. Use of Scroller
Those familiar with android must be familiar with scroroller, which is Elastic sliding object, You can make a lot of cool sliding effects, Lancher's sliding screen effect is used to Scroller. We know that the scrollTo and scrollBy methods in the View class provide slide operations, but this slide operation is completed instantly, that is, you provide the end point coordinates for scrollTo. This method only needs to be called once, we will find that we have already rolled to the destination. This method obviously has a poor user experience. Therefore, android engineers encapsulate the scroroller class for us, which can bring the View Slow Movement. Generally, you can call startScroll of scroroller in your custom View and refresh the View. In addition, you need to override the computeScroll method (this method will be called during View refresh ), in this method, computescroloffset of Scroller is called to calculate the position to which it should be rolled. Then, scrollTo is used to scroll to this position, and invalidate is called to refresh, you can achieve the rolling effect. (If you are not familiar with scroroller usage, you are advised to familiarize yourself with Scroller usage first ). Scroller usage example (code snippet ):
public class MyView extends LinearLayout{private Scroller mScroller;    ... ...private void smoothScrollTo(int destX,int destY){int scrollX = getScrollX();int scrollY = getScrollY();int deltaX = destX-scrollX;int deltaY = destY-scrollY;mScroller.startScroll(scrollX,scrollY,deltaX, deltaY, 1000);invalidate();}@Overridepublic void computeScroll(){if(mScroller != null){if(mScroller.computeScrollOffset()){scrollTo(mScroller.getCurrX(),mScroller.getCurrY());Log.d(TAG,"scrollX="+getScrollX()+",scrollY="+getScrollY());postInvalidate();}}}}

Ii. Analysis of ScrollTo, ScrollBy, getScrollX, and getScrollY Methods
Before analyzing the Scroller source code, we must first understand the functions of ScrollTo, ScrollBy, getScrollX, and getScrollY methods. The four methods are provided by the View class. First, we analyze the getScrollX and getScrollY methods and view the source code implementation:
/**     * 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;    }    /**     * Return the scrolled top position of this view. This is the top edge of     * the displayed part of your view. You do not need to draw any pixels above     * it, since those are outside of the frame of your view on screen.     *     * @return The top edge of the displayed part of your view, in pixels.     */    public final int getScrollY() {        return mScrollY;    }
The getScrollX and getScrollY Methods return the variables mScrollX and mScrollY. What are the two variables?
Here I will tell you directly, MScrollX and mScrollY refer to the offset between the View content and the original starting coordinate of the view., The default values of mScrollX and mScrollY are 0, because there is no offset by default. In addition, pay attention to the positive and negative offset. Because it is relative to the starting coordinate of the view, if you are If the offset is to the right, the value of mScrollX should be negative, and the offset to the left is positive.. For example, if you define an ImageView, the coordinates in the upper-left corner of the Image view are (, 80). At this time, the values of mScrollX and mScrollY are both 0 (no offset ), now you want to move the ImageView to (120,100), that is, the bottom right of the image, so your mScrollX should be 100-120 =-20, and mScrollY should be 80-100 =-20, now you should understand. Now that we understand this problem, scrollBy and scrollTo are all connected. Let's look at the source code:
 public void scrollTo(int x, int y) {        if (mScrollX != x || mScrollY != y) {            int oldX = mScrollX;            int oldY = mScrollY;            mScrollX = x;            mScrollY = y;            invalidateParentCaches();            onScrollChanged(mScrollX, mScrollY, oldX, oldY);            if (!awakenScrollBars()) {                postInvalidateOnAnimation();            }        }    }    public void scrollBy(int x, int y) {        scrollTo(mScrollX + x, mScrollY + y);    }
First, let's look at the scrollTo method. It first checks whether the offset (that is, mScrollX and mScrollY) in the direction of x and y is the same as the offset (that is, x, y) in the input. If it is the same, it returns directly, otherwise, we will update the mScrollX and mScrollY, and notify the interface to change the request for re-painting. In this way, you can achieve the rolling effect, but it is moving instantly. Looking at this scrollBy, scrollTo is directly called. According to the parameters, it is easy to find that the function of this method is to continue the offset (x, y) unit based on the current offset. Note that the two methods The parameter is an offset rather than an actual location.Oh!
You need to pay attention to the following points: When you call the scrollTo method of a View to scroll, it is not the View itself, but the View content.For example, if you want to scroll a Button, you should open a ViewGroup outside the Button, and then call the scrollTo method of ViewGroup. This is also very important. You need to pay attention to it!

Iii. Scroller source code analysis
The essence of Scroller is the computescroloffset and startScroll methods, So we focus on these two methods. Before analysis, let's take a look at the variables defined in this class:
Private int mMode; // mode, including SCROLL_MODE and FLING_MODE private int mStartX; // The start x offset from private int mStartY; // The start y offset from private int mFinalX; // offset the end point x to private int mFinalY; // offset the end point y to private int mCurrX; // offset the current x to private int mCurrY; // offset the current y to private long mStartTime; // start time private int mDuration; // rolling duration private float mDurationReciprocal; // The reciprocal private float mDeltaX of the duration; // The scroll distance in the x direction, mDeltaX = mFinalX-mStartX private float mDeltaY; // The scroll distance in the y direction. mDeltaY = mFinalY-mStartY private boolean mFinished; // whether or not to end

After having a general impression on these variables, we will start to look at the startScroll source code:
public void startScroll(int startX, int startY, int dx, int dy) {        startScroll(startX, startY, dx, dy, DEFAULT_DURATION);    }    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;    }
In this so-called startScroll method, all values are assigned to the preceding variables. For example, you can set the current mode to SCROLL_MODE, set the duration, start point and end point offset, and start time. This method does not seem to trigger start scroll. Well, it does. So how does one trigger scroll? We leave this issue to the next part for analysis. To help you understand the functions of these variables, I drew a picture. In this example, we assume that we scroll from point A to point B, If you know mStartX, mStartY (obtained by startScroll's startX and startY parameters), mDeltaX, mDeltaY (obtained by startScroll's dx and dy parameters ). Then, mFinalX and mFinalY are easy to get. In addition, with the duration, we can calculate the current position based on the ratio of (current time-mStartTime) to duration, that is, mCurrX and mCurrY.I won't tell you, this is the role of computescroloffset! Through the above analysis, we found that, The startScroll method is actually equivalent to preprocessing and provides data for computescroloffset.
The following shows the source code of computescroloffset:
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:                 ... ...                break;            }        }        else {            mCurrX = mFinalX;            mCurrY = mFinalY;            mFinished = true;        }        return true;    }
Check whether it is easy to understand. First, determine whether to end (mFinished = true ?), If it does not end, the program continues to run down and calculates timePassed, that is, the current time minus the start time. If it is smaller than mDuration, the current position can be calculated based on the ratio. Of course, here Interpolator is used to control the animation effect, acceleration or deceleration, and so on. If mDuration is exceeded, the scrolling should be stopped, so mFinished is set to true. The next time computescroloffset is called, false will be returned.
At this point, we have analyzed all the important parts of Scroller, but we found that neither of these methods involves specific scrolling. So how is rolling triggered? Next we will analyze the source code of the entire process.
4. Rolling Process Analysis
In the first part, I gave an example of Scroller. In the smoothScrollTo method, we called the startScroll of Scroller and then called the invalidate method to refresh the view, this rolling effect is triggered by invalidate! We know that calling the invalidate method will cause repainting of the entire view system, so we have to start with the painting of the View. Experienced users should know that the View is drawn in three main processes: measure, layout, and draw. Let's take a look at the draw method source code of the View class:
 public void draw(Canvas canvas) {          ... ...        /*         * 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) {            drawBackground(canvas);        }          ... ...        // Step 2, save the canvas' layers          ... ...        // Step 3, draw the content        if (!dirtyOpaque) onDraw(canvas);        // Step 4, draw the children        dispatchDraw(canvas);        // Step 5, draw the fade effect and restore layers          ... ...    }
The draw method divides the painting into six steps. The third step is to call onDraw to draw the content, and the fourth step is to call dispatchDraw to draw the subview. Let's take a look at the dispatchDraw method:
 /**     * 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, that's right. It's an empty method. Because only ViewGroup has a subview, we come to ViewGroup and find dispatchDraw:
@Override    protected void dispatchDraw(Canvas canvas) {        boolean usingRenderNodeProperties = canvas.isRecordingFor(mRenderNode);        final int childrenCount = mChildrenCount;        final View[] children = mChildren;         ... ...        for (int i = 0; i < childrenCount; i++) {            int childIndex = customOrder ? getChildDrawingOrder(childrenCount, i) : i;            final View child = (preorderedList == null)                    ? children[childIndex] : preorderedList.get(childIndex);    if ((child.mViewFlags & VISIBILITY_MASK) == VISIBLE || child.getAnimation() != null) {                more |= drawChild(canvas, child, drawingTime);            }        }      }
The Code is also extremely long, and part of it is intercepted here. We can see that the drawChild method is called in this method. We can see from the name that this method is used to draw a subview and find its implementation:
protected boolean drawChild(Canvas canvas, View child, long drawingTime) {        return child.draw(canvas, this, drawingTime);    }
The draw method of view is called here. Of course, this draw method is different from the draw method above, because it has three parameters! Then, let's go back to the View code and find the draw method containing three parameters:
Boolean draw (Canvas canvas, ViewGroup parent, long drawingTime) {boolean more = false; final boolean childHasIdentityMatrix = hasIdentityMatrix (); final int flags = parent. mGroupFlags ;...... int sx = 0; int sy = 0; if (! HasDisplayList) {computeScroll (); // finally found computeScroll sx = mScrollX; sy = mScrollY ;}...... return more ;}
Here, we found the computeScroll method! Of course, the computeScroll method in the View is empty, and we need to rewrite it based on the scenario. For example, I found that TextView has the method of rewriting:
 @Override    public void computeScroll() {        if (mScroller != null) {            if (mScroller.computeScrollOffset()) {                mScrollX = mScroller.getCurrX();                mScrollY = mScroller.getCurrY();                invalidateParentCaches();                postInvalidate();  // So we draw again            }        }    }

Okay, it's been a long time, Next let's review this process: in the Custom view, we call the startScroll method to set some basic data for scrolling, And then refresh the view through invalidate from top to bottom, first, the draw method of the Root View (usually ViewGroup) is called, and onDraw is called to draw the View content. Then the dispatchDraw method is called to draw the subview, the dispatchDraw method calls the drawChild method for each sub-View, while the drawChild method calls the draw method of the sub-View (three parameters), and the draw method calls the computeScroll Method for scrolling, the computeScroll method is rewritten, depending on the scenario. Generally, the computescroloffset is used to determine whether to slide. If necessary, call postInvalidate and re-draw from top to bottom, in this way, smooth scrolling is achieved!OK. Now we have made Scroller clear!

V. instance analysis
For ease of understanding, I have written a small example here to help you understand the content mentioned above by printing logs. First, there are several custom views:
MyLinearLayout:
package com.example.scrollerdemo;import android.content.Context;import android.graphics.Canvas;import android.util.AttributeSet;import android.util.Log;import android.widget.LinearLayout;public class MyLinearLayout extends LinearLayout{private static final String TAG = "TEST";public MyLinearLayout(Context context){super(context);}public MyLinearLayout(Context context, AttributeSet attrs){super(context, attrs);}@Overridepublic void draw(Canvas canvas){Log.i(TAG, "MyLinearLayout--->draw");super.draw(canvas);}@Overrideprotected void onDraw(Canvas canvas){Log.i(TAG, "MyLinearLayout--->onDraw");super.onDraw(canvas);}@Overridepublic void computeScroll(){Log.i(TAG, "MyLinearLayout--->computeScroll");super.computeScroll();}}
MyView (implements smooth scrolling ):
Package com. example. scrollerdemo; import android. content. context; import android. graphics. canvas; import android. util. attributeSet; import android. util. log; import android. view. view; import android. widget. linearLayout; import android. widget. scroller;/*** @ author Rowandjj **/public class MyView extends LinearLayout {private static final String TAG = "TEST"; private Scroller roller; public MyView (Context Context) {super (context); init ();} public MyView (Context context, AttributeSet attrs) {super (context, attrs); init ();} private void init () {mScroller = new Scroller (getContext ();} public void testSmoothScroll () {// smooth scroll to the bottom right (100 to the right, 100 to the down offset) smoothScrollTo (-100, -100);} private void smoothScrollTo (int destX, int destY) {int scrollX = getScrollX (); int scrollY = getScrollY (); Log. d (TAG, "scrollX =" + scrollX + ", scrollY = "+ ScrollY); int deltaX = destX-scrollX; int deltaY = destY-scrollY; mScroller. startScroll (scrollX, scrollY, deltaX, deltaY, 1000); invalidate () ;}@ Overridepublic void draw (Canvas canvas) {Log. I (TAG, "MyView ------> draw run"); super. draw (canvas) ;}@ Overrideprotected void onDraw (Canvas canvas) {Log. I (TAG, "MyView ------> onDraw run"); super. onDraw (canvas) ;}@ Overrideprotected void dispatchDraw (Canvas canvas) {Log. I (TA G, "MyView ------> dispatchDraw run"); super. dispatchDraw (canvas) ;}@ Overrideprotected boolean drawChild (Canvas canvas, View child, long drawingTime) {Log. I (TAG, "MyView ------> drawChild run"); return super. drawChild (canvas, child, drawingTime) ;}@ Overridepublic void computeScroll () {Log. I (TAG, "MyView ------> computeScroll run"); if (mScroller! = Null) {if (mScroller. computescroloffset () {scrollTo (mScroller. getCurrX (), mScroller. getCurrY (); Log. d (TAG, "scrollX =" + getScrollX () + ", scrollY =" + getScrollY (); postInvalidate ();}}}}

MyImageView:
package com.example.scrollerdemo;import android.content.Context;import android.graphics.Canvas;import android.util.AttributeSet;import android.util.Log;import android.widget.ImageView;public class MyImageView extends ImageView{private static final String TAG = "TEST";public MyImageView(Context context){super(context);}public MyImageView(Context context, AttributeSet attrs){super(context, attrs);}@Overrideprotected void onDraw(Canvas canvas){Log.i(TAG,"myImageView---->onDraw run");super.onDraw(canvas);}@Overridepublic void computeScroll(){Log.i(TAG,"myImageView---->computeScroll run");super.computeScroll();}}

We made a layout based on the above three custom views. The outer layer is MyLinearLayout, the middle is MyView, And the innermost layer is MyImageView. activity_main.xml:
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"    android:layout_width="match_parent"    android:layout_height="match_parent"    android:orientation="vertical" >    <com.example.scrollerdemo.MyLinearLayout        xmlns:tools="http://schemas.android.com/tools"        android:layout_width="match_parent"        android:layout_height="match_parent"        android:background="#ffaaff"        tools:context="com.example.scrollerdemo.MainActivity" >        <com.example.scrollerdemo.MyView            android:id="@+id/mv"            android:layout_width="200dp"            android:layout_height="200dp"            android:layout_marginLeft="50dp"            android:layout_marginTop="100dp"            android:background="@android:color/darker_gray"            android:orientation="vertical" >            <com.example.scrollerdemo.MyImageView                android:layout_width="wrap_content"                android:layout_height="wrap_content"                android:src="@drawable/ic_launcher" />        </com.example.scrollerdemo.MyView>    </com.example.scrollerdemo.MyLinearLayout></LinearLayout>
The following is the MainActivity code:
Package com. example. scrollerdemo; import android. app. activity; import android. OS. bundle; import android. view. view; import android. view. view. onClickListener; public class MainActivity extends Activity implements OnClickListener {private MyView mv = null; @ Overrideprotected void onCreate (Bundle savedInstanceState) {super. onCreate (savedInstanceState); setContentView (R. layout. activity_main); mv = (MyView) findViewById (R. id. mv); mv. setOnClickListener (this) ;}@ Overridepublic void onClick (View v) {switch (v. getId () {case R. id. mv: // click to trigger the scroll effect mv. testSmoothScroll (); break; default: break ;}}}
The code is very simple and does not need to be described too much. The following shows the effect:
Open logcat to view logs:
This is the first draw, consistent with the above analysis process. First, the draw and onDraw methods of MyLinearLayout are called, and then the child is drawn through dispatchDraw, therefore, MyView's computeScroll, draw, onDraw, dispatchDraw and other methods are called, and then MyImageView's computeScroll and onDraw methods are called, so that they can be drawn from top to bottom.
Next we click the android robot icon and call the testSmoothScroll method. The robot will scroll smoothly from the upper left corner to the lower right corner. Now we can view the logcat log:


Too many intermediate logs are omitted. The following section is the last one.
The log shows that the above analysis is correct. By calling postInvalidate in the computeScroll method of MyView, redraw continues until the offset reaches the value specified in advance by startScroll (-100, -100 ).
So far, this article is over and I hope it will help you!

Related Article

Contact Us

The content source of this page is from Internet, which doesn't represent Alibaba Cloud's opinion; products and services mentioned on that page don't have any relationship with Alibaba Cloud. If the content of the page makes you feel confusing, please write us an email, we will handle the problem within 5 days after receiving your email.

If you find any instances of plagiarism from the community, please send an email to: info-contact@alibabacloud.com and provide relevant evidence. A staff member will contact you within 5 working days.

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.