El framework de Android basado en el portapapeles para copiar y pegar admite tipos de datos primitivos y complejos, como los siguientes:
- Cadenas de texto
- Estructuras de datos complejas
- Datos de flujo de texto y binario
- Recursos de aplicación
Los datos de texto simples se almacenan directamente en el portapapeles mientras que los datos complejos se almacenan como referencia que la aplicación de pegado resuelve con un proveedor de contenido.
La opción de copiar y pegar funciona tanto dentro de una aplicación como entre aplicaciones que implementan el framework.
Debido a que parte del framework usa proveedores de contenido, En este documento, se asume que conoces la API de Android Content Provider.
Cómo 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
.
Muestras el menú para pegar el texto por un elemento
tocar y 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 información.
Copiar con ClipboardManager
Puedes copiar textos en el portapapeles con ClipboardManager
.
Su método setText() copia
el objeto de cadena pasado al portapapeles.
En el siguiente fragmento, se copia “Hola, portapapeles”
al 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("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 del portapapeles.
Contiene un objeto ClipData
que describe los datos del portapapeles.
El método ClipData.newPlainText()
es un método de conveniencia para crear un objeto ClipData
a partir de un objeto String.
Puedes configurar el objeto ClipEntry
creado en el portapapeles llamando al método setClip() sobre 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
Para acceder al texto copiado en el portapapeles, llama al método getText()
sobre ClipboardManager
.
Su método getText()
muestra un objeto AnnotatedString
cuando se copia un texto en el portapapeles.
El siguiente fragmento agrega texto del portapapeles al texto 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")
}
}
Cómo trabajar con contenido enriquecido
A los usuarios les encantan las imágenes, los videos y otro 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.
Cómo copiar 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 información.
// 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")
}
Cómo pegar 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 imagen a una lista de objetos 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)
)
El modificador contentReceiver
toma un objeto ReceiveContentListener
como su argumento y llama a onReceive
del objeto que se pasó cuando el usuario pega datos
a BasicTextField
dentro del componente modificado.
Se pasa un objeto TransferableContent
al método onRecibir.
que describe los datos que se pueden transferir entre apps
pegando en este caso.
Para acceder al objeto ClipEntry
, puedes consultar el atributo clipEntry
.
Un objeto ClipEntry
puede tener varios objetos ClipData.Item
Cuando el usuario selecciona varias imágenes y las copia en el portapapeles
por ejemplo.
Debes marcar como consumido o ignorado cada objeto ClipData.Item
y mostrar un TransferableContent
que contenga los objetos ClipData.Item
ignorados para que el modificador contentReceiver
de 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 muestra true
.
si el objeto TransferableContent
puede proporcionar una imagen.
transferableContent.hasMediaType(MediaType.Image)
Cómo trabajar con datos complejos
Puedes copiar datos complejos en el portapapeles tal como lo haces para el contenido enriquecido. Para obtener más información, consulta Usa proveedores de contenido para copiar datos complejos.
También puedes controlar las pegaciones de datos complejos
del mismo modo para el contenido enriquecido.
Puedes recibir un URI de los datos pegados.
Los datos reales se pueden recuperar a partir de un ContentProvider
.
Para obtener más información, consulta Cómo recuperar datos del proveedor.
Comentarios sobre la copia de contenido
Los usuarios esperan algún tipo 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 notificaciones duplicadas. Puedes obtener más información sobre este caso extremo en Cómo evitar notificaciones duplicadas.
Proporciona comentarios a los usuarios de forma manual cuando se copian en Android 12L (nivel de API 32) y versiones anteriores. Consulta la recomendación.
Contenido sensible
Si decides 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()
sobre 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)
}
}