Kopiuj i wklej

Platforma kopiowania i wklejania oparta na schowku Androida obsługuje proste i złożone typy danych, w tym:

  • ciągi tekstowe,
  • złożone struktury danych,
  • dane tekstowe i binarne,
  • komponenty aplikacji.

Proste dane tekstowe są przechowywane bezpośrednio w schowku, a złożone dane są przechowywane jako odniesienie, które aplikacja wklejająca rozwiązuje za pomocą dostawcy treści.

Kopiowanie i wklejanie działa zarówno w obrębie aplikacji, jak i między aplikacjami, które implementują platformę.

Ponieważ część platformy korzysta z dostawców treści, w tym dokumencie zakłada się, że masz pewną wiedzę na temat interfejsu Android Content Provider API.

Praca z tekstem

Niektóre komponenty obsługują kopiowanie i wklejanie tekstu od razu po zainstalowaniu, jak pokazano w tabeli poniżej.

Komponent Kopiowanie tekstu Wklejanie tekstu
BasicTextField ✅ ✅
TextField ✅ ✅
SelectionContainer ✅

Na przykład w tym fragmencie kodu możesz skopiować tekst z karty do schowka i wkleić go do TextField. Aby wyświetlić menu wklejania tekstu, dotknij i przytrzymaj TextField lub naciśnij uchwyt kursora.

val textFieldState = rememberTextFieldState()

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

Tekst możesz wkleić za pomocą skrótu klawiszowego: Ctrl+V . Skrót klawiszowy jest też domyślnie dostępny. Więcej informacji znajdziesz w artykule Obsługa działań klawiatury.

Kopiowanie za pomocą ClipboardManager

Tekst możesz kopiować do schowka za pomocą ClipboardManager. Metoda setText() kopiuje przekazany obiekt AnnotatedString do schowka. Ten fragment kodu kopiuje do schowka tekst „Hello, clipboard”, gdy użytkownik kliknie przycisk.

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

Ten fragment kodu robi to samo, ale daje większą kontrolę. Częstym przypadkiem użycia jest kopiowanie treści o charakterze kontrowersyjnym, np. hasła. ClipEntry opisuje element w schowku. Zawiera obiekt ClipData, który opisuje dane w schowku. ClipData.newPlainText() method is a convenience method to create a ClipData object from a String object. Utworzony obiekt ClipEntry możesz ustawić w schowku wywołując metodę setClip() na obiekcie 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")
}

Wklejanie za pomocą ClipboardManager

Dostęp do tekstu skopiowanego do schowka możesz uzyskać wywołując getText() metodę na obiekcie ClipboardManager. Gdy tekst zostanie skopiowany do schowka, metoda getText() zwraca obiekt AnnotatedString. Ten fragment kodu dołącza tekst ze schowka do tekstu w 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")
    }
}

Praca ze szczegółowymi treściami

Użytkownicy uwielbiają obrazy, filmy i inne ekspresyjne treści. Twoja aplikacja może umożliwiać użytkownikom kopiowanie szczegółowych treści za pomocą ClipboardManager i ClipEntry. Modyfikator contentReceiver pomaga w implementowaniu wklejania szczegółowych treści.

Kopiowanie szczegółowych treści

Twoja aplikacja nie może kopiować szczegółowych treści bezpośrednio do schowka. Zamiast tego przekazuje do schowka obiekt URI i zapewnia dostęp do treści za pomocą ContentProvider. Ten fragment kodu pokazuje, jak skopiować obraz JPEG do schowka. Więcej informacji znajdziesz w artykule Kopiowanie strumieni danych.

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

Wklejanie szczegółowych treści

Za pomocą modyfikatora contentReceiver możesz obsługiwać wklejanie szczegółowych treści do BasicTextField w zmodyfikowanym komponencie. Ten fragment kodu dodaje wklejony adres URI danych obrazu do listy obiektów 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)
)

Modyfikator contentReceiver przyjmuje jako argument obiekt ReceiveContentListener i wywołuje metodę onReceive przekazanego obiektu, gdy użytkownik wkleja dane do BasicTextField w zmodyfikowanym komponencie.

Do metody onReceive przekazywany jest obiekt TransferableContent, który opisuje dane, które można przenosić między aplikacjami przez wklejanie. Dostęp do obiektu ClipEntry możesz uzyskać, odwołując się do atrybutu clipEntry.

Obiekt ClipEntry może mieć kilka obiektów ClipData.Item gdy użytkownik wybierze kilka obrazów i skopiuje je do schowka na przykład. Każdy obiekt ClipData.Item należy oznaczyć jako zużyty lub zignorowany i zwrócić obiekt TransferableContent zawierający zignorowane obiekty ClipData.Item, aby najbliższy przodek modyfikatora contentReceiver mógł go otrzymać.

Metoda TransferableContent.hasMediaType() może pomóc Ci określić czy obiekt TransferableContent może udostępnić element o danym typie multimediów. Na przykład to wywołanie metody zwraca wartość true, jeśli obiekt TransferableContent może udostępnić obraz.

transferableContent.hasMediaType(MediaType.Image)

Praca ze złożonymi danymi

Złożone dane możesz kopiować do schowka w taki sam sposób jak szczegółowe treści. Więcej informacji znajdziesz w artykule Kopiowanie złożonych danych za pomocą dostawców treści.

Wklejanie złożonych danych możesz też obsługiwać w taki sam sposób jak wklejanie szczegółowych treści. Możesz otrzymać adres URI wklejonych danych. Rzeczywiste dane można pobrać z ContentProvider. Więcej informacji znajdziesz w artykule Pobieranie danych od dostawcy.

Informacje zwrotne dotyczące kopiowania treści

Gdy użytkownicy kopiują treści do schowka, oczekują informacji zwrotnych. Dlatego oprócz platformy, która umożliwia kopiowanie i wklejanie, Android wyświetla domyślny interfejs, gdy użytkownicy kopiują treści w Androidzie 13 (poziom API 33) i nowszym. Z tego powodu istnieje ryzyko duplikowania powiadomień. Więcej informacji na ten temat znajdziesz w artykule Unikanie duplikatów powiadomień.

Animacja pokazująca powiadomienie o schowku w Androidzie 13
Rysunek 1. Interfejs wyświetlany, gdy treści trafiają do schowka w Androidzie 13 i nowszym.

Gdy kopiowanie odbywa się w Androidzie 12L (poziom API 32) i starszym, informacje zwrotne musisz przekazywać użytkownikom ręcznie. Zapoznaj się z zaleceniem.

Treści o charakterze kontrowersyjnym

Jeśli zdecydujesz się umożliwić użytkownikom kopiowanie do schowka treści o charakterze kontrowersyjnym, np. haseł, Twoja aplikacja musi poinformować o tym system, aby mógł on uniknąć wyświetlania skopiowanych treści o charakterze kontrowersyjnym w interfejsie (rysunek 2).

Podgląd skopiowanego tekstu z oznaczeniem treści poufnych.
Rysunek 2. Podgląd skopiowanego tekstu z flagą treści o charakterze kontrowersyjnym.

Zanim wywołasz metodę setClip() na obiekcie ClipboardManager, musisz dodać flagę do ClipDescription w ClipData:

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