点击APP手机一个屏幕,可以理解为一个空的屏幕,在这里,肯定会硬件和软件进行一部分复杂的工作,简单的来理解,就是硬件调用我们软件层,然后调用底层一些C++的代码,C++代码又调用java层,我们进入Activity里面的源码,可以看到有一个方法叫public boolean dispatchTouchEvent(MotionEvent ev),这样我们就知道我们手指触碰的一些最基本的一些指令。
源码
public boolean dispatchTouchEvent(MotionEvent ev) {
if (ev.getAction() == MotionEvent.ACTION_DOWN) {
onUserInteraction();//空方法,以便于自己后续实现一些需求
}
if (getWindow().superDispatchTouchEvent(ev)) {
return true;
}
return onTouchEvent(ev);
}
分发的核心方法
dispatchTouchEvent(MotionEvent ev):用来进行事件分发
onInterceptTouchEvent(MotionEvent ev):判断是否拦截事件(只存在于ViewGroup中)
onTouchEvent(MotionEvent ev):处理点击事件
注意:View是没有拦截方法的,只有容器里面才有

以下是事件分发的传递过程:

事件分发实例
多指触控过程中:
ACTION_DOWN:触控时总是第一个被触发,之后就不会再触发。
ACTION_POINTER_DOWN:只要还有触控点在屏幕上,之后手指下去都是只触发这个事件。
ACTION_UP:触控点离开事,仅当最后一个触控点消失时才会触发。
ACTION_POINTER_UP:只要还有触控点在屏幕上,每当手指离开都会触发这个事件
ACTION_MOVE:可以获取按下触控点的位置(getX和getY)
多点触控重要的方法:
getPointerCount():返回触控点总数
getActionMasked():用这个方法才能出现掩盖多指触控的事件。
getActionIndex():屏幕上每个手指,都对应一个index,返回触发按下或抬起动作的下标index.
getX(int pointerIndex):获取指定触控点的x坐标
getY(int pointerIndex):获取指定触控点的y坐标
我们经常可见的滑动冲突用下列几张图代表:
1、外部滑动与内部滑动方向不一致

2、外部滑动与内部滑动方向一致

3、以上两种情况的组合

解决滑动冲突的步骤
1、指定合适的滑动策略
2、按滑动策略分发事件
外部拦截法
所谓外部拦截发,是指点击事件都先经过父容器的拦截处理,如果父容器需要就拦截,否则就不拦截,这种方法比较符合点击事件的分发机制,外部拦截法需要重写父容器的onIntercepterTouchEvent方法,在内部做相应的拦截即可。

内部拦截法
如果是个容器也可以在onInterceptTouchEvent中实现此段代码
内部拦截法是指父容器不拦截任何事件,所有的事件都传递给子元素,如果子元素需要此事件就直接消耗掉,否则就交给父容器进行处理,这种方法和Android的事件分发机制不一致,需要配合requestDisallowInterceptTouchEvent方法才能正常工作,较外部拦截法稍显复杂。内部拦截法需要重写子元素的dispatchTouchEvent方法

ViewPager和RecycleView其实都对内部的一些滑动冲突自动做了处理。
常见实际案例中处理方法

以上的布局,其实最外层是一个大的Scroll(命名为ConScrollView)包含了一个linerLayout,LinerLayout分三部分,最上面红色的部分是一个Scroll(命名为ConScrollView1),中间的是一个RecycleView(自动做了一系列处理事件),最下面就是一个LinerLayout包含的几个TextView
代码如下
<?xml version="1.0" encoding="utf-8"?>
<com.dn_alan.myapplication.conflict.ConScrollView xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".MainActivity">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<com.dn_alan.myapplication.conflict.ConScrollView1
android:id="@+id/viewpager"
android:layout_width="match_parent"
android:layout_height="200dp">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<TextView
android:id="@+id/text"
android:layout_width="match_parent"
android:layout_height="100dp"
android:background="#f00"
android:text="TwoScrollView1" />
<TextView
android:layout_width="match_parent"
android:layout_height="100dp"
android:background="#f00"
android:text="TwoScrollView1" />
<TextView
android:layout_width="match_parent"
android:layout_height="100dp"
android:background="#f00"
android:text="TwoScrollView1" />
</LinearLayout>
</com.dn_alan.myapplication.conflict.ConScrollView1>
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/rel_view"
android:layout_width="match_parent"
android:layout_height="400dp"
app:layoutManager="LinearLayoutManager"/>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<TextView
android:layout_width="match_parent"
android:layout_height="100dp"
android:textColor="#f00"
android:text="TwoScrollView" />
<TextView
android:layout_width="match_parent"
android:layout_height="100dp"
android:textColor="#f00"
android:text="TwoScrollView" />
<TextView
android:layout_width="match_parent"
android:layout_height="100dp"
android:textColor="#f00"
android:text="TwoScrollView" />
</LinearLayout>
</LinearLayout>
</com.dn_alan.myapplication.conflict.ConScrollView>
如果我们什么都不处理的话,效果图并不是上面那样子的,你会发现当你滑动红色的地方的时候,会出现拖不动红色框内部的Scroll,因为父控件已经把他给拦截了。我们想要处理这个事件,有两种方法。
第一种就是不让父控件拦截,重写父控件里面的OnInterceptTouchEvent(外部拦截)
public class ConScrollView extends ScrollView {
private int downY;
private int mTouchSlop; //滑动系数
public ConScrollView(Context context) {
super(context);
mTouchSlop = ViewConfiguration.get(context).getScaledTouchSlop();
}
public ConScrollView(Context context, AttributeSet attrs) {
super(context, attrs);
mTouchSlop = ViewConfiguration.get(context).getScaledTouchSlop();
}
public ConScrollView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
mTouchSlop = ViewConfiguration.get(context).getScaledTouchSlop();
}
@TargetApi(21)
public ConScrollView(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
super(context, attrs, defStyleAttr, defStyleRes);
mTouchSlop = ViewConfiguration.get(context).getScaledTouchSlop();//系统给出来滑动多少才算滑动的意思。
}
//外部拦截法
@Override
public boolean onInterceptTouchEvent(MotionEvent ev) {
switch (ev.getAction()){
case MotionEvent.ACTION_DOWN:
downY = (int)ev.getY();
break;
case MotionEvent.ACTION_MOVE:
//请求父View 不要拦截事件
/* if (ev.getY() > 200) {
return true; //拦截
} else {
return false; //不拦截
}*/
int moveY = (int)getY();
if(Math.abs(moveY - downY) > mTouchSlop){
return false; //不拦截
}
case MotionEvent.ACTION_UP:
break;
}
return super.onInterceptTouchEvent(ev);
}
}
第二种就是让子控件去自己处理(内部拦截)需要重写子控件dispatchTouchEvent方法,并且注意了,还要用上requestDisallowInterceptTouchEvent,主要是让上层父控件拦不拦截的意思。要配合着写。
public class ConScrollView1 extends ScrollView {
private int mTouchSlop;
private int downY;
public ConScrollView1(Context context) {
super(context);
mTouchSlop = ViewConfiguration.get(context).getScaledTouchSlop();
}
public ConScrollView1(Context context, AttributeSet attrs) {
super(context, attrs);
mTouchSlop = ViewConfiguration.get(context).getScaledTouchSlop();
}
public ConScrollView1(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
mTouchSlop = ViewConfiguration.get(context).getScaledTouchSlop();
}
@TargetApi(21)
public ConScrollView1(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
super(context, attrs, defStyleAttr, defStyleRes);
mTouchSlop = ViewConfiguration.get(context).getScaledTouchSlop();
}
//内部拦截法
@Override
public boolean dispatchTouchEvent(MotionEvent ev) {
switch (ev.getAction()) {
case MotionEvent.ACTION_DOWN:
downY = (int) ev.getY();
//请求父View 不要拦截事件,这样保存子View 能够接收到action_move
getParent().requestDisallowInterceptTouchEvent(true);
break;
case MotionEvent.ACTION_MOVE:
// //请求父View 不要拦截事件
if (ev.getY() > 200) {
getParent().requestDisallowInterceptTouchEvent(false);
} else {
getParent().requestDisallowInterceptTouchEvent(true);
}
break;
case MotionEvent.ACTION_UP:
break;
}
return super.dispatchTouchEvent(ev);
}
}
事件拦截的基本意义就是这样了,比如有那种仿照QQ的侧滑删除,网上太多了,基本原理都差不多。demo和案例都奉上
链接:https://pan.baidu.com/s/1u22c0pY3tEdq_vrnS5gksA
提取码:kh7m
网友评论