Rendre une vue personnalisée interactive

Essayer Compose
Jetpack Compose est le kit d'outils d'interface utilisateur recommandé pour Android. Découvrez comment utiliser les mises en page dans Compose.

Dessiner une interface utilisateur n'est qu'une partie de la création d'une vue personnalisée. Vous devez également faire en sorte que votre vue réponde aux entrées utilisateur d'une manière semblable à l'action du monde réel que vous imitez.

Faites en sorte que les objets de votre application se comportent comme des objets réels. Par exemple, ne laissez pas les images de votre application sortir de l'existence et réapparaître ailleurs, car les objets du monde réel ne le font pas. Déplacez plutôt vos images d'un endroit à un autre.

Les utilisateurs captent des comportements ou des sensations même subtils dans une interface, et réagissent mieux à des subtilités imitant le monde réel. Par exemple, lorsque les utilisateurs font glisser un objet d'interface utilisateur, donnez-leur au début un sentiment d'inertie qui retarde le mouvement. À la fin du mouvement, donnez-leur un sentiment de mouvement qui transporte l'objet au-delà du geste.

Cette page explique comment utiliser les fonctionnalités du framework Android pour ajouter ces comportements réels à votre vue personnalisée.

Vous trouverez des informations supplémentaires dans les pages Présentation des événements d'entrée et Présentation de l'animation des propriétés.

Gérer les gestes d'entrée

Comme de nombreux autres frameworks d'UI, Android est compatible avec un modèle d'événement d'entrée. Les actions des utilisateurs deviennent des événements qui déclenchent des rappels. Vous pouvez ignorer ces rappels pour personnaliser la réaction de votre application à l'utilisateur. L'événement d'entrée le plus courant dans le système Android est touch, qui déclenche onTouchEvent(android.view.MotionEvent). Remplacez cette méthode pour gérer l'événement comme suit:

Kotlin

override fun onTouchEvent(event: MotionEvent): Boolean {
    return super.onTouchEvent(event)
}

Java

@Override
   public boolean onTouchEvent(MotionEvent event) {
    return super.onTouchEvent(event);
   }

Les événements tactiles en eux-mêmes ne sont pas particulièrement utiles. Les interfaces utilisateur tactiles modernes définissent les interactions en termes de gestes (par exemple, appuyer, tirer, pousser, faire glisser la souris et zoomer). Pour convertir les événements tactiles bruts en gestes, Android fournit GestureDetector.

Créez un GestureDetector en transmettant une instance d'une classe qui implémente GestureDetector.OnGestureListener. Si vous ne souhaitez traiter que quelques gestes, vous pouvez étendre GestureDetector.SimpleOnGestureListener au lieu d'implémenter l'interface GestureDetector.OnGestureListener. Par exemple, ce code crée une classe qui étend GestureDetector.SimpleOnGestureListener et remplace onDown(MotionEvent).

Kotlin

private val myListener =  object : GestureDetector.SimpleOnGestureListener() {
    override fun onDown(e: MotionEvent): Boolean {
        return true
    }
}

private val detector: GestureDetector = GestureDetector(context, myListener)

Java

class MyListener extends GestureDetector.SimpleOnGestureListener {
   @Override
   public boolean onDown(MotionEvent e) {
       return true;
   }
}
detector = new GestureDetector(getContext(), new MyListener());

Que vous utilisiez GestureDetector.SimpleOnGestureListener ou non, implémentez toujours une méthode onDown() qui renvoie true. Cela est nécessaire, car tous les gestes commencent par un message onDown(). Si vous renvoyez false à partir de onDown(), comme le fait GestureDetector.SimpleOnGestureListener, le système suppose que vous souhaitez ignorer le reste du geste, et les autres méthodes de GestureDetector.OnGestureListener ne sont pas appelées. Ne renvoyez false à partir de onDown() que si vous souhaitez ignorer un geste entier.

Après avoir implémenté GestureDetector.OnGestureListener et créé une instance de GestureDetector, vous pouvez utiliser GestureDetector pour interpréter les événements tactiles que vous recevez dans onTouchEvent().

Kotlin

override fun onTouchEvent(event: MotionEvent): Boolean {
    return detector.onTouchEvent(event).let { result ->
        if (!result) {
            if (event.action == MotionEvent.ACTION_UP) {
                stopScrolling()
                true
            } else false
        } else true
    }
}

Java

@Override
public boolean onTouchEvent(MotionEvent event) {
   boolean result = detector.onTouchEvent(event);
   if (!result) {
       if (event.getAction() == MotionEvent.ACTION_UP) {
           stopScrolling();
           result = true;
       }
   }
   return result;
}

Lorsque vous transmettez onTouchEvent() à un événement tactile qu'il ne reconnaît pas dans le cadre d'un geste, il renvoie false. Vous pouvez ensuite exécuter votre propre code de détection de gestes personnalisé.

Créer des mouvements physiquement plausibles

Les gestes sont un moyen efficace de contrôler les appareils à écran tactile, mais ils peuvent être contre-intuitifs et difficiles à retenir, sauf s'ils produisent des résultats physiquement plausibles.

Par exemple, supposons que vous souhaitiez implémenter un geste de déplacement horizontal qui permet de faire tourner l'élément dessiné dans la vue sur son axe vertical. Ce geste est logique si l'interface utilisateur répond en se déplaçant rapidement dans la direction du balayage, puis en ralentissant, comme si l'utilisateur pousse un volant et le fait tourner.

La documentation sur l'animation d'un geste de défilement explique en détail comment mettre en œuvre votre propre comportement Scoll. Mais simuler la sensation d'un volant n'est pas simple. Beaucoup de physique et de mathématiques sont nécessaires au bon fonctionnement d'un modèle de volant. Heureusement, Android fournit des classes d'assistance pour simuler ce comportement et d'autres. La classe Scroller est la base pour gérer les gestes de glissement d'un geste vif.

Pour lancer un balayage, appelez fling() avec la vitesse de départ, ainsi que les valeurs x et y minimales et maximales du balayage. Pour la valeur de vélocité, vous pouvez utiliser la valeur calculée par GestureDetector.

Kotlin

fun onFling(e1: MotionEvent, e2: MotionEvent, velocityX: Float, velocityY: Float): Boolean {
    scroller.fling(
            currentX,
            currentY,
            (velocityX / SCALE).toInt(),
            (velocityY / SCALE).toInt(),
            minX,
            minY,
            maxX,
            maxY
    )
    postInvalidate()
    return true
}

Java

@Override
public boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX, float velocityY) {
   scroller.fling(currentX, currentY, velocityX / SCALE, velocityY / SCALE, minX, minY, maxX, maxY);
   postInvalidate();
    return true;
}

L'appel de fling() configure le modèle physique pour le geste de glissement d'un geste vif. Ensuite, mettez à jour le Scroller en appelant Scroller.computeScrollOffset() à intervalles réguliers. computeScrollOffset() met à jour l'état interne de l'objet Scroller en lisant l'heure actuelle et en utilisant le modèle physique pour calculer les positions x et y à ce moment-là. Appelez getCurrX() et getCurrY() pour récupérer ces valeurs.

La plupart des affichages transmettent les positions x et y de l'objet Scroller directement à scrollTo(). Cet exemple est un peu différent: il utilise la position de défilement x actuelle pour définir l'angle de rotation de la vue.

Kotlin

scroller.apply {
    if (!isFinished) {
        computeScrollOffset()
        setItemRotation(currX)
    }
}

Java

if (!scroller.isFinished()) {
    scroller.computeScrollOffset();
    setItemRotation(scroller.getCurrX());
}

La classe Scroller calcule les positions de défilement pour vous, mais elle ne les applique pas automatiquement à votre vue. Appliquez les nouvelles coordonnées suffisamment souvent pour que l'animation de défilement soit fluide. Pour ce faire, vous pouvez procéder de deux façons:

  • Forcez une redessinage en appelant postInvalidate() après avoir appelé fling(). Cette technique nécessite de calculer les décalages de défilement dans onDraw() et d'appeler postInvalidate() chaque fois que le décalage de défilement change.
  • Configurez un ValueAnimator pour l'animer pendant la durée du glissement d'un geste vif et ajoutez un écouteur pour traiter les mises à jour de l'animation en appelant addUpdateListener(). Cette technique vous permet d'animer les propriétés d'un View.

Proposez des transitions fluides

Les utilisateurs s'attendent à ce qu'une interface utilisateur moderne passe d'un état à l'autre en douceur: les éléments de l'interface utilisateur apparaissent et disparaissent au lieu d'apparaître et de disparaître, et les mouvements commencent et se terminent en douceur au lieu de démarrer et de s'arrêter brusquement. Le framework d'animation des propriétés Android facilite les transitions en douceur.

Pour utiliser le système d'animation, lorsqu'une propriété change ce qui affecte l'apparence de votre vue, ne la modifiez pas directement. Utilisez plutôt ValueAnimator pour effectuer la modification. Dans l'exemple suivant, la modification du composant enfant sélectionné dans la vue fait pivoter l'ensemble de la vue affichée afin de centrer le pointeur de sélection. ValueAnimator modifie la rotation sur une période de plusieurs centaines de millisecondes, plutôt que de définir immédiatement la nouvelle valeur de rotation.

Kotlin

autoCenterAnimator = ObjectAnimator.ofInt(this, "Rotation", 0).apply {
    setIntValues(targetAngle)
    duration = AUTOCENTER_ANIM_DURATION
    start()
}

Java

autoCenterAnimator = ObjectAnimator.ofInt(this, "Rotation", 0);
autoCenterAnimator.setIntValues(targetAngle);
autoCenterAnimator.setDuration(AUTOCENTER_ANIM_DURATION);
autoCenterAnimator.start();

Si la valeur que vous souhaitez modifier est l'une des propriétés View de base, l'animation est encore plus facile, car les vues disposent d'un ViewPropertyAnimator intégré optimisé pour l'animation simultanée de plusieurs propriétés, comme dans l'exemple suivant:

Kotlin

animate()
    .rotation(targetAngle)
    .duration = ANIM_DURATION
    .start()

Java

animate().rotation(targetAngle).setDuration(ANIM_DURATION).start();