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

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 ไปยังพื้นที่หน่วยความจำของแอปพลิเคชัน

คลิปบอร์ดจะเก็บออบเจ็กต์คลิปได้ทีละ 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()
ความตั้งใจ
หาก 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() การเพิ่มแฟล็กนี้จะป้องกันไม่ให้เนื้อหาที่ละเอียดอ่อนปรากฏในการยืนยันด้วยภาพของเนื้อหาที่คัดลอกมาใน 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

หากต้องการวางความตั้งใจ ให้เริ่มจากคลิปบอร์ดส่วนกลาง ตรวจสอบออบเจ็กต์ 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()

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

ผู้ให้บริการเนื้อหารองรับการคัดลอกข้อมูลที่ซับซ้อน เช่น ระเบียนฐานข้อมูลหรือสตรีมไฟล์ หากต้องการคัดลอกข้อมูล ให้วาง 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 "คัดลอก"
  • หากแอปพลิเคชันของคุณดูแลรักษาฐานข้อมูลภายใน คุณอาจต้องย้ายฐานข้อมูลนี้ไปยังผู้ให้บริการเนื้อหาเพื่อให้คัดลอกได้ง่ายขึ้น
  • หากไม่ได้ใช้ฐานข้อมูล คุณสามารถใช้ผู้ให้บริการเนื้อหาแบบง่ายที่มีวัตถุประสงค์เพียงอย่างเดียวคือนำเสนอข้อมูลแก่แอปพลิเคชันที่วางจากคลิปบอร์ด

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

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 ประเภท คุณสามารถอนุญาตให้ผู้ใช้เลือกประเภทที่จะใช้