Principle Analysis of PullToRefresh and pulltorefresh
In the Code session, there is a very classic saying: "Do not duplicate wheel Manufacturing". After reading it, many people will take this as the base. Lu Xun's "come-as-you-go" will be carried forward, and only search for wheels without wheels. But now I want to add the following sentence: "Do not duplicate wheel manufacturing. It doesn't mean you don't need to know how wheel is made "!
After reading the source code of PullToRefresh, I made a small Demo. The following explains the principle. Which code implements such powerful functions, and which method runs throughout the full text?
Principle: There is a scrollTo method in the View to move the entire View to the specified position. PullToRefresh overwrites the onTouchEvent method to detect the offset distance of the user's sliding, then, use the sliding distance to call the scrollTo method to move the entire View from top to bottom to left.
First:
My Demo:
The mobile phone QQ can slide up and down. My demo is up, down, left, and right. After the mobile phone is released, it will automatically return to its original position.
Note: In my demo, the rewrites are inherited from FrameLayout. You can also rewrite LinearLayout or other viewgroups, you can see a large number of la s in the new mobile QQ version that support up and down sliding (or bullet motion) similar to my demo)
1. First, let's look at the required variables:
Private float mLastMotionX, mLastMotionY; // record the position of the finger touch X, Y coordinate private float mDeltaX, mDeltaY; // record the offset of the X and Y offset of the current finger pull private ScrollToHomeRunnable; // The private enum State used to return from the offset point to the origin {// The Current Status: Refreshing? Horizontal Pull? Vertical pull? Normal? REFRESHING, PULLING_HORIZONTAL, PULLING_VERTICAL, NORMAL,} private enum Orientation {// record pulling direction: horizontal? Vertical? HORIZONTAL, VERTICAL} private State mState; // The current State private Orientation mOrientation; // the current direction of pulling
2. Initialization Method. You only need to set the mState to NORMAL during initialization.
private void init(Context context){mState = State.NORMAL;}
3-1. Rewrite the onTouchEvent method to detect the sliding distance and direction of the user, and then call scrollTo to offset the entire View. This is the core code. first look at the pseudo code:
@ Overridepublic boolean onTouchEvent (MotionEvent event) {int action = event. getAction (); switch (action) {case MotionEvent. ACTION_DOWN:/*** records the X and Y coordinates, and restores mDeltaX and mDeltaY to 0 */break; case MotionEvent. ACTION_MOVE:/*** get the Increment Based on the x or y coordinates of the last record of the current touch point, apply it to the scrollTo method, and * record x again, y coordinate */break; case MotionEvent. ACTION_UP:/*** after you release your finger, the View automatically returns to the position where the offset is 0 */break;} return true ;}
3-2: Let's first implement what work should we do when the finger ACTION_DOWN is just clicked? The code below shows that the work is very simple. You only need to record the X and Y coordinates of the current finger each time, and adjust the X offset and Y offset to 0, each time it is pulled from the farthest point.
case MotionEvent.ACTION_DOWN:mLastMotionX = event.getX();mLastMotionY = event.getY();mDeltaY = .0F;mDeltaX = .0F;break;
3-3: when the user slides on the screen, that is, ACTION_MOVE, the X coordinates (or Y coordinates) clicked by the user should be recorded) the difference between the X coordinate (or Y coordinate) at the beginning of ACTION_DOWN, record the difference, and call the scrollTo method:
Case MotionEvent. ACTION_MOVE: float innerDeltaY = event. getY ()-mLastMotionY; // record the difference float innerDeltaX of Y = event. getX ()-mLastMotionX; // record the difference float absInnerDeltaY = Math. abs (innerDeltaY); // the absolute value of the Y difference float absInnerDeltaX = Math. abs (innerDeltaX); // absolute value of the X difference // when the absolute value of the Y difference is greater than the absolute value of the X difference, we can think that the user is sliding up and down if (absInnerDeltaY> absInnerDeltaX & mState! = State. PULLING_HORIZONTAL) {mOrientation = Orientation. VERTICAL; // set the current sliding direction to VERTICAL sliding mState = State. PULLING_VERTICAL; // sliding status: vertical pull if (innerDeltaY> 1.0F) {// The number of innerDeltaY is positive. The user is pulling down. 1.0F can be seen as the threshold value. below is similar to mDeltaY-= absInnerDeltaY; // note that this field is-=, that is, the pull (mDeltaY);} else if (innerDeltaY <-1.0F) {// innerDeltaY is negative, the user is pulling up mDeltaY + = absInnerDeltaY; // accumulate pull (mDeltaY);} // the following code is horizontal slide} else if (absInnerDeltaY <absInnerDeltaX & MState! = State. PULLING_VERTICAL) {mOrientation = Orientation. HORIZONTAL; mState = State. PULLING_HORIZONTAL; if (innerDeltaX> 1.0F) {mDeltaX-= absInnerDeltaX; pull (mDeltaX);} else if (innerDeltaX <-1.0F) {mDeltaX + = absInnerDeltaX; pull (mDeltaX) ;}// record the new coordinate value mLastMotionX = event. getX (); mLastMotionY = event. getY (); break;
3-4 you may have noticed that there is an mState when you determine whether to move horizontally or vertically! = State. PULLING_XXX condition, which is used to restrict the sliding direction. That is, horizontal sliding is prohibited when the user is in vertical sliding; vertical sliding is prohibited when the user is in horizontal sliding, you can only slide in one direction at a time.
During the test, you can see that when you pull down the screen, for example, if the offset value is 200, if you want to make the screen offset 200, you need to call scrollTo (0, -200). This is why innerDelta needs to be accumulated when it is positive. It is a negative value (the user starts to slide upwards.
3-5 when the user's finger leaves, according to PullToRefresh, the user starts to execute the user's task (such as refreshing data). After the task is completed, view re-calls scrollTo a certain distance (this distance is in the ACTION_MOVE phase, the recorded mDeltaY and mDeltaX offset) back to its original location, in my demo, it's just that after the user's finger leaves, the user immediately returns to the original position:
Case MotionEvent. ACTION_UP: switch (mOrientation) {// judge case VERTICAL: smoothScrollTo (mDeltaY) based on the direction determined by ACTION_MOVE; // pull vertically to re-mDeltaY the View, return to Y's origin break again; case HORIZONTAL: smoothScrollTo (mDeltaX); // if it is horizontally pulled, let the View re-slide mDeltaX and re-return to X's origin break; default: break ;} break;
3-6: The pull method of the user touch phase (ACTION_MOVE). Divided by 2.0, the pull distance is scaled. This indicates that if the user knows to slide from top to bottom, the entire View can be offset by up to half of the screen.
Private void pull (float diff) {int value = Math. round (diff/2.0F); // diff is the offset. Dividing by 2.0 is equivalent to scaling if (mOrientation = Orientation. VERTICAL) {scrollTo (0, value); // note that this is the core. The distance between values is moved on the Y direction, and the distance from the X direction remains unchanged.} else if (mOrientation = Orientation. HORIZONTAL) {scrollTo (value, 0); // move the value distance in the X direction, and keep the value distance in the Y direction unchanged }}
3-7: How to automatically return the View (ACTION_UP) to the original position after the user disconnects:
Private void smoothScrollTo (float diff) {int value = Math. round (diff/2.0F); mScrollToHomeRunnable = new ScrollToHomeRunnable (value, 0); mState = State. REFRESHING; // The current status is REFRESHING post (mScrollToHomeRunnable); // view itself has a post method, and we submit a scrollTo task to it}
3-8-1: Let's take a look at the definition of the ScrollToHomeRunnable class. ScrollToHomeRunnable is used to call scrollTo, so that the View can be returned from the X offset or Y offset to the initial position, so why should we encapsulate it into a Runnable class separately? One is to call the View itself. post (Runnable runnable) and View. postDelayed (Runnable runnable, int delayMills) method. The second is to give him some decorative effects, such as the deceleration difference here to simulate some special effects.
Through the post and postDelayed methods, we can keep this task in the View message queue to call scrollTo continuously. Once we return to the origin, we will stop calling the task. Actually, Handler is used here. post (Runnable runnable) can also perform this operation (continuously Add a Runnable task to the Message Queue). You can try it, But I tested that Handler does not directly View it. post (Runnable) is smooth.
3-8-2: ScrollToHomeRunnable constructor. It stores the target value and the current offset and initializes the DecelerateInterpolator.
public ScrollToHomeRunnable(int current, int target){this.target = target;this.current = current;mInterpolator = new DecelerateInterpolator();}
3-8-3: The run method of ScrollToHomeRunnable. In this place, the author of the original PullToRefresh is very smart. He knows that he can call the getInterpolation method to obtain a series of difference points, to obtain different sliding distances, such as "178, 157, 130, 107,-84, -65,-49,-35,-22,-13,-6,-2, 0 ", is such a string value, to make users feel this "sudden return to the original position.
In terms of normalization time, the authors of PullToRefresh thought very well. All of them use the long storage type, avoiding the use of float to allow the software to do more float computing, he first zoomed in the time difference by 1000 times, normalized it to a value in (), then reduced it to a floating point value in (0, 1.0F), and called the getInterpolation method as a parameter, get the next location to be moved to, so that the delta value is continuously determined and the current value is continuously determined. After scrollTo, it is found that the current value still does not reach the target value, then, call the postDelayed method again and return scrollTo.
Note: The value 200 in the Code indicates 200 ms, that is, the original position is returned after ms.
@ Overridepublic void run () {if (mStartTime =-1) {mStartTime = System. currentTimeMillis ();} else {long normalizedTime = (1000 * (System. currentTimeMillis ()-mStartTime)/200; normalizedTime = Math. max (Math. min (normalizedTime, 1000), 0); final int delta = Math. round (current-target) * mInterpolator. getInterpolation (normalizedTime/1000f); current = current-delta; if (mOrientation = Orientation. HORI ZONTAL) {scrollTo (current, 0); // horizontal scroll} else if (mOrientation = Orientation. VERTICAL) {scrollTo (0, current); // VERTICAL scroll} if (current! = Target) {// No back to origin: after 16 milliseconds, continue the postDelayed task postDelayed (this, 16);} else {mState = State. NORMAL; // return to the origin, mState is set to NORMAL }}
3-9: The final supplement. Without the help of the difference tool above, or not 200 ms, or every time the current is a simple decline, the user can clearly see the process of returning to the original position. During this period, if you click, The mDeltaX and mDeltaY values will be 0, and the View will be immediately transferred to the original location, although it is too late to click here, however, you still need to add this code at the beginning of onTouchEvent. If the code is currently in the refresh status, it will be returned immediately (user clicking is prohibited ):
if(mState == State.REFRESHING){return true;}
3-10: Personal QQ: 1291700520, Android Programmer. For example, reprint, reference, etc. Also hope to indicate the source of the link, code:
Https://github.com/anxiaoyi/PullToRefreshTheory