1 unfold from one detail
The previous days collection of @ Zheng Haibo-mobctrl swiperefreshlayout, want to study how to achieve. When the implementation of their own to find a problem: in the ListView distance above a certain distance to start the pull down, to resist the above content after the slide, while the swiperefreshlayout can continue to pull down, and trigger the drop-down refresh. :
The left image starts to slide and the right pull to the top cannot continue pulling down
After some troubleshooting, found that my own implementation of the code, in the Onintercepttouchevent can receive 1 Action_down, and 2 action_move, and then no longer accept action_move events, Causes the child view to be unable to update whether it can drop down, whether it is in a drop-down state, and Swiperefreshlayout can receive successive Action_move events.
Finally found that incredibly is swiperefreshlayout in a humble function rewrite implementation, the code is as follows:
@OverridepublicvoidrequestDisallowInterceptTouchEvent(boolean b) { // Nope.}
Swiperefreshlayout inherited from Viewgroup,requestdisallowintercepttouchevent is covered by the following code in ViewGroup:
Public void requestdisallowintercepttouchevent(BooleanDisallowintercept) {if(Disallowintercept = = ((Mgroupflags & flag_disallow_intercept)! =0)) {//We ' re already in the assume our ancestors is too return; }if(disallowintercept) {mgroupflags |= flag_disallow_intercept; }Else{mgroupflags &= ~flag_disallow_intercept; }//Pass It up to our Parent if(Mparent! =NULL) {mparent.requestdisallowintercepttouchevent (disallowintercept); }}
Why a simple rewrite, can solve this problem?
2 Android TouchEvent
The touch event is received through the underlying, passed to the Viewrootimpl, distributed to the Phonewindow Decorview, first callback to the activity's dispatchtouchevent processing, Then go back to Decorview and start dispatch to the child view, and the delivery logic in a viewgroup is as follows:
TouchEvent dispatchtouchevent Process
When TouchEvent dispatchtouchevent into a viewgroup, there is a three-step judgment, as shown in light green.
- Disallowintercept?
the role of Disallowintercept
The ViewGroup has a disallowintercept switch that lets you set whether this ViewGroup masks onintercepttouchevent events. If this switch is turned on, this viewgroup skips its own onintercepttouchevent event and dispatchtouchevent directly to the child view.
Reset disallowintercept
Disallowintercept, the Action_down is reset every time, and the default is to allow Onintercepttouchevent to be called.
//viewgroup.dispatchtouchevent@Override Public Boolean dispatchtouchevent(Motionevent ev) { ...Booleanhandled =false;if(Onfiltertoucheventforsecurity (EV)) {Final intAction = Ev.getaction ();Final intactionmasked = action & motionevent.action_mask;//Handle an initial down. if(actionmasked = = Motionevent.action_down) {//Throw away all previous state when starting a new touch gesture. //The framework may has dropped the up or cancel event for the previous gesture //Due to a app switch, ANR, or some other state change.Cancelandcleartouchtargets (EV); Resettouchstate (); } ... } ...}/** * * Resets all touch state in preparation for a new cycle. *///viewgroup.resettouchstatePrivate void resettouchstate() {cleartouchtargets (); Resetcancelnextupflag ( This); Mgroupflags &= ~flag_disallow_intercept; Mnestedscrollaxes = Scroll_axis_none;}
Each time the user presses the swipe to lift the action for a complete set of actions. A new set of actions starts, that is, when the user starts tapping the screen, ViewGroup resets the current disallowintercept switch and reverts to allowing the onintercepttouchevent state to be called.
Intercept?
onintercepttouchevent return value is True
When the ViewGroup onintercepttouchevent is called, the return value is true, indicating that the current ViewGroup intercepts the TouchEvent event, and this ViewGroup ontouchevent receives a callback;
Onintercepttouchevent return value is False
If the return value is False, call Dispatchtransformedtouchevent to find the child view hit on this point, and if you find a child view, call the Dispatchtouchevent event of the child view, Otherwise, call Super.dispatchtouchevent, which calls the dispatchtouchevent implementation of view, where the Ontouchevent function is called to handle this touchevent event.
onintercepttouchevent Summary
The onintercepttouchevent process is the parent viewgroup-> child viewgroup-> Sun Viewgruop, if one of the ViewGroup intercepts the event, then this viewgroup, This viewgroup directly handles the Ontouchevent event, and TouchEvent is not dispatch down, but begins to return.
Handled?
ontouchevent return value is True
If the return value is true, the touchevent is processed
Ontouchevent return value is False
If False, return to the parent ViewGroup, and the parent ViewGroup will continue to hand over to this viewgroup sibling view processing.
3 requestdisallowintercepttouchevent
Child View calls Requestdisallowintercepttouchevent (true) after the action_down of Onintercepttouchevent. Then all parent viewgroup of this child view will skip the onintercepttouchevent callback, which is what happens at the beginning of the article: After Action_move starts, the latter Action_move events of the parent ViewGroup are not received. Then it can be concluded that ScrollView, ListView, and other sub-view after the judge began to swipe and intercept the event, called Requestdisallowintercepttouchevent (True), Causes all parent ViewGroup to skip the onintercepttouchevent callback, directly dispatchtransformedtouchevent to the ScrollView or ListView, implementing the Code as follows:
@Override Public Boolean onintercepttouchevent(Motionevent ev) { ...Switch(Action & Motionevent.action_mask) { CaseMotionevent.action_move: {...Final intYdiff = Math.Abs (y-mlastmotiony);if(Ydiff > Mtouchslop && (getnestedscrollaxes () & scroll_axis_vertical) = =0) {misbeingdragged =true; Mlastmotiony = y; Initvelocitytrackerifnotexists (); Mvelocitytracker.addmovement (EV); Mnestedyoffset =0;if(Mscrollstrictspan = =NULL) {Mscrollstrictspan = Strictmode.entercriticalspan ("Scrollview-scroll"); }FinalViewparent parent = GetParent ();if(Parent! =NULL) {Parent.requestdisallowintercepttouchevent (true); } } Break; ... } }returnmisbeingdragged;}
If the slide exceeds the mtouchslop threshold, it is determined that scrollview is sliding, so the onintercepttouchevent callback of the parent ViewGroup is started to be masked. So if requestdisallowintercepttouchevent is covered in the parent viewgroup of this scrollview, and nothing is done, Then ScrollView can not block out the parent ViewGroup Onintercepttouchevent callback, then ScrollView began to handle the sliding action_move will also be received by the parent ViewGroup, It also solves the problem.
4 applications
This problem also exists in Chrisbanes's Android-pulltorefresh project, which can only be repaired in the following 2 steps:
1. Create a new Refreshableviewwrapperlayout.java
PackageCom.handmark.pulltorefresh.library;ImportAndroid.annotation.TargetApi;ImportAndroid.content.Context;ImportAndroid.os.Build;ImportAndroid.util.AttributeSet;ImportAndroid.widget.FrameLayout;/** * Created by Asha on 15-8-28. * Asha [email protected] */ Public class refreshableviewwrapperlayout extends framelayout { Public Refreshableviewwrapperlayout(Context context) {Super(context); } Public Refreshableviewwrapperlayout(context context, AttributeSet attrs) {Super(context, attrs); } Public Refreshableviewwrapperlayout(context context, AttributeSet attrs,intDEFSTYLEATTR) {Super(Context, attrs, defstyleattr); }@TargetApi(Build.version_codes. LOLLIPOP) Public Refreshableviewwrapperlayout(context context, AttributeSet attrs,intDefstyleattr,intDefstyleres) {Super(Context, Attrs, defstyleattr, defstyleres); }@Override Public void requestdisallowintercepttouchevent(BooleanDisallowintercept) {//do Nothing}}
- Implementation of Addrefreshableview in replacement pulltorefreshbase
privatevoidaddRefreshableView(Context context, T refreshableView) { //mRefreshableViewWrapper = new FrameLayout(context); //替换为 new RefreshableViewWrapperLayout(context); mRefreshableViewWrapper.addView(refreshableView, ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT); new LinearLayout.LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT));}
5 Annotations of the image
The startscrollifneeded function is called after the action_move of Abslistview is started, and there is a comment in the function:
Time to start stealing events! Once we ' ve stolen them, don ' t let anyone steal from us
hahaha, my events, no one will ever steal from me!
6 additional small details in the Swiperefreshlayout implementation
- To determine if the child can also slide upwards
If you can swipe, let the child view handle slide
ViewCompat.canScrollVertically(child,-1);
- Standard sliding Start threshold value
final ViewConfiguration configuration = ViewConfiguration.get(mContext);mTouchSlop = configuration.getScaledTouchSlop();
- Synchronous displacement Method of view
Compared to asynchronous Requestlayout, this method is executed synchronously.
child.offsetTopAndBottom(offset);
7 questions
Why are the ListView and ScrollView masking these events so that the parent ViewGroup callback onintercepttouchevent? For efficiency reasons or simplifying logic to avoid slipping errors, expect expert answers.
8 Reference
Swiperefreshlayout Source Code
Android SDK 22 Source code
Explore the causes of requestdisallowintercepttouchevent failure
Copyright NOTICE: This article for Bo Master original article, without Bo Master permission not reproduced.
Android TouchEvent's Requestdisallowintercepttouchevent