敬请期待 12L,这是一次将于明年年初面向大屏设备推出的功能投放。立即试用!

滚动效果

在搭载 Android 12 及更高版本的设备上,滚动事件的视觉行为发生了变化。

在 Android 11 及更低版本中,滚动事件会使视觉元素发光。在 Android 12 及更高版本中,发生拖动事件时,视觉元素会拉伸和反弹;发生快速滑动事件时,它们会快速滑动和反弹:

新的滚动行为会影响拖动和快速滑动动画。

该行为会应用于使用 EdgeEffect 的所有应用,并且适用于以下类中的所有内容:

视觉效果对垂直滚动和水平滚动都适用。由于它默认应用于未停用滚动的所有应用,因此可以为用户提供更一致的界面体验。

最佳做法

为了确保新的滚动体验与您的应用完美搭配,请遵循以下最佳做法:

拉伸 EdgeEffect 的用法

EdgeEffect 添加了两个用于实现拉伸滚动效果的 API。

float getDistance()
float onPullDistance(float deltaDistance, float displacement)

为了利用拉伸滚动提供最佳用户体验,请执行以下操作:

  • 当用户在释放动画过程中释放并轻触内容时,将轻触注册为“捕捉”。用户停止动画并再次开始操控拉伸。
  • 当用户沿拉伸的相反方向移动手指时,释放拉伸,直到其完全消失,然后开始滚动。
  • 当用户在拉伸过程中快速滑动时,快速滑动 EdgeEffect 以增强拉伸效果。

捕捉动画

当用户捕捉活动的拉伸动画时,EdgeEffect.isFinished() 会返回 false。这表示拉伸应由轻触动作操控。在大多数容器中,这在 onInterceptTouchEvent() 中检测,如以下代码段所示:

Kotlin

override fun onInterceptTouchEvent(ev: MotionEvent): Boolean {
  ...
  when (action and MotionEvent.ACTION_MASK) {
    MotionEvent.ACTION_DOWN ->
      ...
      isBeingDragged = !edgeEffectBottom.isFinished() ||
          !edgeEffectTop.isFinished()
      ...
  }
  return isBeingDragged
}

Java

@Override
public boolean onInterceptTouchEvent(MotionEvent ev) {
  ...
  switch (action & MotionEvent.ACTION_MASK) {
    case MotionEvent.ACTION_DOWN:
      ...
      mIsBeingDragged = !mEdgeEffectBottom.isFinished()
          || !mEdgeEffectTop.isFinished();
      ...

在前面的示例中,当 mIsBeingDraggedtrue 时,onInterceptTouchEvent() 返回 true,因此这对于在子级有机会消耗事件之前消耗事件已经足够了。

释放滚动效果

务必在滚动之前释放拉伸效果,以防止将拉伸应用于滚动内容。以下代码示例展示了这一点:

Kotlin

override fun onTouchEvent(ev: MotionEvent): Boolean {
  val activePointerIndex = ev.actionIndex

  when (ev.getActionMasked()) {
    MotionEvent.ACTION_MOVE ->
      val x = ev.getX(activePointerIndex)
      val y = ev.getY(activePointerIndex)
      var deltaY = y - mLastMotionY
      val pullDistance = deltaY / height
      val displacement = x / width

      if (deltaY < 0f && mEdgeEffectTop.distance > 0f) {
        deltaY -= height * mEdgeEffectTop
            .onPullDistance(pullDistance, displacement);
      }
      if (deltaY > 0f && mEdgeEffectBottom.distance > 0f) {
        deltaY += height * mEdgeEffectBottom
            .onPullDistance(-pullDistance, 1 - displacement);
      }

      ...
  }

Java

@Override
public boolean onTouchEvent(MotionEvent ev) {

  final int actionMasked = ev.getActionMasked();

  switch (actionMasked) {
    case MotionEvent.ACTION_MOVE:
      final float x = ev.getX(activePointerIndex);
      final float y = ev.getY(activePointerIndex);
      float deltaY = y - mLastMotionY;
      float pullDistance = deltaY / getHeight();
      float displacement = x / getWidth();

      if (deltaY < 0 && mEdgeEffectTop.getDistance() > 0) {
        deltaY -= getHeight() * mEdgeEffectTop
            .onPullDistance(pullDistance, displacement);
      }
      if (deltaY > 0 && mEdgeEffectBottom.getDistance() > 0) {
        deltaY += getHeight() * mEdgeEffectBottom
            .onPullDistance(-pullDistance, 1 - displacement);
      }
            ...

拖动时,在将触摸事件传递给嵌套滚动或拖动滚动内容之前,必须消耗 EdgeEffect 的拉取距离。在前面的代码示例中,当正在显示边缘效果且可以通过动作将其释放时,getDistance() 会返回一个正值。当触摸事件释放拉伸时,它首先由 EdgeEffect 消耗,这样就可以在显示其他效果(如嵌套滚动)之前完全释放。您可以使用 getDistance() 了解释放当前效果所需的拉取距离。

onPullDistance()onPull() 的不同之处在于返回所传递增量的已消耗量。onPull() 以前允许发光效果的总距离为负值。在 Android 12 及更高版本中,如果在 getDistance() 为 0 时向 onPull()onPullDistance() 传递负的 deltaDistance 值,则拉伸不会发生任何变化。

停用

您可以在 XML 布局文件中或以编程方式停用滚动。以下 XML 代码展示了布局文件中设置的 android:overScrollMode

<!-- Via markup -->
<ScrollView
  ...
  android:overScrollMode="never"
  ...
>

以编程方式停用,如以下代码段所示:

Kotlin

<!-- Programmatically-->
...
recyclerview.overScrollMode = View.OVER_SCROLL_NEVER
...

Java

<!-- Programmatically-->
...
recyclerview.setOverScrollMode(View.OVER_SCROLL_NEVER);
...

提供反馈

您的反馈对我们至关重要。如果您发现了问题,或对此功能的改进有自己的见解,请告诉我们。创建新问题前,请先查看现有问题。您可以点击星标按钮,为现有问题投票。

创建新问题

如需了解详情,请参阅问题跟踪器文档