工作有一段时间,有必要掌握事件传递的机制,最近研究了一下,记录下心得。
1 Android中的事件
android中触摸事件比较多,封装中MotionEvent类中,点击、触摸、滑动是我们常用的事件
- MotionEvent.ACTION_DOWN
- MotionEvent.ACTION_MOVE
- MotionEvent.ACTION_UP
- MotionEvent.ACTION_CANCEL
2 核心方法
了解传递机制前,我们需要先学习几个方法。
2.1ViewGroup
- dispatchTouchEvent 分发事件,默认接受事件并往下分发。如果返回true,拦截事件,不分发。下同。
- onInterceptTouchEvent 预处理事件 默认返回false ,返回true表示拦截
- onTouchEvent 处理事件 默认返回false ,不消费事件。如果在根布局或代码中设置了clickable=true,则super.onTouchEvent返回true,表示消费了事件。
2.2View
- dispatchTouchEvent
- onTouchEvent 处理事件 返回值需要看view的属性clickable==true
2.3View/ViewGroup 的 onTouchEvent返回值:
- 如果view/viewGroup可点击的,比如Button,clickable = true,则super.onTouchEvent 返回true
- 如果view/viewGroup不可点击,比如TextView,clickable = false,则super.onTouchEvent 返回false
- 在xml中设置view/viewGroup clickable属性为true, 则该view:super.onTouchEvent 返回true
2.4核心
- clickable影响了super.onTouchEvent返回值
- 如果返回false,View只能处理down事件,后续事件不会触发
- 如果返回true,View的downmoveup都能触发
3 事件传递
一次完整的屏幕触摸事件:手指按下,滑动,抬起。这个事件由一个action_down,若干个action_move,和一个action_up组成。那么它在我们的屏幕中是如何传递的呢?
默认情况下事件传递是从Activity到ViewGroup到View的过程,最终由View接受到。如图:
我们定义一个MyViewGroup和一个MyView,打印其中的方法。
MyViewGroup,clickable为false,super.onTouchEvent默认为false,不消费事件
public class MyViewGroup extends FrameLayout { String Tag = "===MyViewGroup"; public MyViewGroup(Context context) { super(context); } @Override public boolean dispatchTouchEvent(MotionEvent ev) { switch (ev.getAction()){ case MotionEvent.ACTION_DOWN: Log.e(Tag,"dispatchTouchEvent ACTION_DOWN"); break; case MotionEvent.ACTION_MOVE: Log.e(Tag,"dispatchTouchEvent ACTION_MOVE"); break; case MotionEvent.ACTION_UP: Log.e(Tag,"dispatchTouchEvent ACTION_UP"); break; } return super.dispatchTouchEvent(ev); } @Override public boolean onInterceptTouchEvent(MotionEvent ev) { switch (ev.getAction()) { case MotionEvent.ACTION_DOWN: Log.e(Tag, "onInterceptTouchEvent ACTION_DOWN"); break; case MotionEvent.ACTION_MOVE: Log.e(Tag, "onInterceptTouchEvent ACTION_MOVE"); break; case MotionEvent.ACTION_UP: Log.e(Tag, "onInterceptTouchEvent ACTION_UP"); break; } return super.onInterceptTouchEvent(ev); } @Override public boolean onTouchEvent(MotionEvent ev) { switch (ev.getAction()) { case MotionEvent.ACTION_DOWN: Log.e(Tag, "onTouchEvent ACTION_DOWN"); break; case MotionEvent.ACTION_MOVE: Log.e(Tag, "onTouchEvent ACTION_MOVE"); break; case MotionEvent.ACTION_UP: Log.e(Tag, "onTouchEvent ACTION_UP"); break; } return super.onTouchEvent(ev); } }
MyView 继承TextView,clickable默认false,super.onTouchEvent默认为false,不消费事件
public class MyView extends android.support.v7.widget.AppCompatTextView { String Tag = "===MyView"; public MyView(Context context) { super(context); } @Override public boolean dispatchTouchEvent(MotionEvent ev) { switch (ev.getAction()){ case MotionEvent.ACTION_DOWN: Log.e(Tag,"dispatchTouchEvent ACTION_DOWN"); break; case MotionEvent.ACTION_MOVE: Log.e(Tag,"dispatchTouchEvent ACTION_MOVE"); break; case MotionEvent.ACTION_UP: Log.e(Tag,"dispatchTouchEvent ACTION_UP"); break; } return super.dispatchTouchEvent(ev); } @Override public boolean onTouchEvent(MotionEvent ev) { switch (ev.getAction()){ case MotionEvent.ACTION_DOWN: Log.e(Tag,"onTouchEvent ACTION_DOWN"); break; case MotionEvent.ACTION_MOVE: Log.e(Tag,"onTouchEvent ACTION_MOVE"); break; case MotionEvent.ACTION_UP: Log.e(Tag,"onTouchEvent ACTION_UP"); break; } return super.onTouchEvent(ev); } }
activity布局中引用
<com.zcwipe.frecyclerviewdemo.MyViewGroup android:layout_width="match_parent" android:layout_height="wrap_content"> <com.zcwipe.frecyclerviewdemo.MyView android:layout_width="match_parent" android:layout_height="50dp" android:background="@color/color_theme" android:gravity="center" android:text="content" /> </com.zcwipe.frecyclerviewdemo.MyViewGroup>
事件都是由action_down开始,
首先activity执行super.dispatchTouchEvent分发给viewgroup。
然后viewgroup执行super.dispatchTouchEvent,super.onInterceptTouchEvent。
最后view执行super.dispatchTouchEvent,onTouchEvent。这个时候view在onTouchEvent里就接受到了action_down事件。
后续的action_move,action_up事件不再传递给ViewGroup和View。直接由activity处理。
如果拦截,viewgroup的action_move返回true,拦截事件,
@Override public boolean onInterceptTouchEvent(MotionEvent ev) { switch (ev.getAction()) { case MotionEvent.ACTION_DOWN: Log.e(Tag, "onInterceptTouchEvent ACTION_DOWN"); break; case MotionEvent.ACTION_MOVE: Log.e(Tag, "onInterceptTouchEvent ACTION_MOVE"); return true; case MotionEvent.ACTION_UP: Log.e(Tag, "onInterceptTouchEvent ACTION_UP"); break; } return super.onInterceptTouchEvent(ev); }
后续move,up动作由viewgroup的onTouchEvent处理,且不再执行onInterception方法
情况3:
如果viewgroup拦截down事件,那么down事件不再传递给子view,直接由viewgroup的ontouch处理。在viewgroup的onTouchEvent事件中:
如果消费了down事件,返回true,那么后续事件move,up都会直接交由viewgroup的onTouchEvent处理,且不再执行viewgroup的onInterceptTouchEvent方法。日志输出:
如果没有消费,那么move,up将由上层view处理。
案例:加入我们有这么一个需求,在自定义viewgroup中接受action_move事件,实现滑动效果,如何处理?
由情况1我们知道,如果view不消费down事件,viewgroup也不消费事件,那么无法实现。
由情况2我们知道,如果view消费了down事件,viewgroup也不拦截,那么也无法实现。
实现方案两种:
* view onTouchEvent中down消费,viewgroup:onInterceptTouchEvent中move拦截
* view onTouchEvent中down不消费 ,viewgroup:onTouchEvent中down消费
有时候我们不知道view是否消费了事件,那么使用以下方案写出健壮行代码,viewgroup中
1. ontouchevent down消费事件
2. onInterceptTouchEvent中move拦截
@Override public boolean onInterceptTouchEvent(MotionEvent ev) { switch (ev.getAction()) { case MotionEvent.ACTION_DOWN: Log.e(Tag, "onInterceptTouchEvent ACTION_DOWN"); break; case MotionEvent.ACTION_MOVE: return true; // break; case MotionEvent.ACTION_UP: Log.e(Tag, "onInterceptTouchEvent ACTION_UP"); break; } return super.onInterceptTouchEvent(ev); } @Override public boolean onTouchEvent(MotionEvent ev) { switch (ev.getAction()) { case MotionEvent.ACTION_DOWN: Log.e(Tag, "onTouchEvent ACTION_DOWN"); return true; case MotionEvent.ACTION_MOVE: Log.e(Tag, "onTouchEvent ACTION_MOVE"); break; case MotionEvent.ACTION_UP: Log.e(Tag, "onTouchEvent ACTION_UP"); break; } return super.onTouchEvent(ev); }
这样可以稳定的在viewgroup中处理action_move事件,且不影响子view的点击事件。日志输出: