النسخ واللصق

يدعم إطار عمل 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 الذي تم تمريره إلى الحافظة. ينسخ الرمز البرمجي التالي "Hello, clipboard" إلى الحافظة عندما ينقر المستخدم على الزر.

// 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() method is a convenience method to create a ClipData object from a String object. يمكنك ضبط كائن 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 واجهة مستخدم تلقائية للمستخدمين عند النسخ في 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)
    }
}