スクロール操作のアニメーション化

Compose を試す
Jetpack Compose は、Android に推奨される UI ツールキットです。Compose でタップと入力を使用する方法について学習します。
<ph type="x-smartling-placeholder"></ph> スクロール →

Android では通常、 ScrollView クラスです。境界を超える可能性がある標準レイアウトをネストする ScrollView 内のコンテナに表示して、Google Cloud によって管理されるスクロール可能なビューを提供します。 説明します。カスタム スクローラーの実装は、 説明します。このドキュメントでは、レスポンスでスクロール効果を表示する方法について説明します。 スクローラーを使用したタップ操作。

アプリは スクローラー - Scroller または OverScroller - 宛先 タップに反応してスクロール アニメーションを生成するために必要なデータを収集します。 イベントです。類似していますが、OverScroller には次のメソッドも含まれています。 パンまたはフリングの後にコンテンツの端に達したことをユーザーに知らせる 行います。

  • Android 12(API レベル 31)以降、視覚要素が引き伸ばされたりバウンドしたりする ドラッグ イベントでフリングし、フリング イベントでバウンスします。
  • Android 11(API レベル 30)以前では、境界線に「グロー」が表示されます。 端までドラッグまたはフリング ジェスチャーした後のエフェクトです。

このドキュメントの InteractiveChart サンプルでは、 EdgeEffect クラスを使用して、これらのオーバースクロール効果を表示します。

スクローラーを使用して、時間の経過に伴うスクロールをアニメーション化できます。 摩擦、速度など、プラットフォーム標準のスクロールの 品質。スクローラー自体は何も描画しません。スクローラーがスクロールを追跡する 時間の経過に伴うオフセットが自動的に適用されますが、それらの位置が できます。新しい座標の取得と適用の速度は、 スクロールアニメーションが滑らかに見えるようにします

スクロールの用語について

「スクロール」という言葉は、 説明します。

スクロールは、ビューポートを移動する一般的なプロセスです。 「ウィンドウ」特定できます。スクロールが両方の x 軸と y 軸はパンといいます。「 このドキュメントの InteractiveChart サンプルアプリでは、次の 2 つについて説明します。 さまざまなタイプのスクロール、ドラッグ、フリング:

  • ドラッグ: ユーザーが操作を行う際に発生するスクロールのタイプです。 ユーザーがタッチスクリーン上で指をドラッグする。ドラッグを実装するには、 オーバーライド onScroll() GestureDetector.OnGestureListener。 ドラッグについて詳しくは、以下をご覧ください。 ドラッグ&スケール
  • フリング: ユーザーが操作したときに発生するスクロールの一種です。 すばやくドラッグして指を離すユーザーが指を離すと、 通常はビューポートの移動を続け、 表示されなくなります。フリングを実装するには、Terraform で onFling() GestureDetector.OnGestureListener 内、スクローラーを使用 渡されます。
  • パン:x-」と y 軸のことをパンと呼びます。

スクローラー オブジェクトはフリング操作と組み合わせて使用するのが一般的ですが、 UI にスクロールを表示させたいあらゆるコンテキストで使用できます。 レスポンスを返します。たとえば onTouchEvent() タップイベントを直接処理して、スクロール効果や 「ページへのスナップ」アニメーション化します

組み込みのスクロール実装を含むコンポーネント

以下の Android コンポーネントには、スクロールとロールの組み込みサポートが オーバースクロール動作:

アプリで別のナビゲーション モード内でのスクロールとオーバースクロールをサポートする必要がある場合は、 次の手順を完了します。

  1. カスタムのタップベースのスクロールを作成する 実装をご覧ください。
  2. Android 12 以降を搭載したデバイスをサポートするには、 ストレッチのオーバースクロールを実装する 効果があります

カスタムのタップベースのスクロール実装を作成する

このセクションでは、アプリが サポートしていません。 組み込みサポートが スクロールとオーバースクロールを サポートします

次のスニペットは InteractiveChart サンプルをご覧ください。使用される GestureDetector オーバーライドします GestureDetector.SimpleOnGestureListener メソッド onFling()OverScroller を使用して フリング ジェスチャーを行えます。ユーザーが以下を実行した後にコンテンツの端に達した場合 フリング ジェスチャーにより、コンテナはユーザーが 説明します。表示されるかどうかは、デバイスが搭載されている Android のバージョンによって異なります。 以下を実行します。

  • Android 12 以降では、視覚要素が引き伸ばされ、 戻ってきます
  • Android 11 以前では、視覚要素にグローが表示されます。 できます。
で確認できます。 <ph type="x-smartling-placeholder">

次のスニペットの前半は、 onFling():

Kotlin

// Viewport extremes. See currentViewport for a discussion of the viewport.
private val AXIS_X_MIN = -1f
private val AXIS_X_MAX = 1f
private val AXIS_Y_MIN = -1f
private val AXIS_Y_MAX = 1f

// The current viewport. This rectangle represents the visible chart
// domain and range. The viewport is the part of the app that the
// user manipulates via touch gestures.
private val currentViewport = RectF(AXIS_X_MIN, AXIS_Y_MIN, AXIS_X_MAX, AXIS_Y_MAX)

// The current destination rectangle—in pixel coordinates—into which
// the chart data must be drawn.
private lateinit var contentRect: Rect

private lateinit var scroller: OverScroller
private lateinit var scrollerStartViewport: RectF
...
private val gestureListener = object : GestureDetector.SimpleOnGestureListener() {

    override fun onDown(e: MotionEvent): Boolean {
        // Initiates the decay phase of any active edge effects.
        if (Build.VERSION.SDK_INT < Build.VERSION_CODES.S) {
            releaseEdgeEffects()
        }
        scrollerStartViewport.set(currentViewport)
        // Aborts any active scroll animations and invalidates.
        scroller.forceFinished(true)
        ViewCompat.postInvalidateOnAnimation(this@InteractiveLineGraphView)
        return true
    }
    ...
    override fun onFling(
            e1: MotionEvent,
            e2: MotionEvent,
            velocityX: Float,
            velocityY: Float
    ): Boolean {
        fling((-velocityX).toInt(), (-velocityY).toInt())
        return true
    }
}

private fun fling(velocityX: Int, velocityY: Int) {
    // Initiates the decay phase of any active edge effects.
    // On Android 12 and later, the edge effect (stretch) must
    // continue.
    if (Build.VERSION.SDK_INT < Build.VERSION_CODES.S) {
            releaseEdgeEffects()
    }
    // Flings use math in pixels, as opposed to math based on the viewport.
    val surfaceSize: Point = computeScrollSurfaceSize()
    val (startX: Int, startY: Int) = scrollerStartViewport.run {
        set(currentViewport)
        (surfaceSize.x * (left - AXIS_X_MIN) / (AXIS_X_MAX - AXIS_X_MIN)).toInt() to
                (surfaceSize.y * (AXIS_Y_MAX - bottom) / (AXIS_Y_MAX - AXIS_Y_MIN)).toInt()
    }
    // Before flinging, stops the current animation.
    scroller.forceFinished(true)
    // Begins the animation.
    scroller.fling(
            // Current scroll position.
            startX,
            startY,
            velocityX,
            velocityY,
            /*
             * Minimum and maximum scroll positions. The minimum scroll
             * position is generally 0 and the maximum scroll position
             * is generally the content size less the screen size. So if the
             * content width is 1000 pixels and the screen width is 200
             * pixels, the maximum scroll offset is 800 pixels.
             */
            0, surfaceSize.x - contentRect.width(),
            0, surfaceSize.y - contentRect.height(),
            // The edges of the content. This comes into play when using
            // the EdgeEffect class to draw "glow" overlays.
            contentRect.width() / 2,
            contentRect.height() / 2
    )
    // Invalidates to trigger computeScroll().
    ViewCompat.postInvalidateOnAnimation(this)
}

Java

// Viewport extremes. See currentViewport for a discussion of the viewport.
private static final float AXIS_X_MIN = -1f;
private static final float AXIS_X_MAX = 1f;
private static final float AXIS_Y_MIN = -1f;
private static final float AXIS_Y_MAX = 1f;

// The current viewport. This rectangle represents the visible chart
// domain and range. The viewport is the part of the app that the
// user manipulates via touch gestures.
private RectF currentViewport =
  new RectF(AXIS_X_MIN, AXIS_Y_MIN, AXIS_X_MAX, AXIS_Y_MAX);

// The current destination rectangle—in pixel coordinates—into which
// the chart data must be drawn.
private final Rect contentRect = new Rect();

private final OverScroller scroller;
private final RectF scrollerStartViewport =
  new RectF(); // Used only for zooms and flings.
...
private final GestureDetector.SimpleOnGestureListener gestureListener
        = new GestureDetector.SimpleOnGestureListener() {
    @Override
    public boolean onDown(MotionEvent e) {
         if (Build.VERSION.SDK_INT < Build.VERSION_CODES.S) {
            releaseEdgeEffects();
        }
        scrollerStartViewport.set(currentViewport);
        scroller.forceFinished(true);
        ViewCompat.postInvalidateOnAnimation(InteractiveLineGraphView.this);
        return true;
    }
...
    @Override
    public boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX, float velocityY) {
        fling((int) -velocityX, (int) -velocityY);
        return true;
    }
};

private void fling(int velocityX, int velocityY) {
    // Initiates the decay phase of any active edge effects.
    // On Android 12 and later, the edge effect (stretch) must
    // continue.
    if (Build.VERSION.SDK_INT < Build.VERSION_CODES.S) {
            releaseEdgeEffects();
    }
    // Flings use math in pixels, as opposed to math based on the viewport.
    Point surfaceSize = computeScrollSurfaceSize();
    scrollerStartViewport.set(currentViewport);
    int startX = (int) (surfaceSize.x * (scrollerStartViewport.left -
            AXIS_X_MIN) / (
            AXIS_X_MAX - AXIS_X_MIN));
    int startY = (int) (surfaceSize.y * (AXIS_Y_MAX -
            scrollerStartViewport.bottom) / (
            AXIS_Y_MAX - AXIS_Y_MIN));
    // Before flinging, stops the current animation.
    scroller.forceFinished(true);
    // Begins the animation.
    scroller.fling(
            // Current scroll position.
            startX,
            startY,
            velocityX,
            velocityY,
            /*
             * Minimum and maximum scroll positions. The minimum scroll
             * position is generally 0 and the maximum scroll position
             * is generally the content size less the screen size. So if the
             * content width is 1000 pixels and the screen width is 200
             * pixels, the maximum scroll offset is 800 pixels.
             */
            0, surfaceSize.x - contentRect.width(),
            0, surfaceSize.y - contentRect.height(),
            // The edges of the content. This comes into play when using
            // the EdgeEffect class to draw "glow" overlays.
            contentRect.width() / 2,
            contentRect.height() / 2);
    // Invalidates to trigger computeScroll().
    ViewCompat.postInvalidateOnAnimation(this);
}

onFling() が呼び出したタイミング postInvalidateOnAnimation(), トリガーする computeScroll() これにより、xy の値が更新されます。これは通常、 View の子は、前の図に示すように、スクローラー オブジェクトを使用してスクロールをアニメーション化しています。 例です。

ほとんどのビューは、スクローラー オブジェクトの x 位置と y 位置を直接渡します。 から scrollTo()。 次の computeScroll() の実装では、 アプローチです。 computeScrollOffset() を使用して xy の現在位置を取得する。アラートの条件が オーバースクロールの「グロー」を表示するつまり、ディスプレイの ズームインされ、x または y が境界外にあり、アプリがまだ存在していない オーバースクロールの表示です。コードはオーバースクロールのグロー効果を設定し、 postInvalidateOnAnimation() を呼び出して 表示されます。

Kotlin

// Edge effect/overscroll tracking objects.
private lateinit var edgeEffectTop: EdgeEffect
private lateinit var edgeEffectBottom: EdgeEffect
private lateinit var edgeEffectLeft: EdgeEffect
private lateinit var edgeEffectRight: EdgeEffect

private var edgeEffectTopActive: Boolean = false
private var edgeEffectBottomActive: Boolean = false
private var edgeEffectLeftActive: Boolean = false
private var edgeEffectRightActive: Boolean = false

override fun computeScroll() {
    super.computeScroll()

    var needsInvalidate = false

    // The scroller isn't finished, meaning a fling or
    // programmatic pan operation is active.
    if (scroller.computeScrollOffset()) {
        val surfaceSize: Point = computeScrollSurfaceSize()
        val currX: Int = scroller.currX
        val currY: Int = scroller.currY

        val (canScrollX: Boolean, canScrollY: Boolean) = currentViewport.run {
            (left > AXIS_X_MIN || right < AXIS_X_MAX) to (top > AXIS_Y_MIN || bottom < AXIS_Y_MAX)
        }

        /*
         * If you are zoomed in, currX or currY is
         * outside of bounds, and you aren't already
         * showing overscroll, then render the overscroll
         * glow edge effect.
         */
        if (canScrollX
                && currX < 0
                && edgeEffectLeft.isFinished
                && !edgeEffectLeftActive) {
            edgeEffectLeft.onAbsorb(scroller.currVelocity.toInt())
            edgeEffectLeftActive = true
            needsInvalidate = true
        } else if (canScrollX
                && currX > surfaceSize.x - contentRect.width()
                && edgeEffectRight.isFinished
                && !edgeEffectRightActive) {
            edgeEffectRight.onAbsorb(scroller.currVelocity.toInt())
            edgeEffectRightActive = true
            needsInvalidate = true
        }

        if (canScrollY
                && currY < 0
                && edgeEffectTop.isFinished
                && !edgeEffectTopActive) {
            edgeEffectTop.onAbsorb(scroller.currVelocity.toInt())
            edgeEffectTopActive = true
            needsInvalidate = true
        } else if (canScrollY
                && currY > surfaceSize.y - contentRect.height()
                && edgeEffectBottom.isFinished
                && !edgeEffectBottomActive) {
            edgeEffectBottom.onAbsorb(scroller.currVelocity.toInt())
            edgeEffectBottomActive = true
            needsInvalidate = true
        }
        ...
    }
}

Java

// Edge effect/overscroll tracking objects.
private EdgeEffectCompat edgeEffectTop;
private EdgeEffectCompat edgeEffectBottom;
private EdgeEffectCompat edgeEffectLeft;
private EdgeEffectCompat edgeEffectRight;

private boolean edgeEffectTopActive;
private boolean edgeEffectBottomActive;
private boolean edgeEffectLeftActive;
private boolean edgeEffectRightActive;

@Override
public void computeScroll() {
    super.computeScroll();

    boolean needsInvalidate = false;

    // The scroller isn't finished, meaning a fling or
    // programmatic pan operation is active.
    if (scroller.computeScrollOffset()) {
        Point surfaceSize = computeScrollSurfaceSize();
        int currX = scroller.getCurrX();
        int currY = scroller.getCurrY();

        boolean canScrollX = (currentViewport.left > AXIS_X_MIN
                || currentViewport.right < AXIS_X_MAX);
        boolean canScrollY = (currentViewport.top > AXIS_Y_MIN
                || currentViewport.bottom < AXIS_Y_MAX);

        /*
         * If you are zoomed in, currX or currY is
         * outside of bounds, and you aren't already
         * showing overscroll, then render the overscroll
         * glow edge effect.
         */
        if (canScrollX
                && currX < 0
                && edgeEffectLeft.isFinished()
                && !edgeEffectLeftActive) {
            edgeEffectLeft.onAbsorb((int)mScroller.getCurrVelocity());
            edgeEffectLeftActive = true;
            needsInvalidate = true;
        } else if (canScrollX
                && currX > (surfaceSize.x - contentRect.width())
                && edgeEffectRight.isFinished()
                && !edgeEffectRightActive) {
            edgeEffectRight.onAbsorb((int)mScroller.getCurrVelocity());
            edgeEffectRightActive = true;
            needsInvalidate = true;
        }

        if (canScrollY
                && currY < 0
                && edgeEffectTop.isFinished()
                && !edgeEffectTopActive) {
            edgeEffectRight.onAbsorb((int)mScroller.getCurrVelocity());
            edgeEffectTopActive = true;
            needsInvalidate = true;
        } else if (canScrollY
                && currY > (surfaceSize.y - contentRect.height())
                && edgeEffectBottom.isFinished()
                && !edgeEffectBottomActive) {
            edgeEffectRight.onAbsorb((int)mScroller.getCurrVelocity());
            edgeEffectBottomActive = true;
            needsInvalidate = true;
        }
        ...
    }

実際のズームを行うコードのセクションを次に示します。

Kotlin

lateinit var zoomer: Zoomer
val zoomFocalPoint = PointF()
...
// If a zoom is in progress—either programmatically
// or through double touch—this performs the zoom.
if (zoomer.computeZoom()) {
    val newWidth: Float = (1f - zoomer.currZoom) * scrollerStartViewport.width()
    val newHeight: Float = (1f - zoomer.currZoom) * scrollerStartViewport.height()
    val pointWithinViewportX: Float =
            (zoomFocalPoint.x - scrollerStartViewport.left) / scrollerStartViewport.width()
    val pointWithinViewportY: Float =
            (zoomFocalPoint.y - scrollerStartViewport.top) / scrollerStartViewport.height()
    currentViewport.set(
            zoomFocalPoint.x - newWidth * pointWithinViewportX,
            zoomFocalPoint.y - newHeight * pointWithinViewportY,
            zoomFocalPoint.x + newWidth * (1 - pointWithinViewportX),
            zoomFocalPoint.y + newHeight * (1 - pointWithinViewportY)
    )
    constrainViewport()
    needsInvalidate = true
}
if (needsInvalidate) {
    ViewCompat.postInvalidateOnAnimation(this)
}

Java

// Custom object that is functionally similar to Scroller.
Zoomer zoomer;
private PointF zoomFocalPoint = new PointF();
...
// If a zoom is in progress—either programmatically
// or through double touch—this performs the zoom.
if (zoomer.computeZoom()) {
    float newWidth = (1f - zoomer.getCurrZoom()) *
            scrollerStartViewport.width();
    float newHeight = (1f - zoomer.getCurrZoom()) *
            scrollerStartViewport.height();
    float pointWithinViewportX = (zoomFocalPoint.x -
            scrollerStartViewport.left)
            / scrollerStartViewport.width();
    float pointWithinViewportY = (zoomFocalPoint.y -
            scrollerStartViewport.top)
            / scrollerStartViewport.height();
    currentViewport.set(
            zoomFocalPoint.x - newWidth * pointWithinViewportX,
            zoomFocalPoint.y - newHeight * pointWithinViewportY,
            zoomFocalPoint.x + newWidth * (1 - pointWithinViewportX),
            zoomFocalPoint.y + newHeight * (1 - pointWithinViewportY));
    constrainViewport();
    needsInvalidate = true;
}
if (needsInvalidate) {
    ViewCompat.postInvalidateOnAnimation(this);
}

これは、コンテナ内で呼び出される computeScrollSurfaceSize() メソッドです。 必要があります。現在のスクロール可能なサーフェス サイズは、 ピクセルです。たとえば、グラフ領域全体が表示されている場合、 mContentRect のサイズ。両方でグラフを 200% 拡大すると、 返されるサイズは、水平方向と垂直方向の 2 倍になります。

Kotlin

private fun computeScrollSurfaceSize(): Point {
    return Point(
            (contentRect.width() * (AXIS_X_MAX - AXIS_X_MIN) / currentViewport.width()).toInt(),
            (contentRect.height() * (AXIS_Y_MAX - AXIS_Y_MIN) / currentViewport.height()).toInt()
    )
}

Java

private Point computeScrollSurfaceSize() {
    return new Point(
            (int) (contentRect.width() * (AXIS_X_MAX - AXIS_X_MIN)
                    / currentViewport.width()),
            (int) (contentRect.height() * (AXIS_Y_MAX - AXIS_Y_MIN)
                    / currentViewport.height()));
}

スクローラーの別の使用例については、 ソースコード 作成しました。ViewPagerフリングに反応してスクロールします。 「ページ位置へのスナップ」を実装するために作成します。

ストレッチのオーバースクロール効果を実装する

Android 12 以降では、EdgeEffect によって以下が追加されます。 ストレッチ オーバースクロール効果を実装するための次の API:

  • getDistance()
  • onPullDistance()

ストレッチ オーバースクロールで最適なユーザー エクスペリエンスを実現するには、以下を行います。 次のとおりです。

  1. ユーザーが タップを「キャッチ」として登録します。ユーザーがアニメーションを停止し、 ストレッチの操作を再度開始します
  2. 指をストレッチの反対方向に動かすと 最後までストレッチを放して スクロールを開始します
  3. ストレッチ中にユーザーがフリングするときに EdgeEffect をフリングする ストレッチ効果を強化します

アニメーションをキャッチする

ユーザーがアクティブなストレッチ アニメーションをキャッチすると、 EdgeEffect.getDistance()0 を返します。この条件 は、ストレッチをタッチモーションで操作する必要があることを示します。ほとんどの コンテナの場合、catch は次のように onInterceptTouchEvent() で検出されます。 次のコード スニペットをご覧ください。

Kotlin

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

Java

@Override
public boolean onInterceptTouchEvent(MotionEvent ev) {
  ...
  switch (action & MotionEvent.ACTION_MASK) {
    case MotionEvent.ACTION_DOWN:
      ...
      isBeingDragged = EdgeEffectCompat.getDistance(edgeEffectBottom) > 0
          || EdgeEffectCompat.getDistance(edgeEffectTop) > 0;
      ...
  }
}

上記の例では、onInterceptTouchEvent() は以下を返します。 mIsBeingDraggedtrue の場合は 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 - lastMotionY
      val pullDistance = deltaY / height
      val displacement = x / width

      if (deltaY < 0f && EdgeEffectCompat.getDistance(edgeEffectTop) > 0f) {
        deltaY -= height * EdgeEffectCompat.onPullDistance(edgeEffectTop,
            pullDistance, displacement);
      }
      if (deltaY > 0f && EdgeEffectCompat.getDistance(edgeEffectBottom) > 0f) {
        deltaY += height * EdgeEffectCompat.onPullDistance(edgeEffectBottom,
            -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 - lastMotionY;
      float pullDistance = deltaY / getHeight();
      float displacement = x / getWidth();

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

ユーザーがドラッグしているときに EdgeEffect の pull 距離を消費する タップイベントをネストされたスクロール コンテナに渡すか、 スクロールできます。上記のコードサンプルでは、getDistance() が エッジ効果が表示され、ロックを解除できる場合は正の値。 あります。タッチイベントがストレッチを解放すると、まず EdgeEffect にして、他のエフェクトの前に完全に解放されるようにします。 ネストされたスクロールなどの 操作メニューが表示されますgetDistance() を使用できます。 して、現在のエフェクトを解放するために必要な pull 距離を確認します。

onPull() とは異なり、onPullDistance() は 合格したデルタの消費量。Android 12 以降、 onPull() または onPullDistance() に負の値を渡す getDistance() の場合の deltaDistance0 の場合、ストレッチ効果は変化しません。Android 11 の場合 それ以前では、onPull() では合計距離として負の値を使用できます グロー効果を表示できます。

オーバースクロールをオプトアウトする

オーバースクロールは、レイアウト ファイル内で、またはプログラムでオプトアウトできます。

レイアウト ファイルでオプトアウトするには、android:overScrollMode を次のように設定します。 例を示しています。

<MyCustomView android:overScrollMode="never">
    ...
</MyCustomView>

プログラムでオプトアウトするには、次のようなコードを使用します。

Kotlin

customView.overScrollMode = View.OVER_SCROLL_NEVER

Java

customView.setOverScrollMode(View.OVER_SCROLL_NEVER);

参考情報

以下の関連リソースもご覧ください。