Unlimited Plugins, WordPress themes, videos & courses! Unlimited asset downloads! From $16.50/m
Advertisement
  1. Code
  2. Android SDK

Android SDK:手势简介

Difficulty:IntermediateLength:LongLanguages:

Chinese (Simplified) (中文(简体)) translation by Zhang Xiang Liang (you can also view the original English article)

在过去几年中使用触摸屏设备的最普遍的变化之一是采用手指手势,例如滑动和甩尾。它们允许用户和设备之间的交互变得非常直观和自然。 在本教程中,您将学习如何在自己的Android应用程序中使用手势。

本教程将使用在开源项目中提供的代码。作者假设读者对Android和Java有一些经验。但是,如果您对我们所做的事情有疑问,请随时提问。

本教程将教您如何开始在应用程序中处理手指手势。我们将通过使用自定义View对象上的基本Canvas对象绘图来完成此操作。 这种技术可以应用于任何您使用的图形环境,无论是2D表面还是OpenGL ES渲染。 如果您对多点触控手势感兴趣(更高级的手势处理主题),我们将在另一个即将推出的教程中介绍。

第0步:创建项目

我们开始简单吧。创建一个新的Android项目。我们已经命名了我们的项目Gesture Fun,并配置了一个名为GestureFunActivity的Activity。将默认布局文件main.xml修改为以下非常基本的布局:

此布局中的重要项目是FrameLayout定义。FrameLayout控件用于保存将绘制图像的自定义视图。

最后,让我们更新Activity类的onCreate()方法以初始化FrameLayout控件并为其提供一些内容:

此时,我们尚未定义PlayAreaView类,因此该项目无法编译。有耐心,我们会在下一步中做到这一点。

第1步:创建新视图:PlayAreaView

通过我们所有的项目设置,我们现在可以专注于有趣的部分:绘制Canvas对象。 获取Canvas对象的简单方法是重写View对象的onDraw()方法。方便的是,这个方法有一个参数:Canvas对象。 在Canvas对象上绘制位图图形与调用Canvas对象的drawBitmap()方法一样简单。以下是我们的新PlayAreaView类中定义的onDraw()方法实现的简单示例:

我们的onDraw()方法实现是非常基础的。像往常一样,您需要在活动中的某处定义您的DEBUG_TAG日志标记变量。 大部分的onDraw()方法只是信息输出。在这个方法中唯一真正的工作是在drawBitmap()调用中进行的,其中第一个参数是要绘制的图像。 第二个参数是一个名为translate的Matrix对象,顾名思义,它决定了相对于Canvas对象所在的View绘制位图的位置。 本教程中的所有其他代码都将涉及基于某些用户触摸事件操作翻译矩阵。这反过来会改变位图对象在画布内以及因此在画面上绘制的位置。

第2步:配置新视图

PlayAreaView类需要一个构造函数来执行一些初始设置。由于我们的自定义View需要对手势做出反应,因此我们需要在此处添加一个GestureDetector。 GestureDetector是一个Android类,可以采取运动事件,做一些数学魔术来确定它们是什么,然后将调用委托给GestureListener对象作为特定的手势或其他动作回调。 GestureListener对象是我们实现的类,它接收这些GestureDetector识别的特定手势的调用,并允许我们在我们认为合适的时候对它们做出反应(在这种情况下,在我们的PlayAreaView中移动图形)。 尽管GestureDetector处理某些动作的检测,但它不会执行任何特定的动作,也不处理所有类型的手势。但是,为了本教程的目的,它只提供了足够的信息。 .

让我们更详细地看看PlayAreaView的构造函数。首先,我们将翻译矩阵初始化为一个单位矩阵(默认值)。 回想一下,单位矩阵将不会对位图进行修改:它将被绘制在其原始位置。
接下来,我们创建并初始化GestureDetector - 一个默认的 - 并为其分配一个有效的GestureListener对象(我们稍后再详细讨论)。 最后,名为droid的位图可绘制直接从项目资源中加载。您可以使用任何您想要的图像 - 棒球,苹果,幸运饼干等。这是您将在Canvas对象上晃动的图形。

第3步:连接手势检测器

接下来我们会到达GestureListener,因为它是一个自定义对象。现在,让我们连接GestureDetector对象,以便它接收运动数据以完成其手势识别魔术。

现在让我们连接称为手势的GestureDector对象来接收事件。为此,请按如下所示覆盖PlayAreaView类中的View控件的onTouchEvent()方法:

我们在这里所做的就是使GestureDetector成为此自定义视图的所有触摸事件中的最后一个单词。但是,GestureDetector实际上并没有对动作事件做任何事情,它只是识别它们并调用已注册的GestureListener类。

步骤4:实现手势监听器

为了对由GestureDetector类识别的事件做出反应,我们需要实现GestureListener类。我们最感兴趣的动作事件是双击和任何形式的手势。要监听这些类型的动作事件,我们的GestureListener类必须实现OnGestureListener和OnDoubleTapListener接口。 .

将该类添加为Activity的子类后,为每个所需方法添加默认实现。例如,以下是onDown()方法的实现:

通过实现这些方法,您可以研究各种由GestureDetector对象识别的事件。 有趣的是,如果onDown()方法不返回true,我们感兴趣的主要手势 - 滚动(或拖动) - 将不会被检测到。但是,对于您不感兴趣的其他已识别事件,您可以返回false。

作为参数传递给每个回调方法的MotionEvent对象有时代表启动手势识别的触摸事件,其他时间代表完成手势识别的最后一个事件。出于我们的目的,我们让GestureDetector类处理解密MotionEvent表示哪种动作的所有细节。

注意:Android框架还提供了一个名为SimpleOnGestureListener的便捷类,它将两个接口(OnGestureListener和OnDoubleTapListener)组合成一个类,并为所有方法使用默认实现。默认实现返回false。

第5步:处理简单的运动事件

我们想要处理的第一个事件是滚动事件。当用户触摸屏幕然后将手指移过屏幕时,会发生滚动事件。 这个手势也被称为拖动事件。此事件通过OnGestureListener接口的onScroll()方法进入。

以下是onScroll()方法的实现:

使用滚动事件将移动请求传递给PlayAreaView对象。此方法的实施是映射手指动作事件如何提示图形移动的重要第一步。我们很快会做到这一点。与此同时,你已经处理了你的第一个手势! 。

图形将在整个屏幕上移动 - 有时甚至是关闭的。根据定义,图像只有在View对象的边界内绘制时才可见。 如果图形的坐标位于View对象的边界之外,则会剪切图形(不可见)。 您可以放入边缘检测和其他各种逻辑(超出本教程的范围,我们害怕),或者添加双击检测并重置图形位置。 以下是onDoubleTap()方法的示例实现(来自OnDoubleTapListener接口):

与之前的方法实现一样,我们使用这种已识别的运动,双击,在视图控件内触发更改。在这种情况下,我们只需重置视图的位置。

第6步:拖拽

拖拽手势本质上是在正在拖动屏幕的项目上留下速度。运动中的项目通常会逐渐减慢,但这种行为是由开发人员的实施决定的。 例如,在一个游戏中,速度可能受到游戏世界物理学的影响。在其他应用中,速度可以基于任何公式对于其所代表的动作都适合。 测试是了解其感觉的最佳方式。根据我们的经验,需要一些试验和错误来解决感觉 - 看起来 - 恰到好处的事情。

在我们的实现中,我们将改变由投掷造成的运动停止之前的时间长度,然后根据onFling()方法传递给我们的速度简单地启动图像到最终目标的动画,以及我们设定的时间。 请记住,直到用户手指不再触摸显示屏后,才会检测到手势。想想它就像扔石头 - 当你释放它时岩石会继续行进 - 这是用户“放手”后我们想要制作动画的部分。

听起来很复杂?代码如下:

我们甚至不需要检查两个MotionEvent参数,速度数据对我们来说已经足够了。速度单位以像素/秒为单位。 因此,我们可以使用速度数据来决定缩放因子,该因子将用于确定图像完全停止前的最终时间长度。在我们的例子中,我们使用了40%的秒(400毫秒)。 因此,将这两个速度值的一半乘以40%(又称distanceTimeFactor变量),我们就可以得出在这一小部分时间后所达到的总移动量。 最后,我们将这些信息传递给View对象的自定义onAnimateMove()方法,这实际上会使我们的图形显示为使用fling motion事件提供的信息在屏幕上移动。

为什么一半的初始速度?如果我们从速度A开始,以速度B结束于任何时间量,则平均速度为(A + B)/ 2。 在这种情况下,结束速度为0.因此,我们在这里将速度减半,这样我们就不会无意中使图像看起来像是从我们的手指跳开的速度比它在释放之前快。

为什么400毫秒?没有理由,但它在大多数设备上看起来相当不错。 这与校准鼠标移动无异 - 太快了,感觉很刺眼,很难看清,太慢了,你正在等待你迟钝的鼠标指针赶上你的大脑。 这个值是调整你的投掷“感觉”的主要变量。值越高,滑动到屏幕上停止时图像看起来所具有的“摩​​擦”就越小。 如果你有真实的表面变化,你需要应用常规的物理计算。在这里,我们只是做一个固定的功能减速,没有任何真正的物理。

第7步:移动图像

现在我们对区域感兴趣的所有手势都处理完毕了,是时候实施底层图形的实际移动了。返回PlayAreaView类,添加onMove()方法:

这个方法有两件事。首先,它根据手指移动的距离将我们自己的矩阵转换(翻译=从A点移动到B点的图形项)。然后它使视图无效,以便重绘。 绘图时,图像将在视图内的不同位置绘制。如果我们想要平移整个View对象,我们可以使用View对象的translate()方法更新其Canvas使用的内部矩阵,以执行所有绘图。 这对某些事情可能会有用,但如果我们在视图内部有一些静态(我们指的是固定,静止,不动,如山),它不会。相反,对于这种情况,我们只是更新我们自己的矩阵,翻译,我们每次绘制图形时都会使用它。 。 。

现在我们还添加onResetLocation()方法:

该方法简单地将矩阵重置为单位矩阵,并通过invalidate()方法再次重绘视图。当视图重新绘制时,图形将回到其初始位置。

第8步:平滑移动图像

对于投掷运动,我们还有一些工作要做,而不是仅仅把它放在一个新的位置。我们希望它在这个位置生动活泼。 通过动画可以实现平滑的移动 - 也就是说,可以非常迅速地在不同位置绘制图像。 我们不是动画View对象。相反,我们正在由View控制的Canvas上移动图像。所以,我们必须实现我们自己的动画。该死。☺

Android提供了不同的插值器,可以在使用内置动画类的动画期间的特定时间调整对象的位置。我们可以在我们自己的动画中利用这些不同的插补器来节省一些工作 - 并应用一些有趣的效果。这是可能的,因为提供的内插器很好地通用,并且不依赖于内置视图动画的具体工作方式。 。

我们从onAnimateMove()方法开始:

在这种方法中,我们跟踪起始位置,开始时间和结束时间。

在这里,我们使用OvershootInterpolator类来初始化我们的动画。由于这个内插器使得图像移动得比我们计算的更远,整体上,图像在技术上会稍微快一点。 这是一个足够小的差别,它不应该被注意到,但如果你追求精度的准确性,你需要调整它(这将意味着编写自己的插补器 - 
超出本教程的范围 - 并有一个方法用于计算总行程)。

所有这些信息都用于确定何时(及时)我们沿着动画的总时长。我们使用这些数据来确定何时(以百分比时间为单位)我们要将此信息传递给插补器。 内插器需要这个,所以它可以告诉我们在哪里(以百分比的距离)我们是从运动的起点到终点。我们用这个来确定我们从起点开始的地方(以像素为单位)。

这个计算全部在onAnimateStep()方法中完成,如下所示。我们通过邮件调用onAnimationStep()方法到消息队列。我们不想陷入紧张的循环 - 我们会导致系统无响应。 所以,简单的方法是只发布消息。这允许系统通过提供异步行为而不必处理线程来保持响应。由于我们必须在UI线程上进行绘图,无论如何,在这个简单的例子中,对于线程没有什么意义。 .

现在我们来实现onAnimateStep()方法:

关注我们的公众号
Advertisement
Advertisement
Advertisement
Advertisement
Looking for something to help kick start your next project?
Envato Market has a range of items for sale to help get you started.