面试题八:事件分发机制
目录
相关文章:
描述一下Android的事件分发机制?
两个实际遇到的案例:
1、 ScrollView和ListView滑动冲突:
Demo:
TouchEventActivity
activity_touch_event
colors.xml
LocalRelativeLayout
LocalButton
测试:
测试一:
测试二:
测试三:
测试四:
Android事件处理的三个重要函数
2.1 事件分发:public boolean dispatchTouchEvent(MotionEvent ev)
2.2 事件拦截:public boolean onInterceptTouchEvent(MotionEvent ev)
2.3 事件响应:public boolean onTouchEvent(MotionEvent ev)
事件分发流程图:
为什么会有事件分发机制?
PhoneWindow和DecorView是什么?
三个重要的事件分发方法:
事件分发流程:
相关文章:
浅谈Android事件分发机制 (详细、专业)
完全理解android事件分发机制 (有demo)
Android:事件分发机制 (自己的)
Android事件分发之Activity篇 -- dispatchTouchEvent、onTouchEvent之间关系
描述一下Android的事件分发机制?
Android事件分发机制的本质:事件从哪个对象发出,经过哪些对象,最终由哪个对象处理了该事件。此处对象指的是Activity、Window与View。
Android事件的分发顺序:Activity(Window) -> ViewGroup -> View
Android事件的分发主要由三个方法来完成,如下所示:
// 父View调用dispatchTouchEvent()开始分发事件public boolean dispatchTouchEvent(MotionEvent event){ boolean consume = false; // 父View决定是否拦截事件 if(onInterceptTouchEvent(event)){ // 父View调用onTouchEvent(event)消费事件,如果该方法返回true,表示 // 该View消费了该事件,后续该事件序列的事件(Down、Move、Up)将不会在传递 // 该其他View。 consume = onTouchEvent(event); }else{ // 调用子View的dispatchTouchEvent(event)方法继续分发事件 consume = child.dispatchTouchEvent(event); } return consume;}
两个实际遇到的案例:
1、 ScrollView和ListView滑动冲突:
解决办法:重写ScrollView,在其onInterceptTouchEvent方法中进行相应处理
public class ListScrollView extends ScrollView { private XListView xListView; public ListScrollView(Context context) { super(context); } public ListScrollView(Context context, AttributeSet attrs) { super(context, attrs); } public void setxListView(XListView xListView) { this.xListView = xListView; } /** * 覆写onInterceptTouchEvent方法,点击操作发生在ListView的区域的时候, * 返回false让ScrollView的onTouchEvent接收不到MotionEvent,而是把Event传到下一级的控件中 */ @Override public boolean onInterceptTouchEvent(MotionEvent ev) { if (xListView != null && checkArea(xListView, ev)) { return false; } return super.onInterceptTouchEvent(ev); } /** * 测试view是否在点击范围内 * @param v * @return */ private boolean checkArea(View v, MotionEvent event){ float x = event.getRawX(); float y = event.getRawY(); int[] locate = new int[2]; v.getLocationOnScreen(locate); int l = locate[0]; int r = l + v.getWidth(); int t = locate[1]; int b = t + v.getHeight(); if (l < x && x < r && t < y && y < b) { return true; } return false; }}
Demo:
TouchEventActivity
public class TouchEventActivity extends AppCompatActivity { @BindView(R.id.local_btn) LocalButton localBtn; @BindView(R.id.local_rl) LocalRelativeLayout localRl; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_touch_event); ButterKnife.bind(this); } @Override public boolean dispatchTouchEvent(MotionEvent ev) { LogUtils.e("TouchEventActivity --> dispatchTouchEvent"); return super.dispatchTouchEvent(ev);// return true; } @Override public boolean onTouchEvent(MotionEvent event) { LogUtils.e("TouchEventActivity --> onTouchEvent"); return super.onTouchEvent(event); } @OnClick({R.id.local_btn, R.id.local_rl}) public void onViewClicked(View view) { switch (view.getId()) { case R.id.local_btn: LogUtils.e("local_btn --> 我被点击了"); break; case R.id.local_rl: break; } }}
activity_touch_event
<?xml version="1.0" encoding="utf-8"?>
colors.xml
#ABDFDC #87C09B #B5BC85 #E7B1AD
LocalRelativeLayout
public class LocalRelativeLayout extends RelativeLayout { public LocalRelativeLayout(Context context) { this(context, null); } public LocalRelativeLayout(Context context, AttributeSet attrs) { this(context, attrs, 0); } public LocalRelativeLayout(Context context, AttributeSet attrs, int defStyleAttr) { super(context, attrs, defStyleAttr); } @Override public boolean dispatchTouchEvent(MotionEvent ev) { LogUtils.e("LocalRelativeLayout --> dispatchTouchEvent"); return super.dispatchTouchEvent(ev); } @Override public boolean onInterceptTouchEvent(MotionEvent ev) { LogUtils.e("LocalRelativeLayout --> onInterceptTouchEvent"); return super.onInterceptTouchEvent(ev); } @Override public boolean onTouchEvent(MotionEvent event) { LogUtils.e("LocalRelativeLayout --> onTouchEvent"); return super.onTouchEvent(event); }}
LocalButton
public class LocalButton extends android.support.v7.widget.AppCompatButton { public LocalButton(Context context) { this(context, null); } public LocalButton(Context context, AttributeSet attrs) { this(context, attrs, 0); } public LocalButton(Context context, AttributeSet attrs, int defStyleAttr) { super(context, attrs, defStyleAttr); } @Override public boolean dispatchTouchEvent(MotionEvent ev) { LogUtils.e("LocalButton --> dispatchTouchEvent"); return super.dispatchTouchEvent(ev); } @Override public boolean onTouchEvent(MotionEvent event) { LogUtils.e("LocalButton --> onTouchEvent"); return super.onTouchEvent(event); }}
测试:
测试一:
Activity的dispatchTouchEvent()返回super.dispatchTouchEvent(ev),
LocalRelativeLayout的dispatchTouchEvent()返回super.dispatchTouchEvent(ev),结果如下:
TouchEventActivity --> dispatchTouchEvent LocalRelativeLayout --> dispatchTouchEvent LocalRelativeLayout --> onInterceptTouchEvent LocalButton --> dispatchTouchEvent LocalButton --> onTouchEvent local_btn --> 我被点击了
说明: Android事件响应机制是“由外到内”分发、“由内到外”处理的形式实现的。
测试二:
将Activity的dispatchTouchEvent的返回值设为true或者false,那么事件都不会传到下一层。
- return true : View消费所有事件。
- return false :停止分发,交由上层控件的onTouchEvent方法进行消费,如果本层控件是Activity,那么事件将被系统消费、处理。
打印结果为:
TouchEventActivity --> dispatchTouchEvent
结论:事件被Activity拦截了。
测试三:
Activity的dispatchTouchEvent()返回super.dispatchTouchEvent(ev),
LocalRelativeLayout的dispatchTouchEvent()返回false,
- return false :停止分发,交由上层控件的onTouchEvent方法进行消费
TouchEventActivity --> dispatchTouchEvent LocalRelativeLayout --> dispatchTouchEvent TouchEventActivity --> onTouchEvent
结论:LocalRelativeLayout将事件返回给Activity的onTouchEvent处理。
测试四:
Activity的dispatchTouchEvent()返回super.dispatchTouchEvent(ev),
LocalRelativeLayout的dispatchTouchEvent()返回true,
TouchEventActivity --> dispatchTouchEvent LocalRelativeLayout --> dispatchTouchEvent
Android事件处理的三个重要函数
Android事件分发机制主要由“事件分发”—>“事件拦截”—>“事件响应”这三步来进行逻辑控制的。本文也将从这三步对应的函数来分析。
2.1 事件分发:public boolean dispatchTouchEvent(MotionEvent ev)
当监听到有触发事件时,首先由Activity进行捕获,然后事件就进入事件分发的流程。Activity本身没有事件拦截,从而将事件传递给最外层的View的dispatchTouchEvent(MotionEvent ev)方法,该方法将对事件进行分发。
- return true : View消费所有事件。
- return false :停止分发,交由上层控件的onTouchEvent方法进行消费,如果本层控件是Activity,那么事件将被系统消费、处理。
- super.dispatchTouchEvent(ev): 将事件交由本层的事件拦截onInterceptTouchEvent方法处理。
2.2 事件拦截:public boolean onInterceptTouchEvent(MotionEvent ev)
- return true: 对事件拦截,交由本层的onTouchEvent进行处理。
- return false: 不拦截,分发到子View,由子View的dispatchTouchEvent方法处理。
- super.onInterceptTouchEvent(ev):默认表示事件拦截,交由本层的onTouchEvent进行处理。
2.3 事件响应:public boolean onTouchEvent(MotionEvent ev)
- return true: 表示onTouchEvent处理完事件后消费了此次事件。
- return false: 不响应事件,不断的传递给上层的onTouchEvent方法处理,直到某个View的onTouchEvent返回true,则认为该事件被消费。如果到最顶层View还是返回false,那么该事件不消费,将交由Activity的onTouchEvent进行处理。
- return: super.onTouchEvent,不响应事件,结果与return返回false一样。
事件分发流程图:
结合上面的理解,我们再来看看Touch事件传递机制流程图
为什么会有事件分发机制?
安卓上面的View是树形结构,View可能会重叠在一起,当我们点击的地方有多个View都可以响应的时候,这个点击事件应该由谁处理呢?为了解决这一个问题,就有了事件分发机制。我们看一下示例:
图一再来看一下layout的布局结构:
图二PhoneWindow和DecorView是什么?
在图二中我们看到了PhoneWindow和DecorView,这两个是什么玩意呢?
先来说一下DecorView,在图一中,没有被view覆盖的界面会显示主题的颜色,这部分区域以及最上面的标题也没有出现在layout布局当中,它们都属于DecorView。
PhoneWindow是抽象类Window的一个实现类,而Window是所有视图最顶层的管理容器。view视图、viewgroup的外观和行为还有背景显示、标题栏、事件处理都是属于Window来管理。但是它是抽象类,所以管理的实现都是由它唯一的实现PhoneWindow来实现。PhoneWindow也可以说是View的事件管理容器。而PhoneWindow一般都是通过它的内部类DecorView来进行消息传递的。PhoneWindow通过指示DecorView来给下面的View传递消息,而下面View的信息也是通过DecorView返回给PhoneWindow。
Window.java的源码:
public abstract class Window { public interface Callback { public boolean dispatchKeyEvent(KeyEvent event); public boolean dispatchKeyShortcutEvent(KeyEvent event); public boolean dispatchTouchEvent(MotionEvent event); public boolean dispatchTrackballEvent(MotionEvent event); public boolean dispatchGenericMotionEvent(MotionEvent event); }}
PhoneWindow.java的源码:
public class PhoneWindow extends Window implements MenuBuilder.Callback { private final class DecorView extends FrameLayout implements RootViewSurfaceTaker { @Override public boolean dispatchKeyEvent(KeyEvent event) {...} @Override public boolean dispatchKeyShortcutEvent(KeyEvent ev) {...} @Override public boolean dispatchTouchEvent(MotionEvent ev) {} @Override public boolean dispatchTrackballEvent(MotionEvent ev) {} @Override public boolean dispatchGenericMotionEvent(MotionEvent ev) {} @Override public boolean onTouchEvent(MotionEvent event) { return onInterceptTouchEvent(event); } @Override public boolean onInterceptTouchEvent(MotionEvent event) {...} }}
三个重要的事件分发方法:
1、dispatchTouchEvent
2、onInterceptTouchEvent
3、onTouchEvent
需要注意的是:Activity和View是没有第二个方法的,因为Activity是作为事件的最原始分发者,如果Activity拦截了这个事件,就会导致整个屏幕都无法响应事件。而View作为事件传递的最末端,要么消费这个事件要么把这个事件回传,是不需要有这个方法的。
事件分发流程:
Activity -> PhoneWindow -> DecorView -> ViewGroup -> ... -> View
更多相关文章
- 【Android】Android 发送短信和打电话的方法
- Android中AutoCompleteTextView的特殊使用方法
- Android 解决fragment replace方法低效的问题
- Android实验法分析Touch事件传递
- Android中WARNING: Application does not specify an API level
- 使用android中的handler延迟执行方法
- 一张图看明白 Android Handler 消息机制
- 实现Android计时与倒计时方法