面试题八:事件分发机制

目录

相关文章:

描述一下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方法中进行相应处理:覆写onInterceptTouchEvent方法,点击操作发生在ListView的区域的时候, 返回false让ScrollView的onTouchEvent接收不到MotionEvent,而是把Event传到下一级的控件中

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"?>
<RelativeLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:background="@color/lightBlue">

    <com.gs.sumok2.touch.LocalRelativeLayout
        android:id="@+id/local_rl"
        android:layout_width="300dp"
        android:layout_height="300dp"
        android:background="@color/lightGreen"
        android:layout_centerInParent="true">

        <com.gs.sumok2.touch.LocalButton
            android:id="@+id/local_btn"
            android:layout_width="150dp"
            android:layout_height="150dp"
            android:layout_centerInParent="true"
            android:background="@color/lightRed"
            />

    </com.gs.sumok2.touch.LocalRelativeLayout>

</RelativeLayout>

colors.xml

<color name="lightBlue">#ABDFDC</color>
    <color name="lightGreen">#87C09B</color>
    <color name="lightYellow">#B5BC85</color>
    <color name="lightRed">#E7B1AD</color>

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

 

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值