คัดลอกและวาง

ลองใช้ Compose
Jetpack Compose เป็นชุดเครื่องมือ UI ที่แนะนำสำหรับ Android ดูวิธีใช้การคัดลอกและวางใน Compose

Android มีเฟรมเวิร์กที่ทรงพลังซึ่งอิงตามคลิปบอร์ดสำหรับการคัดลอกและวาง โดยรองรับประเภทข้อมูลอย่างง่าย และซับซ้อน รวมถึงสตริงข้อความ โครงสร้างข้อมูลที่ซับซ้อน ข้อมูลสตรีมข้อความและไบนารี และชิ้นงานของแอปพลิเคชัน ระบบจะจัดเก็บข้อมูลข้อความธรรมดาไว้ในคลิปบอร์ดโดยตรง ส่วนข้อมูลที่ซับซ้อน จะจัดเก็บเป็นข้อมูลอ้างอิงที่แอปพลิเคชันที่วางจะแก้ไขด้วยผู้ให้บริการเนื้อหา การคัดลอก และการวางจะทำงานทั้งภายในแอปพลิเคชันและระหว่างแอปพลิเคชันที่ใช้เฟรมเวิร์ก

เนื่องจากเฟรมเวิร์กบางส่วนใช้ผู้ให้บริการเนื้อหา เอกสารนี้จึงถือว่าคุณคุ้นเคยกับ Android Content Provider API ซึ่งอธิบายไว้ใน ผู้ให้บริการเนื้อหา

ผู้ใช้คาดหวังว่าจะได้รับความคิดเห็นเมื่อคัดลอกเนื้อหาไปยังคลิปบอร์ด ดังนั้นนอกเหนือจากเฟรมเวิร์กที่ขับเคลื่อนการคัดลอกและวางแล้ว Android ยังแสดง UI เริ่มต้นต่อผู้ใช้เมื่อคัดลอกใน Android 13 (API ระดับ 33) ขึ้นไป ฟีเจอร์นี้อาจทำให้เกิดความเสี่ยงที่จะมีการแจ้งเตือนซ้ำ ดูข้อมูลเพิ่มเติมเกี่ยวกับ กรณีข้อยกเว้นนี้ได้ในส่วน หลีกเลี่ยงการแจ้งเตือนที่ซ้ำกัน

ภาพเคลื่อนไหวแสดงการแจ้งเตือนคลิปบอร์ดของ Android 13
รูปที่ 1 UI ที่แสดงเมื่อเนื้อหาเข้าสู่คลิปบอร์ดใน Android 13 ขึ้นไป

แสดงความคิดเห็นต่อผู้ใช้ด้วยตนเองเมื่อคัดลอกใน Android 12L (API ระดับ 32) และต่ำกว่า ดูคำแนะนำสำหรับเรื่องนี้ในเอกสารนี้

เฟรมเวิร์กของคลิปบอร์ด

เมื่อใช้เฟรมเวิร์กคลิปบอร์ด ให้ใส่ข้อมูลลงในออบเจ็กต์คลิป แล้วใส่ออบเจ็กต์คลิปลงในคลิปบอร์ดทั่วทั้งระบบ ออบเจ็กต์คลิปอาจอยู่ในรูปแบบใดรูปแบบหนึ่งต่อไปนี้

ข้อความ
สตริงข้อความ วางสตริงลงในออบเจ็กต์คลิปโดยตรง จากนั้นวางออบเจ็กต์คลิปลงใน คลิปบอร์ด หากต้องการวางสตริง ให้รับออบเจ็กต์คลิปจากคลิปบอร์ดและคัดลอก สตริงลงในพื้นที่เก็บข้อมูลของแอปพลิเคชัน
URI
ออบเจ็กต์ Uri ที่แสดง URI ในรูปแบบใดก็ได้ ซึ่งส่วนใหญ่ใช้สำหรับการคัดลอกข้อมูลที่ซับซ้อนจากผู้ให้บริการเนื้อหา หากต้องการคัดลอก ข้อมูล ให้ใส่Uriออบเจ็กต์ลงในออบเจ็กต์คลิป แล้ววางออบเจ็กต์คลิปลงใน คลิปบอร์ด หากต้องการวางข้อมูล ให้รับออบเจ็กต์คลิป รับออบเจ็กต์ Uri แก้ไขเป็นแหล่งข้อมูล เช่น Content Provider แล้วคัดลอกข้อมูลจาก แหล่งที่มาไปยังที่เก็บข้อมูลของแอปพลิเคชัน
ความตั้งใจ
Intent ซึ่ง รองรับการคัดลอกทางลัดของแอปพลิเคชัน หากต้องการคัดลอกข้อมูล ให้สร้าง Intent วางไว้ในออบเจ็กต์คลิป แล้ววางออบเจ็กต์คลิปลงในคลิปบอร์ด หากต้องการวางข้อมูล ให้รับออบเจ็กต์คลิปแล้วคัดลอกออบเจ็กต์ Intent ลงในพื้นที่หน่วยความจำของแอปพลิเคชัน

คลิปบอร์ดจะเก็บออบเจ็กต์คลิปได้ทีละ 1 รายการเท่านั้น เมื่อแอปพลิเคชันวางออบเจ็กต์คลิปลงในคลิปบอร์ด ออบเจ็กต์คลิปก่อนหน้าจะหายไป

หากต้องการอนุญาตให้ผู้ใช้วางข้อมูลลงในแอปพลิเคชัน คุณไม่จำเป็นต้องจัดการข้อมูลทุกประเภท คุณสามารถตรวจสอบข้อมูลในคลิปบอร์ดก่อนที่จะให้ตัวเลือกแก่ผู้ใช้ในการวางข้อมูล นอกจากจะมีรูปแบบข้อมูลที่แน่นอนแล้ว ออบเจ็กต์คลิปยังมีข้อมูลเมตาที่บอกให้ทราบว่ามี MIME types ใดบ้าง ข้อมูลเมตานี้ช่วยให้คุณตัดสินใจได้ว่าแอปพลิเคชันของคุณสามารถทำสิ่งที่เป็นประโยชน์ กับข้อมูลในคลิปบอร์ดได้หรือไม่ เช่น หากคุณมีแอปพลิเคชันที่จัดการข้อความเป็นหลัก คุณอาจต้องการไม่สนใจออบเจ็กต์คลิปที่มี URI หรือ Intent

นอกจากนี้ คุณยังอาจต้องการอนุญาตให้ผู้ใช้วางข้อความโดยไม่คำนึงถึงรูปแบบข้อมูลในคลิปบอร์ดด้วย หากต้องการทำเช่นนี้ ให้บังคับข้อมูลในคลิปบอร์ดให้เป็นข้อความ แล้ววางข้อความนี้ ซึ่งอธิบายไว้ในส่วนบังคับให้คลิปบอร์ดเป็นข้อความ

คลาสคลิปบอร์ด

ส่วนนี้อธิบายคลาสที่ใช้โดยเฟรมเวิร์กคลิปบอร์ด

ClipboardManager

คลิปบอร์ดของระบบ Android แสดงโดยคลาส global ClipboardManager อย่าสร้างอินสแตนซ์ของคลาสนี้โดยตรง แต่ให้รับข้อมูลอ้างอิงโดยเรียกใช้ getSystemService(CLIPBOARD_SERVICE) แทน

ClipData, ClipData.Item และ ClipDescription

หากต้องการเพิ่มข้อมูลลงในคลิปบอร์ด ให้สร้างออบเจ็กต์ ClipDataที่มี คำอธิบายของข้อมูลและข้อมูลนั้น คลิปบอร์ดจะเก็บ ClipData ได้ครั้งละ 1 รายการ ClipData มีออบเจ็กต์ ClipDescription และออบเจ็กต์ ClipData.Item อย่างน้อย 1 รายการ

ออบเจ็กต์ ClipDescription มีข้อมูลเมตาเกี่ยวกับคลิป โดยเฉพาะอย่างยิ่ง มีอาร์เรย์ของประเภท MIME ที่พร้อมใช้งานสำหรับข้อมูลของคลิป นอกจากนี้ ใน Android 12 (API ระดับ 31) ขึ้นไป ข้อมูลเมตาจะรวมข้อมูลเกี่ยวกับว่าออบเจ็กต์มีข้อความที่มีสไตล์หรือไม่ และเกี่ยวกับประเภทของข้อความในออบเจ็กต์ เมื่อวางคลิปลงในคลิปบอร์ด แอปพลิเคชันที่ใช้ในการวางจะเข้าถึงข้อมูลนี้ได้ ซึ่งจะตรวจสอบได้ว่าแอปพลิเคชันนั้นจัดการข้อมูลคลิปได้หรือไม่

ออบเจ็กต์ ClipData.Item มีข้อความ, URI หรือข้อมูล Intent ดังนี้

ข้อความ
A CharSequence
URI
A Uri โดยปกติแล้วจะมี URI ของผู้ให้บริการเนื้อหา แม้ว่าจะอนุญาตให้ใช้ URI ใดก็ได้ ก็ตาม แอปพลิเคชันที่ให้ข้อมูลจะวาง URI ไว้ในคลิปบอร์ด แอปพลิเคชัน ที่ต้องการวางข้อมูลจะรับ URI จากคลิปบอร์ดและใช้เพื่อเข้าถึงผู้ให้บริการเนื้อหา หรือแหล่งข้อมูลอื่นๆ และดึงข้อมูล
ความตั้งใจ
Intent ข้อมูลประเภทนี้ช่วยให้คุณคัดลอกทางลัดของแอปพลิเคชันไปยัง คลิปบอร์ดได้ จากนั้นผู้ใช้จะวางทางลัดลงในแอปพลิเคชันเพื่อใช้ในภายหลังได้

คุณเพิ่มออบเจ็กต์ ClipData.Item ลงในคลิปได้มากกว่า 1 รายการ ซึ่งจะช่วยให้ผู้ใช้คัดลอกและ วางรายการที่เลือกหลายรายการเป็นคลิปเดียวได้ เช่น หากคุณมีวิดเจ็ตรายการที่ให้ ผู้ใช้เลือกได้มากกว่า 1 รายการในครั้งเดียว คุณจะคัดลอกรายการทั้งหมดไปยังคลิปบอร์ดได้พร้อมกัน หากต้องการทำเช่นนี้ ให้สร้าง ClipData.Item แยกกันสำหรับรายการแต่ละรายการ แล้วเพิ่มออบเจ็กต์ ClipData.Item ลงในออบเจ็กต์ ClipData

วิธีการอำนวยความสะดวกของ ClipData

คลาส ClipData มีเมธอดแบบคงที่ที่สะดวกในการสร้างออบเจ็กต์ ClipData ที่มีออบเจ็กต์ ClipData.Item รายการเดียวและออบเจ็กต์ ClipDescription แบบง่ายๆ ดังนี้

newPlainText(label, text)
แสดงผลออบเจ็กต์ ClipData ที่มีออบเจ็กต์ ClipData.Item เดียว ซึ่งมีสตริงข้อความ ตั้งค่าป้ายกำกับของออบเจ็กต์ ClipDescription เป็น label ประเภท MIME เดียวใน ClipDescription คือ MIMETYPE_TEXT_PLAIN

ใช้ newPlainText() เพื่อสร้างคลิปจากสตริงข้อความ

newUri(resolver, label, URI)
แสดงผลออบเจ็กต์ ClipData ซึ่งมีออบเจ็กต์ ClipData.Item รายการเดียว ที่มี URI ตั้งค่าป้ายกำกับของออบเจ็กต์ ClipDescription เป็น label หาก URI เป็น URI ของเนื้อหา นั่นคือหาก Uri.getScheme() แสดงผล content: เมธอดจะใช้ออบเจ็กต์ ContentResolver ที่ระบุใน resolver เพื่อเรียกประเภท MIME ที่ใช้ได้จาก ผู้ให้บริการเนื้อหา จากนั้นระบบจะจัดเก็บไว้ใน ClipDescription สำหรับ URI ที่ไม่ใช่ URI content: เมธอดจะตั้งค่าประเภท MIME เป็น MIMETYPE_TEXT_URILIST

ใช้ newUri() เพื่อสร้างคลิปจาก URI โดยเฉพาะอย่างยิ่ง content: URI

newIntent(label, intent)
แสดงผลออบเจ็กต์ ClipData ซึ่งมีออบเจ็กต์ ClipData.Item รายการเดียว ที่มี Intent ตั้งค่าป้ายกำกับของออบเจ็กต์ ClipDescription เป็น label ระบบตั้งค่าประเภท MIME เป็น MIMETYPE_TEXT_INTENT

ใช้ newIntent() เพื่อสร้างคลิปจากออบเจ็กต์ Intent

บังคับให้ข้อมูลในคลิปบอร์ดเป็นข้อความ

แม้ว่าแอปพลิเคชันจะจัดการข้อความเท่านั้น คุณก็สามารถคัดลอกข้อมูลที่ไม่ใช่ข้อความจากคลิปบอร์ดได้โดย แปลงข้อมูลด้วยเมธอด ClipData.Item.coerceToText()

เมธอดนี้จะแปลงข้อมูลใน ClipData.Item เป็นข้อความและส่งคืน CharSequence ค่าที่ ClipData.Item.coerceToText() แสดงผลจะอิงตามรูปแบบข้อมูลใน ClipData.Item ดังนี้

ข้อความ
หาก ClipData.Item เป็นข้อความ นั่นคือหาก getText() ไม่ใช่ค่า Null ฟังก์ชัน coerceToText() จะแสดงผลข้อความ
URI
หาก ClipData.Item เป็น URI นั่นคือหาก getUri() ไม่ใช่ค่าว่าง coerceToText() จะพยายามใช้เป็น URI เนื้อหา
  • หาก URI เป็น URI ของเนื้อหาและผู้ให้บริการสามารถแสดงผลสตรีมข้อความได้ coerceToText() จะแสดงผลสตรีมข้อความ
  • หาก URI เป็น URI ของเนื้อหา แต่ผู้ให้บริการไม่มีสตรีมข้อความ coerceToText() จะแสดงผลการแสดง URI การแสดงผลจะเหมือนกับที่ Uri.toString() แสดงผล
  • หาก URI ไม่ใช่ URI ของเนื้อหา coerceToText() จะแสดงผลการแทน URI การแสดงผลจะเหมือนกับที่ได้จาก Uri.toString()
ความตั้งใจ
หาก ClipData.Item เป็น Intent นั่นคือหาก getIntent() ไม่ใช่ค่าว่าง coerceToText() จะแปลงเป็น URI ของ Intent แล้วแสดงผล การแสดงผลจะเหมือนกับที่ Intent.toUri(URI_INTENT_SCHEME) แสดงผล

กรอบงานของคลิปบอร์ดสรุปได้ดังรูปที่ 2 หากต้องการคัดลอกข้อมูล แอปพลิเคชันจะวางออบเจ็กต์ ClipDataในคลิปบอร์ดส่วนกลาง ClipboardManager ClipData มีออบเจ็กต์ ClipData.Item อย่างน้อย 1 รายการและออบเจ็กต์ ClipDescription 1 รายการ หากต้องการวางข้อมูล แอปพลิเคชันจะรับ ClipData, รับประเภท MIME จาก ClipDescription และรับข้อมูลจาก ClipData.Item หรือจากผู้ให้บริการเนื้อหาที่อ้างอิงโดย ClipData.Item

รูปภาพแสดงบล็อกไดอะแกรมของเฟรมเวิร์กการคัดลอกและวาง
รูปที่ 2 เฟรมเวิร์กคลิปบอร์ดของ Android

คัดลอกไปยังคลิปบอร์ด

หากต้องการคัดลอกข้อมูลไปยังคลิปบอร์ด ให้รับแฮนเดิลไปยังออบเจ็กต์ ClipboardManager ส่วนกลาง สร้างออบเจ็กต์ ClipData และเพิ่มออบเจ็กต์ ClipDescription และออบเจ็กต์ ClipData.Item อย่างน้อย 1 รายการลงในออบเจ็กต์นั้น จากนั้นเพิ่มออบเจ็กต์ ClipData ที่เสร็จสมบูรณ์แล้วลงในออบเจ็กต์ ClipboardManager ซึ่งจะอธิบายเพิ่มเติมในขั้นตอนต่อไปนี้

  1. หากคุณคัดลอกข้อมูลโดยใช้ URI ของเนื้อหา ให้ตั้งค่าผู้ให้บริการเนื้อหา
  2. รับคลิปบอร์ดของระบบ

    Kotlin

    when(menuItem.itemId) {
        ...
        R.id.menu_copy -> { // if the user selects copy
            // Gets a handle to the clipboard service.
            val clipboard = getSystemService(Context.CLIPBOARD_SERVICE) as ClipboardManager
        }
    }

    Java

    ...
    // If the user selects copy.
    case R.id.menu_copy:
    
    // Gets a handle to the clipboard service.
    ClipboardManager clipboard = (ClipboardManager)
            getSystemService(Context.CLIPBOARD_SERVICE);
  3. คัดลอกข้อมูลไปยังออบเจ็กต์ ClipData ใหม่

    • สำหรับข้อความ

      Kotlin

      // Creates a new text clip to put on the clipboard.
      val clip: ClipData = ClipData.newPlainText("simple text", "Hello, World!")

      Java

      // Creates a new text clip to put on the clipboard.
      ClipData clip = ClipData.newPlainText("simple text", "Hello, World!");
    • สำหรับ URI

      ข้อมูลโค้ดนี้สร้าง URI โดยการเข้ารหัสรหัสระเบียนลงใน URI ของเนื้อหาสำหรับ ผู้ให้บริการ เทคนิคนี้จะกล่าวถึงอย่างละเอียดในส่วนการเข้ารหัสตัวระบุใน URI

      Kotlin

      // Creates a Uri using a base Uri and a record ID based on the contact's last
      // name. Declares the base URI string.
      const val CONTACTS = "content://com.example.contacts"
      
      // Declares a path string for URIs, used to copy data.
      const val COPY_PATH = "/copy"
      
      // Declares the Uri to paste to the clipboard.
      val copyUri: Uri = Uri.parse("$CONTACTS$COPY_PATH/$lastName")
      ...
      // Creates a new URI clip object. The system uses the anonymous
      // getContentResolver() object to get MIME types from provider. The clip object's
      // label is "URI", and its data is the Uri previously created.
      val clip: ClipData = ClipData.newUri(contentResolver, "URI", copyUri)

      Java

      // Creates a Uri using a base Uri and a record ID based on the contact's last
      // name. Declares the base URI string.
      private static final String CONTACTS = "content://com.example.contacts";
      
      // Declares a path string for URIs, used to copy data.
      private static final String COPY_PATH = "/copy";
      
      // Declares the Uri to paste to the clipboard.
      Uri copyUri = Uri.parse(CONTACTS + COPY_PATH + "/" + lastName);
      ...
      // Creates a new URI clip object. The system uses the anonymous
      // getContentResolver() object to get MIME types from provider. The clip object's
      // label is "URI", and its data is the Uri previously created.
      ClipData clip = ClipData.newUri(getContentResolver(), "URI", copyUri);
    • สำหรับเจตนา

      ข้อมูลโค้ดนี้สร้าง Intent สำหรับแอปพลิเคชัน แล้ววางไว้ในออบเจ็กต์คลิป

      Kotlin

      // Creates the Intent.
      val appIntent = Intent(this, com.example.demo.myapplication::class.java)
      ...
      // Creates a clip object with the Intent in it. Its label is "Intent"
      // and its data is the Intent object created previously.
      val clip: ClipData = ClipData.newIntent("Intent", appIntent)

      Java

      // Creates the Intent.
      Intent appIntent = new Intent(this, com.example.demo.myapplication.class);
      ...
      // Creates a clip object with the Intent in it. Its label is "Intent"
      // and its data is the Intent object created previously.
      ClipData clip = ClipData.newIntent("Intent", appIntent);
  4. วางออบเจ็กต์คลิปใหม่ลงในคลิปบอร์ด

    Kotlin

    // Set the clipboard's primary clip.
    clipboard.setPrimaryClip(clip)

    Java

    // Set the clipboard's primary clip.
    clipboard.setPrimaryClip(clip);

แสดงความคิดเห็นเมื่อคัดลอกไปยังคลิปบอร์ด

ผู้ใช้คาดหวังว่าจะได้รับข้อเสนอแนะด้านภาพเมื่อแอปคัดลอกเนื้อหาไปยังคลิปบอร์ด ระบบจะดำเนินการนี้โดยอัตโนมัติสำหรับผู้ใช้ใน Android 13 ขึ้นไป แต่ต้องติดตั้งใช้งานด้วยตนเองในเวอร์ชันก่อนหน้า

ตั้งแต่ Android 13 เป็นต้นไป ระบบจะแสดงการยืนยันด้วยภาพมาตรฐานเมื่อมีการเพิ่มเนื้อหา ลงในคลิปบอร์ด การยืนยันใหม่จะทำสิ่งต่อไปนี้

  • ยืนยันว่าคัดลอกเนื้อหาสำเร็จแล้ว
  • แสดงตัวอย่างเนื้อหาที่คัดลอก

ภาพเคลื่อนไหวแสดงการแจ้งเตือนคลิปบอร์ดของ Android 13
รูปที่ 3 UI ที่แสดงเมื่อเนื้อหาเข้าสู่คลิปบอร์ดใน Android 13 ขึ้นไป

ใน Android 12L (API ระดับ 32) และต่ำกว่า ผู้ใช้อาจไม่แน่ใจว่าคัดลอกเนื้อหาสำเร็จหรือไม่หรือคัดลอกอะไร ฟีเจอร์นี้จะกำหนดมาตรฐานการแจ้งเตือนต่างๆ ที่แอปแสดงหลังจากคัดลอก และช่วยให้ผู้ใช้ควบคุมคลิปบอร์ดได้มากขึ้น

หลีกเลี่ยงการแจ้งเตือนที่ซ้ำกัน

ใน Android 12L (API ระดับ 32) และต่ำกว่า เราขอแนะนำให้แจ้งเตือนผู้ใช้เมื่อคัดลอกสำเร็จ โดยแสดงความคิดเห็นในแอปแบบภาพโดยใช้วิดเจ็ต เช่น Toast หรือ Snackbar หลังจากคัดลอก

เราขอแนะนำอย่างยิ่งให้นำโทสต์ หรือแถบแสดงข้อความที่แสดงหลังจากคัดลอกในแอปสำหรับ Android 13 ขึ้นไปออก เพื่อหลีกเลี่ยงการแสดงข้อมูลซ้ำ

แสดงแถบแสดงข้อความหลังจากการคัดลอกในแอป
รูปที่ 4 หากคุณแสดงแถบแสดงข้อความสั้นยืนยันการคัดลอกใน Android 13 ผู้ใช้จะเห็นข้อความที่ซ้ำกัน
โพสต์ข้อความป๊อปอัปหลังจากคัดลอกในแอป
รูปที่ 5 หากคุณแสดงข้อความป๊อปอัปยืนยันการคัดลอกใน Android 13 ผู้ใช้จะเห็นข้อความที่ซ้ำกัน

ตัวอย่างวิธีติดตั้งใช้งานมีดังนี้

fun textCopyThenPost(textCopied:String) {
    val clipboardManager = getSystemService(CLIPBOARD_SERVICE) as ClipboardManager
    // When setting the clipboard text.
    clipboardManager.setPrimaryClip(ClipData.newPlainText   ("", textCopied))
    // Only show a toast for Android 12 and lower.
    if (Build.VERSION.SDK_INT <= Build.VERSION_CODES.S_V2)
        Toast.makeText(context, Copied, Toast.LENGTH_SHORT).show()
}

เพิ่มเนื้อหาที่ละเอียดอ่อนลงในคลิปบอร์ด

หากแอปอนุญาตให้ผู้ใช้คัดลอกเนื้อหาที่ละเอียดอ่อนไปยังคลิปบอร์ด เช่น รหัสผ่านหรือข้อมูลบัตรเครดิต คุณต้องเพิ่ม Flag ไปยัง ClipDescription ใน ClipData ก่อนเรียกใช้ ClipboardManager.setPrimaryClip() การเพิ่มฟีเจอร์นี้จะป้องกันไม่ให้เนื้อหาที่มีความละเอียดอ่อน ปรากฏในการยืนยันด้วยภาพของเนื้อหาที่คัดลอกใน Android 13 ขึ้นไป

ตัวอย่างข้อความที่คัดลอกโดยไม่ต้องแจ้งเนื้อหาที่ละเอียดอ่อน
รูปที่ 6 ตัวอย่างข้อความที่คัดลอกโดยไม่มีการแจ้งว่ามีเนื้อหาที่ละเอียดอ่อน
การแจ้งว่าเนื้อหาที่คัดลอกมีเนื้อหาที่ละเอียดอ่อน
รูปที่ 7 คัดลอกตัวอย่างข้อความพร้อมด้วยธงเนื้อหาที่ละเอียดอ่อน

หากต้องการแจ้งเนื้อหาที่ละเอียดอ่อน ให้เพิ่มบูลีนพิเศษไปยัง ClipDescription แอปทั้งหมดต้องทำ เช่นนี้ ไม่ว่าระดับ API ที่กำหนดเป้าหมายจะเป็นระดับใดก็ตาม

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

วางจากคลิปบอร์ด

ดังที่อธิบายไว้ก่อนหน้านี้ ให้วางข้อมูลจากคลิปบอร์ดโดยรับออบเจ็กต์คลิปบอร์ดส่วนกลาง รับออบเจ็กต์คลิป ดูข้อมูลของออบเจ็กต์ และหากเป็นไปได้ ให้คัดลอกข้อมูลจากออบเจ็กต์คลิป ไปยังพื้นที่เก็บข้อมูลของคุณเอง ส่วนนี้จะอธิบายโดยละเอียดถึงวิธีวางข้อมูลในคลิปบอร์ดทั้ง 3 รูปแบบ

วางข้อความธรรมดา

หากต้องการวางข้อความธรรมดา ให้รับคลิปบอร์ดส่วนกลางและตรวจสอบว่าคลิปบอร์ดสามารถส่งคืนข้อความธรรมดาได้ จากนั้นรับ ออบเจ็กต์คลิปและคัดลอกข้อความไปยังที่เก็บข้อมูลของคุณเองโดยใช้ getText() ตามที่อธิบายไว้ใน ขั้นตอนต่อไปนี้

  1. รับออบเจ็กต์ ClipboardManager ส่วนกลางโดยใช้ getSystemService(CLIPBOARD_SERVICE) นอกจากนี้ ให้ประกาศตัวแปรร่วมเพื่อเก็บ ข้อความที่วาง

    Kotlin

    var clipboard = getSystemService(Context.CLIPBOARD_SERVICE) as ClipboardManager
    var pasteData: String = ""

    Java

    ClipboardManager clipboard = (ClipboardManager) getSystemService(Context.CLIPBOARD_SERVICE);
    String pasteData = "";
  2. พิจารณาว่าคุณต้องเปิดหรือปิดใช้ตัวเลือก "วาง" ในกิจกรรมปัจจุบันหรือไม่ ตรวจสอบว่าคลิปบอร์ดมีคลิปและคุณสามารถจัดการประเภทข้อมูล ที่แสดงโดยคลิปได้

    Kotlin

    // Gets the ID of the "paste" menu item.
    val pasteItem: MenuItem = menu.findItem(R.id.menu_paste)
    
    // If the clipboard doesn't contain data, disable the paste menu item.
    // If it does contain data, decide whether you can handle the data.
    pasteItem.isEnabled = when {
        !clipboard.hasPrimaryClip() -> {
            false
        }
        !(clipboard.primaryClipDescription.hasMimeType(MIMETYPE_TEXT_PLAIN)) -> {
            // Disables the paste menu item, since the clipboard has data but it
            // isn't plain text.
            false
        }
        else -> {
            // Enables the paste menu item, since the clipboard contains plain text.
            true
        }
    }

    Java

    // Gets the ID of the "paste" menu item.
    MenuItem pasteItem = menu.findItem(R.id.menu_paste);
    
    // If the clipboard doesn't contain data, disable the paste menu item.
    // If it does contain data, decide whether you can handle the data.
    if (!(clipboard.hasPrimaryClip())) {
    
        pasteItem.setEnabled(false);
    
    } else if (!(clipboard.getPrimaryClipDescription().hasMimeType(MIMETYPE_TEXT_PLAIN))) {
    
        // Disables the paste menu item, since the clipboard has data but
        // it isn't plain text.
        pasteItem.setEnabled(false);
    } else {
    
        // Enables the paste menu item, since the clipboard contains plain text.
        pasteItem.setEnabled(true);
    }
  3. คัดลอกข้อมูลจากคลิปบอร์ด จุดนี้ในโค้ดจะเข้าถึงได้ก็ต่อเมื่อเปิดใช้รายการเมนู "วาง" เท่านั้น ดังนั้นคุณจึงถือได้ว่าคลิปบอร์ดมีข้อความธรรมดา คุณยังไม่ทราบว่ามีสตริงข้อความหรือ URI ที่ชี้ไปยังข้อความธรรมดาหรือไม่ ข้อมูลโค้ดต่อไปนี้จะทดสอบการดำเนินการนี้ แต่จะแสดงเฉพาะโค้ดสำหรับการจัดการข้อความธรรมดา

    Kotlin

    when (menuItem.itemId) {
        ...
        R.id.menu_paste -> {    // Responds to the user selecting "paste".
            // Examines the item on the clipboard. If getText() doesn't return null,
            // the clip item contains the text. Assumes that this application can only
            // handle one item at a time.
            val item = clipboard.primaryClip.getItemAt(0)
    
            // Gets the clipboard as text.
            pasteData = item.text
    
            return if (pasteData != null) {
                // If the string contains data, then the paste operation is done.
                true
            } else {
                // The clipboard doesn't contain text. If it contains a URI,
                // attempts to get data from it.
                val pasteUri: Uri? = item.uri
    
                if (pasteUri != null) {
                    // If the URI contains something, try to get text from it.
    
                    // Calls a routine to resolve the URI and get data from it.
                    // This routine isn't presented here.
                    pasteData = resolveUri(pasteUri)
                    true
                } else {
    
                    // Something is wrong. The MIME type was plain text, but the
                    // clipboard doesn't contain text or a Uri. Report an error.
                    Log.e(TAG,"Clipboard contains an invalid data type")
                    false
                }
            }
        }
    }

    Java

    // Responds to the user selecting "paste".
    case R.id.menu_paste:
    
    // Examines the item on the clipboard. If getText() does not return null,
    // the clip item contains the text. Assumes that this application can only
    // handle one item at a time.
     ClipData.Item item = clipboard.getPrimaryClip().getItemAt(0);
    
    // Gets the clipboard as text.
    pasteData = item.getText();
    
    // If the string contains data, then the paste operation is done.
    if (pasteData != null) {
        return true;
    
    // The clipboard doesn't contain text. If it contains a URI, attempts to get
    // data from it.
    } else {
        Uri pasteUri = item.getUri();
    
        // If the URI contains something, try to get text from it.
        if (pasteUri != null) {
    
            // Calls a routine to resolve the URI and get data from it.
            // This routine isn't presented here.
            pasteData = resolveUri(Uri);
            return true;
        } else {
    
            // Something is wrong. The MIME type is plain text, but the
            // clipboard doesn't contain text or a Uri. Report an error.
            Log.e(TAG, "Clipboard contains an invalid data type");
            return false;
        }
    }

วางข้อมูลจาก URI ของเนื้อหา

หากออบเจ็กต์ ClipData.Item มี URI เนื้อหาและคุณพิจารณาว่าสามารถ จัดการประเภท MIME อย่างใดอย่างหนึ่งได้ ให้สร้าง ContentResolver และเรียกใช้เมธอด Content Provider ที่เหมาะสมเพื่อดึงข้อมูล

ขั้นตอนต่อไปนี้อธิบายวิธีรับข้อมูลจากผู้ให้บริการเนื้อหาโดยอิงตาม URI ของเนื้อหา ในคลิปบอร์ด โดยจะตรวจสอบว่ามีประเภท MIME ที่แอปพลิเคชันใช้ได้จากผู้ให้บริการหรือไม่

  1. ประกาศตัวแปรร่วมให้มีประเภท MIME ดังนี้

    Kotlin

    // Declares a MIME type constant to match against the MIME types offered
    // by the provider.
    const val MIME_TYPE_CONTACT = "vnd.android.cursor.item/vnd.example.contact"

    Java

    // Declares a MIME type constant to match against the MIME types offered by
    // the provider.
    public static final String MIME_TYPE_CONTACT = "vnd.android.cursor.item/vnd.example.contact";
  2. รับคลิปบอร์ดส่วนกลาง นอกจากนี้ ให้รับตัวแก้ไขเนื้อหาเพื่อให้คุณเข้าถึงผู้ให้บริการเนื้อหาได้

    Kotlin

    // Gets a handle to the Clipboard Manager.
    val clipboard = getSystemService(Context.CLIPBOARD_SERVICE) as ClipboardManager
    
    // Gets a content resolver instance.
    val cr = contentResolver

    Java

    // Gets a handle to the Clipboard Manager.
    ClipboardManager clipboard = (ClipboardManager) getSystemService(Context.CLIPBOARD_SERVICE);
    
    // Gets a content resolver instance.
    ContentResolver cr = getContentResolver();
  3. รับคลิปหลักจากคลิปบอร์ดและรับเนื้อหาเป็น URI โดยทำดังนี้

    Kotlin

    // Gets the clipboard data from the clipboard.
    val clip: ClipData? = clipboard.primaryClip
    
    clip?.run {
    
        // Gets the first item from the clipboard data.
        val item: ClipData.Item = getItemAt(0)
    
        // Tries to get the item's contents as a URI.
        val pasteUri: Uri? = item.uri

    Java

    // Gets the clipboard data from the clipboard.
    ClipData clip = clipboard.getPrimaryClip();
    
    if (clip != null) {
    
        // Gets the first item from the clipboard data.
        ClipData.Item item = clip.getItemAt(0);
    
        // Tries to get the item's contents as a URI.
        Uri pasteUri = item.getUri();
  4. ทดสอบว่า URI เป็น URI เนื้อหาหรือไม่โดยเรียกใช้ getType(Uri) เมธอดนี้จะแสดงผลเป็น Null หาก Uri ไม่ได้ชี้ไปยังผู้ให้บริการเนื้อหาที่ถูกต้อง

    Kotlin

        // If the clipboard contains a URI reference...
        pasteUri?.let {
    
            // ...is this a content URI?
            val uriMimeType: String? = cr.getType(it)

    Java

        // If the clipboard contains a URI reference...
        if (pasteUri != null) {
    
            // ...is this a content URI?
            String uriMimeType = cr.getType(pasteUri);
  5. ทดสอบว่าผู้ให้บริการเนื้อหารองรับประเภท MIME ที่แอปพลิเคชันเข้าใจหรือไม่ หากมี ให้เรียกใช้ ContentResolver.query() เพื่อรับข้อมูล ค่าที่ส่งกลับคือ Cursor

    Kotlin

            // If the return value isn't null, the Uri is a content Uri.
            uriMimeType?.takeIf {
    
                // Does the content provider offer a MIME type that the current
                // application can use?
                it == MIME_TYPE_CONTACT
            }?.apply {
    
                // Get the data from the content provider.
                cr.query(pasteUri, null, null, null, null)?.use { pasteCursor ->
    
                    // If the Cursor contains data, move to the first record.
                    if (pasteCursor.moveToFirst()) {
    
                        // Get the data from the Cursor here.
                        // The code varies according to the format of the data model.
                    }
    
                    // Kotlin `use` automatically closes the Cursor.
                }
            }
        }
    }

    Java

            // If the return value isn't null, the Uri is a content Uri.
            if (uriMimeType != null) {
    
                // Does the content provider offer a MIME type that the current
                // application can use?
                if (uriMimeType.equals(MIME_TYPE_CONTACT)) {
    
                    // Get the data from the content provider.
                    Cursor pasteCursor = cr.query(uri, null, null, null, null);
    
                    // If the Cursor contains data, move to the first record.
                    if (pasteCursor != null) {
                        if (pasteCursor.moveToFirst()) {
    
                        // Get the data from the Cursor here.
                        // The code varies according to the format of the data model.
                        }
                    }
    
                    // Close the Cursor.
                    pasteCursor.close();
                 }
             }
         }
    }

วางความตั้งใจ

หากต้องการวาง Intent ให้รับคลิปบอร์ดส่วนกลางก่อน ตรวจสอบออบเจ็กต์ ClipData.Item เพื่อดูว่ามี Intent หรือไม่ จากนั้นเรียกใช้ getIntent() เพื่อคัดลอก Intent ไปยังที่เก็บข้อมูลของคุณเอง ข้อมูลโค้ดต่อไปนี้แสดงให้เห็นถึงการดำเนินการนี้

Kotlin

// Gets a handle to the Clipboard Manager.
val clipboard = getSystemService(Context.CLIPBOARD_SERVICE) as ClipboardManager

// Checks whether the clip item contains an Intent by testing whether
// getIntent() returns null.
val pasteIntent: Intent? = clipboard.primaryClip?.getItemAt(0)?.intent

if (pasteIntent != null) {

    // Handle the Intent.

} else {

    // Ignore the clipboard, or issue an error if
    // you expect an Intent to be on the clipboard.
}

Java

// Gets a handle to the Clipboard Manager.
ClipboardManager clipboard = (ClipboardManager) getSystemService(Context.CLIPBOARD_SERVICE);

// Checks whether the clip item contains an Intent, by testing whether
// getIntent() returns null.
Intent pasteIntent = clipboard.getPrimaryClip().getItemAt(0).getIntent();

if (pasteIntent != null) {

    // Handle the Intent.

} else {

    // Ignore the clipboard, or issue an error if
    // you expect an Intent to be on the clipboard.
}

การแจ้งเตือนของระบบจะแสดงเมื่อแอปเข้าถึงข้อมูลในคลิปบอร์ด

ใน Android 12 (API ระดับ 31) ขึ้นไป โดยปกติแล้วระบบจะแสดงข้อความ Toast เมื่อแอปของคุณ เรียกใช้ getPrimaryClip() ข้อความภายในข้อความมีรูปแบบต่อไปนี้

APP pasted from your clipboard

ระบบจะไม่แสดงข้อความป๊อปอัปเมื่อแอปของคุณทำอย่างใดอย่างหนึ่งต่อไปนี้

  • เข้าถึง ClipData จากแอปของคุณเอง
  • เข้าถึง ClipData จากแอปหนึ่งๆ ซ้ำๆ การแจ้งเตือนจะปรากฏขึ้นเมื่อ แอปของคุณเข้าถึงข้อมูลจากแอปนั้นเป็นครั้งแรกเท่านั้น
  • เรียกข้อมูลเมตาสำหรับออบเจ็กต์คลิป เช่น โดยการเรียก getPrimaryClipDescription() แทน getPrimaryClip()

ใช้ผู้ให้บริการเนื้อหาเพื่อคัดลอกข้อมูลที่ซับซ้อน

ผู้ให้บริการเนื้อหารองรับการคัดลอกข้อมูลที่ซับซ้อน เช่น บันทึกฐานข้อมูลหรือสตรีมไฟล์ หากต้องการคัดลอก ข้อมูล ให้วาง URI ของเนื้อหาลงในคลิปบอร์ด จากนั้นแอปพลิเคชันที่วางจะรับ URI นี้จากคลิปบอร์ดและใช้เพื่อดึงข้อมูลฐานข้อมูลหรือตัวอธิบายสตรีมไฟล์

เนื่องจากแอปพลิเคชันที่วางมีเพียง URI เนื้อหาสำหรับข้อมูลของคุณ จึงต้องทราบว่าควรดึงข้อมูลใด คุณระบุข้อมูลนี้ได้โดยการเข้ารหัสตัวระบุสำหรับข้อมูล ใน URI เอง หรือจะระบุ URI ที่ไม่ซ้ำกันซึ่งแสดงข้อมูลที่คุณต้องการคัดลอกก็ได้ เทคนิคที่คุณเลือกจะขึ้นอยู่กับการจัดระเบียบข้อมูล

ส่วนต่อไปนี้จะอธิบายวิธีตั้งค่า URI ระบุข้อมูลที่ซับซ้อน และระบุสตรีมไฟล์ คำอธิบายจะถือว่าคุณคุ้นเคยกับหลักการทั่วไปของการออกแบบผู้ให้บริการเนื้อหา

เข้ารหัสตัวระบุใน URI

เทคนิคที่มีประโยชน์ในการคัดลอกข้อมูลไปยังคลิปบอร์ดด้วย URI คือการเข้ารหัสตัวระบุสำหรับ ข้อมูลใน URI เอง จากนั้นผู้ให้บริการเนื้อหาจะรับตัวระบุจาก URI และใช้ เพื่อดึงข้อมูลได้ แอปพลิเคชันที่วางไม่จำเป็นต้องทราบว่ามีตัวระบุอยู่ เพียงแค่ต้องรับ "ข้อมูลอ้างอิง" ซึ่งก็คือ URI บวกตัวระบุจากคลิปบอร์ด ส่งให้ผู้ให้บริการเนื้อหา แล้วรับข้อมูลกลับมา

โดยปกติแล้ว คุณจะเข้ารหัสตัวระบุลงใน URI ของเนื้อหาด้วยการต่อท้ายตัวระบุไว้ที่ท้าย URI เช่น สมมติว่าคุณกำหนด URI ของผู้ให้บริการเป็นสตริงต่อไปนี้

"content://com.example.contacts"

หากต้องการเข้ารหัสชื่อลงใน URI นี้ ให้ใช้ข้อมูลโค้ดต่อไปนี้

Kotlin

val uriString = "content://com.example.contacts/Smith"

// uriString now contains content://com.example.contacts/Smith.

// Generates a uri object from the string representation.
val copyUri = Uri.parse(uriString)

Java

String uriString = "content://com.example.contacts" + "/" + "Smith";

// uriString now contains content://com.example.contacts/Smith.

// Generates a uri object from the string representation.
Uri copyUri = Uri.parse(uriString);

หากใช้ผู้ให้บริการเนื้อหาอยู่แล้ว คุณอาจต้องเพิ่มเส้นทาง URI ใหม่ที่ระบุว่า URI ใช้สำหรับการคัดลอก ตัวอย่างเช่น สมมติว่าคุณมีเส้นทาง URI ต่อไปนี้อยู่แล้ว

"content://com.example.contacts/people"
"content://com.example.contacts/people/detail"
"content://com.example.contacts/people/images"

คุณเพิ่มเส้นทางอื่นสำหรับการคัดลอก URI ได้โดยทำดังนี้

"content://com.example.contacts/copying"

จากนั้นคุณจะตรวจหา URI "สำเนา" ได้โดยการจับคู่รูปแบบและจัดการด้วยโค้ดที่ เฉพาะสำหรับการคัดลอกและวาง

โดยปกติแล้ว คุณจะใช้เทคนิคการเข้ารหัสหากใช้ผู้ให้บริการเนื้อหา ฐานข้อมูลภายใน หรือตารางภายในเพื่อจัดระเบียบข้อมูลอยู่แล้ว ในกรณีเหล่านี้ คุณมีข้อมูลหลายรายการที่ต้องการคัดลอก และอาจมีตัวระบุที่ไม่ซ้ำกันสำหรับแต่ละรายการ เมื่อได้รับคำค้นหาจากแอปพลิเคชันที่วาง คุณสามารถค้นหาข้อมูลตามตัวระบุและส่งคืนได้

หากไม่มีข้อมูลหลายส่วน คุณอาจไม่จำเป็นต้องเข้ารหัสตัวระบุ คุณใช้ URI ที่ไม่ซ้ำกันสำหรับผู้ให้บริการได้ เมื่อได้รับการค้นหา ผู้ให้บริการจะแสดงข้อมูลที่ตนมีอยู่ในปัจจุบัน

คัดลอกโครงสร้างข้อมูล

ตั้งค่าผู้ให้บริการเนื้อหาสำหรับการคัดลอกและวางข้อมูลที่ซับซ้อนเป็นคลาสย่อยของ ContentProvider คอมโพเนนต์ เข้ารหัส URI ที่คุณวางไว้ในคลิปบอร์ดเพื่อให้ชี้ไปยังระเบียนที่ต้องการ ระบุ นอกจากนี้ ให้พิจารณาสถานะปัจจุบันของแอปพลิเคชันด้วย

  • หากมีผู้ให้บริการเนื้อหาอยู่แล้ว คุณสามารถเพิ่มฟังก์ชันการทำงานได้ คุณอาจต้องแก้ไขเฉพาะเมธอด query() เพื่อจัดการ URI ที่มาจากแอปพลิเคชันที่ต้องการวางข้อมูล คุณอาจต้องการแก้ไขวิธีการเพื่อจัดการรูปแบบ URI "copy"
  • หากแอปพลิเคชันของคุณมีฐานข้อมูลภายใน คุณอาจต้องย้ายฐานข้อมูลนี้ ไปยัง Content Provider เพื่อให้คัดลอกข้อมูลจากฐานข้อมูลได้ง่ายขึ้น
  • หากไม่ได้ใช้ฐานข้อมูล คุณสามารถใช้ Content Provider แบบง่ายๆ ซึ่งมี วัตถุประสงค์เพียงอย่างเดียวคือการเสนอข้อมูลให้กับแอปพลิเคชันที่วางจากคลิปบอร์ด

ในผู้ให้บริการเนื้อหา ให้ลบล้างเมธอดต่อไปนี้อย่างน้อย

query()
แอปพลิเคชันที่วางจะถือว่าสามารถรับข้อมูลของคุณได้โดยใช้วิธีนี้กับ URI ที่คุณ วางไว้ในคลิปบอร์ด หากต้องการรองรับการคัดลอก ให้ใช้วิธีนี้เพื่อตรวจหา URI ที่มีเส้นทาง "copy" พิเศษ จากนั้นแอปพลิเคชันจะสร้าง URI "สำเนา" เพื่อวางใน คลิปบอร์ด ซึ่งมีเส้นทางการคัดลอกและตัวชี้ไปยังระเบียนที่แน่นอนที่คุณต้องการคัดลอก
getType()
เมธอดนี้ต้องแสดงผลประเภท MIME สำหรับข้อมูลที่คุณต้องการคัดลอก เมธอด newUri() เรียก getType() เพื่อใส่ประเภท MIME ลงในออบเจ็กต์ ClipData ใหม่

ประเภท MIME สำหรับข้อมูลที่ซับซ้อนอธิบายไว้ใน ผู้ให้บริการเนื้อหา

คุณไม่จำเป็นต้องมีวิธีการของผู้ให้บริการเนื้อหารายอื่นๆ เช่น insert() หรือ update() แอปพลิเคชันที่วางจะต้องได้รับ MIME ประเภทที่รองรับและคัดลอกข้อมูลจากผู้ให้บริการเท่านั้น หากคุณมีวิธีการเหล่านี้อยู่แล้ว วิธีการดังกล่าวจะไม่รบกวนการดำเนินการคัดลอก

ข้อมูลโค้ดต่อไปนี้แสดงวิธีตั้งค่าแอปพลิเคชันเพื่อคัดลอกข้อมูลที่ซับซ้อน

  1. ในค่าคงที่ส่วนกลางของแอปพลิเคชัน ให้ประกาศสตริง URI ฐานและเส้นทางที่ ระบุสตริง URI ที่คุณใช้เพื่อคัดลอกข้อมูล และประกาศประเภท MIME สำหรับข้อมูลที่คัดลอกด้วย

    Kotlin

    // Declares the base URI string.
    private const val CONTACTS = "content://com.example.contacts"
    
    // Declares a path string for URIs that you use to copy data.
    private const val COPY_PATH = "/copy"
    
    // Declares a MIME type for the copied data.
    const val MIME_TYPE_CONTACT = "vnd.android.cursor.item/vnd.example.contact"

    Java

    // Declares the base URI string.
    private static final String CONTACTS = "content://com.example.contacts";
    
    // Declares a path string for URIs that you use to copy data.
    private static final String COPY_PATH = "/copy";
    
    // Declares a MIME type for the copied data.
    public static final String MIME_TYPE_CONTACT = "vnd.android.cursor.item/vnd.example.contact";
  2. ในกิจกรรมที่ผู้ใช้คัดลอกข้อมูล ให้ตั้งค่าโค้ดเพื่อคัดลอกข้อมูลไปยังคลิปบอร์ด เมื่อได้รับคำขอคัดลอก ให้วาง URI ไว้ในคลิปบอร์ด

    Kotlin

    class MyCopyActivity : Activity() {
        ...
    when(item.itemId) {
        R.id.menu_copy -> { // The user has selected a name and is requesting a copy.
            // Appends the last name to the base URI.
            // The name is stored in "lastName".
            uriString = "$CONTACTS$COPY_PATH/$lastName"
    
            // Parses the string into a URI.
            val copyUri: Uri? = Uri.parse(uriString)
    
            // Gets a handle to the clipboard service.
            val clipboard = getSystemService(Context.CLIPBOARD_SERVICE) as ClipboardManager
    
            val clip: ClipData = ClipData.newUri(contentResolver, "URI", copyUri)
    
            // Sets the clipboard's primary clip.
            clipboard.setPrimaryClip(clip)
        }
    }

    Java

    public class MyCopyActivity extends Activity {
        ...
    // The user has selected a name and is requesting a copy.
    case R.id.menu_copy:
    
        // Appends the last name to the base URI.
        // The name is stored in "lastName".
        uriString = CONTACTS + COPY_PATH + "/" + lastName;
    
        // Parses the string into a URI.
        Uri copyUri = Uri.parse(uriString);
    
        // Gets a handle to the clipboard service.
        ClipboardManager clipboard = (ClipboardManager)
            getSystemService(Context.CLIPBOARD_SERVICE);
    
        ClipData clip = ClipData.newUri(getContentResolver(), "URI", copyUri);
    
        // Sets the clipboard's primary clip.
        clipboard.setPrimaryClip(clip);
  3. ในขอบเขตทั่วโลกของผู้ให้บริการเนื้อหา ให้สร้างตัวจับคู่ URI และเพิ่มรูปแบบ URI ที่ ตรงกับ URI ที่คุณวางไว้ในคลิปบอร์ด

    Kotlin

    // A Uri Match object that simplifies matching content URIs to patterns.
    private val sUriMatcher = UriMatcher(UriMatcher.NO_MATCH).apply {
    
        // Adds a matcher for the content URI. It matches.
        // "content://com.example.contacts/copy/*"
        addURI(CONTACTS, "names/*", GET_SINGLE_CONTACT)
    }
    
    // An integer to use in switching based on the incoming URI pattern.
    private const val GET_SINGLE_CONTACT = 0
    ...
    class MyCopyProvider : ContentProvider() {
        ...
    }

    Java

    public class MyCopyProvider extends ContentProvider {
        ...
    // A Uri Match object that simplifies matching content URIs to patterns.
    private static final UriMatcher sURIMatcher = new UriMatcher(UriMatcher.NO_MATCH);
    
    // An integer to use in switching based on the incoming URI pattern.
    private static final int GET_SINGLE_CONTACT = 0;
    ...
    // Adds a matcher for the content URI. It matches
    // "content://com.example.contacts/copy/*"
    sUriMatcher.addURI(CONTACTS, "names/*", GET_SINGLE_CONTACT);
  4. ตั้งค่า query() วิธี วิธีนี้สามารถจัดการรูปแบบ URI ที่แตกต่างกันได้ ขึ้นอยู่กับวิธีที่คุณเขียนโค้ด แต่จะแสดงเฉพาะรูปแบบสำหรับการดำเนินการคัดลอกไปยังคลิปบอร์ดเท่านั้น

    Kotlin

    // Sets up your provider's query() method.
    override fun query(
            uri: Uri,
            projection: Array<out String>?,
            selection: String?,
            selectionArgs: Array<out String>?,
            sortOrder: String?
    ): Cursor? {
        ...
        // When based on the incoming content URI:
        when(sUriMatcher.match(uri)) {
    
            GET_SINGLE_CONTACT -> {
    
                // Queries and returns the contact for the requested name. Decodes
                // the incoming URI, queries the data model based on the last name,
                // and returns the result as a Cursor.
            }
        }
        ...
    }

    Java

    // Sets up your provider's query() method.
    public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs,
        String sortOrder) {
        ...
        // Switch based on the incoming content URI.
        switch (sUriMatcher.match(uri)) {
    
        case GET_SINGLE_CONTACT:
    
            // Queries and returns the contact for the requested name. Decodes the
            // incoming URI, queries the data model based on the last name, and
            // returns the result as a Cursor.
        ...
    }
  5. ตั้งค่าเมธอด getType() เพื่อแสดงผลประเภท MIME ที่เหมาะสมสำหรับข้อมูลที่คัดลอก ดังนี้

    Kotlin

    // Sets up your provider's getType() method.
    override fun getType(uri: Uri): String? {
        ...
        return when(sUriMatcher.match(uri)) {
            GET_SINGLE_CONTACT -> MIME_TYPE_CONTACT
            ...
        }
    }

    Java

    // Sets up your provider's getType() method.
    public String getType(Uri uri) {
        ...
        switch (sUriMatcher.match(uri)) {
        case GET_SINGLE_CONTACT:
            return (MIME_TYPE_CONTACT);
        ...
        }
    }

ส่วนวางข้อมูลจาก URI ของเนื้อหาอธิบายวิธีรับ URI ของเนื้อหาจากคลิปบอร์ดและใช้เพื่อรับและวางข้อมูล

คัดลอกสตรีมข้อมูล

คุณสามารถคัดลอกและวางข้อความและข้อมูลไบนารีจำนวนมากเป็นสตรีมได้ ข้อมูลอาจมีรูปแบบดังต่อไปนี้

  • ไฟล์ที่จัดเก็บไว้ในอุปกรณ์จริง
  • สตรีมจากซ็อกเก็ต
  • ข้อมูลจำนวนมากที่จัดเก็บไว้ในระบบฐานข้อมูลพื้นฐานของผู้ให้บริการ

ผู้ให้บริการเนื้อหาสำหรับสตรีมข้อมูลจะให้สิทธิ์เข้าถึงข้อมูลของตนด้วยออบเจ็กต์ตัวอธิบายไฟล์ เช่น AssetFileDescriptor แทนออบเจ็กต์ Cursor แอปพลิเคชันที่วางจะอ่านสตรีมข้อมูลโดยใช้ ตัวอธิบายไฟล์นี้

หากต้องการตั้งค่าแอปพลิเคชันให้คัดลอกสตรีมข้อมูลกับผู้ให้บริการ ให้ทำตามขั้นตอนต่อไปนี้

  1. ตั้งค่า URI ของเนื้อหาสําหรับสตรีมข้อมูลที่คุณวางไว้ในคลิปบอร์ด ตัวเลือก ในการดำเนินการนี้มีดังนี้
    • เข้ารหัสตัวระบุสำหรับสตรีมข้อมูลลงใน URI ตามที่อธิบายไว้ในส่วนเข้ารหัสตัวระบุใน URI จากนั้นให้ดูแลตารางในผู้ให้บริการที่มีตัวระบุและชื่อสตรีมที่เกี่ยวข้อง
    • เข้ารหัสชื่อสตรีมใน URI โดยตรง
    • ใช้ URI ที่ไม่ซ้ำกันซึ่งจะแสดงสตรีมปัจจุบันจากผู้ให้บริการเสมอ หากคุณ ใช้ตัวเลือกนี้ โปรดอย่าลืมอัปเดตผู้ให้บริการให้ชี้ไปยังสตรีมอื่น ทุกครั้งที่คุณคัดลอกสตรีมไปยังคลิปบอร์ดโดยใช้ URI
  2. ระบุประเภท MIME สำหรับสตรีมข้อมูลแต่ละประเภทที่คุณวางแผนจะเสนอ แอปพลิเคชันที่วาง ต้องใช้ข้อมูลนี้เพื่อพิจารณาว่าจะวางข้อมูลในคลิปบอร์ดได้หรือไม่
  3. ใช้ContentProviderวิธีใดวิธีหนึ่งที่แสดงผลตัวอธิบายไฟล์สำหรับ สตรีม หากเข้ารหัสตัวระบุใน URI ของเนื้อหา ให้ใช้วิธีนี้เพื่อกำหนดสตรีมที่จะเปิด
  4. หากต้องการคัดลอกสตรีมข้อมูลไปยังคลิปบอร์ด ให้สร้าง URI ของเนื้อหาและวางไว้ใน คลิปบอร์ด

หากต้องการวางสตรีมข้อมูล แอปพลิเคชันจะรับคลิปจากคลิปบอร์ด รับ URI และใช้ ในคำสั่งเรียกใช้เมธอดตัวอธิบายไฟล์ ContentResolver ที่เปิดสตรีม เมธอด ContentResolver จะเรียกเมธอด ContentProvider ที่เกี่ยวข้อง โดยส่ง URI ของเนื้อหาไปให้ ผู้ให้บริการจะแสดงตัวอธิบายไฟล์ไปยังเมธอด ContentResolver จากนั้นแอปพลิเคชันที่วางจะมีหน้าที่อ่าน ข้อมูลจากสตรีม

รายการต่อไปนี้แสดงวิธีการระบุไฟล์ที่สำคัญที่สุดสำหรับผู้ให้บริการเนื้อหา แต่ละรายการมีเมธอด ContentResolver ที่สอดคล้องกันโดยมีสตริง "Descriptor" ต่อท้ายชื่อเมธอด ตัวอย่างเช่น ContentResolver อนาล็อกของ openAssetFile() คือ openAssetFileDescriptor()

openTypedAssetFile()

วิธีนี้จะแสดงตัวอธิบายไฟล์ชิ้นงาน แต่จะแสดงก็ต่อเมื่อผู้ให้บริการรองรับประเภท MIME ที่ระบุเท่านั้น ผู้เรียกใช้ ซึ่งก็คือแอปพลิเคชันที่ทำการวาง จะระบุรูปแบบประเภท MIME ผู้ให้บริการเนื้อหาของแอปพลิเคชันที่คัดลอก URI ไปยัง คลิปบอร์ดจะส่งคืนAssetFileDescriptorแฮนเดิลไฟล์หากระบุประเภท MIME นั้นได้ และจะส่งข้อยกเว้นหากระบุไม่ได้

วิธีนี้จะจัดการส่วนย่อยของไฟล์ คุณสามารถใช้ฟีเจอร์นี้เพื่ออ่านเนื้อหาที่ผู้ให้บริการเนื้อหาคัดลอกไปยังคลิปบอร์ดได้

openAssetFile()
เมธอดนี้เป็นรูปแบบทั่วไปของ openTypedAssetFile() โดยจะไม่กรอง ประเภท MIME ที่อนุญาต แต่จะอ่านส่วนย่อยของไฟล์ได้
openFile()
นี่คือรูปแบบทั่วไปของ openAssetFile() อ่านส่วนย่อยของ ไฟล์ไม่ได้

คุณเลือกใช้เมธอด openPipeHelper() กับเมธอดตัวอธิบายไฟล์ได้ ซึ่งช่วยให้แอปพลิเคชันที่วางอ่านข้อมูลสตรีมใน เธรดเบื้องหลังโดยใช้ไปป์ได้ หากต้องการใช้วิธีนี้ ให้ใช้ ContentProvider.PipeDataWriter อินเทอร์เฟซ

ออกแบบฟังก์ชันการคัดลอกและวางที่มีประสิทธิภาพ

หากต้องการออกแบบฟังก์ชันการคัดลอกและวางที่มีประสิทธิภาพสำหรับแอปพลิเคชัน โปรดคำนึงถึงประเด็นต่อไปนี้

  • โดยในแต่ละครั้งจะมีคลิปบอร์ดได้เพียงคลิปเดียว การคัดลอกใหม่โดยแอปพลิเคชันใดๆ ในระบบจะเขียนทับคลิปก่อนหน้า เนื่องจากผู้ใช้อาจออกจากแอปพลิเคชันของคุณและคัดลอกก่อนที่จะกลับมา คุณจึงไม่สามารถถือว่าคลิปบอร์ดมีคลิปที่ผู้ใช้คัดลอกไว้ก่อนหน้านี้ในแอปพลิเคชันของคุณ
  • วัตถุ ClipData.Item หลายรายการต่อคลิปมีจุดประสงค์เพื่อ รองรับการคัดลอกและวางรายการที่เลือกหลายรายการแทนที่จะเป็นรูปแบบต่างๆ ของ การอ้างอิงถึงรายการที่เลือกรายการเดียว โดยปกติแล้ว คุณต้องการให้ClipData.Item ออบเจ็กต์ทั้งหมดในคลิปมีรูปแบบเดียวกัน กล่าวคือ ต้องเป็นข้อความธรรมดา เนื้อหา URI หรือ Intent ทั้งหมด และห้ามใช้ร่วมกัน
  • เมื่อระบุข้อมูล คุณสามารถเสนอการแสดง MIME ที่แตกต่างกันได้ เพิ่มประเภท MIME ที่คุณรองรับลงใน ClipDescription แล้วใช้ประเภท MIME ใน ผู้ให้บริการเนื้อหา
  • เมื่อรับข้อมูลจากคลิปบอร์ด แอปพลิเคชันของคุณมีหน้าที่ตรวจสอบ ประเภท MIME ที่ใช้ได้ แล้วตัดสินใจว่าจะใช้ประเภทใด (หากมี) แม้ว่าจะมีคลิปในคลิปบอร์ดและผู้ใช้ขอวาง แต่แอปพลิเคชันของคุณก็ไม่จำเป็นต้องวาง วางหากประเภท MIME เข้ากันได้ คุณอาจแปลงข้อมูล ในคลิปบอร์ดเป็นข้อความโดยใช้ coerceToText() หากแอปพลิเคชันรองรับ MIME ประเภทที่มีให้มากกว่า 1 ประเภท คุณสามารถให้ผู้ใช้เลือกประเภทที่จะใช้ได้