Android事件分发笔记
2016-04-14 18:04
381 查看
前言
以前在学习Android的时候接触过一次Android事件分发,时间久远就忘记了,前段时间项目碰到一个需求,一个ListviewItem里面嵌套了一个Edittext,然后碰到了各种各样的问题,最后还是用事件分发的知识处理了,记录是最好的学习方式,所以就写了一篇关于时间分发的笔记,以备过后忘记的时候可以看看。1.布局没有别的控件,只有一个activity的时候
<span style="font-size:14px;">public class Activity140 extends Activity{ private static final String TAG = "print"; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_do); } @Override public boolean dispatchTouchEvent(MotionEvent ev) { switch (ev.getAction()) { case MotionEvent.ACTION_DOWN: Log.d(TAG, "Activity-->dispatchTouchEvent-->ACTION_DOWN"); break; case MotionEvent.ACTION_MOVE: Log.d(TAG, "Activity-->dispatchTouchEvent-->ACTION_MOVE"); break; case MotionEvent.ACTION_UP: Log.d(TAG, "Activity-->dispatchTouchEvent-->ACTION_UP"); break; } return super.dispatchTouchEvent(ev); } @Override public boolean onTouchEvent(MotionEvent event) { switch (event.getAction()) { case MotionEvent.ACTION_DOWN: Log.d(TAG, "Activity-->onTouchEvent-->ACTION_DOWN"); break; case MotionEvent.ACTION_MOVE: Log.d(TAG, "Activity-->onTouchEvent-->ACTION_MOVE"); break; case MotionEvent.ACTION_UP: Log.d(TAG, "Activity-->onTouchEvent-->ACTION_UP"); break; } return super.onTouchEvent(event); } }</span>
Log如下:
、
可以看到,事件是从dispatchTouchEvent开始,一个完整的手势是从ACTION_DOWN开始,ACTION_UP结束,不难理解,dispatch英文就是分发的意思,返回super的源码为:
<span style="font-size:14px;">/** * 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); }</span>
其中onUserInteraction();是一个空函数,然后下面有个,下面这个方法分析起来很复杂,涉及很多东西,如果有兴趣可以研究一下,我就直结论接说好了,如果这个顶层的window下的子view消耗了这个事件,就直接返回true,不然就把事件传递给Activity的onTouchevent去处理,所以这也就是为什么当只有一个Activity时dispatchTouchEvent返回super时是默认往本类的onTouchEvent里传的原因
1.2.把Activity的dispatchTouchEvent返回值改成true
log为:从log可以看出,事件并没有被分发,而是由dispatchTouchEvent进行消费。
1.3把Activity的dispatchTouchEvent返回值改成false
log为:从log可以看出事件也没有进行分发,似乎感觉返回true和false都是一样的
2.增加一个子控件到布局中:
public class CustomBtnView extends Button { private static final String TAG = "print"; public CustomBtnView(Context context) { super(context); } public CustomBtnView(Context context, AttributeSet attrs) { super(context, attrs); } @Override public boolean dispatchTouchEvent(MotionEvent ev) { switch (ev.getAction()) { case MotionEvent.ACTION_DOWN: Log.d(TAG, "Button-->dispatchTouchEvent-->ACTION_DOWN"); break; case MotionEvent.ACTION_MOVE: Log.d(TAG, "Button-->dispatchTouchEvent-->ACTION_MOVE"); break; case MotionEvent.ACTION_UP: Log.d(TAG, "Button-->dispatchTouchEvent-->ACTION_UP"); break; } return super.dispatchTouchEvent(ev); } @Override public boolean onTouchEvent(MotionEvent event) { switch (event.getAction()) { case MotionEvent.ACTION_DOWN: Log.d(TAG, "Button-->onTouchEvent-->ACTION_DOWN"); break; case MotionEvent.ACTION_MOVE: Log.d(TAG, "Button-->onTouchEvent-->ACTION_MOVE"); break; case MotionEvent.ACTION_UP: Log.d(TAG, "Button-->onTouchEvent-->ACTION_UP"); break; } return super.onTouchEvent(event); } }
log为:
从log可以看出,Activity的dispatchTouchEvent最先接到用户的点击事件,先接到的是按下这个动作,然后事件并没有由Activity的onTouchEvent进行处理而是往下传递到了Button的dispatchTouchEvent,然后在让button的onTouchEvent对这个点击事件进行处理
2.1.把Button的dispatchTouchEvent返回值改为false
log为:从log可以看出,down事件传递到button的dispatchTouchEvent方法时,返回为false的情况下,down事件并没有被传递给button的onTouchEvent方法,而是向上传递给Activity的onTouchEvent方法,随后的UP事件就不会传递到Button而是直接交由Activity的onTouchEvent方法进行消费,这也比较符合我们平常的逻辑。
2.2.把Button的dispatchTouchEvent返回值改为true
log为:从log可以看出,事件传递到Button的dispatchTouchEvent方法时并没有往下传了,它自己消费了这个点击事件
2.3.把Button的onTouchEvent返回值改成false
log为:从log可以看出,Down事件传递到Button的onTouchEvent方法时由于返回值false,所以事件又被传递到Activity的onTouchEvent方法中进行处理,接下来的Up事件直接交由Activity的onTouchEvent处理
3.用一个LinearLayout嵌套Button
public class CustomBtnLinearLayout extends LinearLayout { private static final String TAG = "print"; public CustomBtnLinearLayout(Context context) { super(context); } public CustomBtnLinearLayout(Context context, AttributeSet attrs) { super(context, attrs); } @Override public boolean dispatchTouchEvent(MotionEvent ev) { switch (ev.getAction()) { case MotionEvent.ACTION_DOWN: Log.d(TAG, "LinearLayout-->dispatchTouchEvent-->ACTION_DOWN"); break; case MotionEvent.ACTION_MOVE: Log.d(TAG, "LinearLayout-->dispatchTouchEvent-->ACTION_MOVE"); break; case MotionEvent.ACTION_UP: Log.d(TAG, "LinearLayout-->dispatchTouchEvent-->ACTION_UP"); break; } return super.dispatchTouchEvent(ev); } @Override public boolean onTouchEvent(MotionEvent event) { switch (event.getAction()) { case MotionEvent.ACTION_DOWN: Log.d(TAG, "LinearLayout-->onTouchEvent-->ACTION_DOWN"); break; case MotionEvent.ACTION_MOVE: Log.d(TAG, "LinearLayout-->onTouchEvent-->ACTION_MOVE"); break; case MotionEvent.ACTION_UP: Log.d(TAG, "LinearLayout-->onTouchEvent-->ACTION_UP"); break; } return super.onTouchEvent(event); } }
布局为:
<?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="match_parent"> <com.example.sjr.all.customview.CustomBtnLinearLayout android:layout_width="400dp" android:layout_height="400dp" android:background="#9c6d6d"> <com.example.sjr.all.customview.CustomBtnView android:id="@+id/btn_cus" android:layout_width="300dp" android:layout_height="300dp" android:text="点我" /> </com.example.sjr.all.customview.CustomBtnLinearLayout> </LinearLayout>
log为:
从log可以看出,事件是从Activity的dispatchTouchEvent传递给LinearLayout的dispatchTouchEvent然后不是传递给LinearLayout的onTouchEvent而是往下传递给Button的dispatchTouchEvent然后由Button的onTouchEvent对事件进行处理
3.1.把Button的onTouchEvent方法返回值改成false
log为:从log可以看出,DOWN事件往上传递由Button的父控件LinearLayout的onTouchEvent方法进行处理,此时onTouchEvent没有重写,返回的是super,所以事件又往上传递到LinearLayout的父窗体也就是Activity的onTouchEvent进行处理,之后的Up事件就直接由Activity的dispatchTouchEvent传递本类的onTouchEvent方法进行处理
然后我们把LinearLayout的onTouchevent的返回值也改成false,log为:
从log可以看出,事件同super一样也是往上进行传递
super.onTOuchEvent的源码为
/** * Implement this method to handle touch screen motion events. * <p> * If this method is used to detect click actions, it is recommended that * the actions be performed by implementing and calling * {@link #performClick()}. This will ensure consistent system behavior, * including: * <ul> * <li>obeying click sound preferences * <li>dispatching OnClickListener calls * <li>handling {@link AccessibilityNodeInfo#ACTION_CLICK ACTION_CLICK} when * accessibility features are enabled * </ul> * * @param event The motion event. * @return True if the event was handled, false otherwise. */ public boolean onTouchEvent(MotionEvent event) { final float x = event.getX(); final float y = event.getY(); final int viewFlags = mViewFlags; final int action = event.getAction(); if ((viewFlags & ENABLED_MASK) == DISABLED) { if (action == MotionEvent.ACTION_UP && (mPrivateFlags & PFLAG_PRESSED) != 0) { setPressed(false); } // A disabled view that is clickable still consumes the touch // events, it just doesn't respond to them. return (((viewFlags & CLICKABLE) == CLICKABLE || (viewFlags & LONG_CLICKABLE) == LONG_CLICKABLE) || (viewFlags & CONTEXT_CLICKABLE) == CONTEXT_CLICKABLE); } if (mTouchDelegate != null) { if (mTouchDelegate.onTouchEvent(event)) { return true; } } if (((viewFlags & CLICKABLE) == CLICKABLE || (viewFlags & LONG_CLICKABLE) == LONG_CLICKABLE) || (viewFlags & CONTEXT_CLICKABLE) == CONTEXT_CLICKABLE) { switch (action) { case MotionEvent.ACTION_UP: boolean prepressed = (mPrivateFlags & PFLAG_PREPRESSED) != 0; if ((mPrivateFlags & PFLAG_PRESSED) != 0 || prepressed) { // take focus if we don't have it already and we should in // touch mode. boolean focusTaken = false; if (isFocusable() && isFocusableInTouchMode() && !isFocused()) { focusTaken = requestFocus(); } if (prepressed) { // The button is being released before we actually // showed it as pressed. Make it show the pressed // state now (before scheduling the click) to ensure // the user sees it. setPressed(true, x, y); } if (!mHasPerformedLongPress && !mIgnoreNextUpEvent) { // This is a tap, so remove the longpress check removeLongPressCallback(); // Only perform take click actions if we were in the pressed state if (!focusTaken) { // Use a Runnable and post this rather than calling // performClick directly. This lets other visual state // of the view update before click actions start. if (mPerformClick == null) { mPerformClick = new PerformClick(); } if (!post(mPerformClick)) { performClick(); } } } if (mUnsetPressedState == null) { mUnsetPressedState = new UnsetPressedState(); } if (prepressed) { postDelayed(mUnsetPressedState, ViewConfiguration.getPressedStateDuration()); } else if (!post(mUnsetPressedState)) { // If the post failed, unpress right now mUnsetPressedState.run(); } removeTapCallback(); } mIgnoreNextUpEvent = false; break; case MotionEvent.ACTION_DOWN: mHasPerformedLongPress = false; if (performButtonActionOnTouchDown(event)) { break; } // Walk up the hierarchy to determine if we're inside a scrolling container. boolean isInScrollingContainer = isInScrollingContainer(); // For views inside a scrolling container, delay the pressed feedback for // a short period in case this is a scroll. if (isInScrollingContainer) { mPrivateFlags |= PFLAG_PREPRESSED; if (mPendingCheckForTap == null) { mPendingCheckForTap = new CheckForTap(); } mPendingCheckForTap.x = event.getX(); mPendingCheckForTap.y = event.getY(); postDelayed(mPendingCheckForTap, ViewConfiguration.getTapTimeout()); } else { // Not inside a scrolling container, so show the feedback right away setPressed(true, x, y); checkForLongClick(0); } break; case MotionEvent.ACTION_CANCEL: setPressed(false); removeTapCallback(); removeLongPressCallback(); mInContextButtonPress = false; mHasPerformedLongPress = false; mIgnoreNextUpEvent = false; break; case MotionEvent.ACTION_MOVE: drawableHotspotChanged(x, y); // Be lenient about moving outside of buttons if (!pointInView(x, y, mTouchSlop)) { // Outside button removeTapCallback(); if ((mPrivateFlags & PFLAG_PRESSED) != 0) { // Remove any future long press/tap checks removeLongPressCallback(); setPressed(false); } } break; } return true; } return false; }
截取源码分析:
if ((viewFlags & ENABLED_MASK) == DISABLED) { if (action == MotionEvent.ACTION_UP && (mPrivateFlags & PFLAG_PRESSED) != 0) { setPressed(false); } // A disabled view that is clickable still consumes the touch // events, it just doesn't respond to them. return (((viewFlags & CLICKABLE) == CLICKABLE || (viewFlags & LONG_CLICKABLE) == LONG_CLICKABLE) || (viewFlags & CONTEXT_CLICKABLE) == CONTEXT_CLICKABLE); }
如果view被禁用了,虽然对事件不做响应,但还是要消费这个事件
if (mTouchDelegate != null) { if (mTouchDelegate.onTouchEvent(event)) { return true; } }
如果这个view有一个代理,事件就交由这个代理去处理,如果代理消费了这个事件,则返回true,view不用处理这个事件
if (((viewFlags & CLICKABLE) == CLICKABLE || (viewFlags & LONG_CLICKABLE) == LONG_CLICKABLE) || (viewFlags & CONTEXT_CLICKABLE) == CONTEXT_CLICKABLE) { switch (action) {
....
如果view不可点击就返回false,不处理这个事件,不然就是一系列处理DOWN、MOVE、UP、CANCEL事件的逻辑.篇幅有限就不详细解释,如果想深入了解得可以自己研究一下
3.2把Button的dispatchTouchEvent改成false
log为:跟上面有点类似Dow事件传递到Button的dispatchTOuchEvent时没有传递到本类的onTouchEvent而是传递给父控件的onTouchEvent再传递到最顶层Activity的onTouchEvent进行处理
3.3把LinearLayout的dispatchTouchEvent改成false
log为:从log可以看出DOWN事件从Activity的dispatchTouchEvent传递到了LInearLayout的dispatchTouchEvent然后又传递到Activity的onTouchEvent方法并由这个方法对Down事件进行处理
3.4在LinearLayout中添加onInterceptTouchEvent方法
代码为:@Override public boolean onInterceptTouchEvent(MotionEvent ev) { int action = ev.getAction(); switch (action) { case MotionEvent.ACTION_DOWN: Log.d(TAG, "LinearLayout--onInterceptTouchEvent--ACTION_DOWN"); break; case MotionEvent.ACTION_MOVE: Log.d(TAG, "LinearLayout--onInterceptTouchEvent--ACTION_MOVE"); break; case MotionEvent.ACTION_UP: Log.d(TAG, "LinearLayout--onInterceptTouchEvent--ACTION_UP"); break; } return super.onInterceptTouchEvent(ev); }
log为:
从log可以看出,事件传递到LinearLayout的dispatchTouchEvent方法的时候在接着传递到onIterceptTouchEvent方法,然后传递到Buton的dispatchonTouchEvent最后交由Button的onTouchEvent进行处理,即默认是不拦截,super.onTerceptTouchEvent方法的源码为
/** * Implement this method to intercept all touch screen motion events. This * allows you to watch events as they are dispatched to your children, and * take ownership of the current gesture at any point. * * <p>Using this function takes some care, as it has a fairly complicated * interaction with {@link View#onTouchEvent(MotionEvent) * View.onTouchEvent(MotionEvent)}, and using it requires implementing * that method as well as this one in the correct way. Events will be * received in the following order: * * <ol> * <li> You will receive the down event here. * <li> The down event will be handled either by a child of this view * group, or given to your own onTouchEvent() method to handle; this means * you should implement onTouchEvent() to return true, so you will * continue to see the rest of the gesture (instead of looking for * a parent view to handle it). Also, by returning true from * onTouchEvent(), you will not receive any following * events in onInterceptTouchEvent() and all touch processing must * happen in onTouchEvent() like normal. * <li> For as long as you return false from this function, each following * event (up to and including the final up) will be delivered first here * and then to the target's onTouchEvent(). * <li> If you return true from here, you will not receive any * following events: the target view will receive the same event but * with the action {@link MotionEvent#ACTION_CANCEL}, and all further * events will be delivered to your onTouchEvent() method and no longer * appear here. * </ol> * * @param ev The motion event being dispatched down the hierarchy. * @return Return true to steal motion events from the children and have * them dispatched to this ViewGroup through onTouchEvent(). * The current target will receive an ACTION_CANCEL event, and no further * messages will be delivered here. */ public boolean onInterceptTouchEvent(MotionEvent ev) { return false; }
3.5.把onTerceptTouchEvent的返回值改成true
log为:从log可以看出,DOWN事件被拦截了,然后不会把事件往下传递给button而是传递给本类的onTouchEvent再传递到Activity的onTouchEvent进行处理,而后的UP事件直接由Activity的onTouchEvent进行处理
3.6给Button添加OnClickListener监听
log为:从log可以看出,onClick是最后才执行的,也就是完成了这一个完整的手势处理以后最后才会响应这个监听
OnclickListener的源码为
/** * Interface definition for a callback to be invoked when a view is clicked. */ public interface OnClickListener { /** * Called when a view has been clicked. * * @param v The view that was clicked. */ void onClick(View v); }
从注释可以看出,这个方法是在view已经被点击了才会被回调,所以onClick最后执行,如果你想深入研究Android是怎么让这个接口在一个完整手势之后才会被触发可以研究一下view类的源码,这里不再累赘..
3.7给Button添加OnTouchListener监听且返回值为false
log为:从log可以看出,当事件传递到Button的dispatchTouchEvent时,会调用oTouche方法,由于返回了false所以事件接着由Button的onTouchEvent进行消费。
OnTouchListener的源码为:
/** * Interface definition for a callback to be invoked when a touch event is * dispatched to this view. The callback will be invoked before the touch * event is given to the view. */ public interface OnTouchListener { /** * Called when a touch event is dispatched to a view. This allows listeners to * get a chance to respond before the target view. * * @param v The view the touch event has been dispatched to. * @param event The MotionEvent object containing full information about * the event. * @return True if the listener has consumed the event, false otherwise. */ boolean onTouch(View v, MotionEvent event); }
可以看到,onTouch被触发在事件分发到一个view之后,然后可以在view响应事件前对这个事件进行响应,所以当onTouch方法返回值改为true时
就可以把事件交由自己处理而不往下传,log也验证了我们的猜测
由于事件在view响应前已经被响应,所以最后的onClick方法也不会被触发.
3.8给Button添加OnLongClickListener,且返回值为false
当长按时log为:可以看到当一次长按事件的DOWN结束时会回调这个方法,因为返回false而长按也算一次点击事件,所以最后还是回调了onClick方法
3.9给Button添加OnLongClickListener,且返回值为true
当长按时,log为:OnLongClickListener源码为:
/** * Interface definition for a callback to be invoked when a view has been clicked and held. */ public interface OnLongClickListener { /** * Called when a view has been clicked and held. * * @param v The view that was clicked and held. * * @return true if the callback consumed the long click, false otherwise. */ boolean onLongClick(View v); }
可以看到这是在view已经被点击而且保持这个状态时会回调这个方法。
总结
Android事件分发可以总结为这句话,隧道式传递,冒泡式处理,即事件从最顶层的view传递到最底层的view, 当最底层不处理时依次往上一层进行处理,当不进行处理时,即默认super时,事件传递时默认是往下传也就是不进行消费,而底层view不处理时默认往上也是不进行消费。相关文章推荐
- 使用C++实现JNI接口需要注意的事项
- Android IPC进程间通讯机制
- Android Manifest 用法
- [转载]Activity中ConfigChanges属性的用法
- Android之获取手机上的图片和视频缩略图thumbnails
- Android之使用Http协议实现文件上传功能
- Android学习笔记(二九):嵌入浏览器
- android string.xml文件中的整型和string型代替
- i-jetty环境搭配与编译
- android之定时器AlarmManager
- android wifi 无线调试
- Android Native 绘图方法
- Android java 与 javascript互访(相互调用)的方法例子
- android 代码实现控件之间的间距
- android FragmentPagerAdapter的“标准”配置
- Android"解决"onTouch和onClick的冲突问题
- android:installLocation简析
- android searchView的关闭事件
- SourceProvider.getJniDirectories