绘制界面只是创建自定义视图的一个部分。您还需要让视图以非常类似于您模仿的真实操作的方式响应用户输入。
让应用中的对象的行为方式与真实对象相似。例如,不要让应用中的图片消失后重新出现在其他位置,因为现实世界中的对象不会这样做。而是应该将图片从一个位置移动到另一个位置。
用户可以感受到界面中的细微行为或感觉,并对模仿现实世界的细微差别做出最佳反应。例如,当用户快滑界面对象时,在开始时为用户提供一种惯性感,以延迟移动。在动作结束时,让他们感受到使物体超出快滑范围的动量。
本页演示了如何使用 Android 框架的功能将这些真实行为添加到您的自定义视图中。
处理输入手势
像许多其他界面框架一样,Android 支持输入事件模型。用户操作会转换为触发回调的事件,您可以替换回调以自定义应用对用户的响应方式。Android 系统中最常见的输入事件是“轻触”,会触发 onTouchEvent(android.view.MotionEvent)
。您可以重写此方法来处理事件,如下所示:
Kotlin
override fun onTouchEvent(event: MotionEvent): Boolean { return super.onTouchEvent(event) }
Java
@Override public boolean onTouchEvent(MotionEvent event) { return super.onTouchEvent(event); }
触摸事件本身并不是特别有用。现代触控界面根据手势定义互动,例如点按、拉、推、快滑和缩放。为了将原始轻触事件转换为手势,Android 提供了 GestureDetector
。
通过传入实现 GestureDetector.OnGestureListener
的类的实例来构建 GestureDetector
。如果您只想处理几个手势,可以扩展 GestureDetector.SimpleOnGestureListener
,而不是实现 GestureDetector.OnGestureListener
接口。例如,以下代码会创建一个扩展 GestureDetector.SimpleOnGestureListener
并替换 onDown(MotionEvent)
的类。
Kotlin
private val myListener = object : GestureDetector.SimpleOnGestureListener() { override fun onDown(e: MotionEvent): Boolean { return true } } private val detector: GestureDetector = GestureDetector(context, myListener)
Java
class MyListener extends GestureDetector.SimpleOnGestureListener { @Override public boolean onDown(MotionEvent e) { return true; } } detector = new GestureDetector(getContext(), new MyListener());
无论您是否使用 GestureDetector.SimpleOnGestureListener
,请始终实现返回 true
的 onDown()
方法。这是必要的,因为所有手势都以 onDown()
消息开头。如果您从 onDown()
返回 false
(就像 GestureDetector.SimpleOnGestureListener
一样),系统会假设您想要忽略其余手势,并且不会调用 GestureDetector.OnGestureListener
的其他方法。仅当您想要忽略整个手势时,才从 onDown()
返回 false
。
实现 GestureDetector.OnGestureListener
并创建 GestureDetector
的实例后,您可以使用 GestureDetector
解读在 onTouchEvent()
中收到的触摸事件。
Kotlin
override fun onTouchEvent(event: MotionEvent): Boolean { return detector.onTouchEvent(event).let { result -> if (!result) { if (event.action == MotionEvent.ACTION_UP) { stopScrolling() true } else false } else true } }
Java
@Override public boolean onTouchEvent(MotionEvent event) { boolean result = detector.onTouchEvent(event); if (!result) { if (event.getAction() == MotionEvent.ACTION_UP) { stopScrolling(); result = true; } } return result; }
当您向 onTouchEvent()
传递无法被识别为手势一部分的触摸事件时,它会返回 false
。然后,您可以运行自己的自定义手势检测代码。
创建物理上合理的动作
手势是控制触摸屏设备的一种强大方式,但它们可能违背常理且难以记住,除非它们产生物理上合理的结果。
例如,假设您想要实现一个水平快速滑动手势,用于设置在视图中绘制的项目围绕其垂直轴旋转。如果界面的响应方式是沿快滑方向快速移动,然后放慢速度,就好像用户推动飞轮并使其旋转一样,这种手势很合理。
有关如何为滚动手势添加动画效果的文档详细介绍了如何实现您自己的滚动行为。但模拟飞轮的感觉并非易事。要使飞轮模型正常工作,需要运用大量的物理知识和数学运算。幸运的是,Android 提供了辅助类来模拟此行为和其他行为。Scroller
类是处理飞轮式快速滑动手势的基础。
如需开始快滑,请调用 fling()
并传入初始速度以及快滑的最小和最大 x 值和最大 y 值。对于速度值,您可以使用由 GestureDetector
计算的值。
Kotlin
fun onFling(e1: MotionEvent, e2: MotionEvent, velocityX: Float, velocityY: Float): Boolean { scroller.fling( currentX, currentY, (velocityX / SCALE).toInt(), (velocityY / SCALE).toInt(), minX, minY, maxX, maxY ) postInvalidate() return true }
Java
@Override public boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX, float velocityY) { scroller.fling(currentX, currentY, velocityX / SCALE, velocityY / SCALE, minX, minY, maxX, maxY); postInvalidate(); return true; }
调用 fling()
将设置快滑手势的物理模型。之后,通过定期调用 Scroller.computeScrollOffset()
来更新 Scroller
。computeScrollOffset()
通过读取当前时间并使用物理模型计算当时的 x 和 y 位置,从而更新 Scroller
对象的内部状态。调用 getCurrX()
和 getCurrY()
可检索这些值。
大多数视图会将 Scroller
对象的 x 和 y 位置直接传递给 scrollTo()
。此示例略有不同:它使用当前的滚动 x 位置来设置视图的旋转角度。
Kotlin
scroller.apply { if (!isFinished) { computeScrollOffset() setItemRotation(currX) } }
Java
if (!scroller.isFinished()) { scroller.computeScrollOffset(); setItemRotation(scroller.getCurrX()); }
Scroller
类会为您计算滚动位置,但不会自动将这些位置应用到视图。应经常应用新坐标,以保证滚动动画的流畅性。您可以采用下列两种方法:
- 调用
fling()
后调用postInvalidate()
,以强制重新绘制。此方法要求您在onDraw()
中计算滚动偏移,并在每次滚动偏移发生变化时调用postInvalidate()
。 - 设置
ValueAnimator
以在快滑期间添加动画效果,并通过调用addUpdateListener()
添加监听器以处理动画更新。 通过此方法,您可以为View
的属性添加动画效果。
让转场更顺畅
用户希望现代界面能够在状态之间流畅过渡:界面元素应淡入和淡出,而不是出现和消失;动作开始和结束平稳,而不是突然开始和停止。Android 属性动画框架可以更轻松地实现平滑过渡。
如需使用动画系统,每当属性更改会影响视图外观时,请勿直接更改该属性,请改用 ValueAnimator
进行更改。在以下示例中,修改视图中的选定子组件会使整个渲染视图旋转,使选择指针居中。ValueAnimator
会用几百毫秒的时间更改旋转,而不是立即设置新的旋转值。
Kotlin
autoCenterAnimator = ObjectAnimator.ofInt(this, "Rotation", 0).apply { setIntValues(targetAngle) duration = AUTOCENTER_ANIM_DURATION start() }
Java
autoCenterAnimator = ObjectAnimator.ofInt(this, "Rotation", 0); autoCenterAnimator.setIntValues(targetAngle); autoCenterAnimator.setDuration(AUTOCENTER_ANIM_DURATION); autoCenterAnimator.start();
如果您要更改的值是基本 View
属性之一,则可以更轻松地执行动画,因为视图具有针对多个属性同时播放动画进行了优化的内置 ViewPropertyAnimator
,如以下示例所示:
Kotlin
animate() .rotation(targetAngle) .duration = ANIM_DURATION .start()
Java
animate().rotation(targetAngle).setDuration(ANIM_DURATION).start();