Android触摸事件分发机制(1)之View
记得大家刚开始接触安卓的时候,一个setOnClickListener就能实现一个View的点击,当时是如此的激动~。这大概就是大家对Android触摸事件最初的接触吧。今天我们来聊下Android重要的触摸事件分发机制。
例子
我们来举一个栗子吧~
MyButton.java
public class MyButton extends Button { @Override public boolean onTouchEvent(MotionEvent event) { int action = event.getAction(); switch (action) { case MotionEvent.ACTION_DOWN: Log.e("w", "onTouchEvent ACTION_DOWN"); break; case MotionEvent.ACTION_MOVE: Log.e("w", "onTouchEvent ACTION_MOVE"); break; case MotionEvent.ACTION_UP: Log.e("w", "onTouchEvent ACTION_UP"); break; default: break; } return super.onTouchEvent(event); } @Override public boolean dispatchTouchEvent(MotionEvent event) { int action = event.getAction(); switch (action) { case MotionEvent.ACTION_DOWN: Log.e("w", "dispatchTouchEvent ACTION_DOWN"); break; case MotionEvent.ACTION_MOVE: Log.e("w", "dispatchTouchEvent ACTION_MOVE"); break; case MotionEvent.ACTION_UP: Log.e("w", "dispatchTouchEvent ACTION_UP"); break; default: break; } return super.dispatchTouchEvent(event); } }
在onOutchEvent和disatchTouchEvent中打印日志
main_activity.xml
MainActivity.java
public class MainActivity extends Activity { private Button mButton ; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); mButton = (Button) findViewById(R.id.id_btn); mButton.setOnTouchListener(new OnTouchListener() { @Override public boolean onTouch(View v, MotionEvent event) { int action = event.getAction(); switch (action) { case MotionEvent.ACTION_DOWN: Log.e(“w”, "onTouch ACTION_DOWN"); break; case MotionEvent.ACTION_MOVE: Log.e(“w”, "onTouch ACTION_MOVE"); break; case MotionEvent.ACTION_UP: Log.e(“w”, "onTouch ACTION_UP"); break; default: break; } return false; } }); } }
我们在MyButton设置了OnTouchListener监听,
我们看一下Log的打印
标志 | 消息 |
---|---|
E/MyButton(879): | dispatchTouchEvent ACTION_DOWN |
E/MyButton(879): | onTouch ACTION_DOWN |
E/MyButton(879): | onTouchEvent ACTION_DOWN |
E/MyButton(879): | dispatchTouchEvent ACTION_MOVE |
E/MyButton(879): | onTouch ACTION_MOVE |
E/MyButton(879): | onTouchEvent ACTION_MOVE |
E/MyButton(879): | dispatchTouchEvent ACTION_UP |
E/MyButton(879): | onTouch ACTION_UP |
E/MyButton(879): | onTouchEvent ACTION_UP` |
按照上面的简单实例我们可以简单得出一个结论:View的事件分发无论DOWN、MOVE、UP都会经过dispatchTouchEvent
、onTouch
(如果设置的话)、onTouchEvent
源码解读:
Step1 View
public boolean dispatchTouchEvent(MotionEvent event) { ... boolean result = false; ... //重点判断onTouch if (li != null && li.mOnTouchListener != null && (mViewFlags & ENABLED_MASK) == ENABLED && li.mOnTouchListener.onTouch(this, event)) { result = true; } //重点判断onTouchEvent if (!result && onTouchEvent(event)) { result = true; } ... return result;}
先判断mOnTouchListener是否为空?改View是否为Enable?onTouch是否返回true?若三个同时成立,返回true,且onTouchEvent不会执行。
mOnTouchListener从哪里来呢?
public void setOnTouchListener(OnTouchListener l) { mOnTouchListener = l; }
可以看到这就是栗子中Activity.java里mButton.setOnTouchListener
设置的。
如果我们设置了onTouchListener,且设置返回为true,那么View的onTouchEvent就不会执行!
Step2 View
public boolean onTouchEvent(MotionEvent event) { final int viewFlags = mViewFlags; //情况1:如果view为disenable且可点击,返回true //此情况还是会消费此触摸事件,只是不做反应罢了 if ((viewFlags & ENABLED_MASK) == DISABLED) { return (((viewFlags & CLICKABLE) == CLICKABLE || (viewFlags & LONG_CLICKABLE) == LONG_CLICKABLE)); } ... //情况2:如果View为enable且可点击,返回ture //大部分的触摸操纵都在这里面 if (((viewFlags & CLICKABLE) == CLICKABLE || (viewFlags & LONG_CLICKABLE) == LONG_CLICKABLE)) { switch (event.getAction()) { case MotionEvent.ACTION_UP: //这是Part02 break; case MotionEvent.ACTION_DOWN: //这是Part01 break; case MotionEvent.ACTION_CANCEL: //这是Part04 break; case MotionEvent.ACTION_MOVE: //这是Part03 break; } return true; } //情况3:如果View为enable但不能点击,直接返回false return false; }
在onTouchEvent工有3个主要情况:
- 情况1:如果view为disEnable且clickable,返回true。此情况还是会消费此触摸事件,只是不做反应
- 情况2:如果View为enable且clickable,返回ture。大部分的触摸操纵都在这里面
- 情况3:如果View为enable但unClickable,直接返回false。其实就是View为unClickable,基本就是返回false了。
view的enable和clickable都可以在java和xml设置。
以上代码可以看出,onToucheEvent里的重点操作都在switch里了,这里我们分几个步骤进行分析
Part01 ACTION_DOWN
case MotionEvent.ACTION_DOWN: if (mPendingCheckForTap == null) { mPendingCheckForTap = new CheckForTap(); } mPrivateFlags |= PREPRESSED; mHasPerformedLongPress = false; postDelayed(mPendingCheckForTap, ViewConfiguration.getTapTimeout()); break;
- 初始化CheckForTap,此类为Runnable
- 给mPrivateFlags设置一个PREPRESSED的标识
- 设置mHasPerformedLongPress=false;表示长按事件还未触发;
- 发送一个延迟为ViewConfiguration.getTapTimeout()=115的延迟消息,到达延时时间后会执行CheckForTap()里面的run方法:
CheckForTap
private final class CheckForTap implements Runnable { public void run() { mPrivateFlags &= ~PREPRESSED; mPrivateFlags |= PRESSED; refreshDrawableState(); //检测长按 if ((mViewFlags & LONG_CLICKABLE) == LONG_CLICKABLE) { postCheckForLongClick(ViewConfiguration.getTapTimeout()); } } }
- 取消mPrivateFlags的PREPRESSED
- 设置PRESSED标识
- 刷新背景
- 如果View支持长按事件,则再发一个延时消息,检测长按;
具体如何检测长按呢?
private void postCheckForLongClick(int delayOffset) { mHasPerformedLongPress = false; if (mPendingCheckForLongPress == null) { mPendingCheckForLongPress = new CheckForLongPress(); } mPendingCheckForLongPress.rememberWindowAttachCount(); postDelayed(mPendingCheckForLongPress, ViewConfiguration.getLongPressTimeout() - delayOffset); }
- 初始化CheckForLongPress,此类为Runnable
- 发送一个延迟为ViewConfiguration.getLongPressTimeout() - delayOffset=(500-115=385)的延迟消息,到达延时时间后会执行CheckForLongPress()里面的run方法:
CheckForLongPress
class CheckForLongPress implements Runnable { private int mOriginalWindowAttachCount; public void run() { if (isPressed() && (mParent != null) && mOriginalWindowAttachCount == mWindowAttachCount) { if (performLongClick()) { mHasPerformedLongPress = true; } } }
经过一系列判断,最终调用performLongClick()即长按的接口调用。
这里我们可以得出一个小结论:
当用户点击视图时,超过500ms后且设置了长按监听的话,会触发长按监听接口!
Wonder疑问
- 那当用户在500ms内将手抬起会是什么情况呢?
- LongClick已经有了,那我们平时使用的的Click呢?
Part02 ACTION_UP
case MotionEvent.ACTION_UP: //判断mPrivateFlags是否包含PREPRESSED boolean prepressed = (mPrivateFlags & PREPRESSED) != 0; //如果包含PRESSED或者PREPRESSED则进入执行体,在115ms的前后抬起都会进入执行体。 if ((mPrivateFlags & PRESSED) != 0 || prepressed) { //如果该视图还未获取焦点,则获之 boolean focusTaken = false; if (isFocusable() && isFocusableInTouchMode() && !isFocused()) { focusTaken = requestFocus(); } //判断是否为长按状态,不是的话,进入执行体 if (!mHasPerformedLongPress) { //这是一个轻点击操作,所以要移除长按检测操作 removeLongPressCallback(); //只有在按下状态时才执行点击动作 if (!focusTaken) { //使用Runnable进行发送消息,而不是直接执行performClick。 //这样视图可以在点击操作前更新其可视化状态 if (mPerformClick == null) { mPerformClick = new PerformClick();//*重点01* } //重点 * 调用平时用的click if (!post(mPerformClick)) { performClick(); } } } if (mUnsetPressedState == null) { mUnsetPressedState = new UnsetPressedState();//*重点02* } //根据视图的mPrivateFlags的状态进行操作 if (prepressed) { mPrivateFlags |= PRESSED; refreshDrawableState(); postDelayed(mUnsetPressedState, ViewConfiguration.getPressedStateDuration()); } else if (!post(mUnsetPressedState)) { // If the post failed, unpress right now mUnsetPressedState.run(); } //移除点击事件的检测操作 removeTapCallback(); } break;
mPrivateFlags的状态:125ms前为prepressed(点击前),125ms后位pressed(点击后)。以上代码已经做了注释。这些操作就是500ms内的点击操作处理。
以上有两个比较重要的点,这里分析一下:
PerformClick
private final class PerformClick implements Runnable { @Override public void run() { performClick(); }}public boolean performClick() { final boolean result; final ListenerInfo li = mListenerInfo; if (li != null && li.mOnClickListener != null) { playSoundEffect(SoundEffectConstants.CLICK); li.mOnClickListener.onClick(this);//<----------这里 result = true; } else { result = false; } sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_CLICKED); return result;} //设置点击事件回调public void setOnClickListener(OnClickListener l) { if (!isClickable()) { setClickable(true); } getListenerInfo().mOnClickListener = l;}
以上代码可以很清楚的看到onClick的调用!
UnsetPressedState
private final class UnsetPressedState implements Runnable { public void run() { setPressed(false); }}public void setPressed(boolean pressed) { if (pressed) { mPrivateFlags |= PRESSED; } else { mPrivateFlags &= ~PRESSED; } refreshDrawableState(); dispatchSetPressed(pressed);}
我们可以看到无论如何这个Runnable都会执行,只是对不同的状态(prePressed,pressed)进行处理。修改mPrivateFlags的状态,刷新背景,分发SetPress等。
这里我们可以得出一个小结论:
当用户点击视图时,低于500ms设置onClick的接口,就会触发onClick的接口。且这个过程是在OnTouchEvent的ACTION_UP完成。
Part03 ACTION_MOVE
case MotionEvent.ACTION_MOVE: final int x = (int) event.getX(); final int y = (int) event.getY(); //判断该触摸事件是否已经移出控件外 int slop = mTouchSlop; if ((x < 0 - slop) || (x >= getWidth() + slop) || (y < 0 - slop) || (y >= getHeight() + slop)) { //当触摸移出当前视图 //移除点击回调 removeTapCallback(); if ((mPrivateFlags & PRESSED) != 0) { //移除长按检测 removeLongPressCallback(); //mPrivateFlags去除PRESSED标志 mPrivateFlags &= ~PRESSED; //刷新背景 refreshDrawableState(); } } break;
ACTION_MOVE的工作相对简单一点:不断的记录x,y。判断当前触摸事件是否已经移除当前控件之外?如果移除了,移除相对应的检测回调,以及刷新相对应的变量和背景。
Part04 ACTION_CANCLE
case MotionEvent.ACTION_CANCEL: mPrivateFlags &= ~PRESSED; refreshDrawableState(); removeTapCallback(); break;
ACTION_CANCEL的工作主要是:刷新相对应的变量和背景,移除响度应的检测回调。一般遇到的比较少。有一种情况是:当用户保持按下操作,并从你的控件转移到外层控件时,会触发ACTION_CANCEL。
总结 Summary
- View的事件转发流程为:View.dispatchEvent->View.setOnTouchListener->View.onTouchEvent
- 在dispatchTouchEvent中会进行OnTouchListener的判断,如果OnTouchListener不为null且返回true,则表示事件被消费,onTouchEvent不会被执行;否则执行onTouchEvent。
- 长按点击的回调是在ACTION_DOWN调用的。
- 轻按点击的回调是在ACTION_UP调用的。
- 判断触摸事件是否移除了当前控件是在ACTION_MOVE监听的。
额外 Extra
我们在来举一个栗子:
public class MainActivity extends Activity { private Button mButton ; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); mButton = (Button) findViewById(R.id.id_btn); mButton.setOnClickListener(new OnClickListener() { @Override public void onClick(View v) { Log.e("e", "轻触点击"); } }); mButton.setOnLongClickListener(new OnLongClickListener() { @Override public boolean onLongClick(View v) { Log.e("e", "长按点击"); return false; } }); } }
如果onLongClick返回的是ture(表示消费了),则onClick无法触发。如果返回false,就可以。大家可以结合下上面的代码解析看看为什么会这样~
Android触摸事件分发机制(2)之ViewGroup
PS:本文
整理
自以下文章,若有发现问题请致邮 [email protected]
工匠若水 Android触摸屏事件派发机制详解与源码分析一(View篇)
Hohohong Android View 事件分发机制 源码解析 (上)
更多相关文章
- Android手势操作示例(上/下/左/右的判断)
- Android蓝牙操作
- Android事件分发机制研究
- Android 4.0 事件输入(Event Input)系统
- Ubuntu 14.04 64位机上配置Android Studio操作步骤
- android中的事件总线
- view对touch事件的处理
- Android Notes(06) - Touch事件分发响应机制