El framework basado en el portapapeles de Android para copiar y pegar admite tipos de datos primitivos y complejos, incluidos los siguientes:
- Cadenas de texto
- Estructuras de datos complejas
- Datos de transmisión binarios y de texto
- Recursos de la aplicación
Los datos de texto simples se almacenan directamente en el portapapeles, mientras que los datos complejos se almacenan como una referencia que la aplicación de pegado resuelve con un proveedor de contenido.
Copiar y pegar funciona tanto dentro de una aplicación como entre aplicaciones que implementan el framework.
Dado que una parte del framework usa proveedores de contenido, en este documento se supone cierta familiaridad con la API de Android Content Provider.
Trabajar con texto
Algunos componentes admiten copiar y pegar texto de forma predeterminada, como se muestra en la siguiente tabla.
| Componente | Copiando texto | Cómo pegar texto |
|---|---|---|
| BasicTextField | ✅ | ✅ |
| TextField | ✅ | ✅ |
| SelectionContainer | ✅ |
Por ejemplo, puedes copiar el texto de la tarjeta en el portapapeles
en el siguiente fragmento y pegar el texto copiado en TextField.
Para mostrar el menú y pegar el texto, mantén presionado el TextField o presiona el controlador del cursor.
val textFieldState = rememberTextFieldState() Column { Card { SelectionContainer { Text("You can copy this text") } } BasicTextField(state = textFieldState) }
Puedes pegar el texto con la siguiente combinación de teclas: Ctrl + V . La combinación de teclas también está disponible de forma predeterminada. Consulta Cómo controlar las acciones del teclado para obtener más detalles.
Copia con ClipboardManager
Puedes copiar textos en el portapapeles con ClipboardManager.
Su método setText() copia el objeto AnnotatedString pasado en el portapapeles.
En el siguiente fragmento, se copia "Hello, clipboard" en el portapapeles cuando el usuario hace clic en el botón.
// 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") }
El siguiente fragmento hace lo mismo, pero te brinda un control más detallado.
Un caso de uso común es copiar contenido sensible, como una contraseña. ClipEntry describe un elemento en el portapapeles.
Contiene un objeto ClipData que describe los datos del portapapeles.
El método ClipData.newPlainText() es un método conveniente para crear un objeto ClipData a partir de un objeto String.
Puedes establecer el objeto ClipEntry creado en el portapapeles llamando al método setClip() en el objeto 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") }
Cómo pegar con ClipboardManager
Puedes acceder al texto copiado en el portapapeles
llamando al método getText()
en ClipboardManager.
Su método getText() devuelve un objeto AnnotatedString cuando se copia un texto en el portapapeles.
En el siguiente fragmento, se agrega texto del portapapeles
al texto 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") } }
Cómo trabajar con contenido enriquecido
A los usuarios les encantan las imágenes, los videos y demás contenido expresivo.
Tu app puede permitir que el usuario copie contenido enriquecido con ClipboardManager y ClipEntry.
El modificador contentReceiver te ayuda a implementar el pegado de contenido enriquecido.
Copia contenido enriquecido
Tu app no puede copiar contenido enriquecido directamente en el portapapeles.
En cambio, tu app pasa un objeto URI al portapapeles y proporciona acceso al contenido con un ContentProvider.
En el siguiente fragmento de código, se muestra cómo copiar una imagen JPEG en el portapapeles.
Consulta Cómo copiar flujos de datos para obtener más detalles.
// 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") }
Pega contenido enriquecido
Con el modificador contentReceiver, puedes controlar el pegado de contenido enriquecido en BasicTextField en el componente modificado.
En el siguiente fragmento de código, se agrega el URI pegado de los datos de una imagen
a una lista de objetos 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) )
El modificador contentReceiver toma un objeto ReceiveContentListener como argumento y llama al método onReceive del objeto pasado cuando el usuario pega datos en el BasicTextField dentro del componente modificado.
Se pasa un objeto TransferableContent al método onReceive,
que describe los datos que se pueden transferir entre apps
pegando en este caso.
Puedes acceder al objeto ClipEntry haciendo referencia al atributo clipEntry.
Un objeto ClipEntry puede tener varios objetos ClipData.Item, por ejemplo, cuando el usuario selecciona varias imágenes y las copia en el portapapeles.
Debes marcar como consumido o ignorado cada objeto ClipData.Item y devolver un TransferableContent que contenga los objetos ClipData.Item ignorados para que el modificador contentReceiver del ancestro más cercano pueda recibirlo.
El método TransferableContent.hasMediaType() puede ayudarte a determinar si el objeto TransferableContent puede proporcionar un elemento con el tipo de medio.
Por ejemplo, la siguiente llamada de método devuelve true si el objeto TransferableContent puede proporcionar una imagen.
transferableContent.hasMediaType(MediaType.Image)
Trabaja con datos complejos
Puedes copiar datos complejos en el portapapeles de la misma manera que lo haces con el contenido enriquecido. Consulta Cómo usar proveedores de contenido para copiar datos complejos para obtener más detalles.
También puedes controlar los pegados de datos complejos de la misma manera para el contenido enriquecido.
Puedes recibir un URI de los datos pegados.
Los datos reales se pueden recuperar de un ContentProvider.
Consulta Recupera datos del proveedor para obtener más información.
Comentarios sobre la copia de contenido
Los usuarios esperan alguna clase de comentario cuando copian contenido al portapapeles, por lo que, además del framework que permite las acciones de copiar y pegar, Android muestra una IU predeterminada a los usuarios cuando copian contenido en Android 13 (nivel de API 33) y versiones posteriores. Debido a esta función, existe el riesgo de que se dupliquen las notificaciones. Puedes obtener más información sobre este caso límite en Cómo evitar las notificaciones duplicadas.
Proporciona comentarios a los usuarios de forma manual cuando copien contenido en Android 12L (nivel de API 32) y versiones anteriores. Consulta la recomendación.
Contenido sensible
Si eliges que tu app permita que el usuario copie contenido sensible en el portapapeles, como contraseñas, la app debe informarle al sistema para que este pueda evitar mostrar el contenido sensible copiado en la IU (figura 2).
Debes agregar una marca a ClipDescription en ClipData antes de llamar al método setClip() en el objeto 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) } }