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 les vues. Vous allez apprendre à mettre en œuvre le glisser-déposer pour les vues, aussi bien au sein de votre application que d'une application à une autre. Vous allez apprendre à implémenter des interactions de glisser-déposer au sein de votre application, mais aussi d'une application à une autre. Cet atelier de programmation vous apprendra à utiliser DropHelper pour permettre le glisser-déposer, à personnaliser le retour visuel lors du glisser-déposer avec ShadowBuilder, à ajouter des autorisations pour le glisser-déposer entre applications et à implémenter un récepteur de contenu fonctionnant de manière universelle.
Conditions préalables
Voici les conditions à remplir pour effectuer cet atelier de programmation :
- Vous maîtrisez la création d'applis Android.
- Vous maîtrisez les concepts suivants : Activités, Fragments, Liaison de vue et mises en page xml.
Objectifs de l'atelier
Créer une application simple qui :
- implémente la fonctionnalité de glisser-déposer à l'aide de
DragStartHelper
etDropHelper
; - modifie ShadowBuilder ;
- ajoute l'autorisation de glisser-déposer entre différentes applications ;
- implémente un récepteur de contenus enrichis pour une implémentation universelle.
Ce dont vous avez besoin
- Android Studio Jellyfish ou version ultérieure
- Appareil ou émulateur Android
2. Événement de glisser-déposer
Un processus de glisser-déposer peut être vu comme un événement en quatre étapes :
- Démarrage : le système lance l'opération de glisser-déposer en réponse au geste de déplacement de l'utilisateur.
- Poursuite de l'opération : l'utilisateur continue le geste de déplacement et DragShadowBuilder intervient lorsque l'utilisateur arrive dans la vue cible.
- Fin : l'utilisateur relâche le déplacement dans le cadre de délimitation d'une cible de glisser-déposer, à savoir la zone cible de dépôt.
- Sortie : 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 :
ActionType
: valeur d'action de l'événement basée sur l'événement de cycle de vie du glisser-déposer. Par exemple,ACTION_DRAG_STARTED
,
ACTION_DROP
, etc.ClipData
: données faisant l'objet du déplacement, encapsulées dans un objetClipData
.ClipDescription
: métadonnées concernant l'objetClipData
.Result
: résultat de l'opération de glisser-déposer.X
: coordonnée X de la position actuelle de l'objet déplacé.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 Views Activity" (Activité Vues vide) :
Conservez les valeurs par défaut de tous les paramètres. Laissez le projet se synchroniser et s'indexer. Vous verrez que le fichier MainActivity.kt
a été créé, ainsi que la vue activity_main.xml
.
4. Glisser-déposer à l'aide de vues
Ajoutons des valeurs de chaîne dans string.xml
.
<resources>
<string name="app_name">DragAndDropCodelab</string>
<string name="drag_image">Drag Image</string>
<string name="drop_image">drop image</string>
</resources>
Ouvrez le fichier source activity_main.xml
et modifiez la mise en page pour inclure deux éléments ImageViews
qui serviront respectivement de source de déplacement et de cible de dépôt.
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".MainActivity">
<TextView
android:id="@+id/tv_greeting"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:layout_constraintBottom_toTopOf="@id/iv_source"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<ImageView
android:id="@+id/iv_source"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:contentDescription="@string/drag_image"
app:layout_constraintBottom_toTopOf="@id/iv_target"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toBottomOf="@id/tv_greeting" />
<ImageView
android:id="@+id/iv_target"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:contentDescription="@string/drop_image"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toBottomOf="@id/iv_source" />
</androidx.constraintlayout.widget.ConstraintLayout>
Dans build.gradle.kts
, activez la liaison de vue.
buildFeatures{
viewBinding = true
}
Dans build.gradle.kts
, ajoutez une dépendance pour Glide.
dependencies {
implementation("com.github.bumptech.glide:glide:4.16.0")
annotationProcessor("com.github.bumptech.glide:compiler:4.16.0")
//other dependencies
}
Ajoutez des URL d'images et un texte d'accueil dans string.xml.
<string name="greeting">Drag and Drop</string>
<string name="target_url">https://services.google.com/fh/files/misc/qq2.jpeg</string>
<string name="source_url">https://services.google.com/fh/files/misc/qq10.jpeg</string>
Dans MainActivity.kt
, initialisez les vues.
class MainActivity : AppCompatActivity() {
val binding by lazy(LazyThreadSafetyMode.NONE) {
ActivityMainBinding.inflate(layoutInflater)
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(binding.root)
binding.tvGreeting.text = getString(R.string.greeting)
Glide.with(this).asBitmap()
.load(getString(R.string.source_url))
.into(binding.ivSource)
Glide.with(this).asBitmap()
.load(getString(R.string.target_url))
.into(binding.ivTarget)
}
}
À ce stade, votre application doit afficher un texte d'accueil et deux images en orientation verticale.
5. Rendre la vue déplaçable
Pour qu'une vue spécifique soit déplaçable, elle doit implémenter la méthode startDragAndDrop()
lorsqu'un geste de déplacement est effectué.
Implémentons un rappel pour onLongClickListener
lorsque l'utilisateur commence à déplacer la vue.
draggableView.setOnLongClickListener{ v ->
//drag logic here
true
}
Ce rappel rend la vue cliquable de manière prolongée, même si elle ne l'est pas à l'origine. La valeur renvoyée est un booléen. "True" signifie que le déplacement est consommé par le rappel.
Préparer ClipData : les données à déplacer
Définissons les données que nous souhaitons glisser-déposer. Les données peuvent être de n'importe quel type. Il peut s'agir aussi bien de texte simple que d'une vidéo. Ces données sont encapsulées dans l'objet ClipData
. L'objet ClipData
contient un ou plusieurs ClipItem
complexes.
Ces éléments possèdent différents types MIME définis dans ClipDescription
.
Nous faisons glisser l'URL de l'image de la vue source. ClipData
se compose de trois éléments principaux :
- label : texte simple permettant d'indiquer à l'utilisateur ce qu'il fait glisser.
- mimeTypes : type MIME des éléments que l'utilisateur fait glisser.
- clipItem : élément à faire glisser, encapsulé dans un objet
ClipData.Item
.
Créons maintenant un objet ClipData
.
val label = "Dragged Image Url"
val clipItem = ClipData.Item(v.tag as? CharSequence)
val mimeTypes = arrayOf(ClipDescription.MIMETYPE_TEXT_PLAIN)
val draggedData = ClipData(
label, mimeTypes, clipItem
)
Démarrer le glisser-déposer
Maintenant que les données sont prêtes à être déplacées, nous allons lancer le déplacement. Pour cela, nous utiliserons startDragAndDrop
.
La méthode startDragAndDrop
accepte quatre arguments :
- data : données que l'utilisateur fait glisser sous la forme d'un objet
ClipData.
. - shadowBuilder : DragShadowBuilder permettant de créer l'ombre de la vue déplacée.
- myLocalState : objet contenant des données locales concernant l'opération de glisser-déposer. Lors de la distribution d'événements de déplacement vers des vues de la même activité, cet objet sera disponible via DragEvent.getLocalState().
- Indicateurs : indicateurs permettant de contrôler les opérations de glisser-déposer.
Une fois cette fonction appelée, l'ombre de l'objet déplacé est créée en fonction de la classe View.DragShadowBuilder
. Une fois que le système présente l'ombre de l'objet déplacé, l'opération de glisser-déposer démarre avec l'envoi de l'événement à la vue visible, qui a implémenté l'interface OnDragListener
.
v.startDragAndDrop(
draggedData,
View.DragShadowBuilder(v),
null,
0
)
Nous avons donc configuré notre vue pour l'opération de déplacement et défini les données à déplacer. L'implémentation finale se présente comme suit :
fun setupDrag(draggableView: View) {
draggableView.setOnLongClickListener { v ->
val label = "Dragged Image Url"
val clipItem = ClipData.Item(v.tag as? CharSequence)
val mimeTypes = arrayOf(ClipDescription.MIMETYPE_TEXT_PLAIN)
val draggedData = ClipData(
label, mimeTypes, clipItem
)
v.startDragAndDrop(
draggedData,
View.DragShadowBuilder(v),
null,
0
)
}
}
À ce stade, vous devriez pouvoir faire glisser la vue à l'aide d'un clic prolongé.
Passons à la configuration de la vue de dépôt.
6. Configurer la vue pour DropTarget
Une vue peut servir de cible de dépôt si elle a implémenté l'interface OnDragListener
.
Configurons la deuxième vue d'image pour en faire une cible de dépôt.
private fun setupDrop(dropTarget: View) {
dropTarget.setOnDragListener { v, event ->
// handle drag events here
true
}
}
Nous allons remplacer la méthode onDrag
de l'interface OnDragListener. La méthode onDrag comporte deux arguments :
- Vue ayant reçu l'événement de déplacement
- Objet de l'événement de déplacement
Cette méthode renvoie la valeur "true" si l'événement de déplacement est traité correctement, ou "false" dans le cas contraire.
DragEvent
Indique qu'un paquet de données est transmis par le système à différents stades d'une opération de glisser-déposer. Cet ensemble de données encapsule des informations essentielles concernant l'opération elle-même et les données concernées.
DragEvent applique une action de déplacement différente selon l'étape de l'opération de glisser-déposer :
ACTION_DRAG_STARTED
: début de l'opération de glisser-déposer.ACTION _DRAG_LOCATION
: l'utilisateur a déposé l'élément déplacé dans la zone d'entrée, c'est-à-dire en dehors de la zone de dépôt cible.ACTION_DRAG_ENTERED
: la vue déplacée se trouve dans les limites de la vue de dépôt cible.ACTION_DROP
: l'utilisateur a déposé l'élément déplacé dans la zone de dépôt cible.ACTION_DRAG_ENDED
: l'opération de glisser-déposer est terminée.ACTION_DRAG_EXITED
: fin de l'opération de glisser-déposer.
Valider DragEvent
Vous pouvez choisir de réaliser l'opération de glisser-déposer si toutes vos contraintes sont remplies dans l'événement ACTION_DRAG_STARTED
. Dans cet exemple, nous pouvons vérifier si le type des données entrantes est correct ou non.
DragEvent.ACTION_DRAG_STARTED -> {
Log.d(TAG, "ON DRAG STARTED")
if (event.clipDescription.hasMimeType(ClipDescription.MIMETYPE_TEXT_PLAIN)) {
(v as? ImageView)?.alpha = 0.5F
v.invalidate()
true
} else {
false
}
}
Dans cet exemple, nous avons vérifié si la ClipDescription
de l'événement présentait un type MIME acceptable ou non. Si oui, nous indiquons que c'est le cas à l'aide d'un repère visuel et renvoyons la valeur "true", qui signale que les données déplacées sont en train d'être traitées. Sinon, nous renvoyons la valeur "false" pour indiquer que la vue déplacée n'est pas acceptée par la vue de dépôt cible.
Gérer les données déposées
Dans l'événement ACTION_DROP
, nous pouvons choisir comment traiter les données déposées. Dans cet exemple, nous extrayons l'URL que nous avons ajoutée à ClipData
en tant que texte. Nous faisons passer cette image de l'URL à notre vue d'image cible.
DragEvent.ACTION_DROP -> {
Log.d(TAG, "On DROP")
val item: ClipData.Item = event.clipData.getItemAt(0)
val dragData = item.text
Glide.with(this).load(item.text).into(v as ImageView)
(v as? ImageView)?.alpha = 1.0F
true
}
En plus de la gestion du dépôt, nous pouvons configurer ce qui se passe lorsqu'un utilisateur fait glisser la vue dans le cadre de délimitation de la vue de dépôt cible, et ce qui se passe lorsqu'il fait glisser la vue hors de la zone cible.
Ajoutons des repères visuels lorsque l'élément déplacé entre dans la zone cible.
DragEvent.ACTION_DRAG_ENTERED -> {
Log.d(TAG, "ON DRAG ENTERED")
(v as? ImageView)?.alpha = 0.3F
v.invalidate()
true
}
Ajoutons aussi des repères visuels lorsque l'utilisateur fait glisser la vue hors du cadre de délimitation de la vue de dépôt cible.
DragEvent.ACTION_DRAG_EXITED -> {
Log.d(TAG, "ON DRAG EXISTED")
(v as? ImageView)?.alpha = 0.5F
v.invalidate()
true
}
Ajoutons d'autres repères visuels pour indiquer la fin de l'opération de glisser-déposer.
DragEvent.ACTION_DRAG_ENDED -> {
Log.d(TAG, "ON DRAG ENDED")
(v as? ImageView)?.alpha = 1.0F
true
}
À ce stade, vous devriez pouvoir faire glisser une image vers la vue d'image cible. Une fois cette image déposée, l'image de la vue d'image cible est modifiée en conséquence.
7. Glisser-déposer en mode multifenêtre
Des éléments peuvent être déplacés d'une application à une autre, à condition que les applications se partagent l'écran en mode multifenêtre. L'implémentation permettant d'activer le glisser-déposer d'une application à une autre est identique, sauf que nous devons ajouter des indicateurs lors du déplacement, ainsi qu'une autorisation lors du dépôt.
Configurer des indicateurs lors du déplacement
Pour rappel, startDragAndDrop
comporte un argument qui permet de spécifier les indicateurs et qui, à terme, contrôle l'opération de glisser-déposer.
v.startDragAndDrop(
draggedData,
View.DragShadowBuilder(v),
null,
View.DRAG_FLAG_GLOBAL or View.DRAG_FLAG_GLOBAL_URI_READ
)
View.DRAG_FLAG_GLOBAL
indique que le déplacement peut se faire hors des limites de la fenêtre et View.DRAG_FLAG_GLOBAL_URI_READ
indique que le destinataire du déplacement est en mesure de lire le ou les URI de contenu.
Pour que la cible de dépôt puisse lire les données déplacées depuis d'autres applications, la vue de cible de dépôt doit déclarer l'autorisation de lecture.
val dropPermission = requestDragAndDropPermissions(event)
Elle doit aussi libérer l'autorisation une fois que les données déplacées ont été traitées.
dropPermission.release()
Voici à quoi ressemble le traitement final de l'élément déplacé :
DragEvent.ACTION_DROP -> {
Log.d(TAG, "On DROP")
val dropPermission = requestDragAndDropPermissions(event)
val item: ClipData.Item = event.clipData.getItemAt(0)
val dragData = item.text
Glide.with(this).load(item.text).into(v as ImageView)
(v as? ImageView)?.alpha = 1.0F
dropPermission.release()
true
}
À ce stade, vous devriez pouvoir faire glisser cette image vers une autre application. De même, les données déplacées depuis une autre application devraient pouvoir être gérées correctement.
8. Bibliothèque de glisser-déposer
Jetpack fournit une bibliothèque DragAndDrop pour simplifier l'implémentation du glisser-déposer.
Ajoutons une dépendance dans le fichier build.gradle.kts afin de pouvoir utiliser la bibliothèque DragAndDrop
.
implementation("androidx.draganddrop:draganddrop:1.0.0")
Pour cet exercice, créez une activité distincte appelée DndHelperActivity.kt
, comportant deux éléments ImageView en orientation verticale. Ils serviront respectivement de source de déplacement et de cible de dépôt.
Modifiez strings.xml
pour ajouter des ressources de chaîne.
<string name="greeting_1">DragStartHelper and DropHelper</string>
<string name="target_url_1">https://services.google.com/fh/files/misc/qq9.jpeg</string>
<string name="source_url_1">https://services.google.com/fh/files/misc/qq8.jpeg</string>
Modifiez activity_dnd_helper.xml pour inclure des éléments ImageView.
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:padding="24dp"
tools:context=".DnDHelperActivity">
<TextView
android:id="@+id/tv_greeting"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:layout_constraintBottom_toTopOf="@id/iv_source"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<ImageView
android:id="@+id/iv_source"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:contentDescription="@string/drag_image"
app:layout_constraintBottom_toTopOf="@id/iv_target"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toBottomOf="@id/tv_greeting" />
<ImageView
android:id="@+id/iv_target"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:contentDescription="@string/drop_image"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toBottomOf="@id/iv_source" />
</androidx.constraintlayout.widget.ConstraintLayout>
Pour finir, initialisez les vues dans DnDHelperActivity.kt
.
class DnDHelperActivity : AppCompatActivity() {
private val binding by lazy(LazyThreadSafetyMode.NONE) {
ActivityMainBinding.inflate(layoutInflater)
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(binding.root)
binding.tvGreeting.text = getString(R.string.greeting)
Glide.with(this).asBitmap()
.load(getString(R.string.source_url_1))
.into(binding.ivSource)
Glide.with(this).asBitmap()
.load(getString(R.string.target_url_1))
.into(binding.ivTarget)
binding.ivSource.tag = getString(R.string.source_url_1)
}
}
Veillez à modifier le fichier AndroidManifest.xml pour définir DndHelperActivity comme activité du lanceur d'applications.
<activity
android:name=".DnDHelperActivity"
android:exported="true">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
DragStartHelper
Nous avons précédemment configuré la vue pour qu'elle soit déplaçable en implémentant onLongClickListener
et en appelant startDragAndDrop
. DragStartHelper simplifie l'implémentation en fournissant des méthodes utilitaires.
DragStartHelper(draggableView)
{ view: View, _: DragStartHelper ->
// prepare clipData
// startDrag and Drop
}.attach()
DragStartHelper
accepte la vue à faire glisser en tant qu'argument. Ici, nous avons implémenté la méthode OnDragStartListener, dans laquelle nous allons préparer l'objet ClipData et lancer l'opération de glisser-déposer.
L'implémentation finale se présente comme suit :
DragStartHelper(draggableView)
{ view: View, _: DragStartHelper ->
val item = ClipData.Item(view.tag as? CharSequence)
val dragData = ClipData(
view.tag as? CharSequence,
arrayOf(ClipDescription.MIMETYPE_TEXT_PLAIN),
item
)
view.startDragAndDrop(
dragData,
View.DragShadowBuilder(view),
null,
0
)
}.attach()
DropHelper
DropHelper
simplifie la configuration de la vue de dépôt cible en fournissant une méthode utilitaire appelée configureView
.
La méthode configureView accepte quatre arguments :
- Activity : l'activité en cours
- dropTarget : la vue en cours de configuration
- mimeTypes : le type MIME des éléments de données déposés
- L'interface
OnReceiveContentListener
permettant de gérer les données déposées
Personnalisez la mise en surbrillance de la cible de dépôt.
DropHelper.configureView(
This, // Current Activity
dropTarget,
arrayOf("text/*"),
DropHelper.Options.Builder().build()
) {
// handle the dropped data
}
OnReceiveContentListener reçoit le contenu déposé. Elle comporte deux paramètres :
- View : l'emplacement où le contenu est déposé.
- Payload : le contenu à déposer.
private fun setupDrop(dropTarget: View) {
DropHelper.configureView(
this,
dropTarget,
arrayOf("text/*"),
) { _, payload: ContentInfoCompat ->
// TODO: step through clips if one cannot be loaded
val item = payload.clip.getItemAt(0)
val dragData = item.text
Glide.with(this)
.load(dragData)
.centerCrop().into(dropTarget as ImageView)
// Consume payload by only returning remaining items
val (_, remaining) = payload.partition { it == item }
remaining
}
}
À ce stade, vous devriez pouvoir glisser-déposer des données à l'aide de DragStartHelper et DropHelper.
Configurer la mise en surbrillance de la zone de dépôt
Comme vous l'avez vu, lorsqu'un élément déplacé entre dans la zone de dépôt, celle-ci est mise en surbrillance. Avec DropHelper.Options, nous pouvons personnaliser la façon dont la zone de dépôt est mise en surbrillance lorsqu'un élément déplacé entre dans les limites de la vue.
DropHelper.Options peut être utilisé pour configurer la couleur et l'arrondi d'angle de la mise en surbrillance de la zone cible de dépôt.
DropHelper.Options.Builder()
.setHighlightColor(getColor(R.color.green))
.setHighlightCornerRadiusPx(16)
.build()
Ces options doivent être transmises en tant qu'arguments à la méthode configureView de DropHelper.
private fun setupDrop(dropTarget: View) {
DropHelper.configureView(
this,
dropTarget,
arrayOf("text/*"),
DropHelper.Options.Builder()
.setHighlightColor(getColor(R.color.green))
.setHighlightCornerRadiusPx(16)
.build(),
) { _, payload: ContentInfoCompat ->
// TODO: step through clips if one cannot be loaded
val item = payload.clip.getItemAt(0)
val dragData = item.text
Glide.with(this)
.load(dragData)
.centerCrop().into(dropTarget as ImageView)
// Consume payload by only returning remaining items
val (_, remaining) = payload.partition { it == item }
remaining
}
}
Vous devriez voir la couleur et l'arrondi d'angle de la mise en surbrillance lors du glisser-déposer.
9. Recevoir du contenu enrichi
OnReceiveContentListener
est l'API unifiée qui permet de recevoir du contenu enrichi, y compris du texte, du code HTML, des images, des vidéos, etc. Il est possible d'insérer le contenu dans les vues à l'aide du clavier, du glisser-déposer ou du presse-papiers. La gestion du rappel pour chaque mécanisme d'entrée peut s'avérer pénible. OnReceiveContentListener
permet de recevoir des contenus tels que du texte, du balisage, de l'audio, de la vidéo, des images, etc. à l'aide d'une seule API. L'API OnReceiveContentListener
consolide ces différents chemins de code sous la forme d'une seule API à implémenter. Vous pouvez ainsi vous concentrer sur la logique propre à votre application et laisser la plate-forme gérer le reste.
Pour cet exercice, créez une activité distincte appelée ReceiveRichContentActivity.kt
, comportant deux éléments ImageView en orientation verticale. Ils serviront respectivement de source de déplacement et de cible de dépôt.
Modifiez strings.xml
pour ajouter des ressources de chaîne.
<string name="greeting_2">Rich Content Receiver</string>
<string name="target_url_2">https://services.google.com/fh/files/misc/qq1.jpeg</string>
<string name="source_url_2">https://services.google.com/fh/files/misc/qq3.jpeg</string>
Modifiez activity_receive_rich_content.xml
pour y inclure des éléments ImageViews.
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".ReceiveRichContentActivity">
<TextView
android:id="@+id/tv_greeting"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:layout_constraintBottom_toTopOf="@id/iv_source"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<ImageView
android:id="@+id/iv_source"
android:layout_width="320dp"
android:layout_height="wrap_content"
android:contentDescription="@string/drag_image"
app:layout_constraintBottom_toTopOf="@id/iv_target"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toBottomOf="@id/tv_greeting" />
<ImageView
android:id="@+id/iv_target"
android:layout_width="320dp"
android:layout_height="wrap_content"
android:contentDescription="@string/drop_image"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toBottomOf="@id/iv_source" />
</androidx.constraintlayout.widget.ConstraintLayout>
Pour finir, initialisez les vues dans ReceiveRichContentActivity.kt
.
class ReceiveRichContentActivity : AppCompatActivity() {
private val binding by lazy(LazyThreadSafetyMode.NONE) {
ActivityReceiveRichContentBinding.inflate(layoutInflater)
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(binding.root)
binding.tvGreeting.text = getString(R.string.greeting_2)
Glide.with(this).asBitmap()
.load(getString(R.string.source_url_2))
.into(binding.ivSource)
Glide.with(this).asBitmap()
.load(getString(R.string.target_url_2))
.into(binding.ivTarget)
binding.ivSource.tag = getString(R.string.source_url_2)
}
}
Veillez à modifier AndroidManifest.xml
pour définir DndHelperActivity comme activité de lanceur.
<activity
android:name=".ReceiveRichContentActivity"
android:exported="true">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
Commençons par créer un rappel qui implémente OnReceiveContentListener.
.
val listener = OnReceiveContentListener { view, payload ->
val (textContent, remaining) =
payload.partition { item: ClipData.Item -> item.text != null }
if (textContent != null) {
val clip = textContent.clip
for (i in 0 until clip.itemCount) {
val currentText = clip.getItemAt(i).text
Glide.with(this)
.load(currentText)
.centerCrop().into(view as ImageView)
}
}
remaining
}
Ici, nous avons implémenté l'interface OnRecieveContentListener
. La méthode onRecieveContent
comporte deux arguments :
- Vue actuelle qui reçoit les données
- Charge utile de données reçue à partir du clavier, du glisser-déposer ou du presse-papiers sous la forme d'une méthode
ContentInfoCompat
Cette méthode renvoie la charge utile qui n'est pas gérée.
Ici, nous avons segmenté la charge utile en contenu textuel et autre contenu à l'aide de la méthode Partition. Nous traitons les données textuelles selon nos besoins et renvoyons la charge utile restante.
Déterminons maintenant quoi faire avec les données déplacées.
val listener = OnReceiveContentListener { view, payload ->
val (textContent, remaining) =
payload.partition { item: ClipData.Item -> item.text != null }
if (textContent != null) {
val clip = textContent.clip
for (i in 0 until clip.itemCount) {
val currentText = clip.getItemAt(i).text
Glide.with(this)
.load(currentText)
.centerCrop().into(view as ImageView)
}
}
remaining
}
Notre écouteur est maintenant prêt. Ajoutons cet écouteur à la vue cible.
ViewCompat.setOnReceiveContentListener(
binding.ivTarget,
arrayOf("text/*"),
listener
)
À ce stade, vous devriez pouvoir effectuer un glisser-déposer d'une image dans la zone cible. Une fois déposée, l'image déplacée doit remplacer l'image d'origine dans la vue de dépôt cible.
10. Félicitations !
Vous savez maintenant implémenter le glisser-déposer dans votre application Android. Dans cet atelier de programmation, vous avez appris à créer des interactions de glisser-déposer interactives dans votre application Android, ainsi que d'une application à une autre, ce qui vous permet d'améliorer l'expérience de l'utilisateur et de lui proposer de nouvelles fonctionnalités. Voici ce que vous avez appris :
- Principes de base du glisser-déposer : comprendre les quatre étapes d'un événement de glisser-déposer (démarrage, poursuite de l'opération, fin, sortie) et les données clés au sein de l'objet DragEvent
- Activation du glisser-déposer : rendre la vue déplaçable et gérer son dépôt dans la vue cible à l'aide de DragEvent
- Glisser-déposer en mode multifenêtre : permettre le glisser-déposer d'une application à une autre en définissant les indicateurs et les autorisations appropriées
- Utilisation de la bibliothèque DragAndDrop : simplifier l'implémentation du glisser-déposer à l'aide de la bibliothèque Jetpack
- Réception de contenus enrichis : implémenter la gestion de différents types de contenus (texte, images, vidéos, etc.) à partir de différentes méthodes d'entrée à l'aide d'une API unifiée.
En savoir plus
- Atelier de programmation sur le glisser-déposer dans Compose
- Activer le glisser-déposer
- Exemple de glisser-déposer
- Dépôt d'exemples pour les ateliers de programmation
- Créer des applis pour les appareils pliables
- Compatibilité avec le mode multifenêtre
- Mises en page responsives pour développement sur grand écran
- Jetpack WindowManager
- Exemple Jetpack WindowManager
- Développer des applications pour les appareils à double écran