Sao chép và dán

Khung dựa trên bảng nhớ tạm của Android để sao chép và dán hỗ trợ các loại dữ liệu 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, trong khi dữ liệu phức tạp được lưu trữ dưới dạng tham chiếu mà ứng dụng dán phân giải thông qua trì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 ra khỏi hộp, như minh hoạ trong bảng sau.

Thành phần Đang sao chép văn bản Dán văn bản
BasicTextField
TextField
Vùng chứa lựa chọn

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 hiển thị trình đơn để dán văn bản bằng cách nhấn và giữ TextField hoặc nhấn vào tay cầm 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ó sẵn theo mặc định. Tham khảo phần 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 ClipboardManager. Phương thức setText() của lớp này sao chép đối tượng Chuỗi đã truyền vào bảng nhớ tạm. Đoạn mã sau đây sẽ 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("Hello, clipboard")
    }
) {
   Text("Click to copy a text")
}

Đoạn mã sau đây thực hiện tương tự, nhưng cung cấp cho bạn quyền kiểm soát chi tiết hơn. Một 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. Lớp này chứa đố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 tiện lợi để tạo đối tượng ClipData từ đối tượng Chuỗi. 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() trên đố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 khay nhớ tạm quản lý

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

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à những 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 ClipboardManagerClipEntry. Đố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 nội dung đa dạng thức trực tiếp vào bảng nhớ tạm. Thay vào đó, ứng dụng của bạn sẽ truyền một đối tượng URI đến bảng nhớ tạm và cung 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ý việ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 được 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 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)
)

Đối tượng sửa đổi contentReceiver lấy đối tượng ReceiveContentListener làm đối số và gọi 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. Phương thức này 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.

Ví dụ: đối tượng ClipEntry có thể có một số đối tượng ClipData.Item khi người dùng chọn một số hình ảnh và sao chép các hình ảnh đó vào bảng nhớ tạm. Bạn nên đánh dấu đã sử dụng hoặc bỏ qua cho từng đố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 đối tượng 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 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ư cách bạn thực hiện đố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ý việc dán dữ liệu phức tạp theo cách tương tự đối với 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 (API cấp 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 đặc biệt này trong phần Tránh thông báo trùng lặp.

Ảnh động minh hoạ thông báo về bảng nhớ tạm của 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 (API cấp 32) trở xuống. Xem đề xuất.

Nội dung nhạy cảm

Nếu bạn chọn 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 cho hệ thống biết để 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ó một cờ nội dung nhạy cảm.

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