Android触摸事件传递机制

 

前言 :我们在Android开发过程中经常会遇到多个View,ViewGroup嵌套的问题,例如: ViewPager中嵌套Fragment,而在Fragment中需要实现一个横向滚动的广告位,这个时候,就会遇到广告栏的滑动事件和Viewpager的滑动事件冲突的问题,想要快速解决这种问题,我们需要对View的事件传递机制有较为深刻的理解.

   接下来会介绍Activity,View,ViewGroup三者的触摸事件传递机制.一次完整的事件主要包括三个阶段,分别是事件的分发,拦截和消费.

目录

 

1.触摸事件的类型

 2.事件传递的三个阶段

3.View的事件传递机制 

4.ViewGroup的事件传递机制


1.触摸事件的类型

 触摸事件对应的是MotionEvent类,事件的类型主要有下面三种: 

  •  ACTION_DOWN  :用户手指按下的操作,一次按下标志着一次触摸事件的开始
  • ACTION_MOVE  : 用户的手指按下之后,在松开手指之前,如果手指一动的距离超过一定的距离,就会被判定为ACTION_MOVE操作,一般情况下,手指轻微的一动就会是触发一系列的移动事件 
  • ACTION_UP :用户手指离开屏幕,一起离开的操作标志着一次触摸事件的结束     

在单次屏幕操作的过程中,DOWN和UP是必须的,但是MOVE是可以不存在的,如果单机一下就松开,那么只会检测到按下和抬起事件

 2.事件传递的三个阶段

  • 分发 Dispatch :事件分发对应着dispatchTouchEvent方法,在Android系统中,所有的触摸事件都是通过这个方法来分发的->方法的原型如下 :
    public boolean dispatchTouchEvent(MotionEvent ev)

    在这个方法中,根据当前的视图的具体实现逻辑,来决定是否消费这个事件还是继续将事件分发给子视图处理,方法的返回值代表是否消费事件,返回true表示消费,不再继续分发下去,返回super.dispatchTouchEvent表示继续分发下去.如果当前的视图是ViewGroup以及子类,则会调用onInterceptTouchEvent方法判断是否拦截事件.

  • 拦截 Intercept : 事件的拦截对应着onInterceptTouchEvent方法,这个方法只是在ViewGroup和其子类中才会有,在View和Activity中是不存在的 . 方法的原型如下 : 
    public boolean onInterceptTouchEvent(MotionEvent ev)

    同理,这个方法也是根据Booelan值来决定是否拦截事件的,返回true表示拦截这个事件,不继续分发,交给自己的OnTouchEvent方法处理.返回false或者是super.onInterceptTouchEvent表示不拦截,交给子视图继续处理.

  • 消费 Consume : 事件的消费对应着onTouchEvent方法,方法原型如下 :
    public boolean onTouchEvent(MotionEvent ev )

    该方法返回值为true表示当前视图可以处理对应的事件,事件不会向上传递给父视图,返回false表示当前视图不处理这个事件,事件会继续传递给父视图的onTouchEvent方法进行处理 

在Android系统中,拥有事件传递处理能力的类有下面三种 :

  • Activity :拥有dispatchTouchEvent和onTouchEvent两个方法 
  • ViewGroup : :拥有dispatchTouchEvent , onInterceptTouchEvent和onTouchEvent三个方法 
  • View :拥有dispatchTouchEvent和onTouchEvent两个方法 

3.View的事件传递机制 

虽然ViewGroup是View的子类,但是这里所说的View是指除了ViewGroup的View控件,例如TextView,Button,CheckBox,ImageView等,view控件本身已经是最小的单位,不能再作为其他控件的View容器.View拥有dispatchTouchEvent和onTouchEvent两个方法.我们通过自定义一个TextView来了解一下事件的传递过程:

 


public class MyTextView extends TextView {
    private static final String TAG = "MyTextView";

    public MyTextView(Context context) {
        super(context);
    }

    public MyTextView(Context context, @Nullable AttributeSet attrs) {
        super(context, attrs);
    }

    public MyTextView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
    }

    /**
     * 分发
     * @param event
     * @return
     */
    @Override
    public boolean dispatchTouchEvent(MotionEvent event) {

        switch (event.getAction()) {


            case MotionEvent.ACTION_DOWN:
                Log.e(TAG,"dispatchTouchEvent->手指按下事件");

                break;

            case MotionEvent.ACTION_MOVE:
                Log.e(TAG,"dispatchTouchEvent->移动事件");
                break;

            case MotionEvent.ACTION_UP:
                Log.e(TAG,"dispatchTouchEvent->手指松开事件");
                break;

            default:
                break;
        }

        return super.dispatchTouchEvent(event);

    }


    @Override
    public boolean onTouchEvent(MotionEvent event) {

        switch (event.getAction()) {

            case MotionEvent.ACTION_DOWN:
                Log.e(TAG,"onTouchEvent->手指按下事件");

                break;

            case MotionEvent.ACTION_MOVE:
                Log.e(TAG,"onTouchEvent->移动事件");
                break;

            case MotionEvent.ACTION_UP:
                Log.e(TAG,"onTouchEvent->手指松开事件");
                break;
            case MotionEvent.ACTION_CANCEL:
                Log.e(TAG ,"onTouchEvent->事件取消");
                break;
            default:
                break;
        }
        return super.onTouchEvent(event);
    }
}

写一个activity测试:


class MainActivity : AppCompatActivity(), View.OnClickListener, View.OnTouchListener {


    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)

        setContentView(R.layout.activity_main)

        /*
        设置TextView的点击事件和触摸事件 Kotlin代码
         */

        mMyTextView.setOnClickListener(this)
        mMyTextView.setOnTouchListener(this)


    }

    val TAG: String = "MainActivity"
    /**
     * View的点击事件
     */
    override fun onClick(v: View?) {

        when (v?.id) {
            R.id.mMyTextView -> Log.e(TAG, "View的点击事件")

        }

    }

    /**
     * View的触摸事件
     */
    override fun onTouch(v: View?, event: MotionEvent?): Boolean {

        when (v?.id) {
            R.id.mMyTextView -> {
                when (event?.action) {

                    MotionEvent.ACTION_DOWN -> Log.e(TAG, "onTouch->按下")


                    MotionEvent.ACTION_MOVE -> Log.e(TAG, "onTouch->移动")


                    MotionEvent.ACTION_UP -> Log.e(TAG, "onTouch->抬起")


                }
            }
        }
        return super.onTouchEvent(event)
    }


    /*
    接下来是Activity的两个方法
     */


    /**
     * 分发
     * @param event
     * @return
     */
    override fun dispatchTouchEvent(event: MotionEvent): Boolean {

        when (event.action) {


            MotionEvent.ACTION_DOWN -> Log.e(TAG, "dispatchTouchEvent->手指按下事件")

            MotionEvent.ACTION_MOVE -> Log.e(TAG, "dispatchTouchEvent->移动事件")

            MotionEvent.ACTION_UP -> Log.e(TAG, "dispatchTouchEvent->手指松开事件")

        }

        return super.dispatchTouchEvent(event)

    }


    override fun onTouchEvent(event: MotionEvent): Boolean {

        when (event.action) {

            MotionEvent.ACTION_DOWN -> Log.e(TAG, "onTouchEvent->手指按下事件")

            MotionEvent.ACTION_MOVE -> Log.e(TAG, "onTouchEvent->移动事件")

            MotionEvent.ACTION_UP -> Log.e(TAG, "onTouchEvent->手指松开事件")

            MotionEvent.ACTION_CANCEL -> Log.e(TAG, "onTouchEvent->事件取消")

        }
        return super.onTouchEvent(event)
    }


}

运行代码 ,点击TextView,看下日志 :

  E/MainActivity: dispatchTouchEvent->手指按下事件
  E/MyTextView: dispatchTouchEvent->手指按下事件
  E/MainActivity: onTouch->按下
  E/MyTextView: onTouchEvent->手指按下事件
  E/MainActivity: dispatchTouchEvent->手指松开事件
  E/MyTextView: dispatchTouchEvent->手指松开事件
  E/MainActivity: onTouch->抬起
  E/MyTextView: onTouchEvent->手指松开事件
  E/MainActivity: View的点击事件

 

上面的代码中,dispatchTouchEvent和onTouchEvent这两个方法的返回值可能存在下面的3种情况 :

  •  false
  • true
  • super的同名方法 

不同的返回值会导致事件传递流程的结果不一样,通过控制返回值不断测试,得到:

(手画的太烂,将就着还是可以看的)

从上面的流程图得出: 

  • 触摸事件的传递流程是从dispatchTouchEvent开始的,如果默认返回的是父类的同名函数,则事件将会依照嵌套层次从外向内传递,到达最内层的时候,就由他的onTouchEvent方法处理,该方法如果能处理,就返回true,如果处理不了就返回false.这是事件向外层传递,由他的onTouchEvent方法处理,依次类推.
  • 如果事件在向内传递的过程中,dispatchTouchEvent返回true,则会导致改事件被提前消费掉,不会向内层传递 ,内层的View将不会收到这个事件 
  • View事件触发顺序是先执行onTouch方法,在最后才执行onClick方法.如果onTouch方法返回true,则事件不会传递,最后也不会调用onClick方法,如果onTouch返回false,则事件继续传递 

 

4.ViewGroup的事件传递机制

 viewGroup是作为View的容器存在的(常见的四大布局控件,列表listView,RecyclerView ,等等 ...) ,ViewGroup拥有dispatchTouchEvent , onInterceptTouchEvent和onTouchEvent三个方法 ,比View多了一个onInterceptTouchEvent方法.我们自定义一个相对布局看看 

 


class MyRelativelayout @JvmOverloads constructor(
        context: Context, attrs: AttributeSet? = null, defStyleAttr: Int = 0
) : RelativeLayout(context, attrs, defStyleAttr) {
    val TAG: String = "MyRelativelayout"


    /**
     * 分发
     * @param event
     * @return
     */
    override fun dispatchTouchEvent(event: MotionEvent): Boolean {

        when (event.action) {


            MotionEvent.ACTION_DOWN -> Log.e(TAG, "dispatchTouchEvent->手指按下事件")

            MotionEvent.ACTION_MOVE -> Log.e(TAG, "dispatchTouchEvent->移动事件")

            MotionEvent.ACTION_UP -> Log.e(TAG, "dispatchTouchEvent->手指松开事件")


        }

        return super.dispatchTouchEvent(event)

    }

    override fun onInterceptTouchEvent(event: MotionEvent?): Boolean {
        when (event?.action) {


            MotionEvent.ACTION_DOWN -> Log.e(TAG, "onInterceptTouchEvent->手指按下事件")

            MotionEvent.ACTION_MOVE -> Log.e(TAG, "onInterceptTouchEvent->移动事件")

            MotionEvent.ACTION_UP -> Log.e(TAG, "onInterceptTouchEvent->手指松开事件")


        }


        return super.onInterceptTouchEvent(event)
    }

    override fun onTouchEvent(event: MotionEvent): Boolean {

        when (event.action) {

            MotionEvent.ACTION_DOWN -> Log.e(TAG, "onTouchEvent->手指按下事件")

            MotionEvent.ACTION_MOVE -> Log.e(TAG, "onTouchEvent->移动事件")

            MotionEvent.ACTION_UP -> Log.e(TAG, "onTouchEvent->手指松开事件")
            MotionEvent.ACTION_CANCEL -> Log.e(TAG, "onTouchEvent->事件取消")

        }
        return super.onTouchEvent(event)
    }

 

修改上面的布局,让TextView处于这个自定义的布局中 

点击,查看日志 

  E/MainActivity: dispatchTouchEvent->手指按下事件
  E/MyRelativelayout: dispatchTouchEvent->手指按下事件
  E/MyRelativelayout: onInterceptTouchEvent->手指按下事件
  E/MyTextView: dispatchTouchEvent->手指按下事件
  E/MainActivity: onTouch->按下
  E/MyTextView: onTouchEvent->手指按下事件
  E/MainActivity: dispatchTouchEvent->手指松开事件
  E/MyRelativelayout: dispatchTouchEvent->手指松开事件
  E/MyRelativelayout:  onInterceptTouchEvent->手指松开事件
  E/MyTextView: dispatchTouchEvent->手指松开事件
  E/MainActivity: onTouch->抬起
  E/MyTextView: onTouchEvent->手指松开事件
  E/MainActivity: View的点击事件

可以看出,与View事件的流程唯一不一样的地方是MainActivity和TextView之间增加了一层MyRelativeLayout,通过控制返回值,得到 :

通过上面的流程图,我们可以得出结论 :

  • 触摸事件的传递顺序: Activity  ==>ViewGroup ==>子View
  • ViewGroup通过onInterceptTouchEvent方法对事件进行拦截,如果方法返回true,则事件不会继续传递给子View,如果返回false或者父类的同名方法,则事件会继续传递给子View 
  • 在子View中对事件进行处理以后,ViewGroup将接受不到任何事件 ...

 

下篇: Android中View的绘制流程 

 

 


  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值