เฟรมเวิร์กที่ใช้คลิปบอร์ดของ 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() เป็นเมธอดที่อำนวยความสะดวกในการสร้างออบเจ็กต์ ClipData จากออบเจ็กต์ String
คุณสามารถตั้งค่าออบเจ็กต์ ClipEntry ที่สร้างขึ้นไปยังคลิปบอร์ดได้โดยเรียกใช้เมธอด setClip() ผ่านออบเจ็กต์ 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") }
วางด้วย ClipboardManager
คุณเข้าถึงข้อความที่คัดลอกไปยังคลิปบอร์ดได้โดยเรียกใช้เมธอด getText() ผ่าน ClipboardManager
เมธอด 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 เป็นอาร์กิวเมนต์และเรียกใช้เมธอด onReceive ของออบเจ็กต์ที่ส่งเมื่อผู้ใช้วางข้อมูลลงใน BasicTextField ภายในคอมโพเนนต์ที่แก้ไข
ระบบจะส่งออบเจ็กต์ TransferableContent ไปยังเมธอด onReceive ซึ่งอธิบายข้อมูลที่สามารถโอนระหว่างแอปได้โดยการวางในกรณีนี้
คุณเข้าถึงออบเจ็กต์ ClipEntry ได้โดยอ้างอิงแอตทริบิวต์ clipEntry
ออบเจ็กต์ ClipEntry สามารถมีออบเจ็กต์ ClipData.Item ได้หลายรายการ เช่น เมื่อผู้ใช้เลือกรูปภาพหลายรูปและคัดลอกไปยังคลิปบอร์ด
คุณควรทำเครื่องหมายว่าใช้แล้วหรือละเว้นสำหรับออบเจ็กต์ ClipData.Item แต่ละรายการ
และแสดงผล TransferableContent ที่มีออบเจ็กต์ ClipData.Item ที่ละเว้น
เพื่อให้ตัวแก้ไข contentReceiver ระดับบนสุดที่ใกล้ที่สุดรับได้
เมธอด TransferableContent.hasMediaType() ช่วยให้คุณระบุได้ว่าออบเจ็กต์ TransferableContent สามารถระบุรายการที่มีประเภทสื่อได้หรือไม่
ตัวอย่างเช่น การเรียกใช้เมธอดต่อไปนี้จะแสดงผล true หากออบเจ็กต์ TransferableContent สามารถแสดงรูปภาพได้
transferableContent.hasMediaType(MediaType.Image)
ทำงานกับข้อมูลที่ซับซ้อน
คุณสามารถคัดลอกข้อมูลที่ซับซ้อนไปยังคลิปบอร์ดได้ในลักษณะเดียวกับที่ทำกับข้อมูลอย่างละเอียด โปรดดูรายละเอียดที่หัวข้อใช้ Content Provider เพื่อคัดลอกข้อมูลที่ซับซ้อน
นอกจากนี้ คุณยังจัดการการวางข้อมูลที่ซับซ้อนในลักษณะเดียวกันสำหรับข้อมูลอย่างละเอียดได้ด้วย
คุณสามารถรับ URI ของข้อมูลที่วางได้
คุณสามารถดึงข้อมูลจริงได้จาก ContentProvider
ดูข้อมูลเพิ่มเติมได้ที่หัวข้อดึงข้อมูลจากผู้ให้บริการ
ความคิดเห็นเกี่ยวกับการคัดลอกเนื้อหา
ผู้ใช้คาดหวังว่าจะได้รับความคิดเห็นเมื่อคัดลอกเนื้อหาไปยังคลิปบอร์ด ดังนั้นนอกจากเฟรมเวิร์กที่ขับเคลื่อนการคัดลอกและวางแล้ว Android ยังแสดง UI เริ่มต้นแก่ผู้ใช้เมื่อคัดลอกใน Android 13 (ระดับ API 33) ขึ้นไป ฟีเจอร์นี้อาจทำให้เกิดความเสี่ยงที่จะมีการแจ้งเตือนซ้ำ ดูข้อมูลเพิ่มเติมเกี่ยวกับกรณีที่เกิดขึ้นไม่บ่อยนี้ได้ในหลีกเลี่ยงการแจ้งเตือนซ้ำ
แสดงความคิดเห็นแก่ผู้ใช้ด้วยตนเองเมื่อคัดลอกใน Android 12L (ระดับ API 32) และต่ำกว่า ดูคำแนะนำ
เนื้อหาที่ละเอียดอ่อน
หากคุณเลือกที่จะให้แอปอนุญาตให้ผู้ใช้คัดลอกเนื้อหาที่ละเอียดอ่อนไปยังคลิปบอร์ด เช่น รหัสผ่าน แอปของคุณต้องแจ้งให้ระบบทราบเพื่อให้ระบบหลีกเลี่ยงการแสดงเนื้อหาที่ละเอียดอ่อนที่คัดลอกไว้ใน UI (รูปที่ 2)
คุณต้องเพิ่มแฟล็กใน ClipDescription ใน ClipData ก่อนเรียกใช้เมธอด setClip() ผ่านออบเจ็กต์ 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) } }