เฟรมเวิร์กที่อิงตามคลิปบอร์ดของ Android สำหรับการคัดลอกและวางรองรับประเภทข้อมูลพื้นฐานและข้อมูลที่ซับซ้อน ซึ่งรวมถึงข้อมูลต่อไปนี้
- สตริงข้อความ
- โครงสร้างข้อมูลที่ซับซ้อน
- ข้อมูลข้อความและข้อมูลสตรีมไบนารี
- ชิ้นงานแอปพลิเคชัน
ระบบจะจัดเก็บข้อมูลข้อความแบบง่ายไว้ในคลิปบอร์ดโดยตรง ส่วนข้อมูลที่ซับซ้อนจะจัดเก็บไว้เป็นการอ้างอิงที่แอปพลิเคชันที่วางจะแก้ไขด้วยผู้ให้บริการเนื้อหา
การคัดลอกและวางจะทำงานทั้งภายในแอปพลิเคชันและระหว่างแอปพลิเคชันที่ใช้เฟรมเวิร์ก
เนื่องจากเฟรมเวิร์กบางส่วนใช้ผู้ให้บริการเนื้อหา เอกสารนี้จึงถือว่าคุณมีความคุ้นเคยกับ 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)
ทำงานกับข้อมูลที่ซับซ้อน
คุณสามารถคัดลอกข้อมูลที่ซับซ้อนไปยังคลิปบอร์ดได้ในลักษณะเดียวกับที่ทำกับข้อมูลอย่างละเอียด โปรดดูรายละเอียดที่หัวข้อใช้ผู้ให้บริการเนื้อหาเพื่อคัดลอกข้อมูลที่ซับซ้อน
นอกจากนี้ คุณยังจัดการการวางข้อมูลที่ซับซ้อนได้ในลักษณะเดียวกับที่ทำกับข้อมูลอย่างละเอียด
โดยคุณจะได้รับ 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) } }