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

เฟรมเวิร์กที่ใช้คลิปบอร์ดของ 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 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)
    }
}