上一篇: [android 自定义控件](2)事件传递
案例
环境:
View类
package com.example.dispatcheventapp;
import android.annotation.SuppressLint;
import android.content.Context;
import android.util.AttributeSet;
import android.util.Log;
import android.view.MotionEvent;
import android.widget.Button;
@SuppressLint("AppCompatCustomView")
public class MyButton extends Button {
public final static String MyButton_TAG="View";
public MyButton(Context context) {
super(context);
}
public MyButton(Context context, AttributeSet attrs) {
super(context, attrs);
}
public MyButton(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
}
public MyButton(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
super(context, attrs, defStyleAttr, defStyleRes);
}
@Override
public boolean dispatchTouchEvent(MotionEvent event) {
Log.e(MyButton_TAG,"ENTER dispatchTouchEvent");
return super.dispatchTouchEvent(event);
}
@Override
public boolean onTouchEvent(MotionEvent event) {
Log.e(MyButton_TAG,"ENTER onTouchEvent");
return super.onTouchEvent(event);
}
}
ViewGoup类
package com.example.dispatcheventapp;
import android.content.Context;
import android.support.annotation.Nullable;
import android.util.AttributeSet;
import android.util.Log;
import android.view.MotionEvent;
import android.widget.LinearLayout;
public class MyLayout extends LinearLayout {
public final static String Mylayout_TAG="ViewGroup";
public MyLayout(Context context) {
super(context);
}
public MyLayout(Context context, @Nullable AttributeSet attrs) {
super(context, attrs);
}
public MyLayout(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
}
public MyLayout(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
super(context, attrs, defStyleAttr, defStyleRes);
}
@Override
public boolean dispatchTouchEvent(MotionEvent event) {
Log.e(Mylayout_TAG,"ENTER dispatchTouchEvent");
return super.dispatchTouchEvent(event);
}
@Override
public boolean onTouchEvent(MotionEvent event) {
Log.e(Mylayout_TAG,"ENTER onTouchEvent");
return super.onTouchEvent(event);
}
}
Activity类
package com.example.dispatcheventapp;
import android.app.Activity;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.util.Log;
import android.view.MotionEvent;
public class MainActivity extends Activity {
public final static String Activity_TAG="Activity";
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
}
@Override
public boolean dispatchTouchEvent(MotionEvent event) {
Log.e(Activity_TAG,"ENTER dispatchTouchEvent");
return super.dispatchTouchEvent(event);
}
@Override
public boolean onTouchEvent(MotionEvent event) {
Log.e(Activity_TAG,"ENTER onTouchEvent");
return super.onTouchEvent(event);
}
}
layout文件
<?xml version="1.0" encoding="utf-8"?>
<com.example.dispatcheventapp.MyLayout 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"
android:id="@+id/my_layout">
<com.example.dispatcheventapp.MyButton
android:id="@+id/my_button"
android:text="点我"
android:layout_width="wrap_content"
android:layout_height="wrap_content" />
</com.example.dispatcheventapp.MyLayout>
案例1:无onTouch监听的默认情况
点一下按钮的结果:
03-25 10:53:33.629 7153-7153/com.example.dispatcheventapp E/Activity: ENTER dispatchTouchEvent
03-25 10:53:33.629 7153-7153/com.example.dispatcheventapp E/ViewGroup: ENTER dispatchTouchEvent
03-25 10:53:33.629 7153-7153/com.example.dispatcheventapp E/View: ENTER dispatchTouchEvent
03-25 10:53:33.629 7153-7153/com.example.dispatcheventapp E/View: ENTER onTouchEvent
03-25 10:53:33.639 7153-7153/com.example.dispatcheventapp E/Activity: ENTER dispatchTouchEvent
03-25 10:53:33.639 7153-7153/com.example.dispatcheventapp E/ViewGroup: ENTER dispatchTouchEvent
03-25 10:53:33.649 7153-7153/com.example.dispatcheventapp E/View: ENTER dispatchTouchEvent
03-25 10:53:33.649 7153-7153/com.example.dispatcheventapp E/View: ENTER onTouchEvent
03-25 10:53:33.679 7153-7153/com.example.dispatcheventapp E/Activity: ENTER dispatchTouchEvent
03-25 10:53:33.679 7153-7153/com.example.dispatcheventapp E/ViewGroup: ENTER dispatchTouchEvent
03-25 10:53:33.679 7153-7153/com.example.dispatcheventapp E/View: ENTER dispatchTouchEvent
03-25 10:53:33.679 7153-7153/com.example.dispatcheventapp E/View: ENTER onTouchEvent
分析:
点按钮,down事件被View接受,然后因为没有onTouch监听,所以调用onTouchEvent的函数,被onTouchEvent函数处理之后,onTouchEvent返回true,View的dispatchTouchEvent返回true,ViewGroup的dispatchTouchEvent返回true,Activity的dispatchTouchEvent返回true,之后的move和up事件同理。
点一下非按钮的结果:
03-25 11:00:37.029 7153-7153/com.example.dispatcheventapp E/Activity: ENTER dispatchTouchEvent
03-25 11:00:37.029 7153-7153/com.example.dispatcheventapp E/ViewGroup: ENTER dispatchTouchEvent
03-25 11:00:37.029 7153-7153/com.example.dispatcheventapp E/ViewGroup: ENTER onTouchEvent
03-25 11:00:37.029 7153-7153/com.example.dispatcheventapp E/Activity: ENTER onTouchEvent
03-25 11:00:37.049 7153-7153/com.example.dispatcheventapp E/Activity: ENTER dispatchTouchEvent
03-25 11:00:37.049 7153-7153/com.example.dispatcheventapp E/Activity: ENTER onTouchEvent
03-25 11:00:37.059 7153-7153/com.example.dispatcheventapp E/Activity: ENTER dispatchTouchEvent
03-25 11:00:37.059 7153-7153/com.example.dispatcheventapp E/Activity: ENTER onTouchEvent
分析:
因为没有View能处理down事件,所以down事件时,ViewGoup的dispatchTouchEvent函数中,mFirstTouchTarget会被初始化为null,所以事件会被传递到ViewGroup的onTouchEvent(和View的onTouchEvent一样)中,又因为viewGroup都是disclickable的所以不能处理事件返回false,ViewGoup的dispatchTouchEven 返回false,,viewGroup的上级ViewGroup也返回false…最后Activity的dispatchTouchEvent中调用onTouchEvent来处理事件,最后也返回false。这样整个ViewGroup链条中每一个ViewGroup的mFirstTouchTarget都是null。之后的move和up往下传时,第一个会被传入根View(DecorView)中,因为mFirstTouchTarget被初始化为null,所以直接调用根View的onTouchEvent函数来处理,返回false。这样这些事件会之前返回给Activity的onTouchEvent来处理。
思考1: 我只想让我自定义的ViewGroup来处理事件,怎么弄?
首先我们要弄清楚为什么会发生这样的问题。通过上面的分析我们知道
- 没有View能处理down事件(这个没办法解决)
- ViewGroup时disclickable的所以onTouchEvent只能返回false
- 我们的布局和系统生成布局组成了一条ViewGroup链条(最少三层),因为上层的ViewGroup的dispatchEvent返回fasle,导致底层的ViewGroup的mFirstTouchTarget被初始化为null,这样整个链条上的ViewGroup就都是mFirstTouchTarget=null,所以下一次的move和up事件就指挥传递到根View的onTouchEvent中,上面的ViewGroup就没机会接收到这些事件了。(机制问题,无法解决)
上面1和3时无法解决的,但是通过2我们就可以实现这样的目标了
//修改mylayout的代码,让他返回true,这样整个VewiGroup链条中前面的ViewGroup中mFirstTouchTarget!=null,事件也就会被传递下来
@Override
public boolean onTouchEvent(MotionEvent event) {
Log.e(Mylayout_TAG,"ENTER onTouchEvent");
super.onTouchEvent(event);
return true;
}
结果:
03-25 11:56:19.059 16517-16517/com.example.dispatcheventapp E/Activity: ENTER dispatchTouchEvent
03-25 11:56:19.069 16517-16517/com.example.dispatcheventapp E/ViewGroup: ENTER dispatchTouchEvent
03-25 11:56:19.069 16517-16517/com.example.dispatcheventapp E/ViewGroup: ENTER onTouchEvent
03-25 11:56:19.079 16517-16517/com.example.dispatcheventapp E/Activity: ENTER dispatchTouchEvent
03-25 11:56:19.079 16517-16517/com.example.dispatcheventapp E/ViewGroup: ENTER dispatchTouchEvent
03-25 11:56:19.079 16517-16517/com.example.dispatcheventapp E/ViewGroup: ENTER onTouchEvent
03-25 11:56:19.089 16517-16517/com.example.dispatcheventapp E/Activity: ENTER dispatchTouchEvent
03-25 11:56:19.089 16517-16517/com.example.dispatcheventapp E/ViewGroup: ENTER dispatchTouchEvent
03-25 11:56:19.099 16517-16517/com.example.dispatcheventapp E/ViewGroup: ENTER onTouchEvent
案例2: onInterceptTouchEvent调用时机问题
通过之前的代码分析我们知道,onInterceptTouchEvent只有 ViewGroup中才有的方法,可以控制事件的拦截。默认是返回false,运行结果就和上面的代码一样。现在我们来看看返回true的结果
//在mylayout类中加入下面的代码
@Override
public boolean onInterceptTouchEvent(MotionEvent ev) {
return true;
}
点击按钮:
03-25 12:26:39.639 21782-21782/com.example.dispatcheventapp E/Activity: ENTER dispatchTouchEvent
03-25 12:26:39.639 21782-21782/com.example.dispatcheventapp E/ViewGroup: ENTER dispatchTouchEvent
03-25 12:26:39.639 21782-21782/com.example.dispatcheventapp E/ViewGroup: ENTER onTouchEvent
03-25 12:26:39.639 21782-21782/com.example.dispatcheventapp E/Activity: ENTER onTouchEvent
03-25 12:26:39.649 21782-21782/com.example.dispatcheventapp E/Activity: ENTER dispatchTouchEvent
03-25 12:26:39.649 21782-21782/com.example.dispatcheventapp E/Activity: ENTER onTouchEvent
03-25 12:26:39.679 21782-21782/com.example.dispatcheventapp E/Activity: ENTER dispatchTouchEvent
03-25 12:26:39.679 21782-21782/com.example.dispatcheventapp E/Activity: ENTER onTouchEvent
分析:
因为onInterceptTouchEvent返回true,所以当dow事件传递到ViewGroup的dispatchTouchEvent中时,intercepted为true,所以之后就跳过寻找View的过程,mFirstTouchTarget也就时null,这样就会调用ViewGroup的onTouchEvent来处理事件。因为ViewGroup是disclickable的所以,返回False。这样整个ViewGroup链条中mFirstTouchTarget就都是null。所以剩下的事件就只能是Activity来处理。你会发现这和案例一中的点击非按钮区域是一样的。这也说明了android 提供的拦截机制还是很好的
点击非按钮:
03-25 12:30:13.059 21782-21782/com.example.dispatcheventapp E/Activity: ENTER dispatchTouchEvent
03-25 12:30:13.069 21782-21782/com.example.dispatcheventapp E/ViewGroup: ENTER dispatchTouchEvent
03-25 12:30:13.069 21782-21782/com.example.dispatcheventapp E/ViewGroup: ENTER onTouchEvent
03-25 12:30:13.069 21782-21782/com.example.dispatcheventapp E/Activity: ENTER onTouchEvent
03-25 12:30:13.079 21782-21782/com.example.dispatcheventapp E/Activity: ENTER dispatchTouchEvent
03-25 12:30:13.079 21782-21782/com.example.dispatcheventapp E/Activity: ENTER onTouchEvent
03-25 12:30:13.099 21782-21782/com.example.dispatcheventapp E/Activity: ENTER dispatchTouchEvent
03-25 12:30:13.099 21782-21782/com.example.dispatcheventapp E/Activity: ENTER onTouchEvent
和上面一样,不分析了。
思考1: 如果我move和up拦截,down不拦截会怎么样?
//修改mylayout中的diamagnetic
@Override
public boolean onInterceptTouchEvent(MotionEvent ev) {
Log.e(Mylayout_TAG,"ENTER onInterceptTouchEvent");
if(ev.getAction()==MotionEvent.ACTION_DOWN)
return false;
return true;
}
点击按钮结果:
03-25 12:47:17.739 24598-24598/com.example.dispatcheventapp E/Activity: ENTER dispatchTouchEvent
03-25 12:47:17.739 24598-24598/com.example.dispatcheventapp E/ViewGroup: ENTER dispatchTouchEvent
03-25 12:47:17.739 24598-24598/com.example.dispatcheventapp E/ViewGroup: ENTER onInterceptTouchEvent
03-25 12:47:17.739 24598-24598/com.example.dispatcheventapp E/View: ENTER dispatchTouchEvent
03-25 12:47:17.739 24598-24598/com.example.dispatcheventapp E/View: ENTER onTouchEvent
03-25 12:47:17.759 24598-24598/com.example.dispatcheventapp E/Activity: ENTER dispatchTouchEvent
03-25 12:47:17.759 24598-24598/com.example.dispatcheventapp E/ViewGroup: ENTER dispatchTouchEvent
03-25 12:47:17.759 24598-24598/com.example.dispatcheventapp E/ViewGroup: ENTER onInterceptTouchEvent
03-25 12:47:17.759 24598-24598/com.example.dispatcheventapp E/View: ENTER dispatchTouchEvent
03-25 12:47:17.759 24598-24598/com.example.dispatcheventapp E/View: ENTER onTouchEvent
03-25 12:47:17.769 24598-24598/com.example.dispatcheventapp E/Activity: ENTER dispatchTouchEvent
03-25 12:47:17.779 24598-24598/com.example.dispatcheventapp E/ViewGroup: ENTER dispatchTouchEvent
03-25 12:47:17.779 24598-24598/com.example.dispatcheventapp E/ViewGroup: ENTER onTouchEvent
03-25 12:47:17.779 24598-24598/com.example.dispatcheventapp E/Activity: ENTER onTouchEvent
03-25 12:47:17.789 24598-24598/com.example.dispatcheventapp E/Activity: ENTER dispatchTouchEvent
03-25 12:47:17.789 24598-24598/com.example.dispatcheventapp E/ViewGroup: ENTER dispatchTouchEvent
03-25 12:47:17.789 24598-24598/com.example.dispatcheventapp E/ViewGroup: ENTER onTouchEvent
03-25 12:47:17.789 24598-24598/com.example.dispatcheventapp E/Activity: ENTER onTouchEvent
分析:
down时调用onInterceptTouchEvent,不拦截。所以mFirstTouchTarget被初始化,View的onTouchEvent处理事件,返回true。所以ViewGroup的dispatchTouchEvent返回true。Activity的dispatchTouchEvent不调用onTouchEvent且返回true。通过down事件的初始化,viewGroup链条中的mFirstTouchTarget都被初始化为!=null。
接着move 传递到ViewGroup的dispatchTouchEvent中,因为mFirstTouchTarget!=null所以调用onInterceptTouchEvent函数,这是返回false,拦截。所以传递消息之前cancelChild 会被设置成True,这样所有的View都会收到cancel消息。因为View是Clickable的所以就算是cancel,onTouchEvent也是True,但是ViewGroup会清空mFirstTouchTarget,让其=null。此时我们自定义的ViewGroup的mFirstTouchTarget被清空了,但是整个链条中的其他ViewGroup的mFirstTouchTarget还是存在的。
当后面的move或up事件再一次传递到我们自定义的ViewGroup的dispatchTouchEvent中时。mFirstTouchTarget==null,所以不会调用onInterceptTouchEvent 方法,直接使intercepted=true。之后因为mFirstTouchTarget==null,所以使用ViewGroup的onTouchEvent处理处理事件返回false.这样整个链条中的ViewGroup的dispatchTouchEvent都返回false。就会进入Activity的onTouchEvent中。
点击非按钮:
03-25 13:08:07.489 24598-24598/com.example.dispatcheventapp E/Activity: ENTER dispatchTouchEvent
03-25 13:08:07.489 24598-24598/com.example.dispatcheventapp E/ViewGroup: ENTER dispatchTouchEvent
03-25 13:08:07.489 24598-24598/com.example.dispatcheventapp E/ViewGroup: ENTER onInterceptTouchEvent
03-25 13:08:07.489 24598-24598/com.example.dispatcheventapp E/ViewGroup: ENTER onTouchEvent
03-25 13:08:07.489 24598-24598/com.example.dispatcheventapp E/Activity: ENTER onTouchEvent
03-25 13:08:07.509 24598-24598/com.example.dispatcheventapp E/Activity: ENTER dispatchTouchEvent
03-25 13:08:07.509 24598-24598/com.example.dispatcheventapp E/Activity: ENTER onTouchEvent
03-25 13:08:07.509 24598-24598/com.example.dispatcheventapp E/Activity: ENTER dispatchTouchEvent
03-25 13:08:07.509 24598-24598/com.example.dispatcheventapp E/Activity: ENTER onTouchEvent
没有View能处理事件,就会和案例1一样结果。
思考2: 如果我down拦截,move和up不拦截又会怎么样?
能看到这说明你对事件传递已经有一个很深刻的认识了。上面的思考通过大脑也应该能想出来。我们来试试
down我们一旦拦截了,ViewGroup的mFirstTouchTarget就会等于null。这样的华以后压根就不会再调用onInterceptTouchEvent方法了。就算你设置了不拦截,结果还会是拦截。所以down事件会被ViewGroup的onTouchEvent处理,且返回false。这样整个VIewGroup的mFirstTouchTarget都会等于null。最后消息也都会由Activity来消化。