Scrollbewegung animieren

Funktion „Schreiben“ ausprobieren
Jetpack Compose ist das empfohlene UI-Toolkit für Android. Weitere Informationen

In Android erfolgt das Scrollen in der Regel mithilfe der Klasse ScrollView. Verschachteln Sie jedes Standardlayout, das über die Grenzen seines Containers hinausgeht, in einem ScrollView, um eine scrollbare Ansicht zu bieten, die vom Framework verwaltet wird. Ein benutzerdefinierter Scroller muss nur bei besonderen Szenarien implementiert werden. In diesem Dokument wird beschrieben, wie ein Scrolleffekt als Reaktion auf Touch-Gesten mit Scrollern dargestellt wird.

In deiner App können Scroller (Scroller oder OverScroller) verwendet werden, um die Daten zu erfassen, die zum Erstellen einer Scrollanimation als Reaktion auf ein Touch-Ereignis erforderlich sind. Sie sind ähnlich, aber OverScroller umfasst auch Methoden, mit denen Nutzer darüber informiert werden, wenn sie die Inhaltsränder nach dem Schwenken oder Schwenken erreichen.

  • Ab Android 12 (API-Level 31) dehnen sich die visuellen Elemente bei einem Drag-Ereignis und springen bei einem Schleuder-Ereignis ab.
  • Unter Android 11 (API-Level 30) und niedriger wird für die Begrenzungen ein „Leuchteffekt“ angezeigt, nachdem eine Geste zum Ziehen oder Wischen zum Rand erfolgt.

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

Sie können einen Scroller verwenden, um das Scrollen im Zeitverlauf zu animieren. Hierzu werden plattformübliche Scroll-Physiken eingesetzt, wie Reibung, Geschwindigkeit und andere Eigenschaften. Der Scroller selbst zeichnet nichts. Scroll-Offsets erfassen die Scroll-Offsets im Zeitverlauf, wenden diese Positionen jedoch nicht automatisch auf Ihre Ansicht an. Sie müssen die neuen Koordinaten mit einer Geschwindigkeit abrufen und anwenden, damit die Scroll-Animation reibungslos wirkt.

Die Scroll-Terminologie verstehen

Scrollen ist ein Wort, das in Android je nach Kontext unterschiedliche Bedeutungen haben kann.

Beim Scrollen wird im Allgemeinen der Darstellungsbereich verschoben, also das „Fenster“ des angezeigten Inhalts. Wenn das Scrollen sowohl in der x- als auch in der y-Achse erfolgt, wird das Schwenken genannt. Die Beispiel-App InteractiveChart in diesem Dokument veranschaulicht zwei verschiedene Arten von Scrollen, Ziehen und Ziehen:

  • Ziehen:Dies ist die Art des Scrollens, die stattfindet, 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.
  • Fliegen:Dies ist die Art des Scrollens, die stattfindet, wenn ein Nutzer seinen Finger schnell zieht und anhebt. Nachdem der Nutzer den Finger angehoben hat, möchten Sie in der Regel den Darstellungsbereich weiterbewegen und die Bewegung verlangsamen, bis er sich nicht mehr bewegt. Sie können das Schleudern implementieren, indem Sie onFling() in GestureDetector.OnGestureListener überschreiben und ein Scroller-Objekt verwenden.
  • Schwenken:Das gleichzeitige Scrollen entlang der x- und der y-Achse wird als Schwenken bezeichnet.

Scroller-Objekte werden häufig in Verbindung mit einer Ziehgeste verwendet. Du kannst sie aber auch in jedem Kontext einsetzen, in dem die UI das Scrollen als Reaktion auf ein Touch-Ereignis anzeigen soll. Du kannst beispielsweise onTouchEvent() überschreiben, um Touch-Ereignisse direkt zu verarbeiten und als Reaktion auf diese Touch-Ereignisse einen Scrolleffekt oder eine „An eine Seite ausrichten“-Animation zu erzeugen.

Komponenten, die integrierte Scrolling-Implementierungen enthalten

Die folgenden Android-Komponenten unterstützen Scroll- und Overscroll-Verhalten:

Wenn Ihre App Scrollen und Overscrollen innerhalb einer anderen Komponente unterstützen muss, führen Sie die folgenden Schritte aus:

  1. Erstelle eine benutzerdefinierte Implementierung für berührungsbasiertes Scrollen.
  2. Zur Unterstützung von Geräten mit Android 12 und höher kannst du den Overscroll-Effekt implementieren.

Benutzerdefinierte Implementierung für berührungsbasiertes Scrollen erstellen

In diesem Abschnitt wird beschrieben, wie du einen eigenen Scroller erstellst, wenn deine App eine Komponente verwendet, die keine integrierte Unterstützung für das Scrollen und Overscroll bietet.

Das folgende Snippet stammt aus dem Beispiel InteractiveChart. Dabei wird ein GestureDetector verwendet und die GestureDetector.SimpleOnGestureListener-Methode onFling() überschrieben. Sie verwendet OverScroller, um die Ziehgeste zu verfolgen. Wenn der Nutzer die Ränder des Inhalts erreicht, nachdem er die Ziehgeste ausgeführt hat, zeigt der Container an, wann er das Ende des Inhalts erreicht. Die Anzeige hängt von der Android-Version ab, auf der ein Gerät ausgeführt wird:

  • Bei Android 12 und höher dehnen sich die visuellen Elemente und springen zurück.
  • Unter Android 11 und niedriger zeigen die visuellen Elemente einen Leuchteffekt.

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 normalerweise, wenn eine untergeordnete Ansicht eine Scrollbewegung mit einem Scroller-Objekt animiert, wie im vorherigen Beispiel gezeigt.

Bei den meisten Ansichten werden die x- und y-Positionen des Scroller-Objekts direkt an scrollTo() übergeben. Die folgende Implementierung von computeScroll() verfolgt einen anderen Ansatz: Sie ruft computeScrollOffset() auf, um den aktuellen Standort von x und y zu erhalten. Wenn die Kriterien für die Anzeige des Overscroll-Glow-Effekts erfüllt sind, das Display also herangezoomt ist, x oder y außerhalb des Bereichs liegt und in der App noch kein Overscroll angezeigt wird, richtet der Code den Overscroll-Glow-Effekt ein und ruft postInvalidateOnAnimation() auf, um eine Ungültigkeit der 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 ist der Abschnitt des Codes, der den eigentlichen Zoom durchführt:

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 Methode computeScrollSurfaceSize(), die im vorherigen Snippet aufgerufen wird. Sie berechnet die aktuelle scrollbare Oberfläche in Pixeln. Wenn beispielsweise der gesamte Diagrammbereich sichtbar ist, ist dies die aktuelle Größe von mContentRect. Wenn das Diagramm in beide Richtungen um 200% gezoomt 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 Nutzung des Scrollers finden Sie im Quellcode für die Klasse ViewPager. Sie scrollt als Reaktion auf Flings und implementiert durch Scrollen die Animation beim Andocken.

Überscrollen-Effekt implementieren

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

  • getDistance()
  • onPullDistance()

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

  1. Wenn die Stretch-Animation wirksam wird, wenn der Nutzer den Inhalt berührt, registrieren Sie die Berührung als „Fang“. Der Nutzer stoppt die Animation und beginnt wieder, die Dehnung zu bearbeiten.
  2. Wenn der Nutzer seinen Finger in die entgegengesetzte Richtung der Dehnung bewegt, lasse ihn los, bis er sich vollständig entfernt hat, und beginne dann mit dem Scrollen.
  3. Wenn der Nutzer während eines Dehnungsbewegungens schleudert, wird das EdgeEffect nach unten geschoben, um den Stretch-Effekt zu verbessern.

Animationsfilm

Wenn ein Nutzer eine aktive Stretch-Animation sieht, gibt EdgeEffect.getDistance() den Wert 0 zurück. Diese Bedingung weist darauf hin, dass die Dehnung durch Berührungsbewegungen verändert 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 den Wert true hat. Es reicht also aus, das Ereignis zu nutzen, bevor das untergeordnete Element die Möglichkeit hat, es zu verarbeiten.

Overscroll-Effekt loslassen

Es ist wichtig, den Streckeffekt vor dem Scrollen aufzuheben, um zu verhindern, dass die Streckung auf den Scroll-Inhalt 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, nutzen Sie die Pull-Entfernung EdgeEffect, bevor Sie das Touch-Ereignis an einen verschachtelten Scroll-Container übergeben oder das Scrollen ziehen. Im vorherigen Codebeispiel gibt getDistance() einen positiven Wert zurück, wenn ein Randeffekt angezeigt wird und bei Bewegung ausgelöst werden kann. Wenn das Touch-Ereignis das Strecke freigibt, wird es zuerst vom EdgeEffect genutzt, sodass es vollständig freigegeben wird, bevor andere Effekte wie verschachteltes Scrollen angezeigt werden. Sie können getDistance() verwenden, um zu erfahren, wie viel Pull-Abstand erforderlich ist, um den aktuellen Effekt auszugleichen.

Im Gegensatz zu onPull() gibt onPullDistance() den verbrauchten Betrag des übergebenen Deltas zurück. Wenn ab Android 12 für onPull() oder onPullDistance() negative deltaDistance-Werte übergeben werden, wenn getDistance() gleich 0 ist, ändert sich der Stretch-Effekt nicht. Unter Android 11 und niedriger können mit onPull() bei negativen Werten für die Gesamtentfernung Scheineffekte angezeigt werden.

Overscroll-Funktion deaktivieren

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

Um die Funktion in Ihrer Layoutdatei zu deaktivieren, legen Sie android:overScrollMode fest, wie im folgenden Beispiel gezeigt:

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

Wenn Sie die automatische Migration deaktivieren möchten, verwenden Sie Code wie den folgenden:

Kotlin

customView.overScrollMode = View.OVER_SCROLL_NEVER

Java

customView.setOverScrollMode(View.OVER_SCROLL_NEVER);

Weitere Informationen

Weitere Informationen finden Sie in den folgenden Ressourcen: