コピー&ペースト用の Android クリップボード ベース フレームワークは、次のようなプリミティブ データ型と複合データ型をサポートしています。
- テキスト文字列
- 複雑なデータ構造
- テキストとバイナリ ストリーム データ
- アプリのアセット
シンプルなテキストデータはクリップボード内に直接保存されます。複雑なデータは参照として保存され、貼り付け側アプリがコンテンツ プロバイダを使用して解決します。
コピーと貼り付けは、フレームワークを実装するアプリ内とアプリ間で機能します。
フレームワークの一部でコンテンツ プロバイダが使用されるため、 このドキュメントは、ある程度 Android Content Provider API に精通していることを前提としています。
テキストを操作する
次の表に示すように、一部のコンポーネントはテキストのコピーと貼り付けをすぐにサポートしています。
| コンポーネント | テキストのコピー | テキストの貼り付け |
|---|---|---|
| BasicTextField | ✅ | ✅ |
| TextField | ✅ | ✅ |
| SelectionContainer | ✅ |
たとえば、次のスニペットでカード内のテキストをクリップボードにコピーし、コピーしたテキストを TextField に貼り付けることができます。
テキストを貼り付けるメニューを表示するには、TextField を長押しするか、カーソル ハンドルをタップします。
val textFieldState = rememberTextFieldState() Column { Card { SelectionContainer { Text("You can copy this text") } } BasicTextField(state = textFieldState) }
テキストは、キーボード ショートカット Ctrl+V で貼り付けることができます。 キーボード ショートカットはデフォルトでも使用できます。 詳しくは、キーボード アクションの処理をご覧ください。
ClipboardManager でコピーする
ClipboardManager を使用してテキストをクリップボードにコピーできます。
その setText() メソッドは、渡された AnnotatedString オブジェクトをクリップボードにコピーします。次のスニペットでは、ユーザーがボタンをクリックすると、「Hello, clipboard」がクリップボードにコピーされます。
// 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") }
次のスニペットも同じ処理を行いますが、よりきめ細かい制御が可能です。
一般的なユースケースは、パスワードなどの
機密コンテンツのコピーです。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 で貼り付ける
クリップボードにコピーされたテキストにアクセスするには、getText() メソッドを ClipboardManager で呼び出します。クリップボードにテキストがコピーされると、getText() メソッドは AnnotatedString オブジェクトを返します。
次のスニペットでは、クリップボード内のテキストが 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") } }
リッチ コンテンツを操作する
ユーザーは、画像、動画、その他の表現力豊かなコンテンツを好みます。
アプリでは、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 = 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) )
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)以降では、コピー時にデフォルトの UI が表示されます。この機能により、通知が重複するリスクがあります。 このエッジケースについて詳しくは、通知の重複を避けるをご覧ください。
Android 12L(API レベル 32)以下でコピーする場合は、ユーザーに手動でフィードバックを提供します。 推奨事項をご覧ください。
デリケートなコンテンツ
パスワードなどの機密コンテンツをアプリでクリップボードにコピーできるようにする場合は、コピーした機密コンテンツが UI(図 2)に表示されないように、システムに通知する必要があります。
ClipboardManager オブジェクトで setClip() メソッドを呼び出す前に、ClipData
の ClipDescription にフラグを追加する必要があります。
// 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) } }