Android 基于剪贴板的复制和粘贴框架支持基本和复杂数据类型,包括
- 文本字符串
- 复杂数据结构
- 文本和二进制流数据
- 应用资产
简单文本数据直接存储在剪贴板中,而复杂数据则存储为引用,由粘贴应用通过内容提供程序进行解析。
复制和粘贴在应用内部以及实现了该框架的应用之间均可进行。
由于该框架部分使用了内容提供程序,本文档假定您对 Android 内容提供程序 API 有一定了解。
处理文本
某些组件开箱即支持文本复制和粘贴,如下表所示。
组件 | 复制文本 | 粘贴文本 |
---|---|---|
BasicTextField | ✅ | ✅ |
TextField | ✅ | ✅ |
SelectionContainer | ✅ |
例如,您可以在以下代码段中将卡片中的文本复制到剪贴板,并将复制的文本粘贴到 TextField
。您可以通过长按 TextField
或点击光标手柄来显示粘贴文本的菜单。
val textFieldState = rememberTextFieldState()
Column {
Card {
SelectionContainer {
Text("You can copy this text")
}
}
BasicTextField(state = textFieldState)
}
您可以使用以下键盘快捷键粘贴文本:Ctrl+V 。默认情况下也提供此键盘快捷键。有关详细信息,请参阅处理键盘操作。
使用 ClipboardManager
复制
您可以使用 ClipboardManager
将文本复制到剪贴板。其 setText() 方法会将传入的 String 对象复制到剪贴板。以下代码段会在用户点击按钮时将“Hello, clipboard”复制到剪贴板。
// 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")
}
以下代码段执行相同操作,但为您提供更精细的控制。常见用例是复制敏感内容,例如密码。ClipEntry
描述了剪贴板上的一个项目。它包含一个 ClipData
对象,用于描述剪贴板上的数据。ClipData.newPlainText()
方法是一个便捷方法,用于从 String 对象创建 ClipData
对象。您可以通过在 ClipboardManager
对象上调用 setClip() 方法,将创建的 ClipEntry
对象设置到剪贴板。
// 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")
}
使用 ClipboardManager 粘贴
您可以通过在 ClipboardManager
上调用 getText()
方法来访问复制到剪贴板的文本。当文本复制到剪贴板时,其 getText()
方法会返回一个 AnnotatedString
对象。以下代码段会将剪贴板中的文本附加到 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")
}
}
处理富媒体内容
用户喜欢图片、视频和其他富有表现力的内容。您的应用可以使用 ClipboardManager
和 ClipEntry
让用户复制富媒体内容。contentReceiver
修饰符可帮助您实现粘贴富媒体内容的功能。
复制富媒体内容
您的应用无法将富媒体内容直接复制到剪贴板。相反,您的应用会将 URI
对象传递给剪贴板,并通过 ContentProvider
提供对内容的访问。以下代码段演示了如何将 JPEG 图像复制到剪贴板。有关详细信息,请参阅复制数据流。
// 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")
}
粘贴富媒体内容
借助 contentReceiver
修饰符,您可以在修改后的组件中处理将富媒体内容粘贴到 BasicTextField
的操作。以下代码段会将粘贴的图像数据的 URI 添加到 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)
)
contentReceiver
修饰符接受一个 ReceiveContentListener
对象作为其参数,当用户将数据粘贴到修改后组件内部的 BasicTextField
时,它会调用传入对象的 onReceive
方法。
一个 TransferableContent
对象会传递给 onReceive 方法,该对象描述了在这种情况下可以通过粘贴在应用之间传输的数据。您可以通过引用 clipEntry
属性来访问 ClipEntry
对象。
当用户选择多张图片并将其复制到剪贴板时,一个 ClipEntry
对象可以包含多个 ClipData.Item
对象。您应该为每个 ClipData.Item
对象标记为已消费或已忽略,并返回一个包含已忽略的 ClipData.Item
对象的 TransferableContent
,以便最近的祖先 contentReceiver
修饰符能够接收它。
TransferableContent.hasMediaType()
方法可以帮助您确定 TransferableContent
对象是否可以提供具有指定媒体类型的项目。例如,如果 TransferableContent
对象可以提供图像,则以下方法调用将返回 true
。
transferableContent.hasMediaType(MediaType.Image)
处理复杂数据
您可以像处理富媒体内容一样,将复杂数据复制到剪贴板。有关详细信息,请参阅使用内容提供程序复制复杂数据。
您也可以像处理富媒体内容一样处理复杂数据的粘贴。您可以接收粘贴数据的 URI。实际数据可以从 ContentProvider
中检索。有关更多信息,请参阅从提供程序检索数据。
复制内容的反馈
用户期望在将内容复制到剪贴板时获得反馈,因此除了支持复制和粘贴的框架之外,Android 13(API 级别 33)及更高版本在用户复制时会向其显示默认 UI。由于此功能,存在重复通知的风险。您可以在避免重复通知中了解有关此极端情况的更多信息。

在 Android 12L(API 级别 32)及更低版本中复制时,请手动向用户提供反馈。请参阅建议。
敏感内容
如果您选择让您的应用允许用户将敏感内容(如密码)复制到剪贴板,您的应用必须告知系统,以便系统避免在 UI 中显示复制的敏感内容(图 2)。

在对 ClipboardManager
对象调用 setClip()
方法之前,您必须在 ClipData
中的 ClipDescription
添加一个标志。
// 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)
}
}