Android开发进阶—View的事件体系

来源:互联网 发布:淘宝商家微淘怎么设置 编辑:程序博客网 时间:2024/05/23 20:56

1.前言

       View虽然不属于Android的四大控件,但是它的作用和四大组件一样重要。在Android的开发中系统先天的为我们提供了很多的控件,比如:TextView、EditText、Button等。但是很多时候仅仅使用系统定义的控件往往是不能满足需求的,因此就需要自定义新的控件,想要自定义新的控件就需要对View的体系有深入的了解,只有这样才能自定义出完美的控件。

2.View是什么?

       在了解View的事件体系之前我们需要知道View到底是什么?View是Android中所有控件的基类,所有控件在写的过程中都必须先继承View,不管是简单的TextView还是复杂的ListView。简单的说View就是界面层所有控件抽取出来的一个模版,所有的控件都需要按照View的标准进行定义,除了View之外还有ViewGroup,从字面意义上看ViewGroup是控件组的意思,言外之意就是ViewGroup是一个可以盛放View的容器,其中可以包含许多的View。ViewGroup自身也是从View中派生出来的。

                 View视图树

       从View视图树中可以看出,最为底层容器的ViewGroup可以包含做为叶子节点的View和ViewGroup,而子ViewGroup又可以包含下一层叶子节点的View和ViewGroup。事实上这种灵活的View树的结构可以形成非常复杂的UI布局。

3.Android控件架构

       通过ViewGroup将整个界面上的所有控件都以View视图树的结构进行管理,上层控件负责下层控件的测量与绘制并传递交互事件,我们通常使用的findViewById()方法就是在View视图树中进行遍历来查找对应的控件。


       通过上图可以看出,每个Activity都包含一个Window对象,在Android中Window对象通常由PhoneWindow来实现。而PhoneWindow又将一个DecorView设置为整个应用窗口的根View,在这里DecorView其实就可以理解为最为底层的ViewGroup,这里所有的View监听事件都通过WindowManagerService来进行接收,并通过Activity对象来回调相应的onClickListener方法。在显示上DecorView又将屏幕分为上下两部分,一部分是TitleView,另一部分是ContentView,我们通常在onCreat()方法中使用setContentView()方法将布局传递到界面显示,这时界面的显示是上部分是TitleView,下部分是我们传递的ContentView,如果想要获取全屏显示可以使用requestWindowFeature(Window.FEATURE_NO_TITLE)方法,这时DecorView中的布局就只剩下ContentView了,这也就解释了调用requestWindowFeature(Window.FEATURE_NO_TITLE)方法一定要在setContentView()方法之前才能生效的原因。

       在实际的运行中,当程序在onCreat()方法中调用setContentView()方法后,ActivityManagerService会调用onResume()方法,此时的Activity处于运行状态,系统会把整个DecorView添加到PhoneWindow中并且显示出来,这样界面的绘制工作就算完成了,用户可以在Activity上进行相应的操作。

4.View的事件分发机制

       当用户在屏幕上去点击一个View,系统就需要对点击这个动作进行捕获,在捕获之后还需要对点击事件进行传递,直到传递到一个具体的View去处理点击事件,这个传递事件的过程就是View的事件分发机制。

       点击事件的分发过程由三个很重要的方法共同完成,这三个方法分别是:dispatchTouchEvent()、onInterceptTouchEvent()和onTouchEvent()


dispatchTouchEvent(MotionEvent event) —用来进行事件的分发,如果事件能够传递到当前View此方法就会被调用,返回的结果受当前View的onTouchEvent()和下级View的dispatchTouchEvent()方法影响,表示是否消耗当前事件

onInterceptTouchEvent(MotionEvent event) — 用来对事件进行拦截,如果当前View拦截了某个事件,在同一个事件序列当中此方法不会再次被调用,返回结果表示是否拦截此事件

onTouchEvent(MotionEvent event) —dispatchTouchEvent()方法中调用,用来对事件进行处理,返回结果表示是否消耗当前事件


接下来通过在上述的几个方法中加入日志,来模拟事件分发的和事件处理的顺序,布局的结构入下图所示


       对于ViewGroupA和ViewGroupB来说需要重写dispatchTouchEvent()、onInterceptTouchEvent()和onTouchEvent()这三个方法,对于ViewButton来说只需要重写dispatchTouchEvent()和onTouchEvent()这两个方法

package com.example.administrator.vieweventdemo;import android.annotation.TargetApi;import android.content.Context;import android.os.Build;import android.util.AttributeSet;import android.util.Log;import android.view.MotionEvent;import android.widget.LinearLayout;/** * Created by ChuPeng on 2017/1/16. */public class MyViewGroupA extends LinearLayout{    public MyViewGroupA(Context context)    {        super(context);    }    public MyViewGroupA(Context context, AttributeSet attrs)    {        super(context, attrs);    }    public MyViewGroupA(Context context, AttributeSet attrs, int defStyleAttr)    {        super(context, attrs, defStyleAttr);    }    @TargetApi(Build.VERSION_CODES.LOLLIPOP)    public MyViewGroupA(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes)    {        super(context, attrs, defStyleAttr, defStyleRes);    }    //事件分发    public boolean dispatchTouchEvent(MotionEvent ev)    {        Log.d("Event", "MyViewGroupA   dispatchTouchEvent");        return super.dispatchTouchEvent(ev);    }    //事件拦截    public boolean onInterceptTouchEvent(MotionEvent ev)    {        Log.d("Event", "MyViewGroupA   onInterceptTouchEvent");        return super.onInterceptTouchEvent(ev);    }    //事件处理    public boolean onTouchEvent(MotionEvent event)    {        Log.d("Event", "MyViewGroupA   onTouchEvent");        return super.onTouchEvent(event);    }}



package com.example.administrator.vieweventdemo;import android.annotation.TargetApi;import android.content.Context;import android.os.Build;import android.util.AttributeSet;import android.util.Log;import android.view.MotionEvent;import android.widget.LinearLayout;/** * Created by ChuPeng on 2017/1/16. */public class MyViewGroupB extends LinearLayout{    public MyViewGroupB(Context context)    {        super(context);    }    public MyViewGroupB(Context context, AttributeSet attrs)    {        super(context, attrs);    }    public MyViewGroupB(Context context, AttributeSet attrs, int defStyleAttr)    {        super(context, attrs, defStyleAttr);    }    @TargetApi(Build.VERSION_CODES.LOLLIPOP)    public MyViewGroupB(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes)    {        super(context, attrs, defStyleAttr, defStyleRes);    }    //事件分发    public boolean dispatchTouchEvent(MotionEvent ev)    {        Log.d("Event", "MyViewGroupB   dispatchTouchEvent");        return super.dispatchTouchEvent(ev);    }    //事件拦截    public boolean onInterceptTouchEvent(MotionEvent ev)    {        Log.d("Event", "MyViewGroupB   onInterceptTouchEvent");        return super.onInterceptTouchEvent(ev);    }    //事件处理    public boolean onTouchEvent(MotionEvent event)    {        Log.d("Event", "MyViewGroupB   onTouchEvent");        return super.onTouchEvent(event);    }}


package com.example.administrator.vieweventdemo;import android.annotation.TargetApi;import android.content.Context;import android.os.Build;import android.util.AttributeSet;import android.util.Log;import android.view.MotionEvent;import android.widget.Button;/** * Created by ChuPeng on 2017/1/16. */public class ViewButton extends Button{    public ViewButton(Context context)    {        super(context);    }    public ViewButton(Context context, AttributeSet attrs)    {        super(context, attrs);    }    public ViewButton(Context context, AttributeSet attrs, int defStyleAttr)    {        super(context, attrs, defStyleAttr);    }    @TargetApi(Build.VERSION_CODES.LOLLIPOP)    public ViewButton(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes)    {        super(context, attrs, defStyleAttr, defStyleRes);    }    public boolean dispatchTouchEvent(MotionEvent event)    {        Log.d("Event", "ViewButton     dispatchTouchEvent");        return super.dispatchTouchEvent(event);    }    public boolean onTouchEvent(MotionEvent event)    {        Log.d("Event", "ViewButton     onTouchEvent");        return super.onTouchEvent(event);    }}
       上面的代码分别重写了dispatchTouchEvent()、onInterceptTouchEvent()和onTouchEvent()这三个方法,在这三个方法中都打印出日志
打印出的日志如下:


从打印的日志中可以看出正常情况下:

事件的传递顺序是ViewGroupA —> ViewGroupB —> ViewButton,

事件传递的返回值:True — 拦截(不传递),False — 不拦截(传递)

事件处理的顺序是ViewButton —> ViewGroupB —> ViewGroupA,

事件处理的返回值:True — 处理了(不传递),False — 未处理(传递)

在初始情况下返回值都是False

为了方便理解将打印出来的日志转化成流程图的形式


5.总结

关于事件传递的机制,这里有一些结论,根据这些结论可以更好的理解整个传递机制

1)同一事件是以手指接触屏幕的那一刻开始(down事件),到手指离开屏幕的那一刻结束(up事件),中间或许含有数量不定的move事件

2)一般情况下一个事件只能被一个View拦截且消耗,因为一旦这个事件被View所拦截,那么这个事件就会交由此View进行处理,并且他的onInterceptTouchEvent()方法不会再被调用

3)ViewGroup默认不拦截任何事件,在源码中ViewGroup的onInterceptTouchEvent()方法默认返回为false

4)View没有onInterceptTouchEvent()方法,一点有点击事件传递给它,它的onTouchEvent()方法将会被调用

5)事件传递的顺序是由外向内的,事件处理的顺序是由内向外的



       以上Demo的源代码地址:点击打开链接







0 0
原创粉丝点击