Animer un geste de défilement

Essayer Compose
Jetpack Compose est le kit d'outils d'interface utilisateur recommandé pour Android. Découvrez comment utiliser l'écran tactile et la saisie dans Compose.
<ph type="x-smartling-placeholder"></ph> Défilement →

Dans Android, le défilement est généralement réalisé à l'aide de la méthode ScrollView . imbriquez toute installation standard pouvant s'étendre au-delà des limites dans un ScrollView pour fournir une vue déroulante gérée par le cadre. L'implémentation d'un conteneur de défilement personnalisé n'est nécessaire que pour différents scénarios. Ce document explique comment afficher un effet de défilement en réponse aux gestes tactiles à l'aide de défileurs.

Votre application peut utiliser curseurs—Scroller ou OverScroller à Collecter les données nécessaires pour produire une animation de défilement en réponse à un appui . Elles sont similaires, mais OverScroller inclut également des méthodes pour Indiquer aux utilisateurs quand les bords du contenu sont atteints après un panoramique ou un glissement d'un geste vif geste.

  • À partir d'Android 12 (niveau d'API 31), les éléments visuels s'étirent et rebondissent. retour sur un événement de déplacement et rebondissement suite à un événement de glissement d'un geste vif.
  • Sur Android 11 (niveau d'API 30) ou version antérieure, les limites affichent un "halo" après un geste de glissement ou de glissement d'un geste vif vers le bord.

L'exemple InteractiveChart de ce document utilise le EdgeEffect pour afficher ces effets de défilement hors limites.

Vous pouvez utiliser un conteneur de défilement pour animer le défilement au fil du temps, en utilisant les éléments physiques du défilement standard de la plate-forme, comme le frottement, la vitesse et d'autres qualités. Le conteneur de défilement lui-même ne dessine rien. Défilement de la piste de défilement des décalages au fil du temps, mais ils n'appliquent pas automatiquement ces positions votre vue. Vous devez obtenir et appliquer de nouvelles coordonnées à un tarif l'animation de défilement soit fluide.

Comprendre la terminologie du défilement

Le défilement est un mot qui peut avoir différentes significations dans Android, selon la le contexte.

Le défilement est le processus général qui consiste à déplacer la fenêtre d'affichage, la "fenêtre" de contenu que vous regardez. Lorsque le défilement s'effectue à la fois dans les axes x et y, cela s'appelle le panoramique. La Dans ce document, l'application exemple InteractiveChart illustre deux différents types de défilement, de déplacement ou de glissement d'un geste vif:

  • Glisser:type de défilement qui se produit lorsqu'un utilisateur fait glisser son doigt sur l'écran tactile. Vous pouvez implémenter le déplacement remplacement onScroll() dans GestureDetector.OnGestureListener Pour en savoir plus sur le déplacement, consultez Glissez-déposez :
  • Flinging:type de défilement qui se produit lorsqu'un utilisateur fait glisser et lève rapidement le doigt. Une fois que l'utilisateur lève le doigt, vous voulez généralement continuer à déplacer la fenêtre d'affichage, mais la décélérer jusqu'à la fenêtre d'affichage cesse de bouger. Vous pouvez implémenter le glissement d'un geste vif en remplaçant onFling() dans GestureDetector.OnGestureListener et à l'aide d'un conteneur de défilement .
  • Panoramique:défilement simultané le long des x et L'axe des y s'appelle panoramique.

Il est courant d'utiliser des objets de défilement avec un geste d'un geste vif, mais vous pouvez les utiliser dans n'importe quel contexte où vous souhaitez que l'interface utilisateur affiche le défilement dans à un événement tactile. Par exemple, vous pouvez remplacer onTouchEvent() pour traiter directement les événements tactiles et générer un effet de défilement ou "snap-to-page" en réponse à ces événements tactiles.

Composants contenant des implémentations de défilement intégrées

Les composants Android suivants prennent en charge le défilement et défilement hors limites:

Si votre application doit prendre en charge le défilement et le défilement excessif dans une autre , procédez comme suit:

  1. Créez un défilement tactile personnalisé l'implémentation.
  2. Pour assurer la compatibilité avec les appareils équipés d'Android 12 ou version ultérieure, procédez comme suit : Implémenter le défilement hors limites pour étirer effet.

Créer une implémentation personnalisée du défilement tactile

Cette section explique comment créer votre propre conteneur de défilement si votre application utilise un qui n'est pas sont compatibles avec les fonctionnalités le défilement ou le défilement hors limites.

L'extrait suivant provient du InteractiveChart exemple. Elle utilise un GestureDetector et remplace les GestureDetector.SimpleOnGestureListener onFling(). Il utilise OverScroller pour suivre l'état un geste d'un geste vif. Si l'utilisateur atteint les bords du contenu après avoir effectué la d'un geste vif, le conteneur indique quand l'utilisateur atteint la fin de la contenus. Cela dépend de la version d'Android utilisée par un appareil exécute:

  • Sur Android 12 et versions ultérieures, les éléments visuels s'étirent et ou non.
  • Sur Android 11 et versions antérieures, les éléments visuels affichent un halo l'effet.
<ph type="x-smartling-placeholder">

La première partie de l'extrait suivant montre l'implémentation 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);
}

Lorsque onFling() appelle postInvalidateOnAnimation(), cela déclenche computeScroll() pour mettre à jour les valeurs de x et y. Cela se fait généralement lorsqu'un la vue enfant anime un défilement à l'aide d'un objet de défilement, comme illustré à titre d'exemple.

La plupart des vues transmettent directement les positions x et y de l'objet de défilement. à scrollTo() L'implémentation suivante de computeScroll() utilise un autre méthode: elle appelle computeScrollOffset() pour obtenir la position actuelle de x et y. Lorsque les critères afficher un "glow" avec défilement hors limites sont remplies, c'est-à-dire que l'écran a fait l'objet d'un zoom avant, x ou y est hors limites et l'application ne l'est pas déjà affichage d'un défilement hors limites : le code configure l'effet illumination du défilement hors limites appelle postInvalidateOnAnimation() pour déclencher une invalidation au niveau du vue.

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

Voici la section du code qui effectue le zoom réel:

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

Il s'agit de la méthode computeScrollSurfaceSize() appelée dans l'extrait précédent. Il calcule la taille actuelle de la surface déroulante de pixels. Par exemple, si la totalité de la zone du graphique est visible, il s'agit de l'état actuel taille de mContentRect. Si un zoom avant de 200% est appliqué sur le graphique à la fois la taille renvoyée est deux fois plus grande horizontalement et verticalement.

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

Pour un autre exemple d'utilisation du conteneur de défilement, consultez la code source pour la classe ViewPager. Il défile en réponse à des glissements d'un geste vif et utilise faites défiler pour implémenter l'instruction "snap-to-page" de l'animation.

Implémenter l'effet de défilement hors limites

À partir d'Android 12, EdgeEffect ajoute le API suivantes pour implémenter l'effet de défilement hors limites:

  • getDistance()
  • onPullDistance()

Pour offrir une expérience utilisateur optimale avec le défilement hors limites, procédez comme suit : suivantes:

  1. Lorsque l'animation d'étirement s'active lorsque l'utilisateur touche contenus, enregistrez le toucher comme un « attrape-minute ». L'utilisateur arrête l'animation et commence à manipuler l'étirement.
  2. Lorsque l'utilisateur déplace son doigt dans la direction opposée à l'étirement, relâchez-le jusqu'à ce qu'il disparaisse complètement, puis faites défiler l'écran.
  3. Lorsque l'utilisateur s'écarte d'un geste vif pendant un étirement, agite la EdgeEffect pour renforcer l'effet d'étirement.

Capturez l'animation

Lorsqu'un utilisateur capture une animation d'étirement active, EdgeEffect.getDistance() renvoie 0. Cette condition indique que l'étirement doit être manipulé par le mouvement tactile. Dans la plupart des cas conteneurs, la récupération est détectée dans onInterceptTouchEvent(), car comme indiqué dans l'extrait de code suivant:

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

Dans l'exemple précédent, onInterceptTouchEvent() renvoie true lorsque mIsBeingDragged est défini sur true. Par conséquent, il suffit d'utiliser l'événement avant que l'enfant n'ait la possibilité les consommer.

Libérer l'effet de défilement hors limites

Il est important de relâcher l'effet d'étirement avant le défilement pour éviter l'étirement n'est pas appliqué au contenu défilant. Le code suivant applique cette bonne pratique:

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

Lorsque l'utilisateur fait glisser, utilisez la distance d'extraction EdgeEffect. avant de transmettre l'événement tactile à un conteneur à défilement imbriqué ou de faire glisser la faire défiler. Dans l'exemple de code précédent, getDistance() renvoie une une valeur positive lorsqu'un effet de contour est affiché et peut être libéré avec du mouvement. Lorsque l'événement tactile libère l'étirement, il est d'abord utilisé par EdgeEffect afin qu'il soit complètement libéré avant d'autres effets, comme le défilement imbriqué. Vous pouvez utiliser getDistance() pour connaître la distance d'extraction requise pour libérer l'effet actuel.

Contrairement à onPull(), onPullDistance() renvoie la quantité consommée du delta transmis. À partir d'Android 12, si onPull() ou onPullDistance() sont transmis comme négatifs Valeurs deltaDistance lorsque getDistance() est 0, l'effet d'étirement ne change pas. Sur Android 11 et auparavant, onPull() autorise les valeurs négatives pour la distance totale afficher des effets d'éclat.

Désactiver le défilement hors limites

Vous pouvez désactiver le défilement hors limites dans votre fichier de mise en page ou par programmation.

Pour désactiver cette fonctionnalité dans votre fichier de mise en page, définissez android:overScrollMode comme illustré dans l'exemple suivant:

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

Pour désactiver cette fonctionnalité par programmation, utilisez le code suivant:

Kotlin

customView.overScrollMode = View.OVER_SCROLL_NEVER

Java

customView.setOverScrollMode(View.OVER_SCROLL_NEVER);

Ressources supplémentaires

Consultez les ressources associées suivantes :