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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

ClipboardManager

คลิปบอร์ดของระบบ Android จะแสดงด้วยคลาส 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 ดังนี้

ข้อความ
CharSequence
URI
ก Uri โดยปกติแล้วจะมี URI ของ Content Provider แม้ว่าจะอนุญาตให้ใช้ URI ใดก็ได้ก็ตาม แอปพลิเคชันที่ให้ข้อมูลจะใส่ URI ไว้ในคลิปบอร์ด แอปพลิเคชันที่ต้องการวางข้อมูลจะรับ URI จากคลิปบอร์ดและใช้เพื่อเข้าถึง Content Provider หรือแหล่งข้อมูลอื่นๆ และดึงข้อมูล
ความตั้งใจ
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 ที่พร้อมใช้งานจาก Content Provider จากนั้นจะจัดเก็บไว้ใน ClipDescription สำหรับ URI ที่ไม่ใช่ URI content: เมธอดจะตั้งค่าประเภท MIME เป็น MIMETYPE_TEXT_URILIST

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

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() ไม่ใช่ค่า Null coerceToText() จะพยายามใช้เป็น URI เนื้อหา
  • หาก URI เป็น URI เนื้อหาและผู้ให้บริการสามารถแสดงผลสตรีมข้อความได้ coerceToText() จะแสดงผลสตรีมข้อความ
  • หาก URI เป็น URI เนื้อหา แต่ผู้ให้บริการไม่มีสตรีมข้อความ coerceToText() จะแสดงผลการนำเสนอ URI การนำเสนอจะเหมือนกับที่แสดงโดย Uri.toString()
  • หาก URI ไม่ใช่ URI เนื้อหา coerceToText() จะแสดงผลการนำเสนอของ URI การนำเสนอจะเหมือนกับที่ Uri.toString() แสดงผล
ความตั้งใจ
หาก ClipData.Item เป็น Intent ซึ่งก็คือหาก getIntent() ไม่ใช่ค่า Null coerceToText() จะแปลงเป็น Intent URI และแสดงผล การนำเสนอจะเหมือนกับที่ Intent.toUri(URI_INTENT_SCHEME) แสดงผล

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

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

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

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

  1. หากคุณคัดลอกข้อมูลโดยใช้ URI เนื้อหา ให้ตั้งค่า Content Provider
  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

      ข้อมูลโค้ดนี้จะสร้าง 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 ผู้ใช้จะเห็นข้อความซ้ำกัน
Post toast after an in-app copy.
รูปที่ 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() การเพิ่ม Flag นี้จะป้องกันไม่ให้เนื้อหาที่ละเอียดอ่อนปรากฏในการยืนยันภาพของเนื้อหาที่คัดลอกใน 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 ที่เหมาะสมเพื่อดึงข้อมูล

ขั้นตอนต่อไปนี้อธิบายวิธีรับข้อมูลจาก 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. รับคลิปบอร์ดส่วนกลาง นอกจากนี้ ให้รับ Content Resolver เพื่อให้คุณเข้าถึง Content Provider ได้ด้วย ดังนี้

    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 ไม่ได้ชี้ไปยัง Content Provider ที่ถูกต้อง

    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. ทดสอบว่า Content Provider รองรับประเภท 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

หากต้องการวาง 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) ขึ้นไป โดยปกติแล้วระบบจะแสดงข้อความโทสต์เมื่อแอปเรียกใช้ getPrimaryClip() ข้อความภายในข้อความมีรูปแบบต่อไปนี้

APP pasted from your clipboard

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

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

ใช้ Content Provider เพื่อคัดลอกข้อมูลที่ซับซ้อน

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

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

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

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

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

โดยปกติแล้ว คุณจะเข้ารหัสตัวระบุลงใน 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);

หากใช้ Content Provider อยู่แล้ว คุณอาจต้องเพิ่มเส้นทาง 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 "คัดลอก" ได้โดยการจับคู่รูปแบบและจัดการด้วยโค้ดที่เฉพาะเจาะจงสำหรับการคัดลอกและวาง

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

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

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

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

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

ใน Content Provider ให้ลบล้างเมธอดต่อไปนี้เป็นอย่างน้อย

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

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

คุณไม่จำเป็นต้องมีเมธอด Content Provider อื่นๆ เช่น 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. ในขอบเขตส่วนกลางของ Content Provider ให้สร้างตัวจับคู่ 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 เนื้อหาจากคลิปบอร์ดและใช้เพื่อรับและวางข้อมูล

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

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

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

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

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

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

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

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

openTypedAssetFile()

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

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

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

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

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

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

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