Copier et coller

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

  • Chaînes de texte
  • Structures de données complexes
  • Données de flux binaires et textuelles
  • 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.

La copie et le collage fonctionnent à la fois dans une application et entre les applications qui implémentent le framework.

Comme une partie du framework utilise des fournisseurs de contenu, ce document suppose une certaine familiarité avec l'API Android Content Provider.

Travailler avec du texte

Certains composants prennent en charge la copie et le collage de texte prêts à l'emploi, comme indiqué dans le tableau suivant.

Composant Copier 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 le TextField. Affichez le menu pour coller le texte en appuyant de manière prolongée sur le TextField ou en appuyant sur le 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 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 suivant fait la même chose, mais vous offre un contrôle plus précis. Un cas d'utilisation courant consiste à copier du contenu sensible, tel qu'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. ClipData.newPlainText() méthode est une méthode pratique permettant de 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 getText() méthode sur le ClipboardManager. Sa méthode getText() renvoie un objet AnnotatedString lorsqu'un texte est copié dans le presse-papiers. L'extrait suivant ajoute du texte dans le presse-papiers au texte du 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")
    }
}

Travailler avec du contenu enrichi

Les utilisateurs apprécient les images, les vidéos et 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 du contenu enrichi

Votre application ne peut pas copier directement du contenu enrichi dans le presse-papiers. Au lieu de cela, votre application transmet un objet URI au presse-papiers et fournit un 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 du contenu enrichi

Avec le modificateur contentReceiver, vous pouvez gérer le collage de contenu enrichi dans BasicTextField dans le composant modifié. L'extrait de code suivant ajoute l'URI collé des données d'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 prend 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 à l'intérieur du composant modifié.

Un objet TransferableContent est transmis à la méthode onReceive, qui décrit les données pouvant être transférées entre les applications en cas de collage. Vous pouvez accéder à l'objet ClipEntry en vous référant à l'attribut clipEntry.

Un objet ClipEntry peut comporter plusieurs objets ClipData.Item lorsque l'utilisateur sélectionne plusieurs images et les copie dans le presse-papiers par exemple. 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)

Travailler avec 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 que 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 des commentaires lorsqu'ils copient du contenu dans le presse-papiers. Par conséquent, en plus du framework qui permet de copier-coller, Android affiche une interface utilisateur par défaut aux utilisateurs lorsqu'ils copient du contenu dans Android 13 (niveau d'API 33) et versions ultérieures. En raison de cette fonctionnalité, il existe un risque de notification en double. Pour en savoir plus sur ce cas limite, consultez Éviter les notifications en double.

Animation montrant la notification du presse-papiers d'Android 13
Figure 1. Interface utilisateur affichée lorsque du contenu est placé dans le presse-papiers sous Android 13 et versions ultérieures.

Fournissez manuellement des commentaires aux utilisateurs lors de la copie sous Android 12L (niveau d'API 32) et versions antérieures. Consultez la recommandation.

Contenu sensible

Si vous choisissez de laisser votre application permettre à l'utilisateur de copier du contenu sensible dans le presse-papiers, tel que des mots de passe, votre application doit en informer le système afin qu'il puisse éviter d'afficher le contenu sensible copié dans l'interface utilisateur (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 indicateur à 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)
    }
}