There's not so much whys, there's not so much whining, because no matter how hard you try, you never try hard enough.Copy the code

Self-reflection:

We learned about event distribution, which is often used to solve sliding conflicts, but can I implement a pull-down refresh operation? When the ListView runs sideways, we often add a drop-down refresh header to the ListView header. This method is not very elegant? More importantly, now popular RecyclerView does not have addHeader this method ah? The list is a cache, it's a display of the data, and the drop-down refresh header is not part of the list data rendering, you changed it, you added it, right? Do we have an elegant way to implement a pull-down refresh, a non-intrusive way to support a pull-down refresh of various lists?Copy the code

The problem

Let’s recall our event distribution related simply by asking a few questions first:

  • How do you implement the pull down refresh effect? Define a header to put in RecyclerView, okay? Or define a header “splicing” in RecyclerView header?

    • Obviously, as reflected on earlier, the purpose of a sliding list is to display the contents of a list, and we also need to create a non-invasive drop-down refresh, we must create a header, and then make the header and RecyclerView “splicken”. So this part of the work, we can wrap in a ViewGroup, first let the ViewGroup slide up the header height, achieve hidden effect, reach the drop-down refresh threshold, follow the gesture drop-down.
  • Pull down. Obviously, after the content area of the list is all leaked out, the head starts to show and the loading effect appears. Then, will this gesture have sliding conflict?

    • Display list is sliding up and down, reach the drop-down refresh threshold, and then pull down, this time, RecyclerView all display, to show the header. RecyclerView is “slippery”, but still want to list neat pull down, intuitive feeling is, RecyclerView “can not move”, it should be “with” the head, the whole move down together, release the loading animation, after the refresh, the whole reset. Consider another situation, pull down to the threshold, continue to pull down, header leakage, did not let go, and then up, until the header hidden, then continue to go up, should be RecyclerView “can slide”, belongs to the normal RecyclerView own operation, RecyclerView can now “slide”.

  • RecyclerView “slippery”, should not accept events, “can slide” can accept events, responsible for the RecyclerView distribution time, the specified is to wrap its ViewGroup, so the event distribution rewrite which method, or rewrite which method?

    • OnTouchEvent, this method is mainly used to process events, do some gestures, monitor left and right slides, slide distance and speed, etc. When RecyclerView “can’t move”, should not accept events, can’t handle, I can only set the parent View not to intercept, can’t set the parent View to intercept. Some people say, I do not process not on the line, can I also set RecyclerView event return false?? This is not invasive-free, and it’s getting more and more complex.

    • OnIntercept, this method is mainly used to determine whether the current View is blocking the event, and this method is not always called. Consider a situation, when RecyclerView is sliding, it is obvious that RecyclerView needs to accept events, when the threshold is reached, “slide”, this time events can not accept, need the parent ViewGroup control overall sliding, display header. So when the finger goes down to the lift, this whole set of events, if RecyclerView processes it, obviously even if it reaches the threshold, it still needs to RecyclerView processes it. But we want to reach the threshold, so we can’t handle it, so we need to handle it with the parent ViewGoup to do a drop-down refresh. At the beginning of their own processing, judge whether RecyclerView has reached the top will know whether to reach the threshold, not to the top, let RecyclerView processing, not since their own processing, this can be achieved. But when it comes to dealing with yourself, there’s a case. Loading continues until the header is hidden, and the event is still handled by the parent ViewGroup. Why is that? Because read the source code know, when the parent ViewGroup itself to process events, onIntercept does not paste go in, resulting in the parent View consistent in the process of events, RecyclerView can not slide normally.

    • DispatchTouchEvent, this method is responsible for event distribution, it’s the entry point for event distribution, every time an event goes through it, it’s called. Since so, RecyclerView “slippery”, I will control the event entry does not give RecyclerView to distribute events, “can slide”, I will give RecyclerView to distribute events not on the line, yes, desirable.

Drop down to refresh library PTRFrameLayout

From the analysis of the production, it is obvious that we only need to analyze the dispatchTouchEvent of the custom View PTRFrameLayout. And I recommend that you, in contrast to my comments, comb through the code logic yourself to enhance your learning and understanding.

Public Boolean dispatchTouchEventSupper(MotionEvent e) {return super.dispatchTouchEvent(e); } // Distribute the cancel event normally and let the child View ignore the event handling. private void sendCancelEvent() { // The ScrollChecker will update position and lead to send cancel event when mLastMoveEvent is null. // fix #104, #80, #92 if (mLastMoveEvent == null) { return; } MotionEvent last = mLastMoveEvent; MotionEvent e = MotionEvent.obtain(last.getDownTime(), last.getEventTime() + ViewConfiguration.getLongPressTimeout(), MotionEvent.ACTION_CANCEL, last.getX(), last.getY(), last.getMetaState()); dispatchTouchEventSupper(e); } @override public Boolean dispatchTouchEvent(MotionEvent e) {// mHeaderView // Do not intercept any event, directly distribute the event. isEnabled() || mContent == null || mHeaderView == null || isDisabledForRefresh) { return dispatchTouchEventSupper(e); } int action = e.getAction(); switch (action) { case MotionEvent.ACTION_UP: case MotionEvent.ACTION_CANCEL: // Check the logic, check the gesture threshold and the current state, whether to display headerView, execute the pull-down refresh mPtrIndicator. OnRelease (); / / that raised her hand, the if (mPtrIndicator. HasLeftStartPosition ()) {onRelease (false); // In other cases, the default event distribution logic is followed, and the default event distribution logic is followed. Because this time not reach the threshold, belongs to the normal sliding RecyclerView if (mPtrIndicator. HasMovedAfterPressedDown ()) {sendCancelEvent (); return true; } return dispatchTouchEventSupper(e); } else { return dispatchTouchEventSupper(e); } case MotionEvent.ACTION_DOWN: // Reset mHasSendCancelEvent = false; . / / press the coordinates of the update mPtrIndicator onPressDown (um participant etX (), um participant etY ()); / / if the current in the slide, hold (is easy to understand, such as you have been, to the very long, let go up, you can be the new touch screen, press and hold) of controls are put mScrollChecker. AbortIfWorking (); mPreventForHorizontal = false; // The cancel event will be sent once the position is moved. // So let the event pass to children. // fix #93, #102 dispatchTouchEventSupper(e); return true; case MotionEvent.ACTION_MOVE: mLastMoveEvent = e; MPtrIndicator. OnMove (LLDB etX(), LLDB etY())); float offsetX = mPtrIndicator.getOffsetX(); float offsetY = mPtrIndicator.getOffsetY(); / / determine whether need to ban horizontal slip if (mDisableWhenHorizontalMove &&! mPreventForHorizontal && (Math.abs(offsetX) > mPagingTouchSlop && Math.abs(offsetX) > Math.abs(offsetY))) { if (mPtrIndicator.isInStartPosition()) { mPreventForHorizontal = true; }} // If horizontal swiping is not disabled, then the pull-down refresh will not come in when the user swipes left or right, If (mPreventForHorizontal) {return dispatchTouchEventSupper(e); // If (mPreventForHorizontal) {return dispatchTouchEventSupper(e); } boolean moveDown = offsetY > 0; boolean moveUp = ! moveDown; boolean canMoveUp = mPtrIndicator.hasLeftStartPosition(); PTRFrameLayout does not need to be processed. PTRFrameLayout only needs to be processed during the pull-down refresh. // Disable move when header not reach top if (moveDown && mPtrHandler! = null && ! mPtrHandler.checkCanDoRefresh(this, mContent, mHeaderView)) { return dispatchTouchEventSupper(e); } // If the current threshold is reached and the headerView continues to leak, the update event is handled by PTRFrameLayout // If the headerView and all leaks, then move up, Can also continue to upgrade (headerView haven't together), then the event is to PTRFrameLayout if ((moveUp && canMoveUp) | | moveDown) {if (mPtrHandler! = null && ! isStartDragging) { mPtrHandler.onStartDragging(); isStartDragging = true; } movePos(offsetY); return true; } else { if (mPtrHandler ! = null && isStartDragging) { isStartDragging = false; mPtrHandler.onRefreshComplete(); } } default: return dispatchTouchEventSupper(e); }}Copy the code

Analysis 1:1. Why call the default time distribution method? 2. Why return true?

  • 1. First call dispatchTouchEventSupper(e), in order to put down all down events and not interfere with the normal response of sub-view. For example, longClick and onClick need to receive down events. If there is no drop-down refresh, I just clicked an item of RecyclerView and needed to jump to a page, but you didn’t put down the time. Wouldn’t that be a problem? So somebody says, what if I don’t want to do sub-view and you put down the down? There’s still cancel, isn’t there?
  • As I said before, when I consume a down sequence of events, that sequence of events is going to be handed over to me, and yes, the purpose is for this drop-down refresh control to consume events, to take over all the events, to make sure that the sequence of events is mine. Again, doesn’t dispatchTouchEvent come in every time by default? Do I have to return true? Good question. If you do not return true, then someone must consume the down. Who will consume the down? HeaderView or contentView? If one of them is consumed, the subsequent events should also be its, right? And then you intercept the move event. OK, that makes sense? But what if they don’t spend? Then, you say they’re not consuming and I’m consuming, isn’t that toxic? Or if I don’t consume, I’ll flip it up and make PTRFrameLayout’s father consume, even worse? If you do not consume the down event, PTRFrameLayout will not receive subsequent events, and there will be no headerView or contentView. So, PTRFrameLayout just return true, take over all the events and distribute them. Yes, the core of PTRFrameLayout is to take over all the events, schedule them uniformly, and put them down when appropriate.

conclusion

  • The PTRFrameLayout core is that when a Down event comes, return True takes over all events.
  • Then, depending on whether the drop-down refresh threshold has been reached or not, decide who should handle the move event. If you handle it yourself, send the cancel event to the child View.
  • If it does, it will send the cancel event to the child View and return true to handle it. Otherwise, the child View will not respond to the normal event distribution logic and send the event normally.