Recently, some problems related to sliding conflict have been encountered in practical application. In the process of solving them, some problems need to be paid attention to, and special notes are made.
I. Application scenarios
Before solving specific problems, this section describes actual application scenarios and problems.
As can be seen from the figure, there are three recyclerViews nested inside a ScrollView, among which two recyclerViews are horizontal and one RecyclerView is vertical.
In this scene, there is a sliding conflict problem, mainly manifested as transverse RecyclerView sliding insensitive, longitudinal RecyclerView sliding stuck.
Ii. Problem analysis
1. Transverse RecyclerView sliding is not sensitive
The sliding conflicts caused by this problem are shown in the figure above.
The solution to this problem is to determine who should handle the event according to the current sliding direction, horizontal or vertical.
Generally, it is judged according to the included Angle (or slope as shown in the figure below) formed by the sliding path and the sliding speed difference between horizontal and vertical directions.
2. Longitudinal RecyclerView sliding card
The sliding conflicts caused by this problem are shown in the figure above.
For this problem, it is generally necessary to use business logic to determine who should handle the event.
Third, sliding conflict resolution method
There are two general solutions to sliding conflicts.
1. External interception
Events are first intercepted by the parent container. If the event is not needed, it is not intercepted, thus resolving the sliding conflict problem. The external interceptor overwrites the parent onInterceptTouchEvent() method and intercepts it internally
@Override
public boolean onInterceptTouchEvent(MotionEvent ev) {
boolean intercepted = false;
int x = (int) ev.getX();
int y = (int) ev.getY();
switch (ev.getAction()) {
case MotionEvent.ACTION_DOWN:
intercepted = false;
break;
case MotionEvent.ACTION_MOVE: {
if(The parent container requires events) {intercepted =true;
} else {
intercepted = false;
}
break;
}
case MotionEvent.ACTION_UP: {
intercepted = false;
break; }}return intercepted;
}
Copy the code
-
In ACTION_DOWN, the parent must return false, that is, do not intercept the ACTION_DOWN event, because once intercepted, the subsequent ACTION_MOVE and ACTION_UP will be handled by the parent, and the event will not be transmitted to the child view
-
ACTION_MOVE events can be intercepted or not intercepted as needed
-
ACTION_UP must return false, which will cause the child View to fail to receive the UP event and the onClick() event in the child element to fail to fire.
2. Internal interception
The parent container does not intercept any events, all events are passed to the child element, if the child element needs the event directly consumed, otherwise the parent container processing. This method need to cooperate with requestDisallowInterceptTouchEvent () method to work properly.
The main thing is to modify the dispatchTouchEvent() method of the child view
@Override
public boolean dispatchTouchEvent(MotionEvent ev) {
int x = (int) ev.getX();
int y = (int) ev.getY();
switch (ev.getAction()) {
case MotionEvent.ACTION_DOWN: {
getParent().requestDisallowInterceptTouchEvent(true);
break;
}
case MotionEvent.ACTION_MOVE: {
if(the parent container need such events) {getParent () requestDisallowInterceptTouchEvent (false);
}
break;
}
case MotionEvent.ACTION_UP: {
break; }}return super.dispatchTouchEvent(ev);
}
Copy the code
The parent container needs to override the onInterceptTouchEvent() method
@Override
public boolean onInterceptTouchEvent(MotionEvent ev) {
int action = ev.getAction();
if(action == MotionEvent.ACTION_DOWN){
return false;
}else {
return true; }}Copy the code
The parent intercepts events other than ACTION_DOWN. Since ACTION_DOWN events are not controlled by the FLAG_DISALLOW_INTERCEPT flag, once the parent intercepts an ACTION_DOWN event, none of the events can be passed to the child view. So the internal interception doesn’t work.
Fourth, problem solving
Let’s actually solve the sliding conflict problem encountered in this article. According to the above analysis, the problems encountered in this article can be solved quickly and simply by rewriting the onInterceptTouchEvent() method of ScrollView through external interception method.
public class FScrollView extends ScrollView {
private float mLastXIntercept = 0f;
private float mLastYIntercept = 0f;
public FScrollView(Context context, AttributeSet attrs) {
super(context, attrs);
}
@Override
public boolean onInterceptTouchEvent(MotionEvent ev) {
boolean intercepted = false;
float x = ev.getX();
float y = ev.getY();
int action = ev.getAction() & MotionEvent.ACTION_MASK;
switch (action) {
case MotionEvent.ACTION_DOWN: {
intercepted = false; / / initialize mActivePointerId super. OnInterceptTouchEvent (ev);break;
}
caseMotionevent.action_move: {// move incrementfloatdeltaX = x - mLastXIntercept; // Vertical displacement incrementfloat deltaY = y - mLastYIntercept;
if (Math.abs(deltaX) < Math.abs(deltaY)) {
intercepted = true;
} else {
intercepted = false;
}
break;
}
case MotionEvent.ACTION_UP: {
intercepted = false;
break;
}
}
mLastXIntercept = x;
mLastYIntercept = y;
returnintercepted; }}Copy the code
-
Math.abs(deltaX) < math.abs (deltaY) means that the horizontal displacement increment is less than the vertical displacement increment, that is, vertical sliding, and ScrollView intercepts the event.
-
Super. OnInterceptTouchEvent (ev), initialize mActivePointerId, avoid Invalid pointerId = 1 in onTouchEvent problem.
-
The sliding of longitudinal RecyclerView is intercepted and handed to ScrollView. The height needs to be measured, and all items will be loaded by default, which is equivalent to the LinearLayout, resulting in greatly reduced reuse efficiency. So if the situation is complicated, the head layout is recommended.