คัดลอกและวาง

เฟรมเวิร์กที่อิงตามคลิปบอร์ดของ 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 13
รูปที่ 1 UI ที่แสดงเมื่อเนื้อหาเข้าสู่คลิปบอร์ดใน Android 13 ขึ้นไป

แสดงความคิดเห็นแก่ผู้ใช้ด้วยตนเองเมื่อคัดลอกใน Android 12L (ระดับ API 32) ลงไป ดูคำแนะนำ

เนื้อหาที่ละเอียดอ่อน

หากคุณเลือกที่จะให้แอปอนุญาตให้ผู้ใช้คัดลอกเนื้อหาที่ละเอียดอ่อนไปยังคลิปบอร์ด เช่น รหัสผ่าน แอปของคุณต้องแจ้งให้ระบบทราบเพื่อให้ระบบหลีกเลี่ยงการแสดงเนื้อหาที่ละเอียดอ่อนที่คัดลอกใน UI (รูปที่ 2)

การแจ้งว่าเนื้อหาที่คัดลอกมีเนื้อหาที่ละเอียดอ่อน
รูปที่ 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)
    }
}