Glisser-déposer dans Compose

1. Avant de commencer

Dans cet atelier de programmation, découvrez des instructions pratiques sur les principes de base de l'implémentation du glisser-déposer pour Compose. Vous apprendrez à permettre le glisser-déposer des vues au sein de votre application et entre différentes applications. Vous apprendrez à implémenter une opération de glisser-déposer dans votre application et entre différentes applications.

Conditions préalables

Voici les conditions à remplir pour effectuer cet atelier de programmation :

Objectifs de l'atelier

Créer une application simple qui :

  • configure le composable pour le rendre déplaçable à l'aide du modificateur dragAndDropSource ;
  • configure le composable en tant que cible de dépose à l'aide du modificateur dragAndDropTarget ;
  • reçoit du contenu enrichi à l'aide de Compose.

Ce dont vous avez besoin

2. Événement "glisser-déposer"

L'opération de glisser-déposer peut être considérée en tant qu'événement en quatre étapes :

  1. Démarré : le système lance l'opération de glisser-déposer en réponse au geste de déplacement de l'utilisateur.
  2. Poursuite de l'opération : l'utilisateur continue le geste de déplacement.
  3. Terminé : l'utilisateur libère le composable de la cible du glisser-déposer.
  4. A existé : le système envoie un signal pour mettre fin à l'opération de glisser-déposer.

Le système envoie l'événement de déplacement dans l'objet DragEvent. L'objet DragEvent peut contenir les données suivantes :

  1. ActionType : valeur de l'action de l'événement basée sur l'événement de cycle de vie de l'événement de glisser-déposer, par exemple ACTION_DRAG_STARTED, ACTION_DROP, etc.
  2. ClipData : données déplacées et encapsulées dans un objet ClipData
  3. ClipDescription : méta-informations sur l'objet ClipData
  4. Result : résultat de l'opération de glisser-déposer
  5. X : coordonnée X de la position actuelle de l'objet déplacé
  6. Y : coordonnée Y de la position actuelle de l'objet déplacé

3. Configuration

Créez un projet et sélectionnez le modèle "Empty Activity" (Activité vide) :

19da275afd995463.png

Conservez les valeurs par défaut de tous les paramètres.

Dans cet atelier de programmation, nous utiliserons ImageView pour illustrer la fonctionnalité de glisser-déposer. Ajoutons une dépendance Gradle pour la bibliothèque glide pour composer et synchroniser le projet.

implementation("com.github.bumptech.glide:compose:1.0.0-beta01")

Dans MainActivity.kt, créez un composable pour "Image", qui servira de source de déplacement pour l'opération voulue.

@Composable
fun DragImage(url: String) {
   GlideImage(model = url, contentDescription = "Dragged Image")
}

De même, créez l'image de la cible du glisser-déposer.

@Composable
fun DropTargetImage(url: String) {
   val urlState = remember {mutableStateOf(url)}
   GlideImage(model = urlState.value, contentDescription = "Dropped Image")
}

Ajoutez un composable de colonne à votre composable pour inclure ces deux images.

Column {
   DragImage(url = getString(R.string.source_url))
   DropTargetImage(url = getString(R.string.target_url))
}

À ce stade, MainActivity affiche deux images verticales. Cet écran devrait s'afficher.

5e12c26cb2ad1068.png

4. Configurer la source du déplacement

Ajoutons un modificateur pour la source du glisser-déposer à notre composable DragImage.

modifier = Modifier.dragAndDropSource {
   detectTapGestures(
       onLongPress = {
           startTransfer(
               DragAndDropTransferData(
                   ClipData.newPlainText("image uri", url)
               )
           )
       }
   )
}

Ici, nous avons ajouté un modificateur dragAndDropSource. Le modificateur dragAndDropSource permet d'utiliser la fonctionnalité de glisser-déposer pour tout élément auquel il est appliqué. Il représente visuellement l'objet déplacé sous la forme d'une ombre.

Le modificateur dragAndDropSource fournit PointerInputScope pour détecter le geste de déplacement. Nous avons utilisé detectTapGesture PointerInputScope pour détecter longPress, qui est notre geste de déplacement.

Avec la méthode onLongPress, nous lançons le transfert des données déplacées.

startTransfer démarre une session de glisser-déposer avec transferData comme données à transférer une fois le geste terminé. Il accepte les données encapsulées dans DragAndDropTransferData, qui comporte les trois champs suivants.

  1. Clipdata: : données à transférer
  2. flags : indicateurs permettant de contrôler l'opération de glisser-déposer
  3. localState : état local de la session lorsque l'utilisateur fait glisser la même activité

ClipData est un objet complexe qui contient des éléments de différents types, y compris du texte, du balisage, de l'audio, de la vidéo, etc. Pour les besoins de cet atelier de programmation, nous utilisons "imageurl" en tant qu'élément dans ClipData.

Très bien, vous pouvez maintenant faire glisser la vue !

415dcef002492e61.gif

5. Configurer la dépose

Pour que la vue accepte l'élément déposé, vous devez ajouter le modifier dragAndDropTarget.

Modifier.dragAndDropTarget(
   shouldStartDragAndDrop = {
       // condition to accept dragged item
   },
   target = // DragAndDropTarget
   )
)

dragAndDropTarget est le modificateur qui permet de faire glisser des données dans le composable. Il comporte deux paramètres.

  1. shouldStartDragAndDrop : permet au composable de décider s'il souhaite recevoir d'une session de glisser-déposer donnée en inspectant l'événement DragAndDropEvent qui a démarré la session.
  2. target : cible DragAndDropTarget qui recevra les événements d'une session de glisser-déposer donnée.

Ajoutons une condition lorsque nous voulons transmettre un événement de déplacement à DragAndDropTarget.

shouldStartDragAndDrop = { event ->
   event.mimeTypes()
       .contains(ClipDescription.MIMETYPE_TEXT_PLAIN)
}

La condition ajoutée ici est d'autoriser une action de déplacement uniquement si au moins un des éléments déplacés est en texte brut. La cible de dépose ne sera pas activée si aucun des éléments n'est en texte brut.

Pour les paramètres cibles, nous pouvons créer un objet DragAndDropTarget qui gère la session de dépose.

val dndTarget = remember{
   object : DragAndDropTarget{
       // handle Drag event
   }
}

DragAndDropTarget comporte un rappel à remplacer pour chaque étape de la session de glisser-déposer.

  1. onDrop : un élément a été déposé dans cette classe DragAndDropTarget. La valeur "true" est renvoyée pour indiquer que l'événement DragAndDropEvent a été utilisé. La valeur "false" indique que la demande a été refusée.
  2. onStarted : une session de glisser-déposer vient de commencer, et DragAndDropTarget peut la recevoir. Cela permet de définir l'état d'un DragAndDropTarget en prévision de l'utilisation d'une session de glisser-déposer.
  3. onEntered : un élément déposé a été placé dans les limites de DragAndDropTarget.
  4. onMoved : un élément déposé a été déplacé dans les limites de DragAndDropTarget.
  5. onExited : un élément déposé a été déplacé en dehors des limites de DragAndDropTarget.
  6. onChanged : un événement de la session de glisser-déposer actuelle a été modifié dans les limites de DragAndDropTarget. Cela peut se produit quand l'utilisateur appuie sur une touche de modification ou la relâche.
  7. onEnded : la session de glisser-déposer est terminée. Toutes les instances DragAndDropTarget de la hiérarchie qui ont précédemment reçu un événement onStarted recevront cet événement. Cela permet de réinitialiser l'état de DragAndDropTarget.

Définissons ce qui se passe lorsqu'un élément est déposé dans le composable cible.

override fun onDrop(event: DragAndDropEvent): Boolean {
   val draggedData = event.toAndroidDragEvent().clipData.getItemAt(0).text
   urlState.value = draggedData.toString()
   return true
}

Dans la fonction onDrop, nous allons extraire l'élément ClipData et l'attribuer à l'URL de l'image. Nous renvoyons également "true" pour indiquer que la dépose a été correctement gérée.

Maintenant, attribuons cette instance DragAndDropTarget au paramètre cible du modificateur dragAndDropTarget.

Modifier.dragAndDropTarget(
   shouldStartDragAndDrop = { event ->
       event.mimeTypes()
           .contains(ClipDescription.MIMETYPE_TEXT_PLAIN)
   },
   target = dndTarget
)

Parfait, nous pouvons maintenant effectuer l'opération de glisser-déposer !

277ed56f80460dec.gif

Nous avons ajouté la fonctionnalité de glisser-déposer, mais nous n'avons pour l'instant aucun repère visuel. Remédions à cela.

Pour le composable de cible de dépose, appliquons ColorFilter à notre image.

var tintColor by remember {
   mutableStateOf(Color(0xffE5E4E2))
}

Après avoir défini une teinte, ajoutons ColorFilter à notre image.

GlideImage(
   colorFilter = ColorFilter.tint(color = backgroundColor,
       blendMode = BlendMode.Modulate),
   // other params
)

Nous voulons appliquer une teinte à l'image lorsqu'un élément déplacé entre dans la zone cible de dépose. Pour cela, nous pouvons ignorer le rappel onEntered.

override fun onEntered(event: DragAndDropEvent) {
   super.onEntered(event)
   tintColor = Color(0xff00ff00)
}

De même, lorsque l'utilisateur fait glisser un objet hors de la zone cible, nous devons revenir au filtre de couleur d'origine. Pour ce faire, nous devons ignorer le rappel onExited.

override fun onExited(event: DragAndDropEvent) {
   super.onEntered(event)
   tintColor = Color(0xffE5E4E2)
}

Si l'opération de glisser-déposer est terminée, nous pouvons également rétablir le filtre de couleur (ColorFilter) d'origine.

override fun onEnded(event: DragAndDropEvent) {
   super.onEntered(event)
   tintColor = Color(0xffE5E4E2)
}

Notre composable de dépose se présente ainsi :

@Composable
fun DropTargetImage(url: String) {
   val urlState = remember {
       mutableStateOf(url)
   }
   var tintColor by remember {
       mutableStateOf(Color(0xffE5E4E2))
   }
   val dndTarget = remember {
       object : DragAndDropTarget {
           override fun onDrop(event: DragAndDropEvent): Boolean {
               val draggedData = event.toAndroidDragEvent()
                   .clipData.getItemAt(0).text
               urlState.value = draggedData.toString()
               return true
           }

           override fun onEntered(event: DragAndDropEvent) {
               super.onEntered(event)
               tintColor = Color(0xff00ff00)
           }
           override fun onEnded(event: DragAndDropEvent) {
               super.onEntered(event)
               tintColor = Color(0xffE5E4E2)
           }
           override fun onExited(event: DragAndDropEvent) {
               super.onEntered(event)
               tintColor = Color(0xffE5E4E2)
           }

       }
   }
   GlideImage(
       model = urlState.value,
       contentDescription = "Dropped Image",
       colorFilter = ColorFilter.tint(color = tintColor,
           blendMode = BlendMode.Modulate),
       modifier = Modifier
           .dragAndDropTarget(
               shouldStartDragAndDrop = { event ->
                   event
                       .mimeTypes()
                       .contains(ClipDescription.MIMETYPE_TEXT_PLAIN)
               },
               target = dndTarget
           )
   )
}

Et voilà, vous savez comment ajouter des repères visuels aux opérations de glisser-déposer !

6be7e749d53d3e7e.gif

6. Félicitations !

Compose fournit une interface facile à utiliser pour implémenter la fonctionnalité de glisser-déposer à l'aide de modificateurs pour la vue.

En résumé, vous avez appris à implémenter le glisser-déposer à l'aide de Compose. Consultez la documentation pour en savoir plus.

En savoir plus