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

เฟรมเวิร์กที่ใช้คลิปบอร์ดของ Android สำหรับการคัดลอกและวาง รองรับข้อมูลประเภทพื้นฐานและซับซ้อน ได้แก่

  • สตริงข้อความ
  • โครงสร้างข้อมูลที่ซับซ้อน
  • ข้อมูลสตรีมข้อความและไบนารี
  • ชิ้นงานแอปพลิเคชัน

ระบบจะจัดเก็บข้อมูลข้อความธรรมดาไว้ในคลิปบอร์ดโดยตรง ส่วนข้อมูลที่ซับซ้อนจะจัดเก็บเป็นข้อมูลอ้างอิงซึ่งแอปพลิเคชันการวางจะแก้ไขกับผู้ให้บริการเนื้อหา

การคัดลอกและวางใช้ได้ทั้งในแอปพลิเคชันและระหว่างแอปพลิเคชันที่ใช้เฟรมเวิร์ก

เนื่องจากเฟรมเวิร์กบางส่วนใช้ผู้ให้บริการเนื้อหา เอกสารนี้จึงถือว่าคุณคุ้นเคยกับ Android Content Provider API อยู่บ้าง

การทำงานกับข้อความ

คอมโพเนนต์บางรายการรองรับการคัดลอกและวางข้อความได้ทันทีตามที่แสดงในตารางต่อไปนี้

ส่วนประกอบ กำลังคัดลอกข้อความ กำลังวางข้อความ
BasicTextField
ฟิลด์ข้อความ
คอนเทนเนอร์ตัวเลือก

เช่น คัดลอกข้อความในการ์ดไปยังคลิปบอร์ด ในข้อมูลโค้ดต่อไปนี้ และวางข้อความที่คัดลอกลงใน TextField คุณแสดงเมนูเพื่อวางข้อความได้โดยแตะ TextField ค้างไว้ หรือแตะแถบเคอร์เซอร์

val textFieldState = rememberTextFieldState()

Column {
    Card {
        SelectionContainer {
            Text("You can copy this text")
        }
    }
    BasicTextField(state = textFieldState)
}

คุณสามารถวางข้อความด้วยแป้นพิมพ์ลัด Ctrl+V แป้นพิมพ์ลัดจะพร้อมใช้งานโดยค่าเริ่มต้นเช่นกัน โปรดดูรายละเอียดที่หัวข้อจัดการการดำเนินการของแป้นพิมพ์

คัดลอกด้วย ClipboardManager

คุณคัดลอกข้อความไปยังคลิปบอร์ดได้โดยใช้ ClipboardManager คัดลอกเมธอด setText() ออบเจ็กต์สตริงที่ส่งผ่านไปยังคลิปบอร์ด ข้อมูลโค้ดต่อไปนี้คัดลอก "สวัสดีคลิปบอร์ด" ไปยังคลิปบอร์ดเมื่อผู้ใช้คลิกปุ่ม

// Retrieve a ClipboardManager object
val clipboardManager = LocalClipboardManager.current

Button(
    onClick = {
        // Copy "Hello, clipboard" to the clipboard
        clipboardManager.setText("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() Method ในช่วง ClipboardManager เมธอด getText() จะแสดงผลออบเจ็กต์ AnnotatedString เมื่อคัดลอกข้อความในคลิปบอร์ด ข้อมูลโค้ดต่อไปนี้จะเพิ่มข้อความในคลิปบอร์ดต่อท้ายข้อความใน TextField

var 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(text.toString())
                }
            }
        }
    ) {
        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 by remember{ mutableListOf<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 คัดลอกตัวอย่างข้อความที่มีแจ้งว่าเนื้อหามีความละเอียดอ่อน

คุณต้องเพิ่ม Flag ไปยัง 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)
    }
}