3_View的事件体系

来源:互联网 发布:阿里云邮箱好用吗 编辑:程序博客网 时间:2024/05/16 07:38

View的事件体系

View的事件体系

移动设备的一个特点就是用户可以直接通过屏幕来进行一系列的操作,一个典型的场景就是屏幕的滑动,用户可以通过滑动来切换到不同的界面。很多情况下我们的应用都需要支持滑动操作,当处于不同层级的View都可以响应用户的滑动操作时,就会带来一个问题---滑动冲突。

1、View基础知识

1)、View

在Android的设计中,ViewGroup也继承了View,这就意味着View本身就可以是单个控件也可以是由多个控件组成的一组控件,通过这种关系就形成了View树的结构。

2)、View的位置参数

View的位置主要由它的四个顶点来决定;这些坐标都是相对于View的父容器来说的,top:左上角纵坐标,left:左上角横坐标,right:右下角横坐标;bottom:右下角纵坐标。

从Android 3.0开始,View增加了额外的几个参数,x、y、translationX和translationY,其中x和y是View左上角的坐标,而translationX和translationY是View左上角相对于父容器的偏移量。这几个参数也是相对于父容器的坐标。

3)、MotionEvent和TouchSlope

MotionEvent:是指手指接触屏幕后所产生的一系列事件

ACTION_DOWM:手指刚接触屏幕

ACTION_MOVE:手指在屏幕上移动

ACTION_UP:手指从屏幕上松开的一瞬间

TouchSlope:系统所能识别出的被认为是滑动的最小距离

4)、VelocityTracker、GestureDetector和Scroller

VelocityTracker:速度追踪,用于追踪手指在滑动过程中的速度,包括水平和竖直方法的速度。

GestureDetector:手势检测,用于辅助检测用户的点击、滑动、长按、双节等行为

在实际开发中,可以不使用GestureDetector,完全可以自己在View的onTouchEvent方法中实现所需的监听。

建议:如果只是监听滑动相关的,建议在onTouchEvent中实现,如果要监听双击这种行为的话,那么就使用GestureDetector。

Scroller:弹性滑动对象,用于实现View的弹性滑动。当使用View的scrollTo/scrollBy方法来进行滑动时,其过程是瞬间完成的,这个没有过渡效果的滑动用户体验效果不好。这时可以使用Scroller来实现有过渡效果的滑动,其过程不是瞬间完成的,而是在一定时间间隔内完成的。Scroller本身无法让View弹性滑动,它需要和View的computeScroll方法配合使用才能共同完成这个功能。

2、View的滑动

通过三种方式可以实现View的滑动:

①:通过View本身提供的scrollTo/scrollBy方法来实现滑动

②:通过动画给View施加平移效果来实现滑动

③:通过改变View的LayoutParams使得View重新布局从而实现滑动

区别:

scrollTo/scrollBy这种方式,是View提供的原生方法,其作用是专门用于View的滑动,它可以比较方便地实现滑动效果并且不影响内部元素的单击事件。缺点:它只能滑动View的内容,并不能滑动View本身。

属性动画,并没有什么却缺点;View动画或者Android3.0以下的属性动画,不会改变View本身的属性。在实际开发中,如果动画元素不需要影响用户交互,那么使用动画来做滑动时比较适合的,否则不太适合。但是使用属性动画有很明显的优点,那就是一些复杂的效果必须要通过动画才能实现。

改变布局,除了使用起来麻烦点外,也没什么明显缺点,主要适用对象是一些具有交互性的View,因为View需要和用户交互,直接通过动画去实现会有问题。

3、弹性滑动

核心思想:将一次大的滑动分成若干次小的滑动并在一个时间段内完成,弹性滑动的具体实行方式有很多,例如通过Scroller、Handler+postDelayed以及Thread+sleep

1)、使用Scroller

Scroller的工作原理:Scroller本身并不能实现View的滑动,它需要配合View的computeScroll方法才能完成弹性滑动的效果,它不断地让View重绘,而每一次重绘距滑动起始时间会有一个时间间隔,通过这个时间间隔Scroller就可以得出View当前的滑动位置,知道了滑动位置就可以通过scrollTo方法来完成View的滑动。就这样,View的每一次重绘都会导致View进行小幅度的滑动,而多次的小幅度滑动就组成了弹性滑动,这就是Scroller的工作机制。

2)通过动画

动画本身就是一种渐变的过程,因此通过它来实现的滑动天然就具有弹性效果

3)使用延时策略

核心思想:通过发送一系列延时消息从而达到一种渐进式的效果

4、View的事件分发机制

1)、点击事件的传递规则

dispatchTouchEvent(MotionEvent ev):用来进行事件的分发,如果事件能够传递给当前View,那么此方法一定会被调用,返回结果受当前View的onTouchEvent和下级的View的dispatchTouchEvent方法的影响,表示是否消耗当前事件。

onInterceptTouchEvent(MotionEvent ev):用来判断是否拦截某个事件,如果当前View拦截了某个事件,那么在同一事件序列当中,此方法不会再次调用,返回结果表示是否拦截当前事件。

onTouchEvent(MotionEvent event):在dispatchTouchEvent中调用,用来处理点击事件,返回结果表示是否消耗当前事件,如果不消耗,则在同一个事件序列中,当前View无法再次接收到事件。

伪代码表示:

public boolean dispatchTouchEvent(MotionEvent ev) {        boolean consume = false;        if (onInterceptTouchEvent(ev)) {            consume = onTouchEvent(ev);        } else {            consume = child.dispatchTouchEvent(ev);        }        return consume;    }

5、View的滑动冲突

滑动冲突:在界面中只要内外两层同时可以滑动,这时候就会产生滑动冲突。解决滑动冲突有固定的套路

1)、常见的滑动冲突场景

场景1:外部滑动方向和内部滑动方向不一致

将ViewPager和Fragment配合使用说组成的页面效果,主流应用几乎都使用这个效果。在这种效果中,可以通过左右滑动来切换页面,而每个页面内部往往又有一个ListView。本来这种情况下是有滑动冲突的,但是ViewPager内部处理了这种滑动冲突,因此采用ViewPager时我们无需关注这个问题,如果我们采用的不是ViewPager而是ScrollView等,那就必须手动处理滑动冲突。

场景2:外部滑动方向和内部滑动方向一致

当内外两层都在同一个方向可以滑动时,显然存在逻辑问题。因为手指开始滑动时,系统无法知道用户到底是想让哪一层滑动,所以当手指滑动时就会出现问题,要么只有一层能够滑动,要么就是内外两层都滑动得很卡顿,在实际开发中,这种场景主要指内外两层同时能上下滑动或者内外两层同时能左右滑动。

场景3:上面两种情况的嵌套

其实是场景1和场景2两种情况的嵌套。

2)、滑动冲突的处理规则

可以根据滑动距离和水平方向形成的夹角;或者根据水平和竖直方向滑动的距离差,或者两个方向上的速度等等。

3)、滑动冲突的解决方式

针对场景1中的滑动,我们可以根据滑动的距离差来进行判断,这个距离差就是所谓的滑动规则。

外部拦截法

指点击事件都先经过父容器的拦截处理,如果父容器需要此事件就拦截,如果不需要此事件就不拦截,这样就可以解决滑动冲突的问题,这种方法比较符合点击事件的分发机制。外部拦截法需要重写父容器的onInterceptTouchEvent方法,在内部做相应对的拦截即可。

public boolean onInterceptTouchEvent(MotionEvent ev) {        boolean intercepted = false;        int x = (int) ev.getX();        int y = (int) ev.getY();        switch (ev.getAction()) {            case MotionEvent.ACTION_DOWN:                intercepted = false;                break;            case MotionEvent.ACTION_MOVE:                if (父容器需要当前点击事件) {                    intercepted = true;                } else {                    intercepted = false;                }                break;            case MotionEvent.ACTION_UP:                intercepted =false;                break;        }        mLastXIntercept = x;        mLastYIntercept = y;        return intercepted;    }
内部拦截法

内部拦截法是指父容器不拦截任何事件,所有的事件都传递给子元素,如果子元素需要此事件就直接消费掉,否则就交由父容器进行处理,这种方法和Android中的事件分发机制不一致,需要配合requestDisallowInterceptTouchEvent方法才能正常工作,使用起来较外部拦截法稍显复杂。

我们需要复写子元素的dispatchTouchEvent方法

    @Override    public boolean dispatchTouchEvent(MotionEvent ev) {        int x = (int) ev.getX();        int y = (int) ev.getY();        switch (ev.getAction()) {            case MotionEvent.ACTION_DOWN:                getParent().requestDisallowInterceptTouchEvent(true);                break;            case MotionEvent.ACTION_MOVE:                int deltaX = x - mLastX;                int deltaY = y - mLastY;                //竖直位移的距离大于水平位移的距离                if (Math.abs(deltaY) > Math.abs(deltaX)) {                    getParent().requestDisallowInterceptTouchEvent(false);                } else {                    getParent().requestDisallowInterceptTouchEvent(true);                }                break;            case MotionEvent.ACTION_UP:                break;        }        mLastX = x;        mLastY = y;        return super.dispatchTouchEvent(ev);    }}

0 0
原创粉丝点击