比如:上图
Button(View)的ViewGroup是FrameLayout2
FragmeLayout2的ViewGroup是FragmeLayout1
当点击Button时,所触发的事件到底是交给谁来处理呢?
常见的事件分发分为两种
注意:严格来说以下流程只是ACTION_DOWN的一种特殊的情况
代码实现:
MyFrameLayout.java 自定义的ViewGroup
重写了以下三个方法
/**
* 自定义ViewGroup(FrameLayout本身就是一个ViewGroup)的MyFragmentLayout
*/
public class MyFrameLayout extends FrameLayout
{
private static final String TAG = "MyFrameLayout";
public MyFrameLayout(Context context, AttributeSet attrs)
{
super(context, attrs);
}
@Override
public boolean dispatchTouchEvent(MotionEvent ev)
{
final int action = ev.getAction();
switch (action)
{
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)
{
final int action = ev.getAction();
switch (action)
{
case MotionEvent.ACTION_DOWN:
Log.e(TAG, "onInterceptTouchEvent - ACTION_DOWN");
mLastY = ev.getAction();
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 event)
{
final int action = event.getAction();
switch (action)
{
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(event);
}
}
MyView.java 自定义View
重写了以下两个方法
/**
* 自定义View MyView
*/
public class MyView extends View {
private static final String TAG = "MyView";
public MyView(Context context, AttributeSet attrs) {
super(context, attrs);
}
@Override
public boolean dispatchTouchEvent(MotionEvent event) {
final int action = event.getAction();
switch (action) {
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(event);
}
@TargetApi(Build.VERSION_CODES.KITKAT)
@Override
public boolean onTouchEvent(MotionEvent event) {
final int action = event.getAction();
switch (action) {
case MotionEvent.ACTION_DOWN:
Log.e(TAG, "onTouchEvent - ACTION_DOWN");
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(event);
}
}
TouchSystemActivity.java Activity
重写了以下两个方法
public class TouchSystemActivity extends AppCompatActivity {
private static final String TAG = "TouchSystemActivity";
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_touch_system);
}
@Override
public boolean dispatchTouchEvent(MotionEvent ev) {
final int action = ev.getAction();
switch (action) {
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 event) {
final int action = event.getAction();
switch (action) {
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(event);
}
}
布局文件:
activity_touch_system.xml
现在来操作:
当手指点击MyView时会执行 ACTION_DOWN ——>ACTION_MOVE
手指离开执行 ACTION_UP
那么这几个事件会怎么进行分发呢?
注意:严格来说以下流程只是ACTION_DOWN的一种特殊的情况
前面4个阶段是捕获(自上而下)的事件分发模式
后面3个阶段是冒泡(自下而上)的事件分发模式
当所有的View和ViewGroup都不消费该事件,那么就会自动传给当前Activity进行处理
之后的事件(直到下次点击开始,完一次点击事件的完成过程)都由它进行处理了。
可见:下面的MOVE 和UP都是由Activity的onTouchEvent方法进行(消费)处理的
E/TouchSystemActivity: dispatchTouchEvent - ACTION_DOWN
E/MyFrameLayout: dispatchTouchEvent - ACTION_DOWN
E/MyFrameLayout: onInterceptTouchEvent - ACTION_DOWN
E/MyView: dispatchTouchEvent - ACTION_DOWN
E/MyView: onTouchEvent - ACTION_DOWN
E/MyFrameLayout: onTouchEvent - ACTION_DOWN
E/TouchSystemActivity: onTouchEvent - ACTION_DOWN
E/TouchSystemActivity: dispatchTouchEvent - ACTION_MOVE
E/TouchSystemActivity: onTouchEvent - ACTION_MOVE
E/TouchSystemActivity: dispatchTouchEvent - ACTION_MOVE
E/TouchSystemActivity: onTouchEvent - ACTION_MOVE
E/TouchSystemActivity: dispatchTouchEvent - ACTION_MOVE
E/TouchSystemActivity: onTouchEvent - ACTION_MOVE
E/TouchSystemActivity: dispatchTouchEvent - ACTION_UP
E/TouchSystemActivity: onTouchEvent - ACTION_UP
上面讲的是一个默认情况下,会交由Activity进行事件的处理
那View自身如何表明对事件感兴趣呢?
最主要是View.dispatchOnTouchEvent()在 ACTION_DOWN的时候返回true。
但是一般情况下,我们主要重写的方法是onTouchEvent, 所以要保证ACTION_DOWN返回true。
- 注:凡是clickable = true 或者 longClickable = ture的控 件,正常情况下View.onTouchEvent()一定返回true.
还是手指点击View的过程:DOWN-MOVE*-UP
如果在View中的OnTouchEvent方法中返回True 表明对该事件感兴趣(消费该事件),进行相应的处理
只要ACTION_DOWN 中返回true其他的事件也是默认交由该View进行处理
那么事件分发机制是以下的流程,虚线是不走的
有两种方式返回true
@TargetApi(Build.VERSION_CODES.KITKAT)
@Override
public boolean onTouchEvent(MotionEvent event) {
final int action = event.getAction();
//Log.e(TAG,MotionEvent.actionToString(event.getAction()));
switch (action) {
case MotionEvent.ACTION_DOWN:
Log.e(TAG, "onTouchEvent - ACTION_DOWN");
//getParent().requestDisallowInterceptTouchEvent(true);
//只要ACTION_DOWN 返回true其他的事件也是默认交由该View进行处理
//1。这里返回true也可以,其他两个默认返回false
return true ;
case MotionEvent.ACTION_MOVE:
Log.e(TAG, "onTouchEvent - ACTION_MOVE");
break;
case MotionEvent.ACTION_UP:
Log.e(TAG, "onTouchEvent - ACTION_UP");
break;
}
//2
return super.onTouchEvent(event);
}
log日志:
E/TouchSystemActivity: dispatchTouchEvent - ACTION_DOWN
E/MyFrameLayout: dispatchTouchEvent - ACTION_DOWN
E/MyFrameLayout: onInterceptTouchEvent - ACTION_DOWN
E/MyView: dispatchTouchEvent - ACTION_DOWN
E/MyView: onTouchEvent - ACTION_DOWN
E/TouchSystemActivity: dispatchTouchEvent - ACTION_MOVE
E/MyFrameLayout: dispatchTouchEvent - ACTION_MOVE
E/MyFrameLayout: onInterceptTouchEvent - ACTION_MOVE
E/MyView: dispatchTouchEvent - ACTION_MOVE
E/MyView: onTouchEvent - ACTION_MOVE
E/TouchSystemActivity: onTouchEvent - ACTION_MOVE
E/TouchSystemActivity: dispatchTouchEvent - ACTION_MOVE
E/MyFrameLayout: dispatchTouchEvent - ACTION_MOVE
E/MyFrameLayout: onInterceptTouchEvent - ACTION_MOVE
E/MyView: dispatchTouchEvent - ACTION_MOVE
E/MyView: onTouchEvent - ACTION_MOVE
E/TouchSystemActivity: onTouchEvent - ACTION_MOVE
E/TouchSystemActivity: dispatchTouchEvent - ACTION_MOVE
E/MyFrameLayout: dispatchTouchEvent - ACTION_MOVE
E/MyFrameLayout: onInterceptTouchEvent - ACTION_MOVE
E/MyView: dispatchTouchEvent - ACTION_MOVE
E/MyView: onTouchEvent - ACTION_MOVE
E/TouchSystemActivity: onTouchEvent - ACTION_MOVE
E/TouchSystemActivity: dispatchTouchEvent - ACTION_UP
E/MyFrameLayout: dispatchTouchEvent - ACTION_UP
E/MyFrameLayout: onInterceptTouchEvent - ACTION_UP
E/MyView: dispatchTouchEvent - ACTION_UP
E/MyView: onTouchEvent - ACTION_UP
E/TouchSystemActivity: onTouchEvent - ACTION_UP
你会发现前面的Down 返回了true该事件经由View消费
但是当Move 和 Up操作时会到 Activity的OnTouchEvent 方法
为什么呢?
在Activity中的dispatchTouchEvent(ev)的源码中
/**
* Called to process touch screen events. You can override this to
* intercept all touch screen events before they are dispatched to the
* window. Be sure to call this implementation for touch screen events
* that should be handled normally.
*
* @param ev The touch screen event.
*
* @return boolean Return true if this event was consumed.
*/
public boolean dispatchTouchEvent(MotionEvent ev) {
if (ev.getAction() == MotionEvent.ACTION_DOWN) {
onUserInteraction();
}
if (getWindow().superDispatchTouchEvent(ev)) {
return true;
}
return onTouchEvent(ev);
}
拦截的目的是交给自己处理(onTouchEvent)
在MyFragmentLayout中的
onInterceptTouchEvent 返回true表示拦截该事件
onTouchEvent 中返回 true表示处理该事件
E/TouchSystemActivity: dispatchTouchEvent - ACTION_DOWN
E/MyFrameLayout: dispatchTouchEvent - ACTION_DOWN
E/MyFrameLayout: onInterceptTouchEvent - ACTION_DOWN
E/MyFrameLayout: onTouchEvent - ACTION_DOWN
E/TouchSystemActivity: dispatchTouchEvent - ACTION_MOVE
E/MyFrameLayout: dispatchTouchEvent - ACTION_MOVE
E/MyFrameLayout: onTouchEvent - ACTION_MOVE
E/TouchSystemActivity: dispatchTouchEvent - ACTION_MOVE
E/MyFrameLayout: dispatchTouchEvent - ACTION_MOVE
E/MyFrameLayout: onTouchEvent - ACTION_MOVE
E/TouchSystemActivity: dispatchTouchEvent - ACTION_UP
E/MyFrameLayout: dispatchTouchEvent - ACTION_UP
E/MyFrameLayout: onTouchEvent - ACTION_UP
可以看到
第一次在Down中拦截事件后,事件就不再往下转发给View,而是交给自己的onTouchEvent方法进行处理
之后的Move*-Up都不执行拦截操作了,默认之后的事件都交给该ViewGroup处理
以下是Down的分发过程:
下面是Move*-Up的分发过程
模拟真实拦截过程:
在ViewGroup中 onInterceptTouchEvent 中
如果点击的点到最后的触摸点的Y大于200dp(实际就是手指下滑200dp后触发拦截事件)
一旦拦截,之后的所有事件都由ViewGroup处理
//记录Down时的点的位置
private int mLastY;
@Override
public boolean onInterceptTouchEvent(MotionEvent ev) {
final int action = ev.getAction();
switch (action) {
case MotionEvent.ACTION_DOWN:
Log.e(TAG, "onInterceptTouchEvent - ACTION_DOWN");
mLastY = (int) ev.getY();
break;
case MotionEvent.ACTION_MOVE:
Log.e(TAG, "onInterceptTouchEvent - ACTION_MOVE");
//如果当前的触摸点和我们之前down时候的点大于200dp
if (ev.getY() - mLastY > 200) {
Log.e(TAG, "down时候Y的位置" + mLastY);
Log.e(TAG, "up时候Y的位置 + ev.getY());
Log.e(TAG, "onInterceptTouchEvent - ACTION_MOVE - return true ");
return true;
}
break;
case MotionEvent.ACTION_UP:
Log.e(TAG, "onInterceptTouchEvent - ACTION_UP");
break;
}
return super.onInterceptTouchEvent(ev);
// return true;
}
有没有方法?让ViewGroup不拦截子View要处理的事件呢?
getParent().requestDisallowInterceptTouchEvent(true);
请求父View不拦截这个事件
@TargetApi(Build.VERSION_CODES.KITKAT) @Override public boolean onTouchEvent(MotionEvent event) { final int action = event.getAction(); Log.e(TAG,MotionEvent.actionToString(event.getAction())); switch (action) { case MotionEvent.ACTION_DOWN: Log.e(TAG, "onTouchEvent - ACTION_DOWN"); getParent().requestDisallowInterceptTouchEvent(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(event); return true ; }
- dispatchTouchEvent ACTION_DOWN的时候:逆序遍历子View,找出对该事件感兴趣的View,标记为targetView,然后对于该手势(DOWN-MOVE*-UP)的后续事件都传给targetView。
- onInterceptTouchEvent 在ACTION_DWON,或者存在targetView的情况下,可以随时对该事件进行拦截,交给自己处理。
- onTouchEvent 拿到事件后做针对当前View的相关操作。
– 可以编写setOnTouchListener(无需继承View) – 复写onTouchEvent
– 如果需要自己获取touch事件进行处理,ACTION_DOWN必须 返回true,保证整个手势的事件都能够传递到该View。
• 包含上述View的所有事项
– 可以在onInterceptTouchEvent()中子View的事件进行拦截, 交给自己的onTouchEvent进行处理。
– 注意:一旦拦截针对当然的手势所有事件都将由当前的 ViewGroup处理。会传递一个ACTION_CANCEL交给当前的子 View,让子View明白后续的事件不会到来了。
requestDisallowInterceptTouchEvent
来让父布局禁用拦截事件功能,从而父布局忽略该事件之后的一切Action
ViewConfiguration 是系统中关于视图的各种特性的常量记录对象
• ViewConfiguration
- – getScaledTouchSlop()
getScaledTouchSlop是一个距离,表示滑动的时候,手的移动要大于这个距离才开始移动控件。如果小于这个距离就不触发移动控件,如viewpager就是用这个距离来判断用户是否翻页
- – getScaledMinimumFlingVelocity()
用于设置最小加速率
- – getLongPressTimeout()
长按事件阈值
• Mastering the Android Touch System
– 视频(中文字幕) http://v.youku.com/v_show/id_XODQ1MjI2MDQ0.html?from=s1.8- 1-1.2
– 英文(Google搜索下)
– 代码 https://github.com/devunwired/custom-touc-examples