النسخ واللصق

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

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

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

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

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

استخدام النصوص

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

المكوّن نسخ النص لصق نص
حقل النص الأساسي
TextField
SelectionContainer

على سبيل المثال، يمكنك نسخ النص في البطاقة إلى الحافظة في المقتطف التالي ولصق النص المنسوخ في TextField. يمكنك عرض القائمة للصق النص من خلال النقر مع الاستمرار على TextField أو النقر على مقبض المؤشر.

val textFieldState = rememberTextFieldState()

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

يمكنك لصق النص باستخدام اختصار لوحة المفاتيح التالي: Ctrl+V . يتوفّر اختصار لوحة المفاتيح أيضًا تلقائيًا. لمعرفة التفاصيل، يُرجى الاطّلاع على مقالة التعامل مع إجراءات لوحة المفاتيح.

النسخ باستخدام "ClipboardManager"

يمكنك نسخ النصوص إلى الحافظة باستخدام ClipboardManager. تنسخ طريقة setText() عنصر String الذي تم تمريره إلى الحافظة. ينسخ المقتطف التالي العبارة "مرحبًا، الحافظة" إلى الحافظة عندما ينقر المستخدم على الزر.

// 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() هي طريقة ملائمة إنشاء كائن 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.

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

العمل باستخدام محتوى وافٍ

يحبّ المستخدمون الصور والفيديوهات والمحتوى التعبيري الآخر. يمكن أن يسمح تطبيقك للمستخدم بنسخ محتوى منسق باستخدام رمزَي 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 في المكوّن المعدَّل. يضيف المقتطف التالي من التعليمات البرمجية عنوان URL المُلصق لبيانات الصورة إلى قائمة بعناصر 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. كوسيطته وتستدعي 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 واجهة مستخدم تلقائية للمستخدمين عند نسخ البيانات في Android 13 (مستوى واجهة برمجة التطبيقات 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)
    }
}