目录
相关文章:
描述一下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事件分发机制的本质:事件从哪个对象发出,经过哪些对象,最终由哪个对象处理了该事件。此处对象指的是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;
}
解决办法:重写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;
}
}
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;
}
}
}
#ABDFDC
#87C09B
#B5BC85
#E7B1AD
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);
}
}
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,那么事件都不会传到下一层。
打印结果为:
TouchEventActivity --> dispatchTouchEvent
结论:事件被Activity拦截了。
Activity的dispatchTouchEvent()返回super.dispatchTouchEvent(ev),
LocalRelativeLayout的dispatchTouchEvent()返回false,
TouchEventActivity --> dispatchTouchEvent
LocalRelativeLayout --> dispatchTouchEvent
TouchEventActivity --> onTouchEvent
结论:LocalRelativeLayout将事件返回给Activity的onTouchEvent处理。
Activity的dispatchTouchEvent()返回super.dispatchTouchEvent(ev),
LocalRelativeLayout的dispatchTouchEvent()返回true,
TouchEventActivity --> dispatchTouchEvent
LocalRelativeLayout --> dispatchTouchEvent
Android事件分发机制主要由“事件分发”—>“事件拦截”—>“事件响应”这三步来进行逻辑控制的。本文也将从这三步对应的函数来分析。
当监听到有触发事件时,首先由Activity进行捕获,然后事件就进入事件分发的流程。Activity本身没有事件拦截,从而将事件传递给最外层的View的dispatchTouchEvent(MotionEvent ev)方法,该方法将对事件进行分发。
结合上面的理解,我们再来看看Touch事件传递机制流程图
安卓上面的View是树形结构,View可能会重叠在一起,当我们点击的地方有多个View都可以响应的时候,这个点击事件应该由谁处理呢?为了解决这一个问题,就有了事件分发机制。我们看一下示例:
再来看一下layout的布局结构:
在图二中我们看到了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