Sao chép và dán

Khung dựa trên bảng nhớ tạm của Android cho thao tác sao chép và dán hỗ trợ các loại dữ liệu nguyên gốc và phức tạp, bao gồm:

  • Chuỗi văn bản
  • Cấu trúc dữ liệu phức tạp
  • Dữ liệu luồng văn bản và nhị phân
  • Thành phần ứng dụng

Dữ liệu văn bản đơn giản được lưu trữ trực tiếp trong bảng nhớ tạm, còn dữ liệu phức tạp được lưu trữ dưới dạng tệp đối chiếu mà ứng dụng dán phân giải thông qua nhà cung cấp nội dung.

Việc sao chép và dán diễn ra cả trong một ứng dụng và giữa các ứng dụng cài đặt khung.

Vì một phần của khung này sử dụng các nhà cung cấp nội dung, nên tài liệu này giả định rằng nhà phát triển biết về Android Content Provider API.

Làm việc với văn bản

Một số thành phần hỗ trợ sao chép và dán văn bản ngay lập tức, như trong bảng sau.

Thành phần Sao chép văn bản Dán văn bản
BasicTextField ✅ ✅
TextField ✅ ✅
SelectionContainer ✅

Ví dụ: bạn có thể sao chép văn bản trong thẻ vào bảng nhớ tạm trong đoạn mã sau và dán văn bản đã sao chép vào TextField. Bạn có thể hiển thị trình đơn để dán văn bản bằng cách chạm và giữ vào TextField hoặc bằng cách nhấn vào ô điều khiển con trỏ.

val textFieldState = rememberTextFieldState()

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

Bạn có thể dán văn bản bằng phím tắt sau: Ctrl+V . Phím tắt cũng được bật theo mặc định. Tham khảo bài viết Xử lý thao tác trên bàn phím để biết thông tin chi tiết.

Sao chép bằng ClipboardManager

Bạn có thể sao chép văn bản vào bảng nhớ tạm bằng tổ hợp phím ClipboardManager. Phương thức setText() của lớp này sao chép đối tượng AnnotatedString được truyền vào bảng nhớ tạm. Đoạn mã sau đây sao chép "Hello, clipboard" (Xin chào, bảng nhớ tạm) vào bảng nhớ tạm khi người dùng nhấp vào nút.

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

Đoạn mã sau đây cũng làm điều tương tự, nhưng giúp bạn kiểm soát chi tiết hơn. Trường hợp sử dụng phổ biến là sao chép nội dung nhạy cảm, chẳng hạn như mật khẩu. ClipEntry mô tả một mục trên bảng nhớ tạm. Đối tượng này chứa một đối tượng ClipData mô tả dữ liệu trên bảng nhớ tạm. Phương thức ClipData.newPlainText() là một phương thức thuận tiện để tạo đối tượng ClipData từ đối tượng String. Bạn có thể đặt đối tượng ClipEntry đã tạo vào bảng nhớ tạm bằng cách gọi phương thức setClip() qua đối tượng 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")
}

Dán bằng ClipboardManager

Bạn có thể truy cập vào văn bản được sao chép vào bảng nhớ tạm bằng cách gọi phương thức getText() trên ClipboardManager. Phương thức getText() của lớp này trả về một đối tượng AnnotatedString khi văn bản được sao chép vào bảng nhớ tạm. Đoạn mã sau đây sẽ thêm văn bản trong bảng nhớ tạm vào văn bản trong 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")
    }
}

Làm việc với nội dung đa dạng thức

Người dùng thích hình ảnh, video và các nội dung biểu đạt khác. Ứng dụng của bạn có thể cho phép người dùng sao chép nội dung đa dạng thức bằng ClipboardManager và ClipEntry. Đối tượng sửa đổi contentReceiver giúp bạn triển khai tính năng dán nội dung đa dạng thức.

Sao chép nội dung đa dạng thức

Ứng dụng của bạn không thể sao chép trực tiếp nội dung đa dạng thức vào bảng nhớ tạm. Thay vào đó, ứng dụng của bạn sẽ truyền đối tượng URI đến bảng nhớ tạm và cấp quyền truy cập vào nội dung bằng ContentProvider. Đoạn mã sau đây cho biết cách sao chép hình ảnh JPEG vào bảng nhớ tạm. Tham khảo bài viết Sao chép luồng dữ liệu để biết thông tin chi tiết.

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

Dán nội dung đa dạng thức

Với đối tượng sửa đổi contentReceiver, bạn có thể xử lý thao tác dán nội dung đa dạng thức vào BasicTextField trong thành phần đã sửa đổi. Đoạn mã sau đây thêm URI đã dán của dữ liệu hình ảnh vào danh sách đối tượng 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)
)

Đối tượng sửa đổi contentReceiver lấy đối tượng ReceiveContentListener làm đối số và gọi phương thức onReceive của đối tượng được truyền khi người dùng dán dữ liệu vào BasicTextField bên trong thành phần được sửa đổi.

Đối tượng TransferableContent được truyền đến phương thức onReceive, mô tả dữ liệu có thể được chuyển giữa các ứng dụng bằng cách dán trong trường hợp này. Bạn có thể truy cập vào đối tượng ClipEntry bằng cách tham chiếu đến thuộc tính clipEntry.

Đối tượng ClipEntry có thể có một số đối tượng ClipData.Item khi người dùng chọn nhiều hình ảnh và sao chép chúng vào bảng nhớ tạm. Bạn nên đánh dấu đã sử dụng hoặc đã bỏ qua cho mỗi đối tượng ClipData.Item, và trả về một TransferableContent chứa các đối tượng ClipData.Item bị bỏ qua để đối tượng sửa đổi contentReceiver cấp trên gần nhất có thể nhận được.

Phương thức TransferableContent.hasMediaType() có thể giúp bạn xác định xem đối tượng TransferableContent có thể cung cấp một mục có loại nội dung nghe nhìn hay không. Ví dụ: lệnh gọi phương thức sau đây sẽ trả về true nếu đối tượng TransferableContent có thể cung cấp hình ảnh.

transferableContent.hasMediaType(MediaType.Image)

Làm việc với dữ liệu phức tạp

Bạn có thể sao chép dữ liệu phức tạp vào bảng nhớ tạm theo cách tương tự như đối với nội dung đa dạng thức. Hãy tham khảo bài viết Sử dụng trình cung cấp nội dung để sao chép dữ liệu phức tạp để biết thông tin chi tiết.

Bạn cũng có thể xử lý các thao tác dán dữ liệu phức tạp theo cách tương tự cho nội dung đa dạng thức. Bạn có thể nhận được URI của dữ liệu đã dán. Bạn có thể truy xuất dữ liệu thực tế từ ContentProvider. Hãy tham khảo phần Truy xuất dữ liệu từ nhà cung cấp để biết thêm thông tin.

Ý kiến phản hồi về việc sao chép nội dung

Người dùng mong muốn nhận được phản hồi khi sao chép nội dung vào bảng nhớ tạm. Do đó, ngoài khung ứng dụng hỗ trợ thao tác sao chép và dán, Android sẽ hiển thị một giao diện người dùng mặc định cho người dùng khi sao chép trong Android 13 (cấp độ API 33) trở lên. Do tính năng này, có nguy cơ thông báo bị trùng lặp. Bạn có thể tìm hiểu thêm về trường hợp hiếm gặp này trong phần Tránh thông báo trùng lặp.

Ảnh động cho thấy thông báo về bảng nhớ tạm trên Android 13
Hình 1. Giao diện người dùng hiển thị khi nội dung nhập vào bảng nhớ tạm trong Android 13 trở lên.

Cung cấp phản hồi cho người dùng theo cách thủ công khi sao chép trong Android 12L (cấp độ API 32) trở xuống. Xem đề xuất.

Nội dung nhạy cảm

Nếu bạn chọn cho phép ứng dụng của mình cho phép người dùng sao chép nội dung nhạy cảm vào bảng nhớ tạm, chẳng hạn như mật khẩu, thì ứng dụng của bạn phải thông báo cho hệ thống để hệ thống có thể tránh hiển thị nội dung nhạy cảm đã sao chép trong giao diện người dùng (hình 2).

Bản xem trước văn bản sao chép có gắn cờ nội dung nhạy cảm.
Hình 2. Bản xem trước văn bản sao chép có gắn cờ nội dung nhạy cảm.

Bạn phải thêm một cờ vào ClipDescription trong ClipData trước khi gọi phương thức setClip() qua đối tượng 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)
    }
}