Copia e incolla

Il framework basato sugli appunti di Android per la copia e l'incolla supporta tipi di dati primitivi e complessi, tra cui:

  • Stringhe di testo
  • Strutture di dati complesse
  • Dati di stream di testo e binari
  • Asset dell'applicazione

I dati di testo semplici vengono archiviati direttamente negli appunti, mentre i dati complessi vengono archiviati come riferimento che l'applicazione di destinazione risolve con un provider di contenuti.

La funzionalità di copia e incolla funziona sia all'interno di un'applicazione sia tra applicazioni che implementano il framework.

Poiché parte del framework utilizza i provider di contenuti, questo documento presuppone una certa familiarità con l'API Android Content Provider.

Lavora con il testo

Alcuni componenti supportano la copia e l'incolla del testo fin da subito, come mostrato nella tabella seguente.

Componente Copia del testo Incolla testo
BasicTextField ✅ ✅
TextField ✅ ✅
SelectionContainer ✅

Ad esempio, puoi copiare il testo della scheda negli appunti nello snippet seguente e incollarlo in TextField. Per visualizzare il menu per incollare il testo, tocca e tieni premuto TextField o tocca il cursore.

val textFieldState = rememberTextFieldState()

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

Puoi incollare il testo con la seguente scorciatoia da tastiera: Ctrl+V . La scorciatoia da tastiera è disponibile anche per impostazione predefinita. Per maggiori dettagli, consulta Gestisci le azioni della tastiera.

Copia con ClipboardManager

Puoi copiare i testi negli appunti con ClipboardManager. Il metodo setText() copia l'oggetto AnnotatedString passato negli appunti. Il seguente snippet copia "Hello, clipboard" negli appunti quando l'utente fa clic sul pulsante.

// 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")
}

Lo snippet seguente esegue la stessa operazione, ma ti offre un controllo più granulare. Un caso d'uso comune è la copia di contenuti sensibili, come le password. ClipEntry descrive un elemento negli appunti. Contiene un oggetto ClipData che descrive i dati negli appunti. Il metodo ClipData.newPlainText() è un metodo pratico per creare un oggetto ClipData da un oggetto String. Puoi impostare l'oggetto ClipEntry creato negli appunti chiamando il metodo setClip() sull'oggetto 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")
}

Incolla con ClipboardManager

Puoi accedere al testo copiato negli appunti chiamando il metodo getText() su ClipboardManager. Il suo metodo getText() restituisce un oggetto AnnotatedString quando un testo viene copiato negli appunti. Il seguente snippet aggiunge il testo negli appunti al testo in 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")
    }
}

Lavora con contenuti avanzati

Gli utenti amano le immagini, i video e altri contenuti espressivi. La tua app può consentire all'utente di copiare contenuti avanzati con ClipboardManager e ClipEntry. Il modificatore contentReceiver ti aiuta a implementare l'incollamento di contenuti avanzati.

Copia contenuti avanzati

La tua app non può copiare contenuti avanzati direttamente negli appunti. Invece, l'app passa un oggetto URI agli appunti e fornisce l'accesso ai contenuti con un ContentProvider. Il seguente snippet di codice mostra come copiare un'immagine JPEG negli appunti. Per maggiori dettagli, consulta Copia gli stream di dati.

// 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")
}

Incolla contenuti avanzati

Con il modificatore contentReceiver, puoi gestire l'incollamento di contenuti avanzati in BasicTextField nel componente modificato. Il seguente snippet di codice aggiunge l'URI incollato dei dati di un'immagine a un elenco di oggetti 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)
)

Il modificatore contentReceiver accetta un oggetto ReceiveContentListener come argomento e chiama il metodo onReceive dell'oggetto passato quando l'utente incolla i dati in BasicTextField all'interno del componente modificato.

Un oggetto TransferableContent viene passato al metodo onReceive, che descrive i dati che possono essere trasferiti tra le app, in questo caso tramite l'operazione di incolla. Puoi accedere all'oggetto ClipEntry facendo riferimento all'attributo clipEntry.

Un oggetto ClipEntry può avere diversi oggetti ClipData.Item, ad esempio quando l'utente seleziona diverse immagini e le copia negli appunti. Devi contrassegnare come consumato o ignorato ogni oggetto ClipData.Item e restituire un TransferableContent contenente gli oggetti ClipData.Item ignorati in modo che il modificatore contentReceiver dell'antenato più vicino possa riceverlo.

Il metodo TransferableContent.hasMediaType() può aiutarti a determinare se l'oggetto TransferableContent può fornire un elemento con il tipo di media. Ad esempio, la seguente chiamata al metodo restituisce true se l'oggetto TransferableContent può fornire un'immagine.

transferableContent.hasMediaType(MediaType.Image)

Lavora con dati complessi

Puoi copiare dati complessi negli appunti nello stesso modo in cui lo fai per i contenuti avanzati. Per maggiori dettagli, consulta Utilizza i provider di contenuti per copiare dati complessi.

Puoi anche gestire gli incollamenti di dati complessi nello stesso modo per i contenuti avanzati. Puoi ricevere un URI dei dati incollati. I dati effettivi possono essere recuperati da un ContentProvider. Per saperne di più, consulta Recupera i dati dal provider.

Feedback sulla copia dei contenuti

Gli utenti si aspettano un feedback quando copiano contenuti negli appunti, quindi, oltre al framework che supporta la funzionalità di copia e incolla, Android mostra un'interfaccia utente predefinita agli utenti quando copiano in Android 13 (livello API 33) e versioni successive. A causa di questa funzionalità, esiste il rischio di notifiche duplicate. Puoi scoprire di più su questo caso limite in Evita notifiche duplicate.

Un'animazione che mostra la notifica degli appunti di Android 13
Figura 1. UI mostrata quando i contenuti vengono inseriti negli appunti in Android 13 e versioni successive.

Fornisci manualmente un feedback agli utenti quando copiano in Android 12L (livello API 32) e versioni precedenti. Visualizza il suggerimento.

Contenuti sensibili

Se scegli di consentire all'utente di copiare contenuti sensibili negli appunti, ad esempio le password, la tua app deve informare il sistema in modo che quest'ultimo possa evitare di visualizzare i contenuti sensibili copiati nell'interfaccia utente (figura 2).

Anteprima del testo copiato che segnala contenuti sensibili.
Figura 2. Anteprima del testo copiato con un flag di contenuti sensibili.

Devi aggiungere un flag a ClipDescription in ClipData prima di chiamare il metodo setClip() sull'oggetto 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)
    }
}