コピーして貼り付け

コピーと貼り付けのための Android クリップボード ベースのフレームワーク は、次のようなプリミティブ データ型と複合データ型をサポートしています。

  • テキスト文字列
  • 複雑なデータ構造
  • テキストとバイナリ ストリーム データ
  • アプリケーション アセット

シンプルなテキストデータはクリップボードに直接保存され、 複雑なデータは参照として保存される 貼り付けるアプリがコンテンツ プロバイダと解決されることを示します。

コピーと貼り付けは、アプリ内でもアプリ間でも機能します。 フレームワークを実装しています。

フレームワークの一部でコンテンツ プロバイダが使用されるため、 このドキュメントは、Android Content Provider API についてある程度の知識があることを前提としています。

テキストを操作する

以下の図に示すように、一部のコンポーネントは、すぐに使用できるテキストをコピーして貼り付けることもできます。 表します

コンポーネント テキストをコピーしています テキストを貼り付けています
BasicTextField
TextField
選択コンテナ

たとえば、カード内のテキストをクリップボードにコピーできます。 コピーしたテキストを 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 オブジェクトをクリップボードに設定するには、ClipboardManager オブジェクトで setClip() メソッドを呼び出します。

// 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 を使用して貼り付ける

クリップボードにコピーされたテキストにアクセスするには、ClipboardManagergetText() メソッドを呼び出します。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 13 以降でコンテンツがクリップボードに入ると表示される UI。

Android 12L(API レベル 32)以前でコピーする場合は、手動でフィードバックを提供します。推奨事項をご覧ください。

デリケートなコンテンツ

パスワードなどの機密コンテンツをユーザーがクリップボードにコピーできるようにする場合は、アプリがシステムに通知して、コピーされた機密コンテンツが UI に表示されないようにする必要があります(図 2)。

機密コンテンツのフラグを設定してコピーされたテキストのプレビュー
図 2. 機密コンテンツのフラグが設定されたコピーされたテキストのプレビュー。

ClipboardManager オブジェクトで setClip() メソッドを呼び出す前に、ClipDataClipDescription にフラグを追加する必要があります。

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