Copier et coller

Le framework Android basé sur le presse-papiers pour copier et coller accepte les types de données primitifs et complexes, y compris :

  • Chaînes de texte
  • Structures de données complexes
  • Données de flux textuelles et binaires
  • Composants d'application

Les données textuelles simples sont stockées directement dans le presse-papiers, tandis que les données complexes sont stockées sous forme de référence que l'application de collage résout avec un fournisseur de contenu.

Le copier-coller fonctionne à la fois dans une application et entre les applications qui implémentent le framework.

Étant donné qu'une partie du framework utilise des fournisseurs de contenu, ce document suppose que vous connaissez l'API Android Content Provider.

Travailler avec du texte

Certains composants sont compatibles avec le copier-coller de texte, comme indiqué dans le tableau suivant.

Composant Copie du texte… Coller du texte
BasicTextField ✅ ✅
TextField ✅ ✅
SelectionContainer ✅

Par exemple, vous pouvez copier le texte de la fiche dans le presse-papiers dans l'extrait suivant et coller le texte copié dans TextField. Vous pouvez afficher le menu pour coller le texte en appuyant de manière prolongée sur TextField ou en appuyant sur la poignée du curseur.

val textFieldState = rememberTextFieldState()

Column {
    Card {
        SelectionContainer {
            Text("You can copy this text")
        }
    }
    BasicTextField(state = textFieldState)
}

Vous pouvez coller le texte à l'aide du raccourci clavier suivant : Ctrl+V . Le raccourci clavier est également disponible par défaut. Pour en savoir plus, consultez Gérer les actions du clavier.

Copier avec ClipboardManager

Vous pouvez copier des textes dans le presse-papiers avec ClipboardManager. Sa méthode setText() copie l'objet AnnotatedString transmis dans le presse-papiers. L'extrait de code suivant copie "Hello, clipboard" dans le presse-papiers lorsque l'utilisateur clique sur le bouton.

// Retrieve a ClipboardManager object
val clipboardManager = LocalClipboardManager.current

Button(
    onClick = {
        // Copy "Hello, clipboard" to the clipboard
        clipboardManager.setText(AnnotatedString("Hello, clipboard"))
    }
) {
    Text("Click to copy a text")
}

L'extrait de code suivant effectue la même opération, mais vous offre un contrôle plus précis. Un cas d'utilisation courant est la copie de contenu sensible, comme un mot de passe. ClipEntry décrit un élément du presse-papiers. Il contient un objet ClipData qui décrit les données du presse-papiers. La méthode ClipData.newPlainText() est une méthode pratique pour créer un objet ClipData à partir d'un objet String. Vous pouvez définir l'objet ClipEntry créé dans le presse-papiers en appelant la méthode setClip() sur l'objet ClipboardManager.

// Retrieve a ClipboardManager object
val clipboardManager = LocalClipboardManager.current

Button(
    onClick = {
        val clipData = ClipData.newPlainText("plain text", "Hello, clipboard")
        val clipEntry = ClipEntry(clipData)
        clipboardManager.setClip(clipEntry)
    }
) {
    Text("Click to copy a text")
}

Coller avec ClipboardManager

Vous pouvez accéder au texte copié dans le presse-papiers en appelant la méthode getText() sur ClipboardManager. Sa méthode getText() renvoie un objet AnnotatedString lorsqu'un texte est copié dans le presse-papiers. L'extrait de code suivant ajoute le texte du presse-papiers au texte de TextField.

val textFieldState = rememberTextFieldState()

Column {
    TextField(state = textFieldState)

    Button(
        onClick = {
            // The getText method returns an AnnotatedString object or null
            val annotatedString = clipboardManager.getText()
            if (annotatedString != null) {
                // The pasted text is placed on the tail of the TextField
                textFieldState.edit {
                    append(annotatedString.text)
                }
            }
        }
    ) {
        Text("Click to paste the text in the clipboard")
    }
}

Utiliser du contenu enrichi

Les utilisateurs adorent les images, les vidéos et les autres contenus expressifs. Votre application peut permettre à l'utilisateur de copier du contenu enrichi avec ClipboardManager et ClipEntry. Le modificateur contentReceiver vous aide à implémenter le collage de contenu enrichi.

Copier le contenu enrichi

Votre application ne peut pas copier de contenu enrichi directement dans le presse-papiers. À la place, votre application transmet un objet URI au presse-papiers et fournit l'accès au contenu avec un ContentProvider. L'extrait de code suivant montre comment copier une image JPEG dans le presse-papiers. Pour en savoir plus, consultez Copier des flux de données.

// Get a reference to the context
val context = LocalContext.current

Button(
    onClick = {
        // URI of the copied JPEG data
        val uri = Uri.parse("content://your.app.authority/0.jpg")
        // Create a ClipData object from the URI value
        // A ContentResolver finds a proper ContentProvider so that ClipData.newUri can set appropriate MIME type to the given URI
        val clipData = ClipData.newUri(context.contentResolver, "Copied", uri)
        // Create a ClipEntry object from the clipData value
        val clipEntry = ClipEntry(clipData)
        // Copy the JPEG data to the clipboard
        clipboardManager.setClip(clipEntry)
    }
) {
    Text("Copy a JPEG data")
}

Coller un contenu enrichi

Le modificateur contentReceiver vous permet de gérer le collage de contenu enrichi dans BasicTextField dans le composant modifié. L'extrait de code suivant ajoute l'URI collé d'une image à une liste d'objets Uri.

// A URI list of images
val imageList = remember { mutableStateListOf<Uri>() }

// Remember the ReceiveContentListener object as it is created inside a Composable scope
val receiveContentListener = remember {
    ReceiveContentListener { transferableContent ->
        // Handle the pasted data if it is image data
        when {
            // Check if the pasted data is an image or not
            transferableContent.hasMediaType(MediaType.Image) -> {
                // Handle for each ClipData.Item object
                // The consume() method returns a new TransferableContent object containging ignored ClipData.Item objects
                transferableContent.consume { item ->
                    val uri = item.uri
                    if (uri != null) {
                        imageList.add(uri)
                    }
                    // Mark the ClipData.Item object consumed when the retrieved URI is not null
                    uri != null
                }
            }
            // Return the given transferableContent when the pasted data is not an image
            else -> transferableContent
        }
    }
}

val textFieldState = rememberTextFieldState()

BasicTextField(
    state = textFieldState,
    modifier = Modifier
        .contentReceiver(receiveContentListener)
        .fillMaxWidth()
        .height(48.dp)
)

Le modificateur contentReceiver utilise un objet ReceiveContentListener comme argument et appelle la méthode onReceive de l'objet transmis lorsque l'utilisateur colle des données dans le BasicTextField du composant modifié.

Un objet TransferableContent est transmis à la méthode onReceive. Il décrit les données qui peuvent être transférées entre les applications, par collage dans ce cas. Vous pouvez accéder à l'objet ClipEntry en faisant référence à l'attribut clipEntry.

Un objet ClipEntry peut comporter plusieurs objets ClipData.Item, par exemple lorsque l'utilisateur sélectionne plusieurs images et les copie dans le presse-papiers. Vous devez marquer chaque objet ClipData.Item comme consommé ou ignoré, et renvoyer un TransferableContent contenant les objets ClipData.Item ignorés afin que le modificateur contentReceiver ancêtre le plus proche puisse le recevoir.

La méthode TransferableContent.hasMediaType() peut vous aider à déterminer si l'objet TransferableContent peut fournir un élément avec le type de média. Par exemple, l'appel de méthode suivant renvoie true si l'objet TransferableContent peut fournir une image.

transferableContent.hasMediaType(MediaType.Image)

Utiliser des données complexes

Vous pouvez copier des données complexes dans le presse-papiers de la même manière que pour le contenu enrichi. Pour en savoir plus, consultez Utiliser des fournisseurs de contenu pour copier des données complexes.

Vous pouvez également gérer les collages de données complexes de la même manière pour le contenu enrichi. Vous pouvez recevoir un URI des données collées. Les données réelles peuvent être récupérées à partir d'un ContentProvider. Pour en savoir plus, consultez Récupérer des données auprès du fournisseur.

Commentaires sur la copie de contenu

Les utilisateurs s'attendent à recevoir un retour lorsqu'ils copient du contenu dans le presse-papiers. Par conséquent, en plus du framework qui gère le copier-coller, Android affiche une UI par défaut aux utilisateurs lorsqu'ils copient du contenu dans Android 13 (niveau d'API 33) ou version ultérieure. En raison de cette fonctionnalité, il existe un risque de notification en double. Pour en savoir plus sur ce cas particulier, consultez Éviter les notifications en double.

Animation montrant la notification du presse-papiers d'Android 13
Figure 1 : UI affichée lorsque du contenu est ajouté au presse-papiers sous Android 13 ou version ultérieure.

Fournissez manuellement des commentaires aux utilisateurs lors de la copie sur Android 12L (niveau d'API 32) ou version antérieure. Consultez la recommandation.

Contenu sensible

Si vous choisissez de permettre à l'utilisateur de copier du contenu sensible dans le presse-papiers, comme des mots de passe, votre application doit en informer le système afin qu'il n'affiche pas le contenu sensible copié dans l'UI (figure 2).

Aperçu du texte copié signalant un contenu sensible.
Figure 2 : Aperçu du texte copié avec un indicateur de contenu sensible.

Vous devez ajouter un flag à ClipDescription dans ClipData avant d'appeler la méthode setClip() sur l'objet ClipboardManager :

// If your app is compiled with the API level 33 SDK or higher.
clipData.apply {
    description.extras = PersistableBundle().apply {
        putBoolean(ClipDescription.EXTRA_IS_SENSITIVE, true)
    }
}

// If your app is compiled with a lower SDK.
clipData.apply {
    description.extras = PersistableBundle().apply {
        putBoolean("android.content.extra.IS_SENSITIVE", true)
    }
}