View的事件体系

一、什么是View?什么是ViewGroup?

View是Android中所有控件的基类,不管是Button、ListView还是RelativeLayout,它们的基类都是View。View是一种界面层的控件的一种抽象,代表了一个控件。

而什么是ViewGroup,从字面上看,ViewGroup应该指的是一个控件组,即ViewGroup中可以包含许多控件。而ViewGroup继承自View,所以View本身就可以是单个控件也可以由多个控件组成的一组控件。这样就构成了View树。

二、View的位置

View的位置由它的四个顶点确定,top(左上角纵坐标)、left(左上角横坐标)、bottom(右下角纵坐标)、right(右下角横坐标),这几个参数都是相对父级容器而言的。

在Android中,X轴和Y轴的正方向分别为向右和向下。

根据四个顶点及AndroidView的坐标系,我们可以很容易得到View的宽高和坐标的关系:

width=right-left

height=bottom-top

那么如何得到这四个顶点呢?

left=getLeft();

right=getRight();

top=getTop();

bottom=getBottom();

从Android3.0开始,View增加了x,y,translationX和translationY。其中x和y是view左上角的坐标(相对坐标系),而translationX和translationY是View左上方相对父容器的偏移量。

x=left+translationX

y=top+translationY

需要注意的是View在平移过程中,top和left表示的是原始左上角的位置信息,其值不会改变,此时改变的是x、y、translationX和translationY

三、View的触摸事件

1.MotionEvent

在手指接触屏幕后所产生的一系列事件中,典型的事件有:

ACTION_DOWN——手指刚接触屏幕

ACTION_MOVE——手指在屏幕上移动

ACTION_DOWN——手指从屏幕上松开

一般我们可以将一次手指接触屏幕的行为分为两种情况:

点击屏幕后松开,事件序列为DOWN->UP

点击屏幕滑动一段时间后松开,事件序列为DOWN->MOVE->…->MOVE->UP

2.TouchSlop

TouchSlop即系统能识别滑动的最小距离,这是一个与设备有关的系统常量。不难得知其意思,当手指在屏幕上滑动小于这个距离时,系统不认为你在进行滑动操作。

通过ViewConfiguration.get(getContext()).getScaledTouchSlop()方法来获取这个系统常量。

3.VelocityTracker

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

具体用法:

在View的onTouchEvent方法中追踪当前单击事件的速度。

VelocityTracker velocityTracker =VelocityTracker.obtain();

velocityTracker.addMovement(event);

//接着当我们我们想知道当前的滑动速度时

//获取速度前先计算速度, 参数  时间间隔 单位ms

velocityTracker.computeCurrentVelocity(1000);

//获取速度

int xVelocity = (int)velocityTracker.getXVelocity();

int yVelocity=(int)velocityTracker.getYVelocity();

需要注意的是,这边的计算得到的速度与时间间隔有关,其计算公式如下:

速度=(终点位置-起点位置)/时间间隔

计算速度时得到是就是一定时间间隔内手指在水平或竖直方向上滑动的像素数,如

100像素/1000ms,这里的速度值即为100。

当然在不需要使用它时,需要调用clear方法来重置并回收内存。

velocityTracker.clear();

velocityTracker.recycle();

4.GestureDetector

手势检测,用于辅助检测用户的单击、滑动、长按、双击等行为。如果只是监听滑动相关的建议在onToucheEvent实现,如果需要监听双击,使用GestureDetector。

5.Scroller

弹性滑动对象,用来实现View的弹性滑动,View的scrollTo/scrollBy是瞬间完成的,使用Scroller配合View的computeScroll方法配合使用达到弹性滑动的效果

其典型代码是通用的.

/**
 * 平滑滚动
 * @param dx 横向位移
 * @param dy
 */
private void smoothScrollBy(int dx, int dy) {
    //水平滑动
    mScroller.startScroll(getScrollX(),0,dx,0,500);
    invalidate();

}

@Override
public void computeScroll() {
    if(mScroller.computeScrollOffset()){
        scrollTo(mScroller.getCurrX(),mScroller.getCurrY());
        postInvalidate();
    }
}

四、View的滑动

实现View滑动的几种方式:


View的滑动方式


特点


适用场景


使用ScrollTo/ScrollBy


只能改变View的内容,不能改变View本身的位置


适合对view内容的滑动


通过动画实现View的平移效果


只对影像进行操作,不能改变View的位置参数


适用于没有交互的View和实现复杂的动画效果。


使用属性动画实现View的平移效果


改变View的位置参数,可响应触摸等事件


适用于有交互的View,适配到Android3.0


改变View的LayoutParams,使得View重新布局实现滑动


改变View的位置参数,可响应触摸等事件


适用于有交互的View,使用稍复杂

前面提到了弹性滑动对象Scroll,其实实现弹性滑动的方法不止这一种,它们的共同思想就是将一次大的滑动分成若干次小的滑动并要求在一定时间内完成。实现弹性滑动的具体实现方式有:

  • 通过Scroll实现
  • 通过动画
  • 使用延时策略

1)使用Scroll

使用Scroll实现弹性滑动需要配合View的computeScroll方法实现,简单来讲就是实现多次重绘,每一次重绘有一定的时间间隔,通过这个时间间隔Scroller可以得到View的当前滑动位置,然后通过ScrollTo方法实现滑动。

具体实现方法是在自己实现的平滑滑动方法中调用invalidate方法,它会导致View重绘,又因为在View的draw方法中又会去调用computeScroll方法,而在computeScroll方法中,我们实现了scrollTo方法来实现滑动,接着调用postInvalidate来进行第二次重绘,此时又会调用View中的draw方法,,继而调用computeScroll方法,如此反复,直到整个滑动过程完成。

2)通过动画

动画本身就是一种渐渐地过程,可以很好地实现弹性滑动。

3)使用延时策略完成滑动,核心思想就是通过发送一系列的延时消息从而达到一种渐进的效果。具体的实现可以采用Handler或View的postDelayed方法,也可以采用sleep休眠。对于postDelayed方法们可以通过它来延时发送一个消息,然后在消息中进行View的滚动。如果接连不断发的发送这种消息,则可以达到弹性滑动对象。

而对于sleep方法,通过在while循环中不断滑动View和sleep即可实现。

五、View的事件分发机制

分发对象:MotionEvent,所谓的事件分发其实就是对MotionEvent事件的分发过程,即需要将这个事件传递到一个具体的View上进行处理。而完成这一过程需要三个重要方法来共同完成:dispatchTouchEvent、onInterceptTouchEvent和onTouchEvent。

public boolean dispatchTouchEvent(MotionEvent ev)

用来进行事件的分发,返回结果受当前View的onTouchEvent和下级View的dispatchTouchEvent方法的影响,表示是否消耗当前事件。

public boolean onInterceptTouchEvent(MotionEvent ev)

在dispatchTouchEvent方法内部调用,用于判断是否拦截某个时间,如果当前View拦截了某个事件,那么在同一个事件序列当中,此方法不会被再次调用。

public boolean onTouchEvent(MotionEvent event)

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

建立在上述方法的基础上,我们简单分析一下事件分发的过程。

     

由上述流程图不难发现,在MotionEvent被一个View所拦截时,其内部的事件分发的过程中,onTouchListener的优先级高于onTouchEvent,而常用的onClickListener的优先级是最低的。即在onTouch->onTouchEvent->onClick。

几个重要的结论:

  • 在整个View树中的事件分发中,如果一个View一旦开始处理事件,但它不消耗ACTION_DOWN事件(onTOuchEvent返回false),那么同一个事件序列中的其他事件也不会交给它来处理,而是将事件重新交给它的父元素进行处理,即父元素的onTouchEvent会被调用。
  • 而如果一个View消耗了ACTION_DOWN,但没有消耗事件序列中的其他事件,那么这个点击事件会消失,并且此时父元素的onTouchEvent也不会被调用,当前View可以持续受到后续的事件,最终这些消失的点击事件会传递给Activity处理。
  • ViewGroup默认不拦截任何事件
  • View没有onInterceptTouchEvent方法,一旦有点击事件传递给它,就好调用它的onTouchEvent方法。
  • View的onTouchEvent默认都是会消耗事件的,除非它是不可点击的(clickable和longClickable为false)
  • 事件传递过程是由外向内的,即事件总是传给父元素,然后再由父元素分发给子View,通过requestDisallowInterceptTouchEvent方法可以在子元素中干预父元素中的分发过程(除ACTION_DOWN)

六、View的滑动冲突问题

View的滑动冲突常见可以简单分为三种:

1.外部滑动和内部滑动方向不一致

2.外部滑动方向和内部滑动方向一致

3.上面两张情况的嵌套

  对于第一种情况,一个很好的例子就是ViewPager和Fragment嵌套使用组成的页面滑动效果,而在Fragment内部又会嵌套一个ListView。大家都知道ViewPager的滑动方向是水平的,而ListView的滑动方向是竖直的,这种情形和第一种情况是相符的。当然ViewPager在内部处理了这种滑动冲突,因此采用ViewPager不用考虑这个问题。而如果我们采用的是ScrollView,则必须手动处理这种滑动冲突了。

  对于第二种情况,即内外两层都是需要上下滑动或者左右滑动的。可以举一个常见的例子,即ViewPager和NavigationDrawer。这两者都是水平方向的滑动。当然在实际使用中,会发现并没有滑动冲突,还是上一个原因,ViewPager内部处理了这种滑动冲突。

   第三种情况即前面两个例子的融合。

2.滑动冲突的处理规则

如何解决滑动冲突,这就需要用到前面讲到的事件分发机制了,其核心思想就是根据实际事件的特点(down的位置,水平滑动距离,竖直滑动距离等)来判断由哪个View来拦截事件。对于第一种情况可以简单地判断是水平滑动还是竖直滑动来判断由哪个View来拦截事件。(可以根据水平和竖直方向上的距离差或速度差来进行判断),而对于第二种情况,可根据down的位置来加以区分。

3.滑动冲突的解决方法

  • 外部拦截法 —— 即点击事件先经过父容器的拦截处理,如果父容器需要此事件就拦截,不需要就不拦截,需要重写父容器的onInterceptTouchEvent方法;在onInterceptTouchEvent方法中,首先ACTION_DOWN这个事件,父容器必须返回false,即不拦截ACTION_DOWN事件,因为一旦父容器拦截了ACTION_DOWN,那么后续的ACTION_MOVE/ACTION_UP都会直接交给父容器处理;其次是ACTION_MOVE,根据需求来决定是否要拦截;最后ACTION_UP事件,这里必须要返回false,在这里没有多大意义。
  • 内部拦截法 —— 所有事件都传递给子元素,如果子元素需要就消耗掉,不需要就交给父元素处理,需要子元素配合requestDisallowInterceptTouchEvent方法才能正常工作;父元素需要默认拦截除ACTION_DOWN以外的事件,这样子元素调用parent.requestDisallowInterceptTouchEvent(false)方法时,父元素才能继续拦截需要的事件。(ACTION_DOWN事件不受requestDisallowInterceptTouchEvent方法影响,所以一旦父元素拦截ACTION_DOWN事件,那么所有元素都无法传递到子元素去)

两种拦截方法的范式(伪代码形式):

外部拦截法:只需要重写父容器的onInterceptTouchEvent方法

    private  int mLastXIntercepet=0;
    private  int mLastYIntercepet=0;

    @Override
    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;
            default:
                break;
        }
        mLastXIntercepet=x;
        mLastYIntercepet=y;
        return  intercepted;
    }

内部拦截法:需要重写子元素的dispatchTouchEvent方法和父容器的onInterceptTouchEvent方法。

子元素的dispatchTouchEvent方法

 //分别记录上次滑动的坐标
    private int mLastX=0;
    private int mLastY=0;

    @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(父容器需要此类点击事件){
                    getParent().requestDisallowInterceptTouchEvent(false);
                }
                break;
            case MotionEvent.ACTION_UP:
                break;
            default:
                break;
        }
        mLastX=x;
        mLastY=y;
        return super.dispatchTouchEvent(ev);
    }

父容器的onInterceptTouchEvent:

 @Override
    public boolean onInterceptTouchEvent(MotionEvent ev) {
        int x= (int) ev.getX();
        int y= (int) ev.getY();

        int action=ev.getAction();
        if(action==MotionEvent.ACTION_DOWN)
            return false;

        else
            return true;
    }

时间: 2024-08-07 13:11:34

View的事件体系的相关文章

Android艺术开发探索第三章————View的事件体系(下)

Android艺术开发探索第三章----View的事件体系(下) 在这里就能学习到很多,主要还是对View的事件分发做一个体系的了解 一.View的事件分发 上篇大致的说了一下View的基础知识和滑动,现在我们再来聊聊一个比较核心的知识点,那就是事件分发了,而且他还是一个难点,我们更加应该掌握,View的滑动冲突一直都是很苦恼的,这里,我们就来一起探索一下 1.点击事件的传递规则 我们分析的点击事件可不是View.OnClickListener,而是我们MotionEvent,即点击事件,关于M

Android开发艺术探索读书(三)-View的事件体系

移动手持客户端作为目前最受欢迎的智能设备,拥有着最为广大的体验用户群体.因此,作为软件开发商,要紧紧抓住用户的胃口,不仅要向用户提供合适的服务项目,也应该更为注重与用户的交互体验.而作为感觉型的用户,应用操作是否流畅,界面内容是不是足够精致,是判断该应用是不是一个好应用的硬性标准.那么,要如何去强化与用户的交互体验呢?这就涉及了本章所讲的内容:View的事件体系 提纲: 一.什么是View 二.View的位置参数 三.几个相关的View知识点 四.View的滑动深入 五.view的事件分发 六.

Android艺术开发探索第三章——View的事件体系(上)

Android艺术开发探索第三章----View的事件体系(上) 我们继续来看这本书,因为有点长,所以又分了上下,你在本片中将学习到 View基础知识 什么是View View的位置参数 MotionEvent和TouchSlop VelocityTracker,GestureDetector和Scroller View的滑动 使用scrollTo/scrollBy 使用动画 改变布局参数 各种滑动方式的对比 弹性滑动 使用Scroller 通过动画' 使用延时策略 这章的概念偏自定义View方

第三章:View的事件体系

3.1 View的基础知识 主要有:View的位置参数,MotionEvent和TouchSlop对象,VelocityTracker,GestureDetector和Scroller对象 3.1.1 View view是android中所有控件的基类,是一种界面层的控件的一种抽象. ViewGroup(控件组),内部包含了很多个控件,即一组view 3.1.2 View的位置参数 View的位置主要由它的四个顶点来决定:top(左上角纵坐标),left(左上角横坐标),right(右下角横坐标

Android View 的事件体系

android 系统虽然提供了很多基本的控件,如Button.TextView等,但是很多时候系统提供的view不能满足我们的需求,此时就需要我们根据自己的需求进行自定义控件.这些控件都是继承自View的. 一.android 控件架构 android 中的控件在界面上都会占一块巨型区域,主要分为两类:ViewGroup和View控件.ViewGroup作为父控件可以包含多个View控件,并管理他们,但其也是继承自View.通过Viewgroup,整个控件界面形成了View的控件树,如图1所示.

第3章 View的事件体系

一.View基础 View的位置参数: 参数:top,left,right,bottom x,y,translationX,translationY(android3.0之后) 四个顶点确定:top(左上纵坐标).left(左上横坐标).right(右下横坐标).bottom(右下纵坐标).相对于父容器来说的. View的位置坐标和父容器的关系: 得出View的宽高和坐标的关系: width  = right - left heigth = bottom - top Left = getLeft

android知识回顾--view的事件体系

1.view的滑动,六种滑动方式:    一:通过layout来实现滑动效果      package com.example.testdragview; import android.content.Context; import android.util.AttributeSet; import android.util.Log; import android.view.MotionEvent; import android.view.View; public class DragView

《Android 开发艺术探索》笔记——(3)View 的事件体系

View 基础知识 View 是 Android 中所有控件的基类,ViewGroup 也继承了 View. Android 中,x 轴和 y 轴的正方向分别为右和下. 位置参数: (left , top ): View 左上角原始坐标 (right, bottom): View 右下角原始坐标 (x , y ): View 左上角最终坐标 translationX: View 左上角横向偏移量 translationY: View 左上角纵向偏移量 x = left + translation

彻底理解View事件体系!

我的简书同步发布:彻底理解View事件体系! 转载请注明出处:[huachao1001的专栏:http://blog.csdn.net/huachao1001] View的事件体系整体上理解还是比较简单的,但是却有很多细节.这些细节很容易忘记,本文的目标是理解性的记忆,争取做到看完不忘.最近在复习,希望本文能对你也有所帮助.如果你已经对View事件体系有一定的了解,那么查漏补缺,看看你是不是已经掌握了以下内容呢? 1 View事件相关基础 在正式接触View事件体系之前,先看看相关基础部分. 1