複製及貼上

用於複製及貼上的 Android 剪貼簿架構 支援原始和複雜的資料類型,包括:

  • 文字字串
  • 複雜的資料結構
  • 文字和二進位串流資料
  • 應用程式素材資源

簡單文字資料會直接儲存在剪貼簿中。 並將複雜資料儲存為參考資源 執行貼上操作的應用程式會解析內容供應器

複製及貼上功能可在應用程式和應用程式之間使用 實作這個架構的

由於該架構的一部分使用內容供應器,因此本文件假設您對 Android 內容供應器 API 有一定程度的瞭解。

處理文字

部分元件支援複製及貼上文字,如下表所示。

元件 複製文字 貼上文字
BasicTextField
文字欄位
SelectContainer

例如,你可以將資訊卡中的文字複製到剪貼簿 貼在下列程式碼片段中,並將複製的文字貼到 TextField。 您可以依照 觸控和按住 TextField,或輕觸遊標控點。

val textFieldState = rememberTextFieldState()

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

你可以使用下列鍵盤快速鍵貼上文字: Ctrl + V 鍵。 根據預設,您也可以使用鍵盤快速鍵。 詳情請參閱「處理鍵盤動作」。

使用「ClipboardManager」複製

你可以使用 ClipboardManager 將文字複製到剪貼簿。 其 setText() 方法會複製 傳遞到剪貼簿的 String 物件 當使用者按下按鈕時,下列程式碼片段會將「Hello, clipboard」複製到剪貼簿。

// 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() 方法是便利方法,可從 String 物件建立 ClipData 物件。您可以將已建立的 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 貼上

您可以透過 ClipboardManager 呼叫 getText() 方法,存取複製到剪貼簿的文字。當文字複製到剪貼簿時,其 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")
    }
}

使用多媒體內容

使用者喜歡圖片、影片和其他生動的內容。您的應用程式可讓使用者透過 ClipboardManagerClipEntry 複製富內容。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 物件做為引數,並在使用者將資料貼到修改後元件中的 BasicTextField 時,呼叫傳遞物件的 onReceive 方法。

TransferableContent 物件會傳遞至 onReceive 方法,該方法會描述可透過貼上方式在應用程式之間傳輸的資料。您可以透過參照 clipEntry 屬性來存取 ClipEntry 物件。

ClipEntry 物件可能有多個 ClipData.Item 物件 使用者選取多張圖片並複製到剪貼簿時 例如 您應為每個 ClipData.Item 物件標示已使用或已略過,並傳回包含已略過 ClipData.Item 物件的 TransferableContent,以便最接近的祖系 contentReceiver 修飾符能夠接收。

TransferableContent.hasMediaType() 方法可協助您判斷 TransferableContent 物件是否能提供具有媒體類型的項目。舉例來說,如果 TransferableContent 物件可以提供圖片,則下列方法呼叫會傳回 true

transferableContent.hasMediaType(MediaType.Image)

處理複雜資料

你可以將複雜資料複製到剪貼簿 和多媒體內容一樣 詳情請參閱「使用內容供應器複製複雜資料」。

您也可以將複雜的資料貼上處理 以符合多媒體內容的形式 您可以接收貼上資料的 URI。您可以從 ContentProvider 擷取實際資料。詳情請參閱「從供應商擷取資料」一文。

複製內容的意見回饋

使用者希望能在複製內容到剪貼簿時獲得回饋。 除了開發複製及貼上功能的架構之外 使用者在 Android 13 (API 級別 33) 中複製內容時,Android 會顯示預設 UI 以及更高版本由於這項功能的緣故,因此可能會有重複通知的風險。 如要進一步瞭解這個極端情況,請參閱「避免重複通知」。

動畫:顯示 Android 13 剪貼簿通知
圖 1. 在 Android 中將內容進入剪貼簿時顯示的 UI 13 以上。

在 Android 12L (API 級別 32) 以下版本中複製內容時,請手動向使用者提供回饋。請參閱建議做法

敏感內容

如果您選擇讓應用程式允許使用者將敏感內容 (例如密碼) 複製到剪貼簿,則應用程式必須通知系統,以免系統在 UI 中顯示複製的敏感內容 (圖 2)。

敏感內容加上標記後的複製文字預覽。
圖 2. 敏感內容加上標記後的複製文字預覽。

您必須在 ClipData 中將標記新增至 ClipDescription 再透過 ClipboardManager 物件呼叫 setClip() 方法:

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