Dans Android, le défilement est généralement réalisé à l'aide de la classe ScrollView
. N'importe quelle mise en page standard qui pourrait s'étendre au-delà des limites de son conteneur dans un ScrollView
pour fournir une vue défilable gérée par le framework. L'implémentation d'un sélecteur personnalisé n'est nécessaire que dans des cas particuliers. Ce document explique comment afficher un effet de défilement en réponse aux gestes tactiles à l'aide des scrollers.
Votre application peut utiliser des scrollers (Scroller
ou OverScroller
) pour collecter les données nécessaires à la production d'une animation de défilement en réponse à un événement tactile. Ils sont similaires, mais OverScroller
inclut également des méthodes permettant d'indiquer aux utilisateurs lorsqu'ils atteignent les bords du contenu après un geste de déplacement ou de balayage.
- À partir d'Android 12 (niveau d'API 31), les éléments visuels s'étirent et rebondissent lors d'un événement de déplacement, et ils rebondissent lors d'un événement de balayage.
- Sur Android 11 (niveau d'API 30) et versions antérieures, les limites affichent un effet de "brillance" après un geste de déplacement ou de balayage vers le bord.
L'exemple InteractiveChart
de ce document utilise la classe EdgeEffect
pour afficher ces effets de dépassement de défilement.
Vous pouvez utiliser un scroller pour animer le défilement au fil du temps, en utilisant la physique de défilement standard de la plate-forme, comme la friction, la vitesse et d'autres qualités. Le scroller lui-même ne dessine rien. Les scrollers suivent les décalages de défilement pour vous au fil du temps, mais ils n'appliquent pas automatiquement ces positions à votre vue. Vous devez obtenir et appliquer de nouvelles coordonnées à une fréquence qui permet à l'animation de défilement d'être fluide.
Comprendre la terminologie associée au défilement
Le terme "défilement" peut avoir différentes significations dans Android, selon le contexte.
Le défilement est le processus général de déplacement de la fenêtre d'affichage, c'est-à-dire la "fenêtre" de contenu que vous regardez. Lorsque le défilement s'effectue à la fois sur les axes x et y, on parle de déplacement. L'application exemple InteractiveChart
de ce document illustre deux types différents de défilement, de déplacement et de balayage :
- Faire glisser : il s'agit du type de défilement qui se produit lorsqu'un utilisateur fait glisser son doigt sur l'écran tactile. Vous pouvez implémenter le glisser-déposer en remplaçant
onScroll()
dansGestureDetector.OnGestureListener
. Pour en savoir plus sur le déplacement, consultez Déplacer et redimensionner. - Défilement rapide : type de défilement qui se produit lorsqu'un utilisateur fait glisser son doigt et le lève rapidement. Une fois que l'utilisateur a retiré son doigt, vous devez généralement continuer à déplacer la fenêtre d'affichage, mais en ralentissant jusqu'à ce qu'elle s'arrête. Vous pouvez implémenter le défilement rapide en remplaçant
onFling()
dansGestureDetector.OnGestureListener
et en utilisant un objet de défilement. - Panoramique : le défilement simultané le long des axes x et y est appelé panoramique.
Il est courant d'utiliser des objets de défilement en association avec un geste de balayage, mais vous pouvez les utiliser dans n'importe quel contexte où vous souhaitez que l'UI affiche le défilement en réponse à un événement tactile. Par exemple, vous pouvez remplacer onTouchEvent()
pour traiter directement les événements tactiles et produire un effet de défilement ou une animation de "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 sont compatibles avec le comportement de défilement et de dépassement de défilement :
GridView
HorizontalScrollView
ListView
NestedScrollView
RecyclerView
ScrollView
ViewPager
ViewPager2
Si votre application doit prendre en charge le défilement et le dépassement de défilement à l'intérieur d'un autre composant, procédez comme suit :
- Créez une implémentation personnalisée du défilement tactile.
- Pour prendre en charge les appareils équipés d'Android 12 ou version ultérieure, implémentez l'effet d'étirement hors limites.
Créer une implémentation de défilement tactile personnalisée
Cette section explique comment créer votre propre scroller si votre application utilise un composant qui ne contient pas de prise en charge intégrée pour le défilement et le dépassement de défilement.
L'extrait suivant provient de l'exemple InteractiveChart
. Il utilise un GestureDetector
et remplace la méthode GestureDetector.SimpleOnGestureListener
onFling()
. Il utilise OverScroller
pour suivre le geste de balayage. Si l'utilisateur atteint les bords du contenu après avoir effectué le geste de balayage, le conteneur indique quand l'utilisateur atteint la fin du contenu. L'indication dépend de la version d'Android exécutée sur l'appareil :
- Sur Android 12 et versions ultérieures, les éléments visuels s'étirent et rebondissent.
- Sur Android 11 ou version antérieure, les éléments visuels affichent un effet de lueur.
La première partie de l'extrait suivant montre l'implémentation de 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()
, il déclenche computeScroll()
pour mettre à jour les valeurs de x et y. Cela se produit généralement lorsqu'un enfant de vue anime un défilement à l'aide d'un objet de défilement, comme illustré dans l'exemple précédent.
La plupart des vues transmettent directement la position x et y de l'objet de défilement à scrollTo()
.
L'implémentation suivante de computeScroll()
adopte une approche différente : elle appelle computeScrollOffset()
pour obtenir la position actuelle de x et y. Lorsque les critères d'affichage d'un effet de bord de "brillance" de dépassement de défilement sont remplis (c'est-à-dire que l'affichage est zoomé, que x ou y sont hors limites et que l'application n'affiche pas déjà un dépassement de défilement), le code configure l'effet de brillance de dépassement de défilement et appelle postInvalidateOnAnimation()
pour déclencher une invalidation sur la 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 :
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. Elle calcule la taille actuelle de la surface de défilement en pixels. Par exemple, si toute la zone du graphique est visible, il s'agit de la taille actuelle de mContentRect
. Si le graphique est zoomé à 200 % dans les deux directions, 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 scroller, consultez le code source de la classe ViewPager
. Il défile en réponse aux déplacements rapides et utilise le défilement pour implémenter l'animation "snap-to-page".
Implémenter l'effet d'étirement du défilement hors limites
À partir d'Android 12, EdgeEffect
ajoute les API suivantes pour implémenter l'effet d'étirement hors limites :
getDistance()
onPullDistance()
Pour offrir la meilleure expérience utilisateur avec l'étirement du défilement hors limites, procédez comme suit :
- Lorsque l'animation d'étirement est en cours lorsque l'utilisateur appuie sur le contenu, enregistrez l'appui comme une "capture". L'utilisateur arrête l'animation et recommence à manipuler l'étirement.
- Lorsque l'utilisateur déplace son doigt dans le sens opposé de l'étirement, relâchez l'étirement jusqu'à ce qu'il disparaisse complètement, puis commencez à faire défiler la page.
- Lorsque l'utilisateur effectue un balayage pendant un étirement, balayez
EdgeEffect
pour renforcer l'effet d'étirement.
Attraper l'animation
Lorsqu'un utilisateur attrape 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 conteneurs, l'exception est détectée dans onInterceptTouchEvent()
, 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 true
. Il suffit donc de consommer l'événement avant que l'enfant ait la possibilité de le consommer.
Relâcher l'effet de défilement hors limites
Il est important de libérer l'effet d'étirement avant de faire défiler le contenu pour éviter que l'étirement ne soit appliqué au contenu défilant. L'exemple de 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 l'écran, consommez la distance de traction EdgeEffect
avant de transmettre l'événement tactile à un conteneur de défilement imbriqué ou de faire glisser le défilement. Dans l'exemple de code précédent, getDistance()
renvoie une valeur positive lorsqu'un effet de bord est affiché et peut être libéré par un mouvement. Lorsque l'événement tactile libère l'étirement, il est d'abord consommé par EdgeEffect
afin qu'il soit complètement libéré avant que d'autres effets, tels que le défilement imbriqué, ne s'affichent. Vous pouvez utiliser getDistance()
pour connaître la distance de traction requise pour libérer l'effet actuel.
Contrairement à onPull()
, onPullDistance()
renvoie la quantité consommée du delta transmis. À partir d'Android 12, si des valeurs deltaDistance
négatives sont transmises à onPull()
ou onPullDistance()
lorsque getDistance()
est 0
, l'effet d'étirement ne change pas. Sur Android 11 et les versions antérieures, onPull()
permet aux valeurs négatives de la distance totale d'afficher des effets de lueur.
Désactiver le défilement hors limites
Vous pouvez désactiver le défilement au-delà des limites dans votre fichier de mise en page ou de manière programmatique.
Pour désactiver le cookie dans votre fichier de mise en page, définissez android:overScrollMode
, comme indiqué dans l'exemple suivant :
<MyCustomView android:overScrollMode="never"> ... </MyCustomView>
Pour désactiver le partage de manière programmatique, utilisez un code semblable à celui-ci :
Kotlin
customView.overScrollMode = View.OVER_SCROLL_NEVER
Java
customView.setOverScrollMode(View.OVER_SCROLL_NEVER);
Ressources supplémentaires
Consultez les ressources associées suivantes :
- Présentation des événements d'entrée
- Présentation des capteurs
- Rendre une vue personnalisée interactive