Scrollbewegung animieren

Compose ausprobieren
Jetpack Compose ist das empfohlene UI-Toolkit für Android. Informationen zur Verwendung von Touch- und Eingabefunktionen in Compose

In Android wird das Scrollen in der Regel mit der Klasse ScrollView erreicht. Verschachteln Sie alle Standardlayouts, die über die Grenzen des Containers hinausgehen, in einem ScrollView, um eine vom Framework verwaltete scrollbare Ansicht zu ermöglichen. Die Implementierung eines benutzerdefinierten Scrollers ist nur in besonderen Fällen erforderlich. In diesem Dokument wird beschrieben, wie Sie mithilfe von Scrollern einen Scrolling-Effekt als Reaktion auf Touch-Gesten anzeigen.

Ihre App kann Scroller – Scroller oder OverScroller – verwenden, um die Daten zu erfassen, die für die Erstellung einer Scrollanimation als Reaktion auf ein Touch-Ereignis erforderlich sind. Sie sind ähnlich, aber OverScroller enthält auch Methoden, mit denen Nutzer darauf hingewiesen werden, wenn sie nach einer Schwenk- oder Wischbewegung die Ränder des Inhalts erreichen.

  • Ab Android 12 (API-Level 31) werden die visuellen Elemente bei einem Ziehen gedehnt und zurückgefedert und bei einem schnellen Wischen gedehnt und zurückgefedert.
  • Unter Android 11 (API-Level 30) und niedriger wird nach einer Zieh- oder Wischbewegung zum Rand ein „Glow“-Effekt angezeigt.

Im InteractiveChart-Beispiel in diesem Dokument wird die Klasse EdgeEffect verwendet, um diese Overscroll-Effekte darzustellen.

Mit einem Scroller können Sie den Bildlauf im Zeitverlauf animieren. Dabei werden standardmäßige Bildlaufmechanismen der Plattform wie Reibung, Geschwindigkeit und andere Eigenschaften verwendet. Der Scroller selbst zeichnet nichts. Scroller verfolgen Scroll-Offsets im Zeitverlauf, wenden diese Positionen aber nicht automatisch auf Ihre Ansicht an. Sie müssen neue Koordinaten in einer Geschwindigkeit abrufen und anwenden, die die Scrollanimation flüssig aussehen lässt.

Begriffe für das Scrollen

„Scrollen“ ist ein Begriff, der in Android je nach Kontext unterschiedliche Bedeutungen haben kann.

Scrollen ist der allgemeine Vorgang, bei dem der sichtbare Bereich, also das „Fenster“ mit den Inhalten, die Sie sich ansehen, verschoben wird. Wenn das Scrollen sowohl auf der x- als auch auf der y-Achse erfolgt, wird es als Schwenken bezeichnet. Die InteractiveChart-Beispiel-App in diesem Dokument veranschaulicht zwei verschiedene Arten von Scrollen, Ziehen und Flinging:

  • Ziehen:Diese Art des Scrollens erfolgt, wenn ein Nutzer seinen Finger über den Touchscreen zieht. Sie können das Ziehen implementieren, indem Sie onScroll() in GestureDetector.OnGestureListener überschreiben. Weitere Informationen zum Ziehen finden Sie unter Ziehen und skalieren.
  • Schleudern:Diese Art des Scrollens tritt auf, wenn ein Nutzer seinen Finger schnell zieht und anhebt. Nachdem der Nutzer den Finger vom Display genommen hat, soll der Darstellungsbereich in der Regel weiter bewegt werden, aber abgebremst werden, bis er zum Stillstand kommt. Sie können das Flinging implementieren, indem Sie onFling() in GestureDetector.OnGestureListener überschreiben und ein Scroller-Objekt verwenden.
  • Schwenken:Das gleichzeitige Scrollen entlang der x- und y-Achse wird als Schwenken bezeichnet.

Scroller-Objekte werden häufig in Verbindung mit einer Wischbewegung verwendet. Sie können sie aber in jedem Kontext einsetzen, in dem die Benutzeroberfläche als Reaktion auf ein Touch-Ereignis scrollen soll. Sie können beispielsweise onTouchEvent() überschreiben, um Touch-Ereignisse direkt zu verarbeiten und als Reaktion auf diese Touch-Ereignisse einen Scrolling-Effekt oder eine „Snap-to-Page“-Animation zu erzeugen.

Komponenten mit integrierten Scrollimplementierungen

Die folgenden Android-Komponenten bieten integrierte Unterstützung für das Scrollen und Overscrollen:

Wenn Ihre App das Scrollen und Überscrollen in einer anderen Komponente unterstützen muss, führen Sie die folgenden Schritte aus:

  1. Benutzerdefinierte Implementierung für das Scrollen per Touch erstellen
  2. Um Geräte mit Android 12 und höher zu unterstützen, implementieren Sie den Stretch-Overscroll-Effekt.

Benutzerdefinierte Touch-basierte Scrollimplementierung erstellen

In diesem Abschnitt wird beschrieben, wie Sie einen eigenen Scroller erstellen, wenn Ihre App eine Komponente verwendet, die keine integrierte Unterstützung für das Scrollen und Overscrollen bietet.

Das folgende Snippet stammt aus dem InteractiveChart-Beispiel. Es verwendet ein GestureDetector und überschreibt die Methode GestureDetector.SimpleOnGestureListener onFling(). Sie verwendet OverScroller, um die Wischbewegung zu erfassen. Wenn der Nutzer nach der Wischbewegung die Ränder des Inhalts erreicht, wird im Container angezeigt, dass er das Ende des Inhalts erreicht hat. Die Anzeige hängt von der Android-Version ab, die auf einem Gerät ausgeführt wird:

  • Unter Android 12 und höher werden die visuellen Elemente gedehnt und springen zurück.
  • Unter Android 11 und niedriger haben die visuellen Elemente einen Glüheffekt.

Der erste Teil des folgenden Snippets zeigt die Implementierung von 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);
}

Wenn onFling() postInvalidateOnAnimation() aufruft, wird computeScroll() ausgelöst, um die Werte für x und y zu aktualisieren. Dies geschieht in der Regel, wenn ein untergeordnetes View-Element einen Scrollvorgang mit einem Scroller-Objekt animiert, wie im vorherigen Beispiel gezeigt.

Die meisten Ansichten übergeben die x- und y-Position des Scroll-Objekts direkt an scrollTo(). Die folgende Implementierung von computeScroll() verfolgt einen anderen Ansatz: Sie ruft computeScrollOffset() auf, um den aktuellen Standort von x und y abzurufen. Wenn die Kriterien für die Anzeige eines Overscroll-„Glow“-Effekts erfüllt sind, d. h. die Anzeige ist vergrößert, x oder y liegt außerhalb des zulässigen Bereichs und die App zeigt noch keinen Overscroll an, wird der Overscroll-Glow-Effekt eingerichtet und postInvalidateOnAnimation() aufgerufen, um eine Ungültigkeit für die Ansicht auszulösen.

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;
        }
        ...
    }

Hier sehen Sie den Codeabschnitt, in dem der Zoomvorgang ausgeführt wird:

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);
}

Dies ist die computeScrollSurfaceSize()-Methode, die im vorherigen Snippet aufgerufen wird. Sie berechnet die aktuelle Größe der scrollbaren Oberfläche in Pixeln. Wenn beispielsweise der gesamte Diagrammbereich sichtbar ist, entspricht dies der aktuellen Größe von mContentRect. Wenn das Diagramm in beide Richtungen um 200% vergrößert wird, ist die zurückgegebene Größe horizontal und vertikal doppelt so groß.

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()));
}

Ein weiteres Beispiel für die Verwendung von Scrollern finden Sie im Quellcode für die Klasse ViewPager. Es scrollt als Reaktion auf Wischbewegungen und verwendet das Scrollen, um die Animation „An Seite ausrichten“ zu implementieren.

Overscroll-Effekt mit Dehnung implementieren

Ab Android 12 fügt EdgeEffect die folgenden APIs zum Implementieren des Stretch-Overscroll-Effekts hinzu:

  • getDistance()
  • onPullDistance()

So sorgen Sie für eine optimale Nutzererfahrung mit dem Stretch-Overscroll-Effekt:

  1. Wenn die Dehnungsanimation aktiv ist, wenn der Nutzer die Inhalte berührt, registrieren Sie die Berührung als „Fangen“. Der Nutzer stoppt die Animation und beginnt, die Dehnung wieder zu bearbeiten.
  2. Wenn der Nutzer seinen Finger in die entgegengesetzte Richtung der Dehnung bewegt, beende die Dehnung, bis sie vollständig verschwunden ist, und beginne dann mit dem Scrollen.
  3. Wenn der Nutzer während einer Dehnübung schleudert, schleudere die EdgeEffect, um den Dehnungseffekt zu verstärken.

Animation aufzeichnen

Wenn ein Nutzer eine aktive Dehnungsübung ausführt, gibt EdgeEffect.getDistance() den Wert 0 zurück. Diese Bedingung gibt an, dass die Dehnung durch die Berührungsbewegung manipuliert werden muss. In den meisten Containern wird der Catch in onInterceptTouchEvent() erkannt, wie im folgenden Code-Snippet gezeigt:

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;
      ...
  }
}

Im vorherigen Beispiel gibt onInterceptTouchEvent() true zurück, wenn mIsBeingDragged true ist. Es reicht also aus, das Ereignis zu verarbeiten, bevor das untergeordnete Element die Möglichkeit dazu hat.

Overscroll-Effekt beenden

Es ist wichtig, den Dehnungseffekt vor dem Scrollen aufzuheben, damit er nicht auf die gescrollten Inhalte angewendet wird. Im folgenden Codebeispiel wird diese Best Practice angewendet:

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);
      }
            ...

Wenn der Nutzer zieht, müssen Sie den EdgeEffect-Ziehweg verarbeiten, bevor Sie das Touch-Ereignis an einen verschachtelten Scrolling-Container übergeben oder den Scroll ziehen. Im vorherigen Codebeispiel gibt getDistance() einen positiven Wert zurück, wenn ein Kanteneffekt angezeigt wird und durch Bewegung ausgelöst werden kann. Wenn das Touch-Ereignis die Dehnung freigibt, wird es zuerst von der EdgeEffect verarbeitet, damit es vollständig freigegeben wird, bevor andere Effekte wie das verschachtelte Scrollen angezeigt werden. Mit getDistance() können Sie herausfinden, wie weit Sie ziehen müssen, um den aktuellen Effekt zu beenden.

Im Gegensatz zu onPull() gibt onPullDistance() den verbrauchten Betrag des übergebenen Deltas zurück. Ab Android 12 ändert sich der Dehnungseffekt nicht, wenn für onPull() oder onPullDistance() negative deltaDistance-Werte übergeben werden, wenn getDistance() gleich 0 ist. Unter Android 11 und niedriger ermöglicht onPull(), dass negative Werte für die Gesamtdistanz Glüheffekte anzeigen.

Overscroll deaktivieren

Sie können Overscroll in Ihrer Layoutdatei oder programmatisch deaktivieren.

Wenn Sie die Speicherung in Ihrer Layoutdatei deaktivieren möchten, legen Sie android:overScrollMode wie im folgenden Beispiel fest:

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

Wenn Sie die Einwilligung programmatisch zurückziehen möchten, verwenden Sie Code wie den folgenden:

Kotlin

customView.overScrollMode = View.OVER_SCROLL_NEVER

Java

customView.setOverScrollMode(View.OVER_SCROLL_NEVER);

Zusätzliche Ressourcen

Weitere Informationen finden Sie in den folgenden Ressourcen: