ใช้วิดเจ็ตคอลเล็กชัน

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

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

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

ListView: มุมมองที่แสดงรายการในรายการที่เลื่อนในแนวตั้ง

GridView: มุมมองที่แสดงรายการในตารางกริดแบบเลื่อนได้ 2 มิติ

StackView: มุมมองการ์ดแบบซ้อนกัน ซึ่งคล้ายกับกล่องเก็บนามบัตรที่ผู้ใช้สามารถปัดการ์ดด้านหน้าขึ้นหรือลงเพื่อดูการ์ดก่อนหน้าหรือถัดไปตามลำดับ

AdapterViewFlipper: ViewAnimator ที่ทำงานร่วมกับอะแดปเตอร์ซึ่งแสดงภาพเคลื่อนไหวระหว่างมุมมอง 2 มุมมองขึ้นไป ระบบจะแสดงบุตรหลานครั้งละ 1 คนเท่านั้น

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

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

RemoteViewsService เป็นบริการที่ช่วยให้อะแดปเตอร์ระยะไกลขอออบเจ็กต์ RemoteViews ได้ RemoteViewsFactory เป็นอินเทอร์เฟซสำหรับอะแดปเตอร์ระหว่างมุมมองคอลเล็กชัน เช่น ListView, GridView และ StackView กับข้อมูลเบื้องหลังของมุมมองนั้น จากตัวอย่าง StackWidget ต่อไปนี้คือตัวอย่างโค้ด Boilerplate เพื่อใช้บริการและอินเทอร์เฟซนี้

class StackWidgetService : RemoteViewsService() {
    override fun onGetViewFactory(intent: Intent): RemoteViewsFactory =
        StackRemoteViewsFactory(this.applicationContext, intent)
}

class StackRemoteViewsFactory(
    private val context: Context, intent: Intent
) : RemoteViewsService.RemoteViewsFactory {

    // See the RemoteViewsFactory API reference for the full list of methods to implement.

}

แอปพลิเคชันตัวอย่าง

ข้อความที่ตัดตอนมาของโค้ดในส่วนนี้ยังดึงมาจากตัวอย่าง StackWidget ด้วย

StackWidget ที่แสดงมุมมองแบบซ้อนกัน
รูปที่ 1 StackWidget

ตัวอย่างนี้ประกอบด้วยสแต็กของมุมมอง 10 รายการที่แสดงค่า 0 ถึง 9 วิดเจ็ตตัวอย่างมีลักษณะการทำงานหลักๆ ดังนี้

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

  • วิดเจ็ตจะเลื่อนผ่านมุมมองต่างๆ ตามลำดับโดยอัตโนมัติเหมือนภาพสไลด์ โดยไม่ต้องมีการโต้ตอบจากผู้ใช้ ซึ่งเกิดจากการตั้งค่า android:autoAdvanceViewId="@id/stack_view" ในไฟล์ res/xml/stackwidgetinfo.xml การตั้งค่านี้จะมีผลกับรหัสยอดดู ซึ่งในกรณีนี้คือรหัสยอดดูของมุมมองสแต็ก

  • หากผู้ใช้แตะมุมมองด้านบน วิดเจ็ตจะแสดงข้อความ Toast "Touched view n" โดยที่ n คือดัชนี (ตำแหน่ง) ของมุมมองที่แตะ ดูการพูดคุยเพิ่มเติมเกี่ยวกับวิธีใช้ลักษณะการทำงานได้ที่ส่วนเพิ่มลักษณะการทำงานให้กับแต่ละรายการ

ติดตั้งใช้งานวิดเจ็ตที่มีคอลเล็กชัน

หากต้องการติดตั้งใช้งานวิดเจ็ตที่มีคอลเล็กชัน ให้ทำตามขั้นตอนเพื่อติดตั้งใช้งานวิดเจ็ต แล้วทำตามขั้นตอนเพิ่มเติมอีก 2-3 ขั้นตอน ได้แก่ แก้ไขไฟล์ Manifest, เพิ่มมุมมองคอลเล็กชันลงในเลย์เอาต์วิดเจ็ต และแก้ไขคลาสย่อย AppWidgetProvider

ไฟล์ Manifest สำหรับวิดเจ็ตที่มีคอลเล็กชัน

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

ตัวอย่างเช่น เมื่อสร้างวิดเจ็ตที่ใช้ RemoteViewsService เพื่อสร้างมุมมองคอลเล็กชัน รายการไฟล์ Manifest อาจมีลักษณะดังนี้

<service android:name="MyWidgetService"
    android:permission="android.permission.BIND_REMOTEVIEWS" />

ในตัวอย่างนี้ android:name="MyWidgetService" หมายถึงคลาสย่อยของ RemoteViewsService

เลย์เอาต์สำหรับวิดเจ็ตที่มีคอลเล็กชัน

ข้อกำหนดหลักสำหรับไฟล์ XML ของเลย์เอาต์วิดเจ็ตคือต้องมีมุมมองคอลเล็กชันอย่างใดอย่างหนึ่งต่อไปนี้ ListView, GridView, StackView หรือ AdapterViewFlipper นี่คือไฟล์ widget_layout.xml สำหรับตัวอย่าง StackWidget

<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent">
    <StackView
        android:id="@+id/stack_view"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:gravity="center"
        android:loopViews="true" />
    <TextView
        android:id="@+id/empty_view"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:gravity="center"
        android:background="@drawable/widget_item_background"
        android:textColor="#ffffff"
        android:textStyle="bold"
        android:text="@string/empty_view_text"
        android:textSize="20sp" />
</FrameLayout>

โปรดทราบว่ามุมมองว่างต้องเป็นพี่น้องของมุมมองคอลเล็กชันที่มุมมองว่างแสดงสถานะว่างเปล่า

นอกจากไฟล์เลย์เอาต์สำหรับวิดเจ็ตทั้งหมดแล้ว ให้สร้างไฟล์เลย์เอาต์อีกไฟล์หนึ่งที่กำหนดเลย์เอาต์สำหรับแต่ละรายการในคอลเล็กชัน เช่น เลย์เอาต์สำหรับหนังสือแต่ละเล่มในคอลเล็กชันหนังสือ ตัวอย่าง StackWidget มีไฟล์เลย์เอาต์รายการเพียงไฟล์เดียวคือ widget_item.xml เนื่องจากรายการทั้งหมดใช้เลย์เอาต์เดียวกัน

คลาส AppWidgetProvider สำหรับวิดเจ็ตที่มีคอลเล็กชัน

เช่นเดียวกับวิดเจ็ตทั่วไป โค้ดส่วนใหญ่ในคลาสย่อย AppWidgetProvider มักจะอยู่ใน onUpdate() ความแตกต่างที่สำคัญในการติดตั้งใช้งานสำหรับ onUpdate() เมื่อสร้างวิดเจ็ตที่มีคอลเล็กชันคือคุณต้องเรียกใช้ setRemoteAdapter() ซึ่งจะบอกมุมมองคอลเล็กชันว่าจะรับข้อมูลจากที่ใด จากนั้น RemoteViewsService จะแสดงผลการติดตั้งใช้งาน RemoteViewsFactory ของคุณ และวิดเจ็ตจะแสดงข้อมูลที่เหมาะสมได้ เมื่อเรียกใช้เมธอดนี้ ให้ส่ง Intent ที่ชี้ไปยังการติดตั้งใช้งาน RemoteViewsService และรหัสวิดเจ็ตที่ระบุวิดเจ็ตที่จะอัปเดต

ตัวอย่างเช่น ต่อไปนี้เป็นวิธีที่ตัวอย่าง StackWidget ใช้เมธอด Callback onUpdate() เพื่อตั้งค่า RemoteViewsService เป็นอะแดปเตอร์ระยะไกลสำหรับคอลเล็กชันวิดเจ็ต

override fun onUpdate(
    context: Context, appWidgetManager: AppWidgetManager, appWidgetIds: IntArray
) {
    // Update each of the widgets with the remote adapter.
    appWidgetIds.forEach { appWidgetId ->

        // Set up the intent that starts the StackViewService, which
        // provides the views for this collection.
        val intent = Intent(context, StackWidgetService::class.java).apply {
            // Add the widget ID to the intent extras.
            putExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, appWidgetId)
            data = Uri.parse(toUri(Intent.URI_INTENT_SCHEME))
        }
        // Instantiate the RemoteViews object for the widget layout.
        val views = RemoteViews(context.packageName, R.layout.widget_layout).apply {
            // Set up the RemoteViews object to use a RemoteViews adapter.
            // This adapter connects to a RemoteViewsService through the
            // specified intent.
            // This is how you populate the data.
            setRemoteAdapter(R.id.stack_view, intent)

            // The empty view is displayed when the collection has no items.
            // It must be in the same layout used to instantiate the
            // RemoteViews object.
            setEmptyView(R.id.stack_view, R.id.empty_view)
        }

        // Do additional processing specific to this widget.

        appWidgetManager.updateAppWidget(appWidgetId, views)
    }
    super.onUpdate(context, appWidgetManager, appWidgetIds)
}

เก็บข้อมูลไว้

ตามที่อธิบายไว้ในหน้านี้ คลาสย่อย RemoteViewsService จะมี RemoteViewsFactory ที่ใช้เพื่อสร้างมุมมองคอลเล็กชันระยะไกล

โดยเฉพาะอย่างยิ่ง ให้ทำตามขั้นตอนต่อไปนี้

  1. คลาสย่อย RemoteViewsService RemoteViewsService เป็นบริการที่อะแดปเตอร์ระยะไกลสามารถขอ RemoteViews ได้

  2. ในคลาสย่อย RemoteViewsService ให้รวมคลาสที่ใช้อินเทอร์เฟซ RemoteViewsFactory RemoteViewsFactory เป็นอินเทอร์เฟซสำหรับอะแดปเตอร์ระหว่างมุมมองคอลเล็กชันระยะไกล เช่น ListView, GridView, StackView และข้อมูลเบื้องหลังสำหรับมุมมองนั้น การติดตั้งใช้งานของคุณมีหน้าที่สร้างออบเจ็กต์ RemoteViews สำหรับแต่ละรายการในชุดข้อมูล อินเทอร์เฟซนี้เป็น Wrapper แบบบางรอบ Adapter

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

เนื้อหาหลักของการติดตั้งใช้งาน RemoteViewsService คือ RemoteViewsFactory ซึ่งอธิบายไว้ในส่วนต่อไปนี้

อินเทอร์เฟซ RemoteViewsFactory

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

เมธอดที่สำคัญที่สุด 2 เมธอดที่คุณต้องใช้สำหรับคลาสย่อย RemoteViewsFactory คือ onCreate() และ getViewAt()

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

ต่อไปนี้เป็นข้อความที่ตัดตอนมาจากการติดตั้งใช้งาน RemoteViewsFactory ของตัวอย่าง StackWidget ซึ่งแสดงส่วนต่างๆ ของเมธอด onCreate()

private const val REMOTE_VIEW_COUNT: Int = 10

class StackRemoteViewsFactory(
    private val context: Context
) : RemoteViewsService.RemoteViewsFactory {

    private lateinit var widgetItems: List<WidgetItem>

    override fun onCreate() {
        // In onCreate(), set up any connections or cursors to your data
        // source. Heavy lifting, such as downloading or creating content,
        // must be deferred to onDataSetChanged() or getViewAt(). Taking
        // more than 20 seconds on this call results in an ANR.
        widgetItems = List(REMOTE_VIEW_COUNT) { index -> WidgetItem("$index!") }
    }

}

เมธอด RemoteViewsFactory getViewAt() จะแสดงผลออบเจ็กต์ RemoteViews ที่สอดคล้องกับข้อมูลที่ position ที่ระบุในชุดข้อมูล ต่อไปนี้เป็นข้อความที่ตัดตอนมาจากการใช้งาน RemoteViewsFactory ของตัวอย่าง StackWidget

override fun getViewAt(position: Int): RemoteViews {
    // Construct a remote views item based on the widget item XML file
    // and set the text based on the position.
    return RemoteViews(context.packageName, R.layout.widget_item).apply {
        setTextViewText(R.id.widget_item, widgetItems[position].text)
    }
}

เพิ่มลักษณะการทำงานให้กับแต่ละรายการ

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

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

แต่หากต้องการเพิ่มลักษณะการคลิกให้กับแต่ละรายการในคอลเล็กชัน ให้ใช้ setOnClickFillInIntent() แทน ตั้งค่าเทมเพลต Intent รอดำเนินการสำหรับมุมมองคอลเล็กชัน และตั้งค่า Intent การกรอกข้อมูลในแต่ละรายการในคอลเล็กชันโดยใช้ RemoteViewsFactory

ส่วนนี้จะใช้ตัวอย่าง StackWidget เพื่ออธิบายวิธีเพิ่มลักษณะการทำงานให้กับแต่ละรายการ ในตัวอย่าง StackWidget หากผู้ใช้แตะมุมมองด้านบน วิดเจ็ตจะแสดงข้อความ Toast "มุมมองที่แตะ n" โดยที่ n คือดัชนี (ตำแหน่ง) ของมุมมองที่แตะ โดยมีวิธีการดังนี้

  • StackWidgetProvider ซึ่งเป็นคลาสย่อยของ AppWidgetProvider จะสร้าง Intent รอดำเนินการที่มีการกระทำที่กำหนดเองชื่อ TOAST_ACTION

  • เมื่อผู้ใช้แตะมุมมอง Intent จะเริ่มทำงานและออกอากาศ TOAST_ACTION

  • การออกอากาศนี้ถูกขัดจังหวะโดยเมธอด onReceive() ของคลาส StackWidgetProvider และวิดเจ็ตจะแสดงข้อความ Toast สำหรับมุมมองที่แตะ ข้อมูลสำหรับรายการในคอลเล็กชันมาจาก RemoteViewsFactory ผ่าน RemoteViewsService

ตั้งค่าเทมเพลต Intent รอดำเนินการ

StackWidgetProvider (คลาสย่อย AppWidgetProvider) จะตั้งค่า Intent รอดำเนินการ รายการแต่ละรายการของคอลเล็กชันจะตั้งค่า Intent รอดำเนินการของตนเองไม่ได้ แต่คอลเล็กชันโดยรวมจะตั้งค่าเทมเพลต Intent รอดำเนินการ และรายการแต่ละรายการจะตั้งค่า Intent การกรอกข้อมูลเพื่อสร้างลักษณะการทำงานที่ไม่ซ้ำกันในแต่ละรายการ

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

const val TOAST_ACTION = "com.example.android.stackwidget.TOAST_ACTION"
const val EXTRA_ITEM = "com.example.android.stackwidget.EXTRA_ITEM"

class StackWidgetProvider : AppWidgetProvider() {

    // ...

    // Called when the BroadcastReceiver receives an Intent broadcast.
    // Checks whether the intent's action is TOAST_ACTION. If it is, the
    // widget displays a Toast message for the current item.
    override fun onReceive(context: Context, intent: Intent) {
        val mgr: AppWidgetManager = AppWidgetManager.getInstance(context)
        if (intent.action == TOAST_ACTION) {
            val appWidgetId: Int = intent.getIntExtra(
                AppWidgetManager.EXTRA_APPWIDGET_ID,
                AppWidgetManager.INVALID_APPWIDGET_ID
            )
            // EXTRA_ITEM represents a custom value provided by the Intent
            // passed to the setOnClickFillInIntent() method to indicate the
            // position of the clicked item. See StackRemoteViewsFactory in
            // Set the fill-in Intent for details.
            val viewIndex: Int = intent.getIntExtra(EXTRA_ITEM, 0)
            Toast.makeText(context, "Touched view $viewIndex", Toast.LENGTH_SHORT).show()
        }
        super.onReceive(context, intent)
    }

    override fun onUpdate(
        context: Context,
        appWidgetManager: AppWidgetManager,
        appWidgetIds: IntArray
    ) {
        // Update each of the widgets with the remote adapter.
        appWidgetIds.forEach { appWidgetId ->

            // Sets up the intent that points to the StackViewService that
            // provides the views for this collection.
            val intent = Intent(context, StackWidgetService::class.java).apply {
                putExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, appWidgetId)
                // When intents are compared, the extras are ignored, so embed
                // the extra sinto the data so that the extras are not ignored.
                data = Uri.parse(toUri(Intent.URI_INTENT_SCHEME))
            }
            val rv = RemoteViews(context.packageName, R.layout.widget_layout).apply {
                setRemoteAdapter(R.id.stack_view, intent)

                // The empty view is displayed when the collection has no items.
                // It must be a sibling of the collection view.
                setEmptyView(R.id.stack_view, R.id.empty_view)
            }

            // This section makes it possible for items to have individualized
            // behavior. It does this by setting up a pending intent template.
            // Individuals items of a collection can't set up their own pending
            // intents. Instead, the collection as a whole sets up a pending
            // intent template, and the individual items set a fillInIntent
            // to create unique behavior on an item-by-item basis.
            val toastPendingIntent: PendingIntent = Intent(
                context,
                StackWidgetProvider::class.java
            ).run {
                // Set the action for the intent.
                // When the user touches a particular view, it has the effect of
                // broadcasting TOAST_ACTION.
                action = TOAST_ACTION
                putExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, appWidgetId)
                data = Uri.parse(toUri(Intent.URI_INTENT_SCHEME))

                // The template must be mutable, because each item fills in its
                // own extras through setOnClickFillInIntent().
                PendingIntent.getBroadcast(
                    context, 0, this,
                    PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_MUTABLE
                )
            }
            rv.setPendingIntentTemplate(R.id.stack_view, toastPendingIntent)

            appWidgetManager.updateAppWidget(appWidgetId, rv)
        }
        super.onUpdate(context, appWidgetManager, appWidgetIds)
    }
}

ตั้งค่าความตั้งใจในการกรอกข้อมูล

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

private const val REMOTE_VIEW_COUNT: Int = 10

class StackRemoteViewsFactory(
    private val context: Context,
    intent: Intent
) : RemoteViewsService.RemoteViewsFactory {

    private lateinit var widgetItems: List<WidgetItem>
    private val appWidgetId: Int = intent.getIntExtra(
        AppWidgetManager.EXTRA_APPWIDGET_ID,
        AppWidgetManager.INVALID_APPWIDGET_ID
    )

    override fun onCreate() {
        // In onCreate(), set up any connections or cursors to your data source.
        // Heavy lifting, such as downloading or creating content, must be
        // deferred to onDataSetChanged() or getViewAt(). Taking more than 20
        // seconds on this call results in an ANR.
        widgetItems = List(REMOTE_VIEW_COUNT) { index -> WidgetItem("$index!") }
        // ...
    }

    // ...

    override fun getViewAt(position: Int): RemoteViews {
        // Construct a remote views item based on the widget item XML file
        // and set the text based on the position.
        return RemoteViews(context.packageName, R.layout.widget_item).apply {
            setTextViewText(R.id.widget_item, widgetItems[position].text)

            // Set a fill-intent to fill in the pending intent template.
            // that is set on the collection view in StackWidgetProvider.
            val fillInIntent = Intent().apply {
                Bundle().also { extras ->
                    extras.putInt(EXTRA_ITEM, position)
                    putExtras(extras)
                }
            }
            // Make it possible to distinguish the individual on-click
            // action of a given item.
            setOnClickFillInIntent(R.id.widget_item, fillInIntent)
            // ...
        }
    }
    // ...
}

อัปเดตข้อมูลคอลเล็กชันอยู่เสมอ

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

การโต้ตอบกับ RemoteViewsFactory ระหว่างการอัปเดต
รูปที่ 2 การโต้ตอบกับ RemoteViewsFactory ระหว่างการอัปเดต

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

โดยใช้ AppWidgetManager เพื่อโทรหา notifyAppWidgetViewDataChanged() การเรียกนี้จะส่งผลให้มีการเรียกกลับไปยังเมธอด onDataSetChanged() ของออบเจ็กต์ RemoteViewsFactory ซึ่งช่วยให้คุณดึงข้อมูลใหม่ได้

คุณสามารถดำเนินการที่ต้องใช้การประมวลผลสูงพร้อมกันภายใน Callback onDataSetChanged() การเรียกนี้จะเสร็จสมบูรณ์ก่อนที่จะดึงข้อมูลเมตาหรือข้อมูลมุมมองจาก RemoteViewsFactory นอกจากนี้ คุณยังสามารถดำเนินการที่ต้องใช้การประมวลผลสูงภายในเมธอด getViewAt() ได้ด้วย หากการเรียกนี้ใช้เวลานาน มุมมองการโหลดที่ระบุโดยเมธอด getLoadingView() ของออบเจ็กต์ RemoteViewsFactory จะแสดงในตำแหน่งที่สอดคล้องกันของมุมมองคอลเล็กชันจนกว่าจะแสดงผล

ใช้ RemoteCollectionItems เพื่อส่งต่อคอลเล็กชันโดยตรง

Android 12 (API ระดับ 31) เพิ่มเมธอด setRemoteAdapter(int viewId, RemoteViews.RemoteCollectionItems items) ซึ่งช่วยให้แอปส่งคอลเล็กชันได้โดยตรงเมื่อสร้างมุมมองคอลเล็กชัน หากตั้งค่าอะแดปเตอร์โดยใช้วิธีนี้ คุณไม่จำเป็นต้องใช้ RemoteViewsFactory และไม่จำเป็นต้องเรียกใช้ notifyAppWidgetViewDataChanged()

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

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

ต่อไปนี้เป็นตัวอย่างวิธีใช้คอลเล็กชัน RemoteViews แบบง่าย

val itemLayouts = listOf(
    R.layout.item_type_1,
    R.layout.item_type_2,
    // ...
)

remoteView.setRemoteAdapter(
    R.id.list_view,
    RemoteViews.RemoteCollectionItems.Builder()
        .addItem(/* id= */ ID_1, RemoteViews(context.packageName, R.layout.item_type_1))
        .addItem(/* id= */ ID_2, RemoteViews(context.packageName, R.layout.item_type_2))
        // ...
        .setViewTypeCount(itemLayouts.count())
        .build()
)