Android View的事件体系教程详解

目录
  • 一、什么是View?什么是ViewGroup?
  • 二、View的位置
  • 三、View的触摸事件
    • 1.MotionEvent
    • 2.TouchSlop
    • 3.VelocityTracker
    • 5.Scroller
  • 四、View的滑动
    • 1)使用Scroll
    • 2)通过动画
    • 3)使用延时策略
  • 五、View的事件分发机制
  • 六、View的滑动冲突问题
    • 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内部处理了这种滑动冲突。

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

滑动冲突的处理规则

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

滑动冲突的解决方法

  • 外部拦截法 —— 即点击事件先经过父容器的拦截处理,如果父容器需要此事件就拦截,不需要就不拦截,需要重写父容器的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;
    }

以上就是Android View的事件体系教程详解的详细内容,更多关于Android View事件体系的资料请关注我们其它相关文章!

(0)

相关推荐

  • Android为View添加拖放效果的方法实例

    目录 1.引言 2.主要方法和类介绍 2.1startDragAndDrop()和startDrag() 2.2setOnDragListener() 2.3View.DragShadowBuilder 2.4DragEvent 3.演示将一张图片拖放到方框内 3.1简易布局 3.2操作被拖放的图片 3.3方框接收图片 4.总结 1.引言 在开发中,拖放是一种比较常见的手势操作,使用它能够让应用的交互更加地便捷和友好,本文将简要介绍如何为Android中的View添加拖放效果. 2.主要方法和类

  • Android通过自定义view实现刮刮乐效果详解

    前言 已经有两个月没有更新博客了,其实这篇文章我早在两个月前就写好了,一直保存在草稿箱里没有发布出来.原因是有一些原理性的东西还没了解清楚,最近抽时间研究了一下混合模式,终于也理解了刮刮乐是怎么实现的,所以想继续分享一下自己的一些心得,先上效果图. 效果图: 实现原理 其实刮刮乐实现原理也不算很复杂,最关键的还是需要了解Paint的混合模式.因为刮刮乐是由两个bitmap组成的,一个是源图另一个是目标图,我们需要把目标图的颜色改成灰色,在源图上面盖上了一张灰色的目标图.当手指滑动屏幕时paint

  • Android自定义View实现数字雨效果的全过程

    目录 效果图 实现步骤 总结 效果图 在安卓中多种类型的动画,有帧动画.补间动画.属性动画,除此之外,使用自定义的View结合数学公式,就可以绘制出复杂的界面或者动画.这篇文章记录的是仿照黑客帝国的数字雨,来看看效果吧. 实现步骤 准备工作,常量的配置信息 // 文字的颜色值 final int DEFAULT_TEXT_COLOR = Color.argb(255, 0, 255, 70); // 文字大小 final int TEXT_SIZE = 24; // 普通画笔 Paint mPa

  • Android实现将View转化为图片并保存到本地

    本文实例为大家分享了Android将View转化为图片并保存到本地的具体代码,供大家参考,具体内容如下 一.概述 app中有需求需要将View转化为图片并保存到本地,这里分两种情况: 1.View本身已经显示在界面上2.View还没有添加到界面上或者没有显示(绘制)过 二.实现方法 对于上述的第一种情况我使用下面代码即可: private void viewSaveToImage(View view) {         view.setDrawingCacheEnabled(true);  

  • Android中RecyclerView实现商品分类功能

    本文实例为大家分享了Android中RecyclerView实现商品分类功能的具体代码,供大家参考,具体内容如下 三个个RecyclerView实现 //左边的布局  <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"     android:layout_width="match_parent"     android:layout_height="50d

  • Android系统view与SurfaceView的基本使用及区别分析

    目录 一.引入: 二.SurfaceView和View的不同之处 三.SurfaceView的基本使用 四.tips: 解决方法 一.引入: Android提供了View来进行绘图处理,在大部分情况下,View都能满足绘图需求.大家都知道View是通过刷新来重绘视图,Android系统通过发出VSYNC信号来进行屏幕的重绘,刷新的间隔时间为16ms.如果在16ms内View完成了你所需要执行的所有操作,那么用户在视觉上,就不会产生卡顿的感觉:反之,如果操作的逻辑过多时,就会掉帧从而使得用户感觉到

  • Android View的事件体系教程详解

    目录 一.什么是View?什么是ViewGroup? 二.View的位置 三.View的触摸事件 1.MotionEvent 2.TouchSlop 3.VelocityTracker 5.Scroller 四.View的滑动 1)使用Scroll 2)通过动画 3)使用延时策略 五.View的事件分发机制 六.View的滑动冲突问题 View的滑动冲突常见可以简单分为三种: 滑动冲突的处理规则 滑动冲突的解决方法 一.什么是View?什么是ViewGroup? View是Android中所有控

  • Android Studio开发环境搭建教程详解

    对于移动端这块,笔者之前一直都是进行iOS开发的,也从来没用过Java.但是因为进入了Google Android全国大学生移动互联网创新挑战赛(进入官网)的总决赛(笔者"西部计算机教育提升计划"的项目被直接推荐进入决赛),这个比赛要求一定要提交apk程序,所以我不得不赶紧学习一下Android开发了. 下面就对自己学习的过程做一个记录. 一.安装Android Studio 笔者用的计算机配置如下: Mac下安装Android Studio应该更简单一些,只需要下载一个Android

  • android studio 3.4配置Android -jni 开发基础的教程详解

    首先下载配置android studio ndk 1.打开sdkManager下载CMake和LLDB 2.配置ndk 项目新建 项目建立完毕后,工程目录如下,cpp文件夹是系统自动生成的 3.自定义 navite方法 接下来开始写自定义的一个native方法,新建一个Hello.java文件,里面写一个add求和的native方法,如下 生成c++头文件 然后在windows控制台Terminal进入hello.java所在的目录执行javac hello.java,如下 执行完毕后hello

  • Android触屏事件和MotionEvent详解

    Android屏幕操作 屏幕是用户和Android设备交互的主要媒介,屏幕分为触屏和非触屏.Android设备目前有四种类型:Android Phone,Android Tablet,Android Wear和Android TV.Android TV大都使用非触屏,其他三类设备则大都使用触屏.对非触屏设备,用户可以通过键盘鼠标或遥控器在屏幕上操作.对触屏设备,用户主要通过手指或触控笔等工具在屏幕上操作,当然也可以通过外接的键盘,鼠标和轨迹球等工具来操作. Android屏幕交互事件 用户在设备

  • Android View类与SurfaceView类详解

    Android游戏开发中主要的类除了控制类就是显示类,比较重要也很复杂的就是显示和游戏逻辑的处理.在J2ME中可以通过Display和Canvas来实现显示,而Android中处理显示的是View类.下面为大家简单介绍android.view.View和android.view.SurfaceView. SurfaceView是从View基类中派生出来的显示类,直接子类有GLSurfaceView和VideoView,可以看出GL和视频播放以及Camera摄像头一般均使用SurfaceView,

  • 微信小程序—微信跳一跳,Android游戏助手(外挂)使用教程详解

    作为一名有着丰富实战经验的Android开发人员,作为一个有着一定基础和实战的逆向新司机,第一次面对github上的这个项目自己也是懵的,即使看完了README,也还是不知道从何下手.在此之前玩游戏从未使用过游戏助手之类的.那么我在想,作为小半个专业选手尚且这样,广大小白又能比我好到哪里去呢? 以前我也曾经是个单纯的好骚年啊! 后来经过仔细阅读README,加上摸索,和查看源码,才慢慢走上正途:下面记录一下,以备你查看: 成果 图片说明:本人排行第二,记得我自己才跳了昨天一百七十多分,大多数都是

  • Android View的事件分发详解

    1.前言 近两天学习了一下view的事件分发,把自己的理解总结了一遍,只表达了自己认为需要明白的地方,毕竟是菜鸟一枚,不对的地方还请大神们多指教! 2.三个方法 public boolean dispatchTouchEvent(MotionEvent ev) 用于事件的分发,返回结果受以下两个方法的影响,表示是否消耗了事件. public boolean onInterceptTouchEvent(MotionEvent ev) 事件是否被拦截,返回true表示拦截,false表示不拦截 pu

  • Android开发实现带有反弹效果仿IOS反弹scrollview教程详解

    首先给大家看一下我们今天这个最终实现的效果图: 这个是ios中的反弹效果.当然我们安卓中如果想要实现这种效果,感觉不会那么生硬,滚动到底部或者顶部的时候.当然 使用scrollview是无法实现的.所以我们需要新建一个view继承ScrollView package davidbouncescrollview.qq986945193.com.davidbouncescrollview; import android.annotation.SuppressLint; import android.

  • Android 拦截返回键事件的实例详解

    Android 拦截返回键事件的实例详解 KeyEvent类 Android.View.KeyEvent类中定义了一系列的常量和方法,用来描述Android中的 按键事件和返回键有关的常量和方法有. KeyEvent.KEYCODE_BACK: 表示key类型为返回键 KeyEvent.ACTION_DOWN:表示事件为按下key,如果一直按住不放,则会不停产生此事件. KeyEvent.ACTION_UP:表示事件为为放开key,一次点击key过程只会调用一次. public final in

  • Android ListView监听滑动事件的方法(详解)

    ListView的主要有两种滑动事件监听方法,OnTouchListener和OnScrollListener 1.OnTouchListener OnTouchListener方法来自View中的监听事件,可以在监听三个Action事件发生时通过MotionEvent的getX()方法或getY()方法获取到当前触摸的坐标值,来对用户的滑动方向进行判断,并可在不同的Action状态中做出相应的处理 mListView.setOnTouchListener(new View.OnTouchLis

随机推荐