از ویجت های مجموعه استفاده کنید

روش نوشتن را امتحان کنید
Jetpack Compose ابزار رابط کاربری پیشنهادی برای اندروید است. یاد بگیرید که چگونه با استفاده از APIهای سبک Compose، ویجت بسازید.

ویجت‌های مجموعه در نمایش بسیاری از عناصر از یک نوع، مانند مجموعه تصاویر از یک برنامه گالری، مقالات از یک برنامه خبری یا پیام‌های از یک برنامه ارتباطی، تخصص دارند. ویجت‌های مجموعه معمولاً بر دو مورد استفاده تمرکز دارند: مرور مجموعه و باز کردن یک عنصر از مجموعه برای نمایش جزئیات آن. ویجت‌های مجموعه می‌توانند به صورت عمودی پیمایش شوند.

این ویجت‌ها از RemoteViewsService برای نمایش مجموعه‌هایی که توسط داده‌های راه دور، مانند داده‌های یک ارائه‌دهنده محتوا، پشتیبانی می‌شوند، استفاده می‌کنند. این ویجت داده‌ها را با استفاده از یکی از انواع نمای زیر که به عنوان نمای مجموعه شناخته می‌شوند، ارائه می‌دهد:

ListView
نمایی که آیتم‌ها را در یک لیست با اسکرول عمودی نمایش می‌دهد.
GridView
نمایی که آیتم‌ها را در یک شبکه‌ی اسکرول دوبعدی نمایش می‌دهد.
StackView
نمای کارت‌های انباشته - چیزی شبیه به رولودکس - که در آن کاربر می‌تواند کارت جلویی را به بالا یا پایین حرکت دهد تا به ترتیب کارت قبلی یا بعدی را ببیند.
AdapterViewFlipper
یک ViewAnimator ساده با پشتیبانی آداپتور که بین دو یا چند نما انیمیشن اجرا می‌کند. در هر زمان فقط یک فرزند نمایش داده می‌شود.

از آنجا که این نماهای مجموعه، مجموعه‌هایی را نمایش می‌دهند که توسط داده‌های راه دور پشتیبانی می‌شوند، از یک Adapter برای اتصال رابط کاربری خود به داده‌هایشان استفاده می‌کنند. یک Adapter آیتم‌های منفرد را از مجموعه‌ای از داده‌ها به اشیاء View منفرد متصل می‌کند.

و از آنجا که این نماهای مجموعه توسط آداپتورها پشتیبانی می‌شوند، چارچوب اندروید باید معماری اضافی را برای پشتیبانی از استفاده آنها در ویجت‌ها در نظر بگیرد. در زمینه یک ویجت، Adapter با یک RemoteViewsFactory جایگزین می‌شود که یک پوشش نازک در اطراف رابط Adapter است. هنگامی که برای یک آیتم خاص در مجموعه درخواست می‌شود، RemoteViewsFactory آیتم را برای مجموعه به عنوان یک شیء RemoteViews ایجاد و برمی‌گرداند. برای گنجاندن یک نمای مجموعه در ویجت خود، RemoteViewsService و RemoteViewsFactory را پیاده‌سازی کنید.

RemoteViewsService سرویسی است که به یک آداپتور راه دور اجازه می‌دهد اشیاء RemoteViews را درخواست کند. RemoteViewsFactory رابطی برای یک آداپتور بین یک نمای مجموعه - مانند ListView ، GridView و StackView - و داده‌های اساسی برای آن نما است. از نمونه StackWidget ، در اینجا مثالی از کد boilerplate برای پیاده‌سازی این سرویس و رابط آورده شده است:

کاتلین

class StackWidgetService : RemoteViewsService() {

    override fun onGetViewFactory(intent: Intent): RemoteViewsFactory {
        return 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.

}

جاوا

public class StackWidgetService extends RemoteViewsService {
    @Override
    public RemoteViewsFactory onGetViewFactory(Intent intent) {
        return new StackRemoteViewsFactory(this.getApplicationContext(), intent);
    }
}

class StackRemoteViewsFactory implements RemoteViewsService.RemoteViewsFactory {

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

}

نمونه درخواست

گزیده‌های کد در این بخش نیز از نمونه StackWidget گرفته شده‌اند:

شکل ۱. یک StackWidget .

این نمونه شامل مجموعه‌ای از ده نما است که مقادیر صفر تا نه را نمایش می‌دهند. ویجت نمونه دارای این رفتارهای اصلی است:

  • کاربر می‌تواند نمای بالایی را در ویجت به صورت عمودی حرکت دهد تا نمای بعدی یا قبلی نمایش داده شود. این یک رفتار داخلی StackView است.

  • بدون هیچ گونه تعامل با کاربر، ویجت به طور خودکار و به ترتیب، مانند یک اسلایدشو، از میان نماهای خود عبور می‌کند. این به دلیل تنظیم android:autoAdvanceViewId="@id/stack_view" در فایل res/xml/stackwidgetinfo.xml است. این تنظیم روی شناسه نما اعمال می‌شود که در این مورد، شناسه نمای پشته‌ای است.

  • اگر کاربر نمای بالایی را لمس کند، ویجت پیام Toast "نمای لمس شده n " را نمایش می‌دهد، که در آن n شاخص (موقعیت) نمای لمس شده است. برای بحث بیشتر در مورد نحوه پیاده‌سازی رفتارها، به بخش افزودن رفتار به موارد جداگانه مراجعه کنید.

پیاده‌سازی ویجت‌ها با مجموعه‌ها

برای پیاده‌سازی یک ویجت با مجموعه‌ها، رویه پیاده‌سازی هر ویجتی را دنبال کنید و به دنبال آن چند مرحله اضافی انجام دهید: مانیفست را تغییر دهید، یک نمای مجموعه به طرح‌بندی ویجت اضافه کنید و زیرکلاس AppWidgetProvider خود را تغییر دهید.

مانیفست برای ویجت‌های دارای مجموعه

فراتر از الزامات ذکر شده در بخش «اعلان ویجت در مانیفست» ، باید امکان اتصال ویجت‌های دارای مجموعه به RemoteViewsService خود را فراهم کنید. این کار را با اعلام سرویس در فایل مانیفست خود با مجوز BIND_REMOTEVIEWS انجام دهید. این کار مانع از دسترسی آزادانه سایر برنامه‌ها به داده‌های ویجت شما می‌شود.

برای مثال، هنگام ایجاد یک ویجت که از RemoteViewsService برای پر کردن یک نمای مجموعه استفاده می‌کند، ورودی مانیفست ممکن است به این شکل باشد:

<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 و شناسه ویجت که ویجت مورد نظر برای به‌روزرسانی را مشخص می‌کند، اشاره می‌کند، ارسال کنید.

برای مثال، در اینجا نحوه پیاده‌سازی متد فراخوانی onUpdate() در نمونه StackWidget برای تنظیم 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)
}

جاوا

public void onUpdate(Context context, AppWidgetManager appWidgetManager, int[] appWidgetIds) {
    // Update each of the widgets with the remote adapter.
    for (int i = 0; i < appWidgetIds.length; ++i) {

        // Set up the intent that starts the StackViewService, which
        // provides the views for this collection.
        Intent intent = new Intent(context, StackWidgetService.class);
        // Add the widget ID to the intent extras.
        intent.putExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, appWidgetIds[i]);
        intent.setData(Uri.parse(intent.toUri(Intent.URI_INTENT_SCHEME)));
        // Instantiate the RemoteViews object for the widget layout.
        RemoteViews views = new RemoteViews(context.getPackageName(), R.layout.widget_layout);
        // 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.
        views.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.
        views.setEmptyView(R.id.stack_view, R.id.empty_view);

        // Do additional processing specific to this widget.

        appWidgetManager.updateAppWidget(appWidgetIds[i], views);
    }
    super.onUpdate(context, appWidgetManager, appWidgetIds);
}

داده‌ها را حفظ کنید

همانطور که در این صفحه توضیح داده شده است، زیرکلاس RemoteViewsService RemoteViewsFactory را ارائه می‌دهد که برای پر کردن نمای مجموعه‌ی ریموت استفاده می‌شود.

به طور خاص، این مراحل را انجام دهید:

  1. زیرکلاس RemoteViewsService . RemoteViewsService سرویسی است که از طریق آن یک آداپتور راه دور می‌تواند RemoteViews درخواست کند.

  2. در زیرکلاس RemoteViewsService خود، کلاسی را قرار دهید که رابط RemoteViewsFactory را پیاده‌سازی کند. RemoteViewsFactory رابطی برای یک رابط بین یک نمای مجموعه راه دور - مانند ListView ، GridView ، StackView - و داده‌های زیربنایی آن نما است. پیاده‌سازی شما مسئول ایجاد یک شیء RemoteViews برای هر آیتم در مجموعه داده است. این رابط یک پوشش نازک در اطراف Adapter است.

شما نمی‌توانید به یک نمونه واحد از سرویس خود یا هر داده‌ای که در آن وجود دارد، برای ماندگاری تکیه کنید. داده‌ها را در RemoteViewsService خود ذخیره نکنید، مگر اینکه ایستا باشند. اگر می‌خواهید داده‌های ویجت شما ماندگار باشند، بهترین روش استفاده از ContentProvider است که داده‌های آن فراتر از چرخه حیات فرآیند باقی بمانند. به عنوان مثال، یک ویجت فروشگاه مواد غذایی می‌تواند وضعیت هر مورد از لیست مواد غذایی را در یک مکان دائمی، مانند یک پایگاه داده SQL، ذخیره کند.

محتوای اصلی پیاده‌سازی RemoteViewsService ، RemoteViewsFactory آن است که در بخش بعدی توضیح داده شده است.

رابط RemoteViewsFactory

کلاس سفارشی شما که رابط RemoteViewsFactory را پیاده‌سازی می‌کند، داده‌های مربوط به آیتم‌های موجود در مجموعه را در اختیار ویجت قرار می‌دهد. برای انجام این کار، فایل طرح‌بندی XML آیتم ویجت شما را با یک منبع داده ترکیب می‌کند. این منبع داده می‌تواند هر چیزی از یک پایگاه داده گرفته تا یک آرایه ساده باشد. در نمونه StackWidget ، منبع داده آرایه‌ای از WidgetItems است. RemoteViewsFactory به عنوان یک آداپتور برای چسباندن داده‌ها به نمای مجموعه راه دور عمل می‌کند.

دو متد مهمی که باید برای زیرکلاس 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!") }
        ...
    }
    ...
}

جاوا

class StackRemoteViewsFactory implements RemoteViewsService.RemoteViewsFactory {
    private static final int REMOTE_VIEW_COUNT = 10;
    private List<WidgetItem> widgetItems = new ArrayList<WidgetItem>();

    public void onCreate() {
        // In onCreate(), setup 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.
        for (int i = 0; i < REMOTE_VIEW_COUNT; i++) {
            widgetItems.add(new WidgetItem(i + "!"));
        }
        ...
    }
...

متد getViewAt() ‎ از RemoteViewsFactory یک شیء 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)
    }
}

جاوا

public RemoteViews getViewAt(int position) {
    // Construct a remote views item based on the widget item XML file
    // and set the text based on the position.
    RemoteViews views = new RemoteViews(context.getPackageName(), R.layout.widget_item);
    views.setTextViewText(R.id.widget_item, widgetItems.get(position).text);
    return views;
}

افزودن رفتار به موارد تکی

بخش‌های قبلی نحوه‌ی اتصال داده‌ها به مجموعه‌ی ویجت‌ها را نشان می‌دهند. اما اگر بخواهید رفتار پویا را به تک تک آیتم‌های موجود در نمای مجموعه‌ی خود اضافه کنید، چه باید کرد؟

همانطور که در بخش رویدادهای Handle با کلاس onUpdate() توضیح داده شد، معمولاً از setOnClickPendingIntent() برای تنظیم رفتار کلیک یک شیء استفاده می‌کنید - مثلاً باعث می‌شود یک دکمه یک Activity را اجرا کند. اما این رویکرد برای نماهای فرزند در یک آیتم مجموعه‌ی منفرد مجاز نیست. برای مثال، می‌توانید از setOnClickPendingIntent() برای تنظیم یک دکمه‌ی سراسری در ویجت Gmail استفاده کنید که مثلاً برنامه را اجرا می‌کند، اما نه روی آیتم‌های لیست منفرد.

در عوض، برای افزودن رفتار کلیک به آیتم‌های منفرد در یک مجموعه، از setOnClickFillInIntent() استفاده کنید. این کار مستلزم تنظیم یک الگوی قصد در انتظار برای نمای مجموعه شما و سپس تنظیم یک قصد پر کردن برای هر آیتم در مجموعه از طریق RemoteViewsFactory است.

این بخش از نمونه StackWidget برای توصیف نحوه افزودن رفتار به آیتم‌های منفرد استفاده می‌کند. در نمونه StackWidget ، اگر کاربر نمای بالایی را لمس کند، ویجت پیام Toast "نمای لمس شده n " را نمایش می‌دهد، که در آن n شاخص (موقعیت) نمای لمس شده است. نحوه کار آن به این صورت است:

  • StackWidgetProvider - یک زیرکلاس AppWidgetProvider - یک intent در حال انتظار با یک اکشن سفارشی به نام TOAST_ACTION ایجاد می‌کند.

  • وقتی کاربر یک نما را لمس می‌کند، اینتنت فعال می‌شود و TOAST_ACTION پخش می‌کند.

  • این پخش توسط متد onReceive() کلاس StackWidgetProvider متوقف می‌شود و ویجت پیام Toast را برای نمای لمس شده نمایش می‌دهد. داده‌های مربوط به آیتم‌های مجموعه توسط RemoteViewsFactory از طریق RemoteViewsService ارائه می‌شوند.

الگوی هدف در انتظار را تنظیم کنید

StackWidgetProvider (یک زیرکلاس AppWidgetProvider ) یک قصد در حال انتظار (pending intent) ایجاد می‌کند. آیتم‌های تکی یک مجموعه نمی‌توانند قصد در حال انتظار خود را ایجاد کنند. در عوض، کل مجموعه یک الگوی قصد در حال انتظار ایجاد می‌کند و آیتم‌های تکی یک قصد پر کردنی (fill-in intent) ایجاد می‌کنند تا رفتار منحصر به فردی را بر اساس هر آیتم ایجاد کنند.

این کلاس همچنین broadcast ارسال شده هنگام لمس یک view توسط کاربر را دریافت می‌کند. این کلاس این رویداد را در متد onReceive() خود پردازش می‌کند. اگر اکشن intent برابر TOAST_ACTION باشد، ویجت یک پیام Toast برای view فعلی نمایش می‌دهد.

کاتلین

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

                PendingIntent.getBroadcast(context, 0, this, PendingIntent.FLAG_UPDATE_CURRENT)
            }
            rv.setPendingIntentTemplate(R.id.stack_view, toastPendingIntent)

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

جاوا

public class StackWidgetProvider extends AppWidgetProvider {
    public static final String TOAST_ACTION = "com.example.android.stackwidget.TOAST_ACTION";
    public static final String EXTRA_ITEM = "com.example.android.stackwidget.EXTRA_ITEM";

    ...

    // 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
    public void onReceive(Context context, Intent intent) {
        AppWidgetManager mgr = AppWidgetManager.getInstance(context);
        if (intent.getAction().equals(TOAST_ACTION)) {
            int appWidgetId = 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.
            int viewIndex = intent.getIntExtra(EXTRA_ITEM, 0);
            Toast.makeText(context, "Touched view " + viewIndex, Toast.LENGTH_SHORT).show();
        }
        super.onReceive(context, intent);
    }

    @Override
    public void onUpdate(Context context, AppWidgetManager appWidgetManager, int[] appWidgetIds) {
        // Update each of the widgets with the remote adapter.
        for (int i = 0; i < appWidgetIds.length; ++i) {

            // Sets up the intent that points to the StackViewService that
            // provides the views for this collection.
            Intent intent = new Intent(context, StackWidgetService.class);
            intent.putExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, appWidgetIds[i]);
            // When intents are compared, the extras are ignored, so embed
            // the extras into the data so that the extras are not
            // ignored.
            intent.setData(Uri.parse(intent.toUri(Intent.URI_INTENT_SCHEME)));
            RemoteViews rv = new RemoteViews(context.getPackageName(), R.layout.widget_layout);
            rv.setRemoteAdapter(appWidgetIds[i], 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.
            rv.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.
            Intent toastIntent = new Intent(context, StackWidgetProvider.class);
            // Set the action for the intent.
            // When the user touches a particular view, it has the effect of
            // broadcasting TOAST_ACTION.
            toastIntent.setAction(StackWidgetProvider.TOAST_ACTION);
            toastIntent.putExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, appWidgetIds[i]);
            intent.setData(Uri.parse(intent.toUri(Intent.URI_INTENT_SCHEME)));
            PendingIntent toastPendingIntent = PendingIntent.getBroadcast(context, 0, toastIntent,
                PendingIntent.FLAG_UPDATE_CURRENT);
            rv.setPendingIntentTemplate(R.id.stack_view, toastPendingIntent);

            appWidgetManager.updateAppWidget(appWidgetIds[i], rv);
        }
        super.onUpdate(context, appWidgetManager, appWidgetIds);
    }
}

هدف پر کردن را تنظیم کنید

RemoteViewsFactory شما باید برای هر آیتم در مجموعه، یک هدف تکمیل (fill-in intent) تعیین کند. این کار امکان تشخیص عمل کلیک روی یک آیتم خاص را فراهم می‌کند. سپس هدف تکمیل با الگوی PendingIntent ترکیب می‌شود تا هدف نهایی که هنگام ضربه زدن روی آیتم اجرا می‌شود را تعیین کند.

کاتلین

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

جاوا

public class StackWidgetService extends RemoteViewsService {
    @Override
    public RemoteViewsFactory onGetViewFactory(Intent intent) {
        return new StackRemoteViewsFactory(this.getApplicationContext(), intent);
    }
}

class StackRemoteViewsFactory implements RemoteViewsService.RemoteViewsFactory {
    private static final int count = 10;
    private List<WidgetItem> widgetItems = new ArrayList<WidgetItem>();
    private Context context;
    private int appWidgetId;

    public StackRemoteViewsFactory(Context context, Intent intent) {
        this.context = context;
        appWidgetId = intent.getIntExtra(AppWidgetManager.EXTRA_APPWIDGET_ID,
                AppWidgetManager.INVALID_APPWIDGET_ID);
    }

    // Initialize the data set.
    public void 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.
        for (int i = 0; i < count; i++) {
            widgetItems.add(new WidgetItem(i + "!"));
        }
        ...
    }

    // Given the position (index) of a WidgetItem in the array, use the
    // item's text value in combination with the widget item XML file to
    // construct a RemoteViews object.
    public RemoteViews getViewAt(int position) {
        // Position always ranges from 0 to getCount() - 1.

        // Construct a RemoteViews item based on the widget item XML
        // file and set the text based on the position.
        RemoteViews rv = new RemoteViews(context.getPackageName(), R.layout.widget_item);
        rv.setTextViewText(R.id.widget_item, widgetItems.get(position).text);

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

        // Return the RemoteViews object.
        return rv;
    }
    ...
}

داده‌های جمع‌آوری‌شده را به‌روز نگه دارید

شکل ۲ جریان به‌روزرسانی را در یک ویجت که از مجموعه‌ها استفاده می‌کند، نشان می‌دهد. این شکل نشان می‌دهد که چگونه کد ویجت با RemoteViewsFactory تعامل دارد و چگونه می‌توانید به‌روزرسانی‌ها را فعال کنید:

شکل ۲. تعامل با RemoteViewsFactory در طول به‌روزرسانی‌ها.

ویجت‌هایی که از مجموعه‌ها استفاده می‌کنند می‌توانند محتوای به‌روز را در اختیار کاربران قرار دهند. برای مثال، ویجت Gmail تصویری از صندوق ورودی کاربران را نشان می‌دهد. برای امکان‌پذیر کردن این امر، RemoteViewsFactory و نمای مجموعه خود را برای دریافت و نمایش داده‌های جدید فعال کنید.

برای انجام این کار، از AppWidgetManager برای فراخوانی notifyAppWidgetViewDataChanged() استفاده کنید. این فراخوانی منجر به فراخوانی مجدد متد onDataSetChanged() از شیء RemoteViewsFactory شما می‌شود که به شما امکان می‌دهد هر داده جدیدی را دریافت کنید.

شما می‌توانید عملیات پردازش-محور را به صورت همزمان در داخل تابع فراخوانی onDataSetChanged() انجام دهید. تضمین می‌شود که این فراخوانی قبل از اینکه داده‌های فراداده یا نما از RemoteViewsFactory واکشی شوند، تکمیل می‌شود. همچنین می‌توانید عملیات پردازش-محور را در داخل متد getViewAt() انجام دهید. اگر این فراخوانی طولانی شود، نمای در حال بارگذاری - که توسط متد getLoadingView() شیء RemoteViewsFactory مشخص شده است - تا زمان بازگشت، در موقعیت مربوطه در نمای مجموعه نمایش داده می‌شود.

از RemoteCollectionItems برای ارسال مستقیم یک مجموعه استفاده کنید

اندروید ۱۲ (سطح 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()
)

جاوا

List<Integer> itemLayouts = Arrays.asList(
    R.layout.item_type_1,
    R.layout.item_type_2,
    ...
);

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