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中派生出来的。

android tablayout第三方控件 android的view控件_控件

                 View视图树

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

3.Android控件架构

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

android tablayout第三方控件 android的view控件_控件_02

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()方法中调用,用来对事件进行处理,返回结果表示是否消耗当前事件


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

android tablayout第三方控件 android的view控件_事件处理_03

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()这三个方法,在这三个方法中都打印出日志

打印出的日志如下:

android tablayout第三方控件 android的view控件_android_04

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

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

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

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

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

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

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

android tablayout第三方控件 android的view控件_android_05

5.总结

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

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

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

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

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

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



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