Salin dan tempel

Framework berbasis papan klip Android untuk menyalin dan menempel mendukung jenis data primitif dan kompleks, termasuk:

  • String teks
  • Struktur data yang kompleks
  • Data aliran teks dan biner
  • Aset aplikasi

Data teks sederhana disimpan langsung di {i>clipboard<i}, sementara data kompleks disimpan sebagai referensi yang diselesaikan aplikasi penempelan dengan penyedia konten.

Fitur salin dan tempel berfungsi di dalam dan di antara aplikasi yang menerapkan kerangka kerja tersebut.

Karena sebagian framework menggunakan penyedia konten, dokumen ini mengasumsikan bahwa Anda memiliki pengetahuan dasar tentang Android Content Provider API.

Menggunakan teks

Beberapa komponen mendukung tindakan menyalin dan menempelkan teks secara langsung, seperti yang ditunjukkan pada pada tabel berikut.

Komponen Menyalin teks Menempelkan teks
DasarTeks Dasar
Bidang Bidang
Penampung Pilihan

Misalnya, Anda dapat menyalin teks di kartu ke papan klip dalam cuplikan berikut dan menempelkan teks yang disalin ke TextField. Anda menampilkan menu untuk menempelkan teks dengan menggeser & menahan TextField, atau dengan mengetuk tuas kursor.

val textFieldState = rememberTextFieldState()

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

Anda dapat menempelkan teks dengan pintasan keyboard berikut: Ctrl+V . Pintasan keyboard juga tersedia secara default. Lihat Menangani tindakan keyboard untuk mengetahui detailnya.

Menyalin dengan ClipboardManager

Anda dapat menyalin teks ke papan klip dengan ClipboardManager. Metode setText() menyalin objek String yang diteruskan ke papan klip. Cuplikan berikut menyalin "Halo, papan klip" ke papan klip saat pengguna mengklik tombol.

// 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")
}

Cuplikan berikut melakukan hal yang sama, tetapi memberi Anda kontrol yang lebih terperinci. Kasus penggunaan yang umum adalah menyalin konten sensitif, seperti sandi. ClipEntry mendeskripsikan item di papan klip. Objek ini berisi objek ClipData yang mendeskripsikan data di papan klip. Metode ClipData.newPlainText() adalah metode praktis untuk membuat objek ClipData dari objek String. Anda dapat menetapkan objek ClipEntry yang dibuat ke papan klip dengan memanggil metode setClip() di atas objek 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")
}

Tempel dengan ClipboardManager

Anda dapat mengakses teks yang disalin ke papan klip dengan memanggil metode getText() di ClipboardManager. Metode getText() menampilkan objek AnnotatedString saat teks disalin di papan klip. Cuplikan berikut menambahkan teks di papan klip ke teks di 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")
    }
}

Menggunakan konten lengkap

Pengguna menyukai gambar, video, dan konten ekspresif lainnya. Aplikasi Anda dapat memungkinkan pengguna menyalin konten kaya dengan ClipboardManager dan ClipEntry. Pengubah contentReceiver membantu Anda menerapkan konten lengkap yang ditempel.

Menyalin konten lengkap

Aplikasi Anda tidak dapat menyalin konten kaya langsung ke papan klip. Sebagai gantinya, aplikasi Anda meneruskan objek URI ke papan klip dan memberikan akses ke konten dengan ContentProvider. Cuplikan kode berikut menunjukkan cara menyalin gambar JPEG ke papan klip. Lihat Menyalin aliran data untuk mengetahui detailnya.

// 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")
}

Menempelkan konten lengkap

Dengan pengubah contentReceiver, Anda dapat menangani menempelkan konten kaya ke BasicTextField di komponen yang diubah. Cuplikan kode berikut menambahkan URI data gambar yang ditempelkan ke daftar objek 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)
)

Pengubah contentReceiver menggunakan objek ReceiveContentListener sebagai argumennya dan memanggil metode onReceive dari objek yang diteruskan saat pengguna menempelkan data ke BasicTextField di dalam komponen yang diubah.

Objek TransferableContent diteruskan ke metode onReceive, yang menjelaskan data yang dapat ditransfer antar-aplikasi dengan menempelkannya dalam {i>casing<i} ini. Anda dapat mengakses objek ClipEntry dengan merujuk ke atribut clipEntry.

Objek ClipEntry dapat memiliki beberapa objek ClipData.Item saat pengguna memilih beberapa gambar dan menyalinnya ke papan klip ke titik akhir pelanggan. Anda harus menandai objek ClipData.Item yang digunakan atau diabaikan, dan menampilkan TransferableContent yang berisi objek ClipData.Item yang diabaikan sehingga pengubah contentReceiver ancestor terdekat dapat menerimanya.

Metode TransferableContent.hasMediaType() dapat membantu Anda menentukan apakah objek TransferableContent dapat menyediakan item dengan jenis media. Misalnya, panggilan metode berikut menampilkan true jika objek TransferableContent dapat memberikan gambar.

transferableContent.hasMediaType(MediaType.Image)

Menangani data yang kompleks

Anda dapat menyalin data yang kompleks ke {i>clipboard<i} dengan cara yang sama seperti yang Anda lakukan untuk konten lengkap. Lihat Menggunakan penyedia konten untuk menyalin data kompleks untuk mengetahui detailnya.

Anda juga dapat menangani penempelan data kompleks dengan cara yang sama untuk konten yang kaya. Anda dapat menerima URI data yang ditempel. Data sebenarnya dapat diambil dari ContentProvider. Lihat Mengambil data dari penyedia untuk mengetahui informasi selengkapnya.

Masukan untuk menyalin konten

Pengguna mengharapkan umpan balik saat mereka menyalin konten ke {i>clipboard<i}, jadi selain kerangka kerja yang mendukung {i>copy<i} dan {i>paste<i}, Android menampilkan UI default kepada pengguna saat mereka menyalin di Android 13 (API level 33) dan lebih tinggi. Karena fitur ini, ada risiko notifikasi duplikat. Anda dapat mempelajari kasus ekstrem ini lebih lanjut di Menghindari notifikasi duplikat.

Animasi yang menampilkan notifikasi papan klip Android 13
Gambar 1. UI ditampilkan saat konten memasuki papan klip di Android 13 dan yang lebih baru.

Berikan masukan secara manual kepada pengguna saat menyalin di Android 12L (API level 32) dan yang lebih rendah. Lihat rekomendasi.

Konten sensitif

Jika Anda memilih untuk mengizinkan aplikasi menyalin konten sensitif ke papan klip, seperti sandi, aplikasi Anda harus memberi tahu sistem sehingga sistem dapat menghindari menampilkan konten sensitif yang disalin di UI (gambar 2).

Pratinjau teks yang disalin dengan menambahkan flag pada konten sensitif.
Gambar 2. Pratinjau teks yang disalin dengan tanda konten sensitif.

Anda harus menambahkan tanda ke ClipDescription di ClipData sebelum memanggil metode setClip() melalui objek 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)
    }
}