在Android中,事件主要包括点按、长按、拖拽、滑动等,点按又包括单击和双击,另外还包括单指操作和多指操作。所有这些都构成了Android中的事件响应。
总的来说,所有的事件都由如下三个部分作为基础:
按下(ACTION_DOWN)
移动(ACTION_MOVE)
抬起(ACTION_UP)
所有的操作事件首先必须执行的是按下操作(ACTION_DOWN),之后所有的操作都是以按下操作作为前提,当按下操作完成后,接下来可能是一段移动(ACTION_MOVE)然后抬起(ACTION_UP),或者是按下操作执行完成后没有移动就直接抬起。这一系列的动作在Android中都可以进行控制。我们知道,所有的事件操作都发生在触摸屏上,而在屏幕上与我们交互的就是各种各样的视图组件(View),在Android中,所有的视图都继承于View,另外通过各种布局组件(ViewGroup)来对View进行布局,ViewGroup也继承于View。所有的UI控件例如Button、TextView都是继承于View,而所有的布局控件例如RelativeLayout、容器控件例如ListView都是继承于ViewGroup。所以,我们的事件操作主要就是发生在View和ViewGroup之间,那么View和ViewGroup中主要有哪些方法来对这些事件进行响应呢?
记住如下3个方法,我们通过查看View和ViewGroup的源码可以看到:
Activity跟View类似,也没有onInterceptTouchEvent,有其他两个
在View和ViewGroup中都存在dispatchTouchEvent和onTouchEvent方法,但是在ViewGroup中还有一个onInterceptTouchEvent方法。
在Android中,所有的事件都是从开始经过传递到完成事件的消费,这些方法的返回值就决定了某一事件是否是继续往下传,还是被拦截了,或是被消费了。这些方法决定了整个事件到底该怎么传递。
这些方法的参数,都接受了一个MotionEvent类型的参数,MotionEvent继承于InputEvent,用于标记各种动作事件。之前提到的ACTION_DOWN、ACTION_MOVE、ACTION_UP都是MotinEvent中定义的常量。我们通过MotionEvent传进来的事件类型来判断接收的是哪一种类型的事件。到现在,这三个方法的返回值和参数你应该都明白了,接下来就解释一下这三个方法分别在什么时候处理事件。
package com.example.zhouyi.eventtest; /** * Created by zhouyi on 16/7/13. */ public class EventUtils { public static String getEvent(int iCode){ switch (iCode){ case 0: return "down"; case 1: return "up"; case 2: return "move"; } return "null"; } }
package com.example.zhouyi.eventtest; import android.content.Context; import android.util.AttributeSet; import android.util.Log; import android.view.MotionEvent; import android.widget.RelativeLayout; public class MyTop1Layout extends RelativeLayout { public MyTop1Layout(Context context) { super(context); } public MyTop1Layout(Context context, AttributeSet attrs) { super(context, attrs); } public MyTop1Layout(Context context, AttributeSet attrs, int defStyleAttr) { super(context, attrs, defStyleAttr); } @Override public boolean dispatchTouchEvent(MotionEvent ev) { Log.d("qf", "MyTop1Layout.dispatchTouchEvent.发生事件:"+ EventUtils.getEvent(ev.getAction())); boolean bRet = super.dispatchTouchEvent(ev); Log.d("qf","MyTop1Layout.dispatchTouchEvent.返回值:"+bRet); return bRet; } @Override public boolean onInterceptTouchEvent(MotionEvent ev) { Log.d("qf", "MyTop1Layout.onInterceptTouchEvent.发生事件:"+ EventUtils.getEvent(ev.getAction())); boolean bRet = super.onInterceptTouchEvent(ev); Log.d("qf","MyTop1Layout.onInterceptTouchEvent.返回值:"+bRet); return bRet; } @Override public boolean onTouchEvent(MotionEvent event) { Log.d("qf", "MyTop1Layout.onTouchEvent.发生事件:"+ EventUtils.getEvent(event.getAction())); boolean bRet = super.onTouchEvent(event); Log.d("qf","MyTop1Layout.onTouchEvent.返回值:"+bRet); return bRet; } }
<?xml version="1.0" encoding="utf-8"?> <com.example.zhouyi.eventtest.MyTop1Layout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent" android:layout_height="match_parent" tools:context="com.example.zhouyi.eventtest.MainActivity"> </com.example.zhouyi.eventtest.MyTop1Layout>
package com.example.zhouyi.eventtest; /** * Created by zhouyi on 16/7/13. */ public class EventUtils { public static String getEvent(int iCode){ switch (iCode){ case 0: return "down"; case 1: return "up"; case 2: return "move"; } return "null"; } }
package com.example.zhouyi.eventtest; import android.os.Bundle; import android.support.v7.app.AppCompatActivity; import android.util.Log; import android.view.MotionEvent; public class MainActivity extends AppCompatActivity { @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); } @Override public boolean dispatchTouchEvent(MotionEvent ev) { Log.d("qf", "MainActivity.dispatchTouchEvent.发生事件:"+ EventUtils.getEvent(ev.getAction())); boolean bRet = super.dispatchTouchEvent(ev); Log.d("qf","MainActivity.dispatchTouchEvent.返回值:"+bRet); return bRet; } @Override public boolean onTouchEvent(MotionEvent event) { Log.d("qf", "MainActivity.onTouchEvent.发生事件:"+ EventUtils.getEvent(event.getAction())); boolean bRet = super.onTouchEvent(event); Log.d("qf","MainActivity.onTouchEvent.返回值:"+bRet); return bRet; } }
package com.example.zhouyi.eventtest; import android.content.Context; import android.util.AttributeSet; import android.util.Log; import android.view.MotionEvent; import android.widget.RelativeLayout; public class MyTop1Layout extends RelativeLayout { public MyTop1Layout(Context context) { super(context); } public MyTop1Layout(Context context, AttributeSet attrs) { super(context, attrs); } public MyTop1Layout(Context context, AttributeSet attrs, int defStyleAttr) { super(context, attrs, defStyleAttr); } @Override public boolean dispatchTouchEvent(MotionEvent ev) { Log.d("qf", "MyTop1Layout.dispatchTouchEvent.发生事件:"+ EventUtils.getEvent(ev.getAction())); boolean bRet = super.dispatchTouchEvent(ev); Log.d("qf","MyTop1Layout.dispatchTouchEvent.返回值:"+bRet); return bRet; } @Override public boolean onInterceptTouchEvent(MotionEvent ev) { Log.d("qf", "MyTop1Layout.onInterceptTouchEvent.发生事件:"+ EventUtils.getEvent(ev.getAction())); boolean bRet = super.onInterceptTouchEvent(ev); Log.d("qf","MyTop1Layout.onInterceptTouchEvent.返回值:"+bRet); return bRet; } @Override public boolean onTouchEvent(MotionEvent event) { Log.d("qf", "MyTop1Layout.onTouchEvent.发生事件:"+ EventUtils.getEvent(event.getAction())); boolean bRet = super.onTouchEvent(event); Log.d("qf","MyTop1Layout.onTouchEvent.返回值:"+bRet); return bRet; } }
package com.example.zhouyi.eventtest; import android.content.Context; import android.util.AttributeSet; import android.util.Log; import android.view.MotionEvent; import android.widget.RelativeLayout; public class MyTop2Layout extends RelativeLayout { public MyTop2Layout(Context context) { super(context); } public MyTop2Layout(Context context, AttributeSet attrs) { super(context, attrs); } public MyTop2Layout(Context context, AttributeSet attrs, int defStyleAttr) { super(context, attrs, defStyleAttr); } @Override public boolean dispatchTouchEvent(MotionEvent ev) { Log.d("qf", "MyTop2Layout.dispatchTouchEvent.发生事件:"+ EventUtils.getEvent(ev.getAction())); boolean bRet = super.dispatchTouchEvent(ev); Log.d("qf","MyTop2Layout.dispatchTouchEvent.返回值:"+bRet); return bRet; } @Override public boolean onInterceptTouchEvent(MotionEvent ev) { Log.d("qf", "MyTop2Layout.onInterceptTouchEvent.发生事件:"+ EventUtils.getEvent(ev.getAction())); boolean bRet = super.onInterceptTouchEvent(ev); Log.d("qf","MyTop2Layout.onInterceptTouchEvent.返回值:"+bRet); return bRet; } @Override public boolean onTouchEvent(MotionEvent event) { Log.d("qf", "MyTop2Layout.onTouchEvent.发生事件:"+ EventUtils.getEvent(event.getAction())); boolean bRet = super.onTouchEvent(event); Log.d("qf","MyTop2Layout.onTouchEvent.返回值:"+bRet); return bRet; } }
package com.example.zhouyi.eventtest; import android.content.Context; import android.util.AttributeSet; import android.util.Log; import android.view.MotionEvent; import android.widget.Button; public class MyButton extends Button { 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); } @Override public boolean dispatchTouchEvent(MotionEvent ev) { Log.d("qf", "MyButton.dispatchTouchEvent.发生事件:"+ EventUtils.getEvent(ev.getAction())); boolean bRet = super.dispatchTouchEvent(ev); Log.d("qf","MyButton.dispatchTouchEvent.返回值:"+bRet); return bRet; } @Override public boolean onTouchEvent(MotionEvent event) { Log.d("qf", "MyButton.onTouchEvent.发生事件:"+ EventUtils.getEvent(event.getAction())); boolean bRet = super.onTouchEvent(event); Log.d("qf","MyButton.onTouchEvent.返回值:"+bRet); return bRet; } }
<?xml version="1.0" encoding="utf-8"?> <com.example.zhouyi.eventtest.MyTop1Layout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent" android:layout_height="match_parent" tools:context="com.example.zhouyi.eventtest.MainActivity"> <com.example.zhouyi.eventtest.MyTop2Layout android:layout_width="match_parent" android:layout_height="match_parent"> <com.example.zhouyi.eventtest.MyButton android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="测试" /> </com.example.zhouyi.eventtest.MyTop2Layout> </com.example.zhouyi.eventtest.MyTop1Layout>
- dispatchTouchEvent返回true,表示在本层view或者本层view的所有下级view中,有view处理完了这个事件(onTouchEvent返回了true)—上例中,最终是button的onTouchEvent返回了true,所以所有dispatchTouchEvent都返回了true
- dispatchTouchEvent返回fasle,表示在本层view或者本层view的所有下级view中,没有view处理完了这个事件(onTouchEvent都返回了false)—上例中,把button的onTouchEvent返回值改成false,再看结果,验证这个规则(所有的dispatch都返回了false)
- onInterceptTouchEvent返回false,表示本层view不拦截这个事件,交给下一层view去处理—上例中,button之上所有view的onInterceptTouchEvent都返回false,所以最终由button来处理事件(button是一个view,没有onInterceptTouchEvent方法,只要它的父控件没有拦截事件,就会调用button的onTouchEvent)
- onInterceptTouchEvent返回true,表示本层view拦截了这个事件,这个事件不会再往下传递了,而是交给本层的ontouchevent处理(调用本层view的ontouchevent)—上例中,把top1或top2layout的onInterceptTouchEvent的返回值改为true验证一下(返回true的那一层的ontouchevent调用了)
- onTouchEvent返回true表示已经处理完这个事件了,不需要父控件再处理了—比如上例中,只有button调用了onTouchEvent
- onTouchEvent返回false表示还没有处理完这个事件,传给父控件继续处理,直到某一层父控件的onTouchEvent返回了true(或调用完activity的onTouchEvent,这时再也没法上溯了)—上例中把button的onTouchEvent返回值改成false,查看结果(所有层级的onTouchEvent都调用了)
- 事件从父布局(activity开始)开始分发,如果本级别view没有拦截(onInterceptTouchEvent返回false),一直分发到直接感应触摸的控件(或布局),如果在某个级别的view拦截了事件(onInterceptTouchEvent返回true),不管本级别有没有消费这个事件(onTouchEvent的返回值),都不会再让其子view分发事件了(其子view的所有事件方法都不会调用了)—上例中top2layout的拦截返回true,则button的事件方法都不会调用了
- ViewGroup的事件被拦截,或者View的事件没有被父控件拦截,都会调用本view(或ViewGroup)的事件处理方法(onTouchEvent),这个方法执行完毕后,如果返回true,表示已经处理完,不会让父控件再处理,如果返回false,则会让父控件再处理(调用父控件的onTouchEvent),一直到某个级别的父控件的onTouchEvent返回true为止,或调用完activity的onTouchEvent为止
- down事件在哪个级别的view消费的,则后续的up事件也会在那个级别的view上消费(调用那个级别的onTouchEvent),如果那个级别是一个ViewGroup,那个级别的onInterceptTouchEvent不会调用了,那个级别的dispatch还会调用,那个级别之上的父view的onInterceptTouchEvent和dispatch都还照样调用
package com.example.zhouyi.eventtest; import android.os.Bundle; import android.support.v7.app.AppCompatActivity; import android.util.Log; import android.view.MotionEvent; import android.view.View; public class MainActivity extends AppCompatActivity { MyButton mBtnTest; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); mBtnTest = (MyButton) findViewById(R.id.btntest); mBtnTest.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { } }); mBtnTest.setOnTouchListener(new View.OnTouchListener() { @Override public boolean onTouch(View v, MotionEvent event) { return false; } }); } @Override public boolean dispatchTouchEvent(MotionEvent ev) { Log.d("qf", "MainActivity.dispatchTouchEvent.发生事件:"+ EventUtils.getEvent(ev.getAction())); boolean bRet = super.dispatchTouchEvent(ev); Log.d("qf","MainActivity.dispatchTouchEvent.返回值:"+bRet); return bRet; } @Override public boolean onTouchEvent(MotionEvent event) { Log.d("qf", "MainActivity.onTouchEvent.发生事件:"+ EventUtils.getEvent(event.getAction())); boolean bRet = super.onTouchEvent(event); Log.d("qf","MainActivity.onTouchEvent.返回值:"+bRet); return bRet; } }
mBtnTest.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { Log.d("qf","MyButton.onClick"); } }); mBtnTest.setOnTouchListener(new View.OnTouchListener() { @Override public boolean onTouch(View v, MotionEvent event) { Log.d("qf","MyButton.onTouch"); return false; } });