النسخ واللصق

يتيح إطار عمل الحافظة في Android لعمليات النسخ واللصق أنواع البيانات البسيطة والمعقدة، بما في ذلك:

  • سلاسل نصية
  • بُنى البيانات المعقّدة
  • بيانات النص ومصدر البيانات الثنائي
  • مواد عرض التطبيق

يتم تخزين بيانات النصوص البسيطة مباشرةً في الحافظة، بينما يتم تخزين البيانات المعقّدة كمرجع يحلّه تطبيق اللصق باستخدام أداة توفير المحتوى.

يعمل النسخ واللصق داخل التطبيق وبين التطبيقات التي تنفذ إطار العمل.

بما أنّ جزءًا من إطار العمل يستخدم أدوات توفير المحتوى، يفترض هذا المستند أنّك على دراية ببعض جوانب واجهة برمجة التطبيقات لأداة توفير المحتوى في Android.

التعامل مع النص

تتيح بعض المكوّنات نسخ النص ولصقه تلقائيًا، كما هو موضّح في الجدول التالي.

المكوّن نسخ النص لصق النص
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 كوسيطة له ويستدعي طريقة onReceive للعنصر الذي تم تمريره عندما يلصق المستخدم البيانات في BasicTextField داخل المكوّن المعدَّل.

يتم تمرير عنصر TransferableContent إلى طريقة onReceive، التي تصف البيانات التي يمكن نقلها بين التطبيقات عن طريق اللصق في هذه الحالة. يمكنك الوصول إلى العنصر ClipEntry من خلال الإشارة إلى السمة clipEntry.

يمكن أن يحتوي عنصر ClipEntry على عدة عناصر ClipData.Item عندما يختار المستخدم عدة صور وينسخها إلى الحافظة على سبيل المثال. يجب وضع علامة "مُستهلَك" أو "تم تجاهله" على كل عنصر ClipData.Item، وإرجاع TransferableContent يحتوي على عناصر ClipData.Item التي تم تجاهلها ليتمكّن أقرب معدِّل contentReceiver من تلقّيه.

يمكن أن تساعدك طريقة TransferableContent.hasMediaType() في تحديد ما إذا كان عنصر TransferableContent يمكنه توفير عنصر بنوع الوسائط. على سبيل المثال، يعرض طلب الإجراء التالي true إذا كان بإمكان العنصر TransferableContent توفير صورة.

transferableContent.hasMediaType(MediaType.Image)

العمل باستخدام بيانات معقّدة

يمكنك نسخ البيانات المعقّدة إلى الحافظة بالطريقة نفسها التي تتّبعها مع المحتوى الوافي. يُرجى الرجوع إلى مقالة استخدام أدوات توفير المحتوى لنسخ البيانات المعقّدة للحصول على التفاصيل.

يمكنك أيضًا التعامل مع عمليات لصق البيانات المعقّدة بالطريقة نفسها التي يتم بها التعامل مع المحتوى الوافي. يمكنك تلقّي معرّف موارد منتظم (URI) للبيانات التي تم لصقها. يمكن استرداد البيانات الفعلية من ContentProvider. يُرجى الرجوع إلى مقالة استرداد البيانات من مقدّم الخدمة لمزيد من المعلومات.

ملاحظات حول نسخ المحتوى

يتوقّع المستخدمون تلقّي ملاحظات عند نسخ المحتوى إلى الحافظة، لذا بالإضافة إلى إطار العمل الذي يتيح النسخ واللصق، يعرض Android واجهة مستخدم تلقائية للمستخدمين عند النسخ في الإصدار 13 من نظام التشغيل Android (المستوى 33 لواجهة برمجة التطبيقات) والإصدارات الأحدث. بسبب هذه الميزة، هناك خطر من تكرار الإشعارات. يمكنك الاطّلاع على مزيد من المعلومات حول هذه الحالة الاستثنائية في مقالة تجنُّب الإشعارات المكرّرة.

صورة متحرّكة تعرض إشعار الحافظة في Android 13
الشكل 1: واجهة المستخدم التي تظهر عند إضافة محتوى إلى الحافظة في Android 13 والإصدارات الأحدث

تقديم ملاحظات للمستخدمين يدويًا عند النسخ في الإصدار Android 12L (المستوى 32 لواجهة برمجة التطبيقات) والإصدارات الأقدم يُرجى الاطّلاع على الاقتراح.

محتوى حسّاس

إذا اخترت أن يسمح تطبيقك للمستخدم بنسخ محتوى حسّاس إلى الحافظة، مثل كلمات المرور، يجب أن يُعلم تطبيقك النظام بذلك حتى يتمكّن النظام من تجنُّب عرض المحتوى الحسّاس المنسوخ في واجهة المستخدم (الشكل 2).

معاينة النص المنسوخ الذي يشير إلى محتوى حسّاس
الشكل 2. معاينة النص المنسوخ مع علامة محتوى حسّاس

يجب إضافة علامة إلى 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)
    }
}