将自定义视图设为互动式

尝试使用 Compose 方式
Jetpack Compose 是推荐用于 Android 的界面工具包。了解如何在 Compose 中使用布局。

绘制界面只是创建自定义视图的一个部分。您还需要让视图以与自己模仿的真实操作非常相似的方式响应用户输入。

让应用中的对象像真实对象一样行动。例如,不要让应用中的图片突然消失并重新出现在其他地方,因为现实世界中的对象不会这样。相反,请将图片从一个位置移动到另一个位置。

用户甚至能感受到界面中的细微行为变化或者给人带来的细微感觉变化,并对细微之处做出最佳反应来模仿现实世界。例如,当用户快速滑动某个界面对象时,开始时要感受到导致运动延迟的惯性。结束时也要感受到使运动超出快速滑动范围的动量。

本页介绍了如何使用 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,都应始终实现返回 trueonDown() 方法。这是必要步骤,因为所有手势都以 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() 并传入初始速度以及快滑的最小和最大 xy 值。对于速度值,您可以使用 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() 来更新ScrollercomputeScrollOffset() 通过读取当前时间并 使用物理模型计算当时的 xy 位置,以此更新 Scroller 对象的内部状态。调用 getCurrX()getCurrY() 以检索这些值。

大多数视图都会将 Scroller 对象的 xy 位置直接传递给 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();