העתקה והדבקה

המסגרת של Android להעתקה והדבקה באמצעות הלוח תומכת בסוגי נתונים פרימיטיביים ומורכבים, כולל:

  • מחרוזות טקסט
  • מבני נתונים מורכבים
  • נתונים של סטרימינג בינארי וטקסט
  • נכסי אפליקציות

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

אפשר להעתיק ולהדביק גם בתוך אפליקציה וגם בין אפליקציות שמטמיעות את המסגרת.

חלק מה-framework משתמש בספקים לניהול גישה, ולכן אנחנו מניחים שאתם מכירים קצת את 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. ה-method‏ 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() היא שיטה נוחה ליצירת אובייקט 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.

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 כארגומנט וקורא ל-method‏ onReceive של האובייקט שהועבר כשהמשתמש מדביק נתונים ב-BasicTextField בתוך הרכיב שהשתנה.

אובייקט TransferableContent מועבר ל-onReceive method, שמתאר את הנתונים שאפשר להעביר בין אפליקציות על ידי הדבקה במקרה הזה. אפשר לגשת לאובייקט ClipEntry על ידי הפניה למאפיין clipEntry.

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

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

transferableContent.hasMediaType(MediaType.Image)

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

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

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

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

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

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

ב-Android 12L (רמת API ‏32) ומטה, צריך לספק משוב למשתמשים באופן ידני כשמעתיקים. להמלצה

תוכן רגיש

אם אתם בוחרים לאפשר למשתמשים להעתיק תוכן רגיש ללוח, כמו סיסמאות, האפליקציה צריכה להודיע על כך למערכת כדי שהמערכת תוכל להימנע מהצגת התוכן הרגיש שהועתק בממשק המשתמש (איור 2).

תצוגה מקדימה של טקסט שהועתק עם סימון של תוכן רגיש.
איור 2. תצוגה מקדימה של טקסט שהועתק עם תווית של תוכן רגיש.

צריך להוסיף דגל ל-ClipDescription ב-ClipData לפני ששולחים קריאה ל-method‏ 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)
    }
}