Copier et coller

Le framework Android basé sur le presse-papiers pour la copie et le collage est compatible avec 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 textuelles et binaires
  • Composants Application

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

Les opérations de copier-coller fonctionnent aussi bien au sein d'une application qu'entre les applications qui implémentent le framework.

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

Travailler avec du texte

Certains composants permettent de copier et de coller du texte directement, comme indiqué dans le tableau suivant.

Component Copie du texte... Collage du texte...
Champ de texte de base
TextField
SelectionContainer

Par exemple, vous pouvez copier le texte de la fiche dans le presse-papiers dans l'extrait de code suivant, puis coller le texte copié dans TextField. Pour afficher le menu permettant de coller le texte, appuyez de manière prolongée sur TextField ou 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 du texte dans le presse-papiers avec ClipboardManager. Sa méthode setText() copie l'objet String transmis dans le presse-papiers. L'extrait de code suivant copie "Hello, presse-papiers" 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("Hello, clipboard")
    }
) {
   Text("Click to copy a text")
}

L'extrait de code 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, comme le 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 créer un objet ClipData à partir d'un objet String. Vous pouvez ajouter 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() pendant ClipboardManager. Sa méthode getText() renvoie un objet AnnotatedString. Lorsqu'un texte est copié dans le presse-papiers. L'extrait de code suivant ajoute du texte dans le presse-papiers au texte de TextField.

var 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(text.toString())
                }
            }
        }
    ) {
        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 du contenu enrichi

Votre application ne peut pas copier directement du contenu enrichi dans le presse-papiers. À la place, votre application transmet un objet URI au presse-papiers et permet d'accéder au contenu à l'aide d'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

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é d'une donnée d'image à une liste d'objets Uri.

// A URI list of images
val imageList by remember{ mutableListOf<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 accepte un objet ReceiveContentListener. comme argument et appelle onReceive de l'objet transmis lorsque l'utilisateur colle des données au 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 les collant dans ce cas. 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 les objets comme consommés ou ignorés pour chaque objet ClipData.Item. et renvoyer une TransferableContent contenant les objets ClipData.Item ignorés. afin que le modificateur contentReceiver de l'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 contenu. 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 aussi gérer le collage 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 des commentaires lorsqu'ils copient du contenu dans le presse-papiers. Par conséquent, en plus du framework qui alimente le copier-coller, Android affiche une UI par défaut aux utilisateurs lorsqu'ils effectuent une copie dans Android 13 (niveau d'API 33) ou version ultérieure. En raison de cette fonctionnalité, vous risquez de recevoir des notifications en double. Pour en savoir plus sur ce cas particulier, consultez Éviter les notifications en double.

Animation montrant la notification du presse-papiers d&#39;Android 13
Figure 1 : UI affichée lorsque le contenu est ajouté au presse-papiers dans Android 13 et versions ultérieures.

Envoyer manuellement des commentaires aux utilisateurs lors de la copie sous 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 évite d'afficher le contenu sensible copié dans l'UI (figure 2).

Aperçu du texte copié signalant le 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)
    }
}