There are two common
- One control slides horizontally and the other slides vertically. For example: Similar to ViewPager, each page is a ListView, but we don't need to deal with the sliding processing, the ViewPager has been processed internally.
- One control slides vertically, and the other control slides vertically. For example: ListView is wrapped in ScollView, which also needs to be understood and mastered.
Android has a built-in Scoller for progressive swiping.
- Create a Scroller object: Scroller mScroller = new Scroller(context);
- Override the computeScroll() method;
- Finally, call the startScroll method in our smoothScrollTo method;
Scroller mScroller = new Scroller(context); @Override public void computeScroll() { if (mScroller.computeScrollOffset()){ scrollTo(mScroller.getCurrX(),mScroller.getCurrY()); postInvalidate(); } } //Here smoothScrollTo achieves smoothness in the x direction public void smoothScrollTo(int destX,int destY){ int scrollX=getScrollX(); int deltaX=destX-scrollX; mScroller.startScroll(scrollX,0,deltaX,0,1000);//The formal parameters of the startScroll function respectively indicate: the x coordinate of the starting position, the y coordinate of the starting position, the distance to be moved in the x direction, and the distance to be moved in the y direction. The distance moved and the time it takes for the entire sliding process to complete. invalidate(); }
Scroller.computeScrollOffset(): This method returns true, indicating that the scrolling has not ended, and the scrolling should continue, and false indicates that the sliding has ended.
scrollTo: Note that the essence of the application here is scrollTo(...), so the View is required to be a View with content, and the content of the smooth movement is also the change of the position of the View itself.
postInvalidate(): used in non-UI threads, and finally calls invalidate() internally (similarly, invalidate is used in UI threads).
startScroll: When we construct a Scroller object and call its startScroll method, nothing is actually done inside the Scroller, it just saves a few parameters we pass.
principle:
How does the Scroller make the View slide gradually? The answer is the invalidate method below the startScroll method. The invalidate method will cause the View to be redrawn, and the computeScroll method will be called in the View's draw method. The computeScroll method is an empty implementation in the View, so we need to implement it ourselves. The computeScroll method is already implemented in our code, so that's what makes it move.
Summarize:
The Scroller itself cannot realize the sliding of the View. We need to use the computeScroll method of the View and let it constantly refresh and redraw.
Sliding conflict resolution:
- external interception
- Via onInterceptTouchEvent(MotionEvent event)
@Override public boolean onInterceptTouchEvent(MotionEvent event) { boolean intercepted = false; int x = (int) event.getX(); int y = (int) event.getY(); switch (event.getAction()) { case MotionEvent.ACTION_DOWN: intercepted = false; break; case MotionEvent.ACTION_MOVE: if (parent container needs current click event) { intercepted = true; } else { intercepted = false; } break; case MotionEvent.ACTION_UP: intercepted = false; break; default: break; } mLastXIntercept = x; mLastYIntercept = y; return intercepted; } /* Notice: 1. ACTION_DOWN must return false, do not intercept it, otherwise according to the View event distribution mechanism, the subsequent ACTION_MOVE and ACTION_UP events will be handled by the parent View by default! 2. In principle, ACTION_UP should also return false. If it returns true, and the sliding event is handed over to the sub-View, then the sub-View will not receive the ACTION_UP event, and the onClick event of the sub-View will not be triggered. The parent View is different. If the parent View starts to intercept events in ACTION_MOVE, the subsequent ACTION_UP will also be handed over to the parent View for processing by default. */
- internal interception
- Via dispatchTouchEvent(MotionEvent event)
- The internal interception method means that the parent container does not intercept any events, and all events are passed to the child element. If the child element needs this event, it is directly consumed, otherwise it is handed over to the parent container for processing. This method is similar to Android's event distribution mechanism. Inconsistent, it needs to cooperate with the requestDisallowInterceptTouchEvent method to work properly, which is more complicated to use than the external interception method.
@Override public boolean dispatchTouchEvent(MotionEvent ev) { int x= (int) ev.getX(); int y = (int) ev.getY (); switch (ev.getAction()){ case MotionEvent.ACTION_DOWN: parent.requestDisallowInterceptTouchEvent(true); break; case MotionEvent.ACTION_MOVE: int deltaX=x-mLastX; int deltaY=y-mLastY; if (parent container needs such click event){ parent.requestDisallowInterceptTouchEvent(false); } break; case MotionEvent.ACTION_UP: break; default: break; } mLastX=x; mLastY=y; return super.dispatchTouchEvent(ev); } /* The internal interception method requires that the parent View cannot intercept the ACTION_DOWN event. Since ACTION_DOWN is not controlled by the FLAG_DISALLOW_INTERCEPT flag, once the parent container intercepts ACTION_DOWN, all events will not be passed to the child View. */
ViewPager source code analysis
Borrowing and understanding articles (
http://blog.csdn.net/huachao1001/article/details/51654692
)
ViewPager's sliding conflict handling:
We know that ViewGroup decides whether to intercept touch events in the onInterceptTouchEvent function, so let's learn the onInterceptTouchEvent function of ViewPager.
@Override public boolean onInterceptTouchEvent(MotionEvent ev) { //1. touch action final int action = ev.getAction() & MotionEventCompat.ACTION_MASK; //2. Always pay attention to whether the touch has ended if (action == MotionEvent.ACTION_CANCEL || action == MotionEvent.ACTION_UP) { //3. Release the drag. if (DEBUG) Log.v(TAG, "Intercept done!"); //4. Reset some variables related to judging whether to intercept touch resetTouch(); //5. The touch ends, no need to intercept return false; } //6. If the current event is not a press event, let's judge whether it is dragging and switching pages if (action != MotionEvent.ACTION_DOWN) { //7. If you are currently dragging and switching pages, intercept the event directly, and no need to make interception judgment later if (mIsBeingDragged) { if (DEBUG) Log.v(TAG, "Intercept returning true!"); return true; } //8. If it is marked as not allowing drag and drop to switch pages, we "let go" of all touch events if (mIsUnableToDrag) { if (DEBUG) Log.v(TAG, "Intercept returning false!"); return false; } } //9. Process according to different actions switch (action) { //10. If it is a finger movement operation case MotionEvent.ACTION_MOVE: { //11. If the code can be executed here, it means that mIsBeingDragged==false, otherwise, the execution has ended at the 7th comment //12. Use touch point Id, mainly to handle multi-touch final int activePointerId = mActivePointerId; if (activePointerId == INVALID_POINTER) { //13. If the current touch point id is not a valid Id, no further processing is required break; } //14. To distinguish different fingers according to the id of the touch point, we only need to focus on one finger final int pointerIndex = MotionEventCompat.findPointerIndex(ev, activePointerId); //15. According to the serial number of this finger, get the x coordinate corresponding to this finger final float x = MotionEventCompat.getX(ev, pointerIndex); //16. The distance moved in the x-axis direction final float dx = x - mLastMotionX; //17. The absolute value of the moving distance in the x-axis direction final float xDiff = Math.abs(dx); //18. For the same reason, refer to comments 16 and 17 final float y = MotionEventCompat.getY(ev, pointerIndex); final float yDiff = Math.abs(y - mInitialMotionY); if (DEBUG) Log.v(TAG, "Moved x to " + x + "," + y + " diff=" + xDiff + "," + yDiff); //19. Determine whether the currently displayed page can be swiped, and if it can be slid, throw the event to the currently displayed page for processing //isGutterDrag is to determine whether to move within the gap between two pages //canScroll is to determine whether the page can be swiped if (dx != 0 && !isGutterDrag(mLastMotionX, dx) && canScroll(this, false, (int) dx, (int) x, (int) y)) { mLastMotionX = x; mLastMotionY = y; //20. Mark ViewPager not to intercept events mIsUnableToDrag = true; return false; } //21. If the x moving distance is greater than the minimum distance and the slope is less than 0.5, it means dragging in the horizontal direction if (xDiff > mTouchSlop && xDiff * 0.5f > yDiff) { if (DEBUG) Log.v(TAG, "Starting drag!"); //22. Horizontal movement requires ViewPager to intercept mIsBeingDragged = true; //23. If the ViewPager has a parent View, it also applies to the parent View to pass the touch event to the ViewPager requestParentDisallowInterceptTouchEvent(true); //24. Set the scroll state setScrollState(SCROLL_STATE_DRAGGING); //25. Save the current position mLastMotionX = dx > 0 ? mInitialMotionX + mTouchSlop : mInitialMotionX - mTouchSlop; mLastMotionY = y; //26. Enable caching setScrollingCacheEnabled(true); } else if (yDiff > mTouchSlop) {//27. Otherwise, it means vertical movement if (DEBUG) Log.v(TAG, "Starting unable to drag!"); //28. Movement in the vertical direction does not intercept touch events mIsUnableToDrag = true; } if (mIsBeingDragged) { // 29. Swipe with your finger if (performDrag(x)) { ViewCompat.postInvalidateOnAnimation(this); } } break; } //30. If the finger is pressed case MotionEvent.ACTION_DOWN: { //31. Record the pressed point position mLastMotionX = mInitialMotionX = ev.getX(); mLastMotionY = mInitialMotionY = ev.getY (); //32. The finger number corresponding to the first ACTION_DOWN event is 0 mActivePointerId = MotionEventCompat.getPointerId(ev, 0); //33. Reset allows drag and drop to switch pages mIsUnableToDrag = false; //34. The marker starts scrolling mIsScrollStarted = true; //35. Manually call to calculate the offset of the sliding mScroller.computeScrollOffset(); //36. If the current scroll state is placing the page to the final position, //and the current position is far enough from the final position if (mScrollState == SCROLL_STATE_SETTLING && Math.abs(mScroller.getFinalX() - mScroller.getCurrX()) > mCloseEnough) { //37. If the user's finger presses at this time, the slide will be suspended immediately mScroller.abortAnimation(); mPopulatePending = false; populate(); mIsBeingDragged = true; //38. If the ViewPager has a parent View, it also applies to the parent View to pass the touch event to the ViewPager requestParentDisallowInterceptTouchEvent(true); //39. Set the current state to be dragging setScrollState(SCROLL_STATE_DRAGGING); } else { //40. End scrolling completeScroll(false); mIsBeingDragged = false; } if (DEBUG) Log.v(TAG, "Down at " + mLastMotionX + "," + mLastMotionY + " mIsBeingDragged=" + mIsBeingDragged + "mIsUnableToDrag =" + mIsUnableToDrag); break; } case MotionEventCompat.ACTION_POINTER_UP: onSecondaryPointerUp(ev); break; } //41. Add speed tracking if (mVelocityTracker == null) { mVelocityTracker = VelocityTracker.obtain(); } mVelocityTracker.addMovement(ev); //42. We will intercept the event only when the current page is dragged and switched return mIsBeingDragged; }
Let's see how ViewPager decides whether to intercept or not to intercept. It can be seen from the source code, but when the slope is less than 0.5, it must be intercepted, otherwise it will not be intercepted. What is the slope? High school mathematics shows that in the first quadrant, the closer the line is to the y-axis, the greater the slope, and the closer to the x-axis the line has a smaller slope. Let’s first look at the simple diagram:
(What is the slope: the slope is the degree of inclination, the slope is generally expressed by k, the slope k value is the tangent of the angle between the straight line and the positive direction of the x-axis, if any two points on the straight line are (x1, y1), (x2, y2) Then the slope of the straight line k=(y2-y1)/(x2-x1). The straight line is parallel to the y-axis, the slope does not exist, it is parallel to the x-axis, and the slope is 0)