کپی کردن و جای‌گذاری

چارچوب مبتنی بر بریده‌دان 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 ارسال‌شده را در بریده‌دان کپی می‌کند. قطعه کد زیر «سلام، بریده‌دان» را وقتی کاربر روی دکمه کلیک می‌کند در بریده‌دان کپی می‌کند.

// 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() روشی راحت برای ایجاد شیء 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، مصرف‌شده یا نادیده‌گرفته‌شده را علامت‌گذاری کنید، و یک TransferableContent حاوی اشیاء ClipData.Item نادیده‌گرفته‌شده را برگردانید تا نزدیک‌ترین اصلاح‌کننده contentReceiver بتواند آن را دریافت کند.

روش TransferableContent.hasMediaType() می‌تواند به شما کمک کند تعیین کنید آیا شیء TransferableContent می‌تواند موردی با نوع رسانه ارائه دهد یا نه. برای مثال، فراخوانی روش زیر true را برمی‌گرداند اگر شیء TransferableContent بتواند تصویری ارائه دهد.

transferableContent.hasMediaType(MediaType.Image)

کار کردن با داده‌های پیچیده

می‌توانید داده‌های پیچیده را به همان روشی که برای محتوای غنی انجام می‌دهید، در بریده‌دان کپی کنید. برای جزئیات بیشتر، به استفاده از ارائه‌دهندگان محتوا برای کپی کردن داده‌های پیچیده مراجعه کنید.

همچنین می‌توانید جای‌گذاری داده‌های پیچیده را به همان روش برای محتوای غنی مدیریت کنید. می‌توانید شناسه منبع یکسان داده‌های جای‌گذاری‌شده را دریافت کنید. داده‌های واقعی را می‌توان از ContentProvider بازیابی کرد. برای اطلاعات بیشتر، به بازیابی داده‌ها از ارائه‌دهنده مراجعه کنید.

بازخورد درباره کپی کردن محتوا

کاربران انتظار دارند وقتی محتوایی را در بریده‌دان کپی می‌کنند بازخورد دریافت کنند، بنابراین علاوه‌بر چارچوبی که کپی و جای‌گذاری را تقویت می‌کند، Android در Android 13 (میانای برنامه‌سازی کاربردی سطح ۳۳) و بالاتر هنگام کپی کردن محتوا، میانای کاربری پیش‌فرضی به کاربران نشان می‌دهد. به‌دلیل این ویژگی، خطر اعلان تکراری وجود دارد. می‌توانید درباره این مورد خاص در جلوگیری از اعلان‌های تکراری بیشتر بدانید.

پویانمایی‌ای که اعلان بریده‌دان Android 13 را نشان می‌دهد
شکل ۱. رابط کاربری‌ای که وقتی محتوا وارد بریده‌دان می‌شود در Android نسخه ۱۳ و بالاتر نشان داده می‌شود.

هنگام کپی کردن در Android 12L (سطح API‏ ۳۲) و پایین‌تر، بازخورد را به‌صورت دستی به کاربران ارائه دهید. توصیه را ببینید.

محتوای حساس

اگر انتخاب کنید که برنامه‌تان به کاربر اجازه دهد محتوای حساس را در بریده‌دان کپی کند، مثلاً گذرواژه‌ها، برنامه‌تان باید به سیستم اطلاع دهد تا سیستم بتواند از نمایش محتوای حساس کپی‌شده در میانای کاربر جلوگیری کند (شکل ۲).

پیش‌نمایش نوشتار کپی‌شده که محتوای حساس را پرچم‌گذاری می‌کند.
شکل ۲. پیش‌نمایش نوشتار کپی‌شده با پرچم محتوای حساس.

باید پرچمی به ClipDescription در ClipData قبل‌از فراخوانی روش setClip() روی شیء 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)
    }
}