View的事件体系

本文详细介绍了Android中View的基础知识,包括View的位置参数、MotionEvent和TouchSlop、VelocityTracker、GestureDetector和Scroller对象。重点讲解了View的滑动机制,包括使用scrollTo/scrollBy、动画以及改变布局参数来实现滑动,并分析了各种滑动方式的优缺点。此外,文章深入探讨了View的事件分发机制,包括点击事件的传递规则和滑动冲突的处理,以及如何解决滑动冲突,提出了外部拦截法和内部拦截法。最后,文章通过示例详细解释了如何在实际场景中应用这些理论,如ViewPager与ListView的滑动冲突解决。
摘要由CSDN通过智能技术生成

转载自 Android开发艺术探索

本章将介绍Android中十分重要的一个概念:View,虽然说View不属于四大组件,但是它的作用堪比四大组件,甚至比Receiver和Provider的重要性都要大。在Android开发中。Activity承担这可视化的功能,同时Android系统提供了很多基础控件,常见的有Button、TextView、CheckBox等。很多时候仅仅使用系统提供的控件是不能满足需求的,因此我们就需要能够根据需求进行新控件的定义,而控件的自定义就需要对Android的View体系有 深入的理解,只有这样才能写出完美的自定义控件。同时Android手机属于移动设备,移动设备的一个特点就是用户可以直接通过屏幕来进行一系列操作,一个典型的场景就是屏幕的滑动,用户可以通过滑动来切换到不同的界面。很多情况下我们的应用都需要支持滑动操作,当处于不同层级的View都可以响应用户的滑动操作时,就会带来一个问题, 那就是滑动冲突。如何解决滑动冲突呢?这对于初学者来说的确是个头疼的问题,其实解决滑动冲突本不难,它需要读者对View的事件分发机制有一定的了解,在这个基础上,我们就可以利用这个特性从而得出滑动冲突的解决方法。上述这些内容就是本章所要介绍的内容,同时,View的内部工作原理和自定义View相关的知识会在第4章进行介绍。

3.1 View基础知识

本节主要介绍View的一些基础知识,从而为更好地介绍后续的内容做铺垫。主要介绍的内容有:View的位置参数、MotionEvent 和TouchSlop对象、VelocityTracker、GestureDetector和Scroller对象,通过对这些基础知识的介绍,可以方便读者理解更复杂的内容。类似的基础概念还有不少,但是本节所介绍的都是一些比较常用的,其他不常用的
基础概念读者可以自行了解。

3.1.1 什么是View

在介绍View的基础知识之前,我们首先要知道到底什么是View。View是Android中所有控件的基类,不管是简单的Button和TextView还是复杂的RelativeLayout和ListView,它们的共同基类都是View。所以说,View是一种界面层的控件的一种抽象,它代表了一个控件。除了View,还有ViewGroup,从名字来看,它可以被翻译为控件组,言外之意是ViewGroup内部包含了许多个控件,即一组View在Android的设计中,ViewGroup也继承了View,这就意味着View本身就可以是单个控件也可以是由多个控件组成的一组控件, 通过这种关系就形成了View树的结构,这和Web前端中的DOM树的概念是相似的。根据这个概念,我们知道,Button显然是个View,而LinearLayout不但是一个View而且还是一个ViewGroup,而ViewGroup内部是可以有子View的,这个子View同样还可以是 ViewGroup,依此类推。

明白View的这种层级关系有助于理解View的工作机制。如图3-1所示,可以看到自定义的TestButton是一个View,它继承了 TextView,而TextView则直接继承了View,因此不管怎么说,TestButton都是一个View,同理我们也可以构造出一个继承自ViewGroup的控件。
在这里插入图片描述

3.1.2 View的位置参数

View的位置主要由它的四个顶点来决定,分别对应于View的四个属性:top、left、right、bottom。其中top是左上角纵坐标,left是左上角横坐标,right是右下角横坐标,bottom是右下角纵坐标。需要注意的是,这些坐标都是相对于View的父容器来说的,因此它是一种相对坐标,View的坐标和父容器的关系如图3-2所示。在Android中,x轴和y轴的正方向分别为右和下,这点不难理解,不仅仅是Android,大部分显示系统都是按照这个标准来定义坐标系的。
在这里插入图片描述
根据图3-2,我们很容易得出View的宽高和坐标的关系:

width = right - left
height = bottom - top

那么如何得到View的这四个参数呢?也很简单,在View的源码中它们对应于mLeft、 mRight、mTop和mBottom这四个成员变量,获取方式如下所示。

•	Left = getLeft();
•	Right = getRight();
•	Top = getTop();
•	Bottom = getBottom();

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

这几个参数也是相对于父容器的坐标,并且translationX和translationY的默认值是0,和View的四个基本的位置参数一样,View也为它们提供了 get/set方法,这几个参数的换算关系如下所示。

x = left + translationX
y = top + translationY

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

3.1.3 MotionEvent和TouchSlop

1. MotionEvent

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

  • ACTION_DOWN——手指刚接触屏幕;
  • ACTION_MOVE——手指在屏幕上移动;
  • ACTION_UP——手机从屏幕上松开的一瞬间。

正常情况下,一次手指触摸屏幕的行为会触发一系列点击事件,考虑如下几种情况:

  • 点击屏幕后离开松开,事件序列为DOWN -> UP
  • 点击屏幕滑动一会再松开,事件序列为DOWN -> MOVE->...> MOVE -> UP

上述三种情况是典型的事件序列,同时通过MotionEvent对象我们可以得到点击事件发生的x和y坐标。为此,系统提供了两组方法:getX/getY和getRawX/getRawY。它们的区别其实很简单,getX/getY返回的是相对于当前View左上角的x和y坐标,而 getRawX/getRawY返回的是相对于手机屏幕左上角的x和y坐标

2. TouchSlop

TouchSlop是系统所能识别出的被认为是滑动的最小距离,换句话说,当手指在屏幕上滑动时,如果两次滑动之间的距离小于这个常量,那么系统就不认为你是在进行滑动操作。 原因很简单:滑动的距离太短,系统不认为它是滑动。这是一个常量,和设备有关,在不同设备上这个值可能是不同的,通过如下方式即可获取这个常量:Viewconfiguration. get(getContext()).getScaledTouchSlop()。这个常量有什么意义呢?当我们在处理滑动时,可以利用这个常量来做一些过滤,比如当两次滑动事件的滑动距离小于这个值,我们就可以认为未达到滑动距离的临界值,因此就可以认为它们不是滑动,这样做可以有更好的用户体验。其实如果细心的话,可以在源码中找到这个常量的定义,在frameworks/base/core/ res/res/values/config.xmI 文件中,如下所示。这个"config_viewConflgurationTouchSlop"对
应的就是这个常量的定义。

<! -- Base "touch slop" value used by Viewconfiguration as a movement threshold where scrolling should begin. -->
<dimen name="config_viewConfigurationTouchSlop">8dp</dimen>

3.1.4 VelocityTracker、GestureDetector和Scroller

1. VelocityTracker

速度追踪,用于追踪手指在滑动过程中的速度,包括水平和竖直方向的速度。它的使用过程很简单,首先在View的onTouchEvent方法中追踪当前单击事件的速度

VelocityTracker velocityTracker = VelocityTracker.obtain();
velocityTracker.addMovement(event);

接着,当我们先知道当前的滑动速度时,这个时候可以采用如下方式来获得当前的速度

velocityTracker.computeCurrentVelocity(1000);
int xVelocity = (int) velocityTracker.getXVelocity();
int yVelocity = (int) velocityTracker.getYVelocity();

在这一步中有两点需要注意,第一点,获取速度之前必须先计算速度,即getXVelocity和getYVelocity这两个方法的前面必须要调用computeCurrentVelocity方法第二点,这里的速度是指一段时间内手指所滑过的像素数,比如将时间间隔设为1000ms时,在1s内, 手指在水平方向从左向右滑过100像素,那么水平速度就是100。注意速度可以为负数,当手指从右往左滑动时,水平方向速度即为负值,这个需要理解一下。速度的计算可以用如下公式来表示:

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

根据上面的公式再加上Android系统的坐标系,可以知道,手指逆着坐标系的正方向滑动,所产生的速度就为负值。另外,computeCunentVelocity这个方法的参数表示的是一个时间单元或者说时间间隔,它的单位是毫秒(ms),计算速度时得到的速度就是在这个时间间隔内手指在水平或竖直方向上所滑动的像素数。针对上面的例子,如果我们通过 velocityTracker.computeCurrentVelocity(100)来获取速度,那么得到的速度就是手指在100ms内所滑过的像素数,因此水平速度就成了10像素/每100ms (这里假设滑动过程是匀速的),即水平速度为10,这点需要好好理解一下。

最后当不需要使用它的时候,需要调用clear方法来重置并回收内存

velocityTracker.clear();
velocityTracker.recycle();

上面就是如何使用VelocityTracker对象的全过程,看起来并不复杂。

2. GestureDetector

手势检测,用于辅助检测用户的单击、滑动、长按、双击等行为。要使用GestureDetector也不复杂,参考如下过程

首先需要创建一个GestureDetector对象并实现OnGestureListener接口,根据需要我们还可以实现OnDoubleTapListener从而能够监听双击行为

GestureDetector mGestureDetector = new GestureDetector(this);
//解决长按屏幕后无法拖动的现象
mGestureDetector.setlsLongpressEnabled(false);

接着接管目标View的onTouchEvent方法,在待监听View的onTouchEvent方法中 添加如下实现

boolean consume = mGestureDetector.onTouchEvent(event);
return consume;

做完了上面两步,我们就可以有选择地实现OnGestureListener和OnDoubleTapListener中的方法了,这两个接口中的方法介绍如表3.1所示。

在这里插入图片描述在这里插入图片描述
表3-1里面的方法很多,但是并不是所有的方法都会被时常用到,在日常开发中,比较常用的有:onSingleTapUp (单击)、onFling(快速滑动)、onScroll(拖动)、onLongPress(长按)和onDoubleTap (双击)。另外这里要说明的是,实际开发中,可以不使用GestureDetector,完全可以自己在View的onTouchEvent方法中实现所需的监听,这个就看个人的喜好了。这里有一个建议供读者参考:如果只是监听滑动相关的,建议自己在onTouchEvent中实现,如果要监听双击这种行为的话,那么就使用GestureDetector

3. Scroller

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

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值