העתקה והדבקה

ה-framework מבוסס-הלוח של 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() מועתקת את אובייקט המחרוזת שהועבר ללוח. קטע הקוד הבא מעתיק את הטקסט 'שלום, הלוח' ללוח כשהמשתמש לוחץ על הלחצן.

// 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 מאובייקט String. אפשר להגדיר את אובייקט 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 ברכיב ששונה. קטע הקוד הבא מוסיף את ה-URI של נתוני התמונה שהועתקו לרשימת אובייקטים מסוג 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 מועבר ל-method onReceive, שמתאר את הנתונים שאפשר להעביר בין אפליקציות באמצעות הדבקה במקרה הזה. אפשר לגשת לאובייקט ClipEntry על ידי הפניה למאפיין clipEntry.

אובייקט ClipEntry יכול לכלול כמה אובייקטים מסוג ClipData.Item, למשל כשהמשתמש בוחר כמה תמונות ומעתיק אותן ללוח. צריך לסמן כל אובייקט ClipData.Item כ'נוצר' או כ'מוזנח', ולהחזיר TransferableContent שמכיל את אובייקטי ה-ClipData.Item שהוזנחו, כדי שהמופך contentReceiver של האב הקרוב ביותר יוכל לקבל אותו.

השיטה TransferableContent.hasMediaType() יכולה לעזור לכם לקבוע אם אובייקט TransferableContent יכול לספק פריט עם סוג המדיה. לדוגמה, הפעלת ה-method הבאה מחזירה את הערך true אם האובייקט TransferableContent יכול לספק תמונה.

transferableContent.hasMediaType(MediaType.Image)

עבודה עם נתונים מורכבים

אפשר להעתיק נתונים מורכבים ללוח בדיוק כמו לגבי תוכן עשיר. פרטים נוספים זמינים במאמר שימוש בספקי תוכן להעתקת נתונים מורכבים.

אפשר גם לטפל בהדבקה של נתונים מורכבים בדיוק כמו לגבי תוכן עשיר. אפשר לקבל URI של הנתונים שהועתקו. אפשר לאחזר את הנתונים בפועל מ-ContentProvider. מידע נוסף זמין במאמר אחזור נתונים מהספק.

משוב על העתקת תוכן

משתמשים מצפים לקבל משוב כשהם מעתיקים תוכן ללוח העריכה, ולכן בנוסף למסגרת שמפעילה את ההעתקה וההדבקה, ממשק המשתמש שמוגדר כברירת מחדל מוצג למשתמשים ב-Android 13 (API ברמה 33) ואילך כשהם מעתיקים. בגלל התכונה הזו, יש סיכון לקבל התראה כפולה. מידע נוסף על מקרה קיצון כזה זמין במאמר הימנעות מהצגת התראות כפולות.

אנימציה שמציגה הודעה על לוח העריכה של Android 13
איור 1. ממשק המשתמש שמוצג כשתוכן מועבר ללוח העריכה ב-Android מגרסה 13 ואילך.

שליחת משוב למשתמשים באופן ידני בזמן העתקה ב-Android 12L (רמת API ‏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)
    }
}