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

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 แปลงเป็นแหล่งข้อมูล เช่น ผู้ให้บริการเนื้อหา แล้วคัดลอกข้อมูลจากแหล่งที่มาไปยังพื้นที่เก็บข้อมูลของแอปพลิเคชัน
Intent
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 ของผู้ให้บริการเนื้อหา แต่จะใช้ URI ใดก็ได้ แอปพลิเคชันที่ให้ข้อมูลจะวาง URI ไว้ในคลิปบอร์ด แอปพลิเคชันที่ต้องการให้วางข้อมูลจะได้รับ URI จากคลิปบอร์ดและใช้ URI ดังกล่าวเพื่อเข้าถึงผู้ให้บริการเนื้อหาหรือแหล่งข้อมูลอื่นๆ และดึงข้อมูล
Intent
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 ที่ไม่ใช่ content: URI วิธีการนี้จะตั้งค่าประเภท 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 เป็น URI ของเนื้อหาและผู้ให้บริการแสดงผลสตรีมข้อความได้ coerceToText() จะแสดงผลสตรีมข้อความ
  • หาก URI เป็น URI ของเนื้อหาแต่ผู้ให้บริการไม่ได้เสนอสตรีมข้อความ coerceToText() จะแสดงการนําเสนอของ URI การนําเสนอจะเหมือนกับที่ Uri.toString() แสดง
  • หาก URI ไม่ใช่ URI ของเนื้อหา coerceToText() จะแสดงการนําเสนอของ URI การนําเสนอจะเหมือนกับที่แสดงโดย Uri.toString()
Intent
หาก 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 หรือจากผู้ให้บริการเนื้อหาที่ 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

      ข้อมูลโค้ดนี้สร้าง 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() การเพิ่ม 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. คัดลอกข้อมูลจากคลิปบอร์ด ระบบจะเข้าถึงจุดนี้ในโค้ดได้ก็ต่อเมื่อเปิดใช้รายการเมนู "วาง" เท่านั้น คุณจึงถือว่าคลิปบอร์ดมีข้อความธรรมดา คุณยังไม่ทราบว่า URL ดังกล่าวมีสตริงข้อความหรือ 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 และเรียกใช้เมธอดของผู้ให้บริการเนื้อหาที่เหมาะสมเพื่อดึงข้อมูล

ขั้นตอนต่อไปนี้อธิบายวิธีรับข้อมูลจากผู้ให้บริการเนื้อหาตาม 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

หากต้องการวาง 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 "copy" โดยการจับคู่รูปแบบและจัดการด้วยโค้ดที่เจาะจงสำหรับการคัดลอกและวาง

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

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

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

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

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

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

query()
การวางแอปพลิเคชันจะถือว่าแอปพลิเคชันสามารถรับข้อมูลของคุณได้โดยใช้เมธอดนี้กับ URI ที่คุณวางไว้ในคลิปบอร์ด หากต้องการรองรับการคัดลอก ให้เมธอดนี้ตรวจหา URI ที่มีเส้นทาง "copy" พิเศษ จากนั้นแอปพลิเคชันจะสร้าง URI "copy" เพื่อวางลงในคลิปบอร์ด ซึ่งมีเส้นทางการคัดลอกและเคอร์เซอร์ไปยังระเบียนที่ต้องการคัดลอก
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 วัตถุทั้งหมดในคลิปมีรูปแบบเดียวกัน กล่าวคือ ทั้งหมดต้องเป็นข้อความธรรมดา, Content URI หรือ Intent และต้องไม่ผสมกัน
  • เมื่อให้ข้อมูล คุณสามารถเสนอการนำเสนอ MIME แบบต่างๆ ได้ เพิ่มประเภท MIME ที่รองรับลงใน ClipDescription แล้วใช้ประเภท MIME นั้นในผู้ให้บริการเนื้อหา
  • เมื่อคุณได้รับข้อมูลจากคลิปบอร์ด แอปพลิเคชันของคุณมีหน้าที่ตรวจสอบประเภท MIME ที่มี จากนั้นจึงตัดสินใจว่าจะใช้ประเภทใด (หากมี) แม้ว่าจะมีคลิปในคลิปบอร์ดและผู้ใช้ขอให้วาง แต่แอปพลิเคชันของคุณก็ไม่จําเป็นต้องวาง วางข้อความหากประเภท MIME เข้ากันได้ คุณอาจบังคับให้ข้อมูลในคลิปบอร์ดเป็นข้อความโดยใช้ coerceToText() หากแอปพลิเคชันรองรับประเภท MIME ที่ใช้ได้มากกว่า 1 ประเภท คุณสามารถอนุญาตให้ผู้ใช้เลือกประเภทที่จะใช้