关闭

Andorid事件分发源码解析

标签: android
150人阅读 评论(1) 收藏 举报
事件分发源码解析:
1.Activity对点击事件的分发过程
点击事件用MotionEvent来表示, 当一个点击操作发生时,事件最先传递给当前Activity, 由Activity的dispatchTouchEvent来进行事件派发, 具体的工作是由Activity内部的Window来完成的. Window会将事件传递给decor View, decor view一般就是当前界面的底层界面(即 setContentView 所设置的 View的父容器) (Activity --> Window --> decor view), 通过Activity.getWindow().getDecorView()可以获得.
源码: Activity#dispatchTouchEvent
public boolean dispatchTouchEvent(MotionEvent ev){
if(ev.getAction == MotionEvent.ACTION_DOWN){
onUserInteraciton();
}
if(getWindow().superDispatchTouchEvent(ev)){
return true;
}
return onTouchEvent(ev);
}
现在分析上面的代码. 首先事件会交给Activity的dispatchTouchEvent(), 然后这个方法里面会先调用onUserInteraction(), 然后调用Activity所属的Window(View层,但是一个Activity并不全是View层,习惯说Activity,但是界面是View层的东西, 通过Window层才到View层)进行分发,如果返回true,表示事件已经被消费了,就会调用本Activity的onTouchEvent()方法来进行事件消费. 如果返回false,则表示Activity不消费本次事件,事件会继续往下传递, 如果所有的View都返回false,那么最终这件事也是往上层层传递会Activity,由它进行消费.
接下来看Window是如何将事件传递给ViewGroup的, 通过源码我们可以知道, Window是个抽象类, 而Window的superDispatchTouchEvent()方法也是个抽象方法, 因此我们必须找到Window的实现类才行.
源码: Window#superDispatchTouchEvent
public abstract boolean superDispatchTouchEvent(MotionEvent event);
那么到底Window的实现类是什么呢? 其实就是PhoneWindow, 这一点从Window的源码中也可以看出来,在window的说明中,有这么一段话:
上面这段话的大概意思是: Window类控制着顶级的View的外观和行为策略,它的唯一实现位于android.policy.PhoneWindow中,当你要实例化这个Window类的时候,你并不知道它的实现细节,因为这个类会被重构, 只有一个工厂方法可以使用. 尽管这看起来有点模糊, 不过我们可以看一下android.policy.PhoneWindow这个类, 尽管实例化的时候此类会被重构, 仅是重构而已, 功能是类似的
由于Window的唯一实现是PhoneWindow,因此接下来请看一下PhoneWindow是如何处理点击事件的,如下所示:
源码:PhoneWindow#superDispatchTouchEvent
public boolean superDispatchTouchEvent(MotionEvent event){
return mDecor.superDispatchTouchEvent(event); // PhoneWindow里面调用DecorView的..
}
到这里逻辑就很清晰了,PhoneWindow将事件直接传递给了DecorView, 这个DecorView是什么呢?
private final class DecorView extends FrameLayout implements RootViewSurfaceTaker{
// This is the top-level view of the window, containing the window decor.
private DecorView mDecor;
@Override
public final View getDecorView(){
if (mDecor == null) {
installDecor();
}
return mDecor;
}
}
我们知道,通过(ViewGroup)getWindow().getDecorView().findViewById(android.R.id.content).getChildAt(0)这种方式就获取Activity所设置的View,
这个mDecor显示就是getWindow().getDecorView()返回的View, 而我们通过setContentView设置的View是它(DecorView)的一个子View. 目前事件已经传递到了DecorView这里,由于DecorView继承自FrameLayout且是父View,所以最终事件会传递给View. 换句话来说, 事件肯定会传递给View, 不然应用如何响应点击事件呢? 不过这不是我们的重点,重点是事件到了View以后应该如何传递,这对我们更有用. 从这里开始, 事件已经传递到顶级View了, 即在Activity中通过setContentView所设置的View, 另外顶级View也叫根View, dingjiView一般来说就是ViewGroup.(Activity --> Window(PhoneWindow是它的实现类) ---> decorView ---> setContentView的View) 这是传递的流程
3.顶级View对点击事件的分发过程
关于点击事件如何在View中进行分发, 上一节

3.5 滑动冲突
在Android开发中,只要内外两层View同时可以滑动,这个时候就会产生滑动冲突.
先说场景一,主要是将ViewPager和Frament配合使用所组成的页面滑动效果, 这种页面中可以左右进行滑动,不过每个页面往往又是一个ListView,本来这种情况是有滑动冲突的,只不过ViewPager内部解决了这种滑动冲突,因此采用ViewPager时我们无须关注这个问题, 如果我们采用的是ScrollView,那就必须我们手动解决这个滑动冲突问题了,否则就造成了内外两层只有一层能够滑动的效果. 这是因为两者的滑动事件之间有冲突.
场景二: 这种情况稍微复杂一点, 当内外两层都在同一个方向上可以滑动的时候, 显然存在逻辑问题, 因为当手指开始滑动的时候,系统无法知道用户到底是想让哪一层滑动,所以当手指滑动的时候就会出现问题, 要么只有一层能够滑动, 要么就是内外两层都滑动得很卡顿.
场景三是场景一和场景二的嵌套. 具体是 外部有一个SlideMenu效果,然后内部有一个ViewPager,然后ViewPager每一个页面中又是一个ListView, 虽然说场景三的滑动冲突看起来更复杂,但是它是几个单一滑动冲突的叠加, 因此只需要分别处理内层和中层,中层和外层之间的滑动冲突即可,而具体的处理方法其实是和场景1,场景2是相同的.
从本质上说,这三种滑动场景的复杂度其实是相同的, 因为它们的区别仅仅是滑动策略的不同, 至于解决滑动冲突的方法, 它们几个是通用的,
针对第一种场景,我们根据滑动距离差来进行判断,这个距离差就是所谓的滑动规则,(如果水平的滑动距离差大于竖直方向上的滑动距离差,那就是水平滑动
1.外部拦截法:(父容器拦截)
所谓的外部拦截法是指点击事情都先经过父容器的拦截处理,如果父容器需要此事件就拦截,如果不需要此事件就不拦截,这样可以解决滑动冲突问题。
外部拦截法需要重写父容器的onInterceptTouchEvent方法,在内部做相应的拦截即可,这种方法的伪代码如下所示:
public boolean onInterceptTouchEvent(MotionEvent event){
boolean intercepted = false;
int x = (int)event.getX();
int y = (int)event.getY();
switch(event.getAction()){
case MotionEvent.ACTION_DOWN: {
intercepted = false; // 手指按下的时候不进行拦截事件,否则子View将无法接收到事件
break;
}
case MotionEvent.ACTION_MOVE: {
if( 父容器需要当前点击事件 ){
intercepted = true; // 手指在屏幕上移动的时候,父容器捕获这个MOVE事件
} else {
intercepted = false; // 手指不在屏幕上移动的时候,就不拦截事件了
// 拦截事件之后会调用自身的onTouchEvent()
}
break;
}
case MotionEvent.ACTION_UP: {
intercepted = false;
break;
}
default:
break;
}
mLastXIntercept = x;
mLastYIntercept = y;
return intercepted;
}
以上代码是外部拦截法的典型逻辑,针对不同的滑动冲突,只需要修改父容器需要当前点击事件这个条件即可,其他均不需做修改而且也不能修改。在onInterceptTouchEvent方法中,首先是ACTION_DOWN这个事件父容器必须返回false,即不拦截ACTION_DOWN事件,这是因为一旦父容器拦截了ACTION_DOWN,那么后续的ACTION_MOVE和ACTION_UP事件都会直接交由父容器处理,这个时候事件没法再传递给子元素了,其次是ACTION_MOVE事件,这个事件可以根据需要来决定是否拦截,如果父容器需要拦截就返回true,否则就返回false;最后是ACTION_UP事件,这里必须要返回false,因为ACTION_UP事件本身没有太多意义,即便是返回false,这个ACTION_UP如果父容器进行了对ACTION_MOVE进行拦截,这个ACTION_UP事件也会被父容器进行处理、消费。
(父容器的onInterceptTouchEvent负责拦截事件,返回值是true或false,拦截了就由父容器处理)
考虑一种情况是,假设事情交由子元素处理,如果父容器在ACTION_UP时返回了true,就会导致子元素无法接收到ACTION_UP事件,子元素无法接收到ACTION_UP事件,那就无法触发onClick方法,(记住onClick方法是必须要同时触发ACTION_DOWN和ACTION_UP才会触发的,有按下同时又有抬起,才算是一个点击事件。)但是父容器比较特殊,一旦它开始拦截任何事件,那么此事件过后的后续事件都会交由它来处理,而ACTION_UP作为最后一个事件也必定可以传递给父容器,即便父容器的onInterceptTouchEvent方法在ACTION_UP上返回了false。
2. 内部拦截法(父容器不拦截,子元素派发事件)
内部拦截法是指父容器不拦截任何事件,所有的事件都传递给子元素,如果子元素需要此事件就直接消耗掉,否则就交由父容器来进行处理,这种方法和Android的事件分发机制不一样,需要配合requestDisallowInterceptTouchEvent方法才能正常工作。
public boolean dispatchTouchEvent(MotionEvent event){
int x = (int)event.getX(); // x, y是用户点击的坐标
int y = (int)event.getY();
switch(event.getAction()){
case MotionEvent.ACTION_DOWN: {

break;
}
case MotionEvent.ACTION_MOVE: {
int deltaX = x - mLastX;
int deltaY = y - mLastY;
if(父容器需要此类点击事件){
parent.requestDisallowInterceptTouchEvent(false);
}
break;
}
case MotionEvent.ACTION_UP: {

break;
}
default:
break;
}
mLastX = x; // 把用户上次点击的x坐标记录下来
mLastY = y; // 把用户上次点击的y坐标记录下来
return super.dispatchTouchEvent(event);
}
上述代码是内部拦截法的典型代码,当面对不同的滑动策略时只需要修改里面的条件即可,其他不需要做改动而且也不能有改动。除了子元素需要做处理以外,父元素也要默认拦截除了ACTION_DOWN以外的其他事件,这样当子元素调用parent.requestDisallowInterceptTouchEvent(false)方法时,父元素才能继续拦截所需的事件。
为什么父容器不能拦截ACTION_DOWN事件呢?(你仔细想想,如果父容器拦截了ACTION_DOWN,后续的事件都会在父容器中被消费,那肯定不会传递到子控件,那子控件的dispatchTouchEvent肯定不会被调用,那么父容器肯定不能拦截ACTION_DOWN事件)那是因为ACTION_DOWN事件并不受FLAG_DISALLOW_INTERCEPT(这是requestDisallowInterceptTouchEvent所影响的标志位)这个标记位的控制,所以一旦父容器拦截ACTION_DOWN事件,那么所有的事件都无法传递到子元素中去,这样内部拦截就无法起作用。父容器所做的修改如下:
public boolean onInterceptTouchEvent(MotionEvent event){
int action = event.getAction();
if(action == MotionEvent.ACTION_DOWN){
return false;
}else{
return true;
}
}
--------------------现在是实际案例:

0
0
查看评论

Android View 事件分发机制 源码解析(ViewGroup篇)

本期三篇文章目录(可点击跳转)一. Android TouchEvent事件传递机制初识 二. android点击事件传递源码讲解(ViewGroup) 三.android点击事件传递源码讲解(View) 1. 前言android点击 事件一直以来都是很多安卓程序员的心病,之前通过demo模拟...
  • dfskhgalshgkajghljgh
  • dfskhgalshgkajghljgh
  • 2016-12-06 20:11
  • 1126

Android View 事件分发机制源码详解(ViewGroup篇)

前言我们在学习View的时候,不可避免会遇到事件的分发,而往往遇到的很多滑动冲突的问题都是由于处理事件分发时不恰当所造成的。因此,深入了解View事件分发机制的原理,对于我们来说是很有必要的。由于View事件分发机制是一个比较复杂的机制,因此笔者将写成两篇文章来详细讲述,分别是ViewGroup和V...
  • a553181867
  • a553181867
  • 2016-04-30 21:05
  • 8659

Android View 事件分发机制源码详解(View篇)

Android View事件分发机制源码解析
  • a553181867
  • a553181867
  • 2016-05-02 14:31
  • 5270

Android 中的事件分发和处理

上次跟大家分享了一下自定义View的一下要点,这次跟大家聊一下View的事件分发及处理,为什么主题都是View,因为作为一名初级应用层Android工程师,跟我打交道最多的莫过于各种各样的View,只有详细了解他们各自的习性,才能更好地跟他们沟通交流,做出自己想要的效果。 基础储备 View、Mot...
  • jyz_2015
  • jyz_2015
  • 2016-07-29 15:45
  • 523

View事件分发源码解析

其实 网上已经有好多大神对 View 事件分发做了很好的分析总结。那么我还写这样的文章说实话也就是为了自己更好的理解。 1Acitivity对事件的分发过程 2ViewGroup对事件的分发过程 3Viw 对事件的分发过程
  • WierZheng
  • WierZheng
  • 2017-03-29 16:33
  • 198

ViewGropw 事件分发源码解析

事件分发的主要三个方法就是 public boolean disptatchTouchEvent(MotionEvent ev); public boolean onIterceptTouchEvent(MotionEvent ev); public boolean onTouchEvent(Mot...
  • sgafdsg
  • sgafdsg
  • 2017-07-24 23:31
  • 110

Android 事件分发机制(最新源码6.0分析)--ViewGrop

ViewGroup的事件分发机制 上一张说到View的事件处理机制。而在Activity的dispatchTouchEvent()中最终定位到了ViewGroup()的事件分发: 上一章 请看:这里 Android  事件分发机制--View
  • u014800493
  • u014800493
  • 2016-07-28 16:07
  • 1916

事件分发源码解析 2

#事件分发源码解析 1 .##            **Activity的事件分发过程** >当一个事件发生时,首先传递给Activity,由Activ...
  • k316378085
  • k316378085
  • 2016-05-18 16:45
  • 260

android_事件分发源码解析

当用户手指触摸手机屏幕时最先将事件MotionEvent传递给activity中的dispatchTouchEvent,然后是将事件交给window去处理,window再将事件交给顶层的View,也就是DecorView处理,一级级地将事件向下传递下去。
  • lwj_zeal
  • lwj_zeal
  • 2016-09-01 11:00
  • 266

事件分发的源码解析

经过上一节的讲解,我们本节从Android源码来分析 1、activity对点击事件的分发过程 点击事件有motionevent来表示,当一个点击操作发生时,事件最先传递给当前的activity,有activity的dispatchTouchevent来进行事件分发,具体的工作由activity的内...
  • qq_28273051
  • qq_28273051
  • 2016-12-28 17:14
  • 79
    个人资料
    • 访问:1451次
    • 积分:102
    • 等级:
    • 排名:千里之外
    • 原创:9篇
    • 转载:0篇
    • 译文:0篇
    • 评论:2条
    文章分类
    文章存档