Copiar e colar

O framework baseado na área de transferência do Android para copiar e colar oferece suporte a tipos de dados primitivos e complexos, incluindo:

  • Strings de texto
  • Estruturas de dados complexas
  • Dados de stream de texto e binários
  • Recursos do aplicativo

Os dados de texto simples são armazenados diretamente na área de transferência, enquanto os dados complexos são armazenados como uma referência que o aplicativo de colagem resolve com um provedor de conteúdo.

A cópia e a colagem funcionam tanto em um aplicativo quanto entre aplicativos que implementam o framework.

Como parte do framework usa provedores de conteúdo, este documento pressupõe certa familiaridade com a API Android Content Provider.

Trabalhar com texto

Alguns componentes são compatíveis com copiar e colar texto, conforme mostrado na tabela a seguir.

Componente Copiando texto Colar texto
BasicTextField ✅ ✅
TextField ✅ ✅
SelectionContainer ✅

Por exemplo, é possível copiar o texto do card para a área de transferência no snippet a seguir e colar o texto copiado no TextField. Para exibir o menu e colar o texto, toque e pressione o TextField ou toque na alça do cursor.

val textFieldState = rememberTextFieldState()

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

Você pode colar o texto com o seguinte atalho de teclado: Ctrl+V . O atalho do teclado também está disponível por padrão. Consulte Processar ações do teclado para mais detalhes.

Copiar com ClipboardManager

Você pode copiar textos para a área de transferência com ClipboardManager. O método setText() copia o objeto AnnotatedString transmitido para a área de transferência. O snippet a seguir copia "Hello, clipboard" para a área de transferência quando o usuário clica no botão.

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

O snippet a seguir faz a mesma coisa, mas oferece um controle mais granular. Um caso de uso comum é copiar conteúdo sensível, como uma senha. ClipEntry descreve um item na área de transferência. Ele contém um objeto ClipData que descreve os dados na área de transferência. O método ClipData.newPlainText() é um método de conveniência para criar um objeto ClipData de um objeto String. É possível definir o objeto ClipEntry criado para a área de transferência chamando o método setClip() no 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")
}

Colar com o ClipboardManager

Para acessar o texto copiado para a área de transferência, chame o método getText() em ClipboardManager. O método getText() retorna um objeto AnnotatedString quando um texto é copiado na área de transferência. O snippet a seguir anexa o texto na área de transferência ao texto no 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")
    }
}

Trabalhar com conteúdo avançado

Os usuários adoram imagens, vídeos e outros conteúdos expressivos. Seu app pode permitir que o usuário copie conteúdo avançado com ClipboardManager e ClipEntry. O modificador contentReceiver ajuda a implementar a colagem de conteúdo avançado.

Copiar conteúdo avançado

O app não pode copiar conteúdo avançado diretamente para a área de transferência. Em vez disso, o app transmite um objeto URI para a área de transferência e fornece acesso ao conteúdo com um ContentProvider. O snippet de código a seguir mostra como copiar uma imagem JPEG para a área de transferência. Consulte Copiar fluxos de dados para mais detalhes.

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

Colar um conteúdo avançado

Com o modificador contentReceiver, é possível processar a colagem de conteúdo avançado em BasicTextField no componente modificado. O snippet de código a seguir adiciona o URI colado de dados de uma imagem a uma 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)
)

O modificador contentReceiver usa um objeto ReceiveContentListener como argumento e chama o método onReceive do objeto transmitido quando o usuário cola dados no BasicTextField dentro do componente modificado.

Um objeto TransferableContent é transmitido para o método onReceive, que descreve os dados que podem ser transferidos entre apps ao colar, neste caso. Você pode acessar o objeto ClipEntry consultando o atributo clipEntry.

Um objeto ClipEntry pode ter vários objetos ClipData.Item quando o usuário seleciona várias imagens e as copia para a área de transferência, por exemplo. Marque como consumido ou ignorado para cada objeto ClipData.Item e retorne um TransferableContent contendo os objetos ClipData.Item ignorados para que o modificador contentReceiver ancestral mais próximo possa recebê-lo.

O método TransferableContent.hasMediaType() pode ajudar a determinar se o objeto TransferableContent pode fornecer um item com o tipo de mídia. Por exemplo, a chamada de método a seguir retornará true se o objeto TransferableContent puder fornecer uma imagem.

transferableContent.hasMediaType(MediaType.Image)

Trabalhar com dados complexos

É possível copiar dados complexos para a área de transferência da mesma maneira que você faz com o conteúdo avançado. Consulte Usar provedores de conteúdo para copiar dados complexos para mais detalhes.

Também é possível processar as colagens de dados complexos da mesma maneira para o conteúdo avançado. Você pode receber um URI dos dados colados. Os dados reais podem ser recuperados de um ContentProvider. Consulte Recuperar dados do provedor para mais informações.

Feedback sobre a cópia de conteúdo

Os usuários esperam feedback ao copiar conteúdo para a área de transferência. Portanto, além do framework que permite copiar e colar, o sistema mostra uma IU padrão quando usuários executam ações de cópia no Android 13 (nível 33 da API) e versões mais recentes. Devido a esse recurso, há o risco de notificações duplicadas. Saiba mais sobre esse caso extremo em Evitar notificações duplicadas.

Animação mostrando a notificação da área de transferência do Android 13
Figura 1. IU exibida quando o conteúdo é inserido na área de transferência no Android 13 e versões mais recentes.

Envie feedback manualmente aos usuários que executam ações de cópia no Android 12L (nível da API 32) e versões anteriores. Consulte a recomendação.

Conteúdo sensível

Se você permitir que o app copie conteúdo sensível para a área de transferência, como senhas, o app precisará informar o sistema para que ele evite mostrar o conteúdo sensível copiado na interface (figura 2).

Visualização do texto copiado com sinalização de conteúdo sensível.
Figura 2. Visualização do texto copiado com uma sinalização de conteúdo sensível.

Adicione uma flag a ClipDescription em ClipData antes de chamar o método setClip() no 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)
    }
}