Android 基于剪贴板的复制和粘贴框架支持原始数据类型和复杂数据类型,包括:
- 文本字符串
- 复杂的数据结构
- 文本和二进制流数据
- 应用素材资源
简单的文本数据直接存储在剪贴板中,而复杂的数据则存储为引用,执行粘贴操作的应用使用 Content Provider 对其进行解析。
复制和粘贴操作既可以在实现该框架的应用内进行,也可以在实现该框架的应用之间进行。
由于该框架的一部分使用 content provider, 因此本文档假设您对 Android Content Provider 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() 方法会将传递的 AnnotatedString 对象复制到剪贴板。以下代码段会在用户点击按钮时将“Hello, clipboard”复制到剪贴板。
// 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") }
以下代码段执行相同的操作,但可让您进行更精细的控制。
一个常见的用例是复制敏感内容,
例如密码。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 中的文本。
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") } }
使用富媒体内容
用户喜欢图片、视频和其他富有表现力的内容。
您的应用可以使用户通过 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 = 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) )
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)
使用复杂数据
您可以采用与富媒体内容相同的方式将复杂数据复制到剪贴板。 如需了解详情,请参阅使用 content provider 复制复杂数据。
您还可以采用与富媒体内容相同的方式处理复杂数据的粘贴。
您可以接收粘贴数据的 URI。
实际数据可以从 ContentProvider 中检索。
如需了解详情,请参阅从提供程序检索数据。
复制内容反馈
用户希望在将内容复制到剪贴板时获得反馈,因此除了为复制和粘贴提供支持的框架之外,Android 还在用户在 Android 13(API 级别 33)及更高版本中复制内容时向用户显示默认界面。由于此功能,存在重复通知的风险。 如需详细了解此极端情况,请参阅避免重复通知。
在 Android 12L(API 级别 32)及更低版本中复制内容时,手动向用户提供反馈。 请参阅建议。
敏感内容
如果您选择让应用允许用户将敏感内容(例如密码)复制到剪贴板,则应用必须让系统知道,以便系统可以避免在界面中显示复制的敏感内容(图 2)。
您必须在 ClipData
中向 ClipDescription 添加一个标志,然后才能对 ClipboardManager 对象调用 setClip() 方法:
// 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) } }