تصميم ودجت للتطبيق [2]

الإعلانات

تصميم ودجت للتطبيق [2]

 

 

تصميم ودجت التطبيق

 

تصميم نشاط تهيئة للودجت


إذا كنت ترغب في أن يقوم المستخدم بتهيئة الإعدادات عندما يضيف ودجت جديد، فيمكنك إنشاء نشاط تهيئة للودجت.

سيتم تشغيل هذا النشاط تلقائياً بواسطة مستضيف الودجت ويسمح للمستخدم بتهيئة الإعدادات المتاحة للودجت في وقت الإنشاء..

مثل لون الودجت، حجمه، فترة تحديثه، أو إعدادات الوظائف الأخرى.

نشاط التهيئة يجب الإعلان عنه كنشاط عادي في ملف إيضاح الأندرويد. ومع ذلك، سيتم تشغيله بواسطة مستضيف الودجت..

بإستخدام إجراء ACTION_APPWIDGET_CONFIGURE، لذلك يحتاج النشاط إلى قبول هذا الغرض. مثال: 

<activity android:name=".ExampleAppWidgetConfigure">
    <intent-filter>
        <action android:name="android.appwidget.action.APPWIDGET_CONFIGURE"/>
    </intent-filter>
</activity>

 

أيضاً، يجب أن يتم الإعلان عن النشاط في AppWidgetProviderInfo في ملف XML مع السمة android:configure ..

(أنظر إضافة بيانات وصفية لـ AppWidgetProviderInfo ). مثال، يمكن الإعلان عن نشاط التهيئة بهذا الشكل:

<appwidget-provider xmlns:android="http://schemas.android.com/apk/res/android"
    ...
    android:configure="com.example.android.ExampleAppWidgetConfigure"
    ... >
</appwidget-provider>

 

لاحظ أنه تم الإعلان عن النشاط بنطاق أسم مؤهل بالكامل، لأنه سيتم الإشارة إليه من خارج نطاق الحزمة الخاصة بك.

هذا كل ما تحتاج إليه للبدء في نشاط التهيئة. الآن كل ما تحتاجه هو النشاط الفعلي. ومع ذلك، هناك أمران مهمان يجب تذكرهما عند تنفيذ النشاط:

– يستدعي مستضيف الودجت نشاط التهيئة ويجب أن يقوم نشاط التهيئة دائماً بإرجاع نتيجة.

يجب أن تتضمن النتيجة معرّف الودجت الذي تم تمريره بواسطة الغرض الذي أطلق النشاط (المحفوظة في إضافات الغرض كـ EXTRA_APPWIDGET_ID).

 

لن يتم إستدعاء الدالة ()onUpdate عند إنشاء الودجت (لن يقوم النظام بإرسال البث ACTION_APPWIDGET_UPDATE عند تشغيل نشاط التهيئة).

تقع مسؤولية طلب تحديث من AppWidgetManager على نشاط التهيئة عند إنشاء الودجت لأول مرة.

ومع ذلك، سيتم إستدعاء ()onUpdate للحصول على التحديثات اللاحقه – يتم تخطيها فقط في المرة الأولى.

راجع مقتطفات الأكواد في القسم التالي، للإطلاع على مثال لكيفية إرجاع نتيجة من التهيئة وتحديث الودجت.

 

 

 

 

 

 

تصميم ودجت التطبيق

 

تحديث الودجت من نشاط التهيئة

عندما يستخدم الودجت نشاط للتهيئة، فإن مسؤولية النشاط هي تحديث الودجت عندما تكتمل التهيئة.

يمكنك القيام بذلك من خلال طلب التحديث مباشرة من AppWidgetManager.

في ما يلي ملخص لإجراءات تحديث الودجت بشكلٍ صحيح وإغلاق نشاط التهيئة:

1- أولاً، أحصل على معرف الودجت من الغرض الذي أطلق النشاط:

Kotlin

appWidgetId = intent?.extras?.getInt(
        AppWidgetManager.EXTRA_APPWIDGET_ID,
        AppWidgetManager.INVALID_APPWIDGET_ID
) ?: AppWidgetManager.INVALID_APPWIDGET_ID

java

Intent intent = getIntent();
Bundle extras = intent.getExtras();
if (extras != null) {
    appWidgetId = extras.getInt(
            AppWidgetManager.EXTRA_APPWIDGET_ID,
            AppWidgetManager.INVALID_APPWIDGET_ID);
}

 

2- قم بتنفيذ تهيئة الودجت.

3- عند إكتمال التهيئة، أحصل على مثيل لـ AppWidgetManager من خلال إستدعاء (getInstance(Context:

Kotlin

val appWidgetManager: AppWidgetManager = AppWidgetManager.getInstance(context)

java

AppWidgetManager appWidgetManager = AppWidgetManager.getInstance(context);

4- تحديث الودجت مع مخطط RemoteViews عن طريق إستدعاء

(updateAppWidget(int, RemoteViews:

 

Kotlin

RemoteViews(context.packageName, R.layout.example_appwidget).also { views->
    appWidgetManager.updateAppWidget(appWidgetId, views)
}

java

RemoteViews views = new RemoteViews(context.getPackageName(),
R.layout.example_appwidget);
appWidgetManager.updateAppWidget(appWidgetId, views);

 

5- أخيراً، قم بإنشاء غرض الإرجاع، وقم بتعيينه مع نتيجة النشاط، ثم أنهي النشاط:

 

Kotlin

val resultValue = Intent().apply {
    putExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, appWidgetId)
}
setResult(Activity.RESULT_OK, resultValue)
finish()

java

Intent resultValue = new Intent();
resultValue.putExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, appWidgetId);
setResult(RESULT_OK, resultValue);
finish();

 

نصيحة: عند فتح نشاط التهيئة لأول مرة، قم بتعيين نتيجة النشاط إلى RESULT_CANCELED، جنباً إلى جنب مع EXTRA_APPWIDGET_ID..

كما هو موضح في الخطوة 5 أعلاه. بهذه الطريقة، إذا قام المستخدم بالخروج من النشاط قبل الوصول إلى النهاية..

يتم إعلام مستضيف الودجت بأن التهيئة قد تم إلغاؤها ولن تتم إضافة ودجت.

راجع نموذج فئة ExampleAppWidgetConfigure.java في ApiDemos كمثال.

 

 

 

 

 

 

تصميم ودجت التطبيق

 

ضبط صورة المعاينة


يقدم أندرويد 3.0 حقل صورة المعاينة previewImage، والذي يحدد معاينة لما يبدو عليه الودجت.

يتم عرض هذه المعاينة للمستخدم من منتقي الودجت. إذا لم يتم توفير هذا الحقل، فسيتم إستخدام أيقونة الودجت للمعاينة.

هذه هي الطريقة التي تحدد بها هذا الإعداد في XML:

<appwidget-provider xmlns:android="http://schemas.android.com/apk/res/android"
  ...
  android:previewImage="@drawable/preview">
</appwidget-provider>

 

للمساعدة في تصميم صورة معاينة لودجت تطبيقك (لتحديدها في حقل previewImage)، يشتمل محاكي الأندرويد على..

تطبيق يسمى “معاينة الودجت”. لـ تصميم صورة معاينة، قم بتشغيل هذا التطبيق، ثم أختر ودجت لتطبيقك..

وقم بإعداد طريقة عرض صورة المعاينة التي تريد، ثم أحفظها وضعها في مصادر الرسوميات الخاصه بتطبيقك.

 

 

 

 

تصميم ودجت التطبيق

 

إستخدام الودجت مع مجموعات


يقدم أندرويد 3.0 ودجت مع مجموعات. هذه الأنواع من الودجت تستخدم RemoteViewsService لعرض المجموعات التي تدعمها البيانات عن بعد..

مثل، المجموعات من موفر المحتوى. البيانات المقدمة بواسطة RemoteViewsService يتم تقديمها في الودجت بإستخدام أحد أنواع العرض التالية..

والتي سنشير إليها بأسم “معاينات المجموعة:”

ListView

معاينه تظهر العناصر في قائمة يتم تمريرها عمودياً. مثال، راجع ودجت Gmail.

GridView

معاينة تظهر العناصر في شبكة تمرير ثنائية الأبعاد. مثال، راجع ودجت الإشارات المرجعية.

StackView

معاينة بطاقة الكومه (نوعاً ما مثل المذكرة rolodex)، حيث يمكن للمستخدم النقر على البطاقة الأمامية..

أعلى/ أسفل لرؤية البطاقة السابقة/التالية، على التوالي. تشمل الأمثلة ودجت اليوتيوب والكتب.

 

AdapterViewFlipper

محول ViewAnimator بسيط مدعوم والذي يتحرك بين طريقتي عرض أو أكثر. يتم عرض تابع واحد فقط في كل مرة.

 

كما هو مذكور أعلاه، طرق عرض المجموعات هذه تعرض مجموعات مدعومة بالبيانات البعيدة.

وهذا يعني أنهم يستخدمون محول لربط واجهة المستخدم بالبيانات الخاصة بهم. يقوم المحول بربط العناصر الفردية من مجموعة من البيانات إلى كائنات عرض مفردة.

لأن طرق عرض المجموعة هذه مدعومة بمحولات، يجب أن يتضمن إطار عمل الأندرويد بنية إضافية لدعم إستخدامها في الودجت.

في سياق الودجت، يتم إستبدال المحول بواسطة RemoteViewsFactory، وهو عبارة عن غلاف نحيف حول واجهة المحول.

عند طلب عنصر محدد في المجموعة، يقوم RemoteViewsFactory بإنشاء وإرجاع العنصر للمجموعة ككائن RemoteViews.

لتضمين عرض مجموعة في ودجت تطبيقك، يجب عليك تنفيذ RemoteViewsService و RemoteViewsFactory.

RemoteViewsService هي خدمة تسمح لمحول بعيد بطلب كائنات RemoteViews.

RemoteViewsFactory عبارة عن واجهة لمحول بين مجموعة عرض (مثل ListView و GridView وما إلى ذلك) والبيانات الأساسية لذلك العرض.

من نموذج StackWidget، إليك مثالاً على كود النسخة المنطقية الذي تستخدمه لتنفيذ هذه الخدمة والواجهة:

Kotlin

class StackWidgetService : RemoteViewsService() {

    override fun onGetViewFactory(intent: Intent): RemoteViewsFactory {
        return StackRemoteViewsFactory(this.applicationContext, intent)
    }
}

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

//... include adapter-like methods here. See the StackView Widget sample.

}

java

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

class StackRemoteViewsFactory implements RemoteViewsService.RemoteViewsFactory {

//... include adapter-like methods here. See the StackView Widget sample.

}

 

 

 

 

تصميم ودجت التطبيق

 

نموذج تطبيق


يتم رسم مقتطفات الكود في هذا القسم من نموذج StackWidget:

يتكون هذا النموذج من مجموعة مكونة من 10 معاينات تعرض القيم “!0” إلى “!9” يشتمل نموذج الودجت على هذه السلوكيات الأساسية:

– يمكن للمستخدم الدفع لأعلى العرض في الودجت لعرض المعاينة التاليه أو السابقه. هذا سلوك مضمن في StackView.

– بدون أي تفاعل من جانب المستخدم، تتقدم الودجت تلقائياً من خلال عروضها بشكلٍ متسلسل، مثل عرض شرائح.

يرجع ذلك إلى إعداد "android:autoAdvanceViewId ="@id/stack_view في ملف res/xml/stackwidgetinfo.xml.

ينطبق هذا الإعداد على معرّف العرض، وهو في هذه الحالة معرّف العرض لمعاينة الكومه.

 

– إذا لمس المستخدم العرض العلوي، فسيعرض الودجت رسالة منبثقه “لمس العرض n”، حيث يشير حرف n إلى..

الفهرس index (موضع) للمعاينة التي تم لمسها. لمزيد من المناقشة حول كيفية تنفيذ ذلك، راجع إضافة السلوك إلى عناصر فردية.

 

 

 

 

تصميم ودجت التطبيق

 

تنفيذ الودجت مع مجموعات

لتنفيذ الودجت مع مجموعات، فإنك سوف تتبع الخطوات الأساسية نفسها التي تستخدمها لتنفيذ أي ودجت.

توضح الأقسام التالية الخطوات الإضافية التي تحتاج إلى تنفيذها لتنفيذ ودجت مع المجموعات.

 

 

ملف الإيضاح لودجت التطبيقات مع المجموعات

بالإضافة إلى المتطلبات المدرجة في الإعلان عن الودجت في ملف الإيضاح، لتمكين الودجت مع المجموعات من الإرتباط..

بخدمة 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:

<?xml version="1.0" encoding="utf-8"?>

<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent">
    <StackView xmlns:android="http://schemas.android.com/apk/res/android"
        android:id="@+id/stack_view"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:gravity="center"
        android:loopViews="true" />
    <TextView xmlns:android="http://schemas.android.com/apk/res/android"
        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 ..

ويمكن أن يقدم الودجت البيانات المناسبة. عند إستدعاء هذه الدالة، يجب عليك تمرير غرض يشير إلى تنفيذك..

لـ RemoteViewsService ومعرف الودجت الذي يحدد الودجت المطلوب لتحديثه.

مثال، إليك كيفية تنفيذ نموذج StackWidget لدالة الإستدعاء ()onUpdate لتعيين RemoteViewsService كمحول بعيد لودجت المجموعه:

Kotlin

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

        // Set up the intent that starts the StackViewService, which will
        // provide the views for this collection.
        val intent = Intent(context, StackWidgetService::class.java).apply {
            // Add the app 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 app widget layout.
        val rv = 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 should be in the same layout used to instantiate the RemoteViews
            // object above.
            setEmptyView(R.id.stack_view, R.id.empty_view)
        }

        //
        // Do additional processing specific to this app widget...
        //

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

java

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

        // Set up the intent that starts the StackViewService, which will
        // provide the views for this collection.
        Intent intent = new Intent(context, StackWidgetService.class);
        // Add the app 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 app widget layout.
        RemoteViews rv = 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.
        rv.setRemoteAdapter(R.id.stack_view, intent);

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

        //
        // Do additional processing specific to this app widget...
        //

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

 

 

 

فئة RemoteViewsService 

إستمرار البيانات

كما هو موضح أعلاه، توفر الفئة الفرعية RemoteViewsService الخاصة بـ RemoteViewsFactory المستخدمة لنشر طريقة عرض المجموعة عن بُعد.

 

على وجه التحديد، تحتاج إلى تنفيذ الخطوات التالية:

1- فئة فرعية RemoteViewsService .RemoteViewsService هي الخدمة التي يمكن من خلالها للمحول البعيد أن يطلب RemoteViews.

2- في الفئة الفرعية RemoteViewsService، قم بتضمين فئة تقوم بتنفيذ واجهة RemoteViewsFactory

. RemoteViewsFactory عبارة عن واجهة لمحول بين عرض المجموعة عن بُعد (مثل ListView و GridView وما إلى ذلك) والبيانات الأساسية لذلك العرض.

يكون التنفيذ مسؤولاً عن إنشاء كائن RemoteViews لكل عنصر في مجموعة البيانات. هذه الواجهة عبارة عن غلاف نحيف حول المحول.

لا يمكنك الإعتماد على مثيل واحد من خدمتك، أو أي بيانات تحتوي عليها، للإستمرار. لذلك يجب ألا تخزن أي بيانات في RemoteViewsService (ما لم تكن ثابتة).

إذا كنت تريد إستمرار بيانات الودجت، فإن أفضل طريقة هي إستخدام ContentProvider الذي تستمر بياناته لما وراء دورة حياة العملية.

تتمثل المحتويات الأساسية لتنفيذ RemoteViewsService في RemoteViewsFactory، الموضح أدناه.

 

واجهة RemoteViewsFactory

الفئة المخصصة التي تنفذ واجهة RemoteViewsFactory توفر ودجت مع البيانات الخاصة بالعناصر الموجودة في المجموعة الخاصة به.

للقيام بذلك، يقوم بالجمع بين ملف مخطط XML الخاص بمصدراً للبيانات. يمكن أن يكون مصدر البيانات هذا أي شيء من قاعدة بيانات إلى مصفوفة بسيطة.

في نموذج StackWidget ، مصدر البيانات عبارة عن مصفوفة من WidgetItems.

تعمل وظيفة RemoteViewsFactory كمحول للصق البيانات إلى عرض المجموعة عن بعد.

أهم دالتان تحتاج لتنفيذها للفئة الفرعية RemoteViewsFactory الخاصة بك هي ()onCreate و ()getViewAt .

يستدعي النظام ()onCreate عند إنشاء المصنع لأول مرة. هذا هو المكان الذي تقوم فيه بإعداد أي إتصالات و / أو مؤشرات إلى مصدر البيانات الخاص بك.

مثال، يستخدم نموذج StackWidget

()onCreate لتهيئة مصفوفة من كائنات WidgetItem. عندما يكون ودجت تطبيقك نشطاً، يصل النظام إلى هذه الكائنات..

بإستخدام موضع فهرستها في المصفوفه ويتم عرض النص الذي تحتوي عليه.

في ما يلي مقتطف من تنفيذ RemoteViewsFactory لـ نموذج StackWidget الذي يعرض أجزاء من دالة ()onCreate :

Kotlin

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() you setup any connections / cursors to your data source. Heavy lifting,
        // for example downloading or creating content etc, should be deferred to onDataSetChanged()
        // or getViewAt(). Taking more than 20 seconds in this call will result in an ANR.
        widgetItems = List(REMOTE_VIEW_COUNT) { index -> WidgetItem("$index!") }
        ...
    }
    ...
}

java

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

    public void onCreate() {
        // In onCreate() you setup any connections / cursors to your data source. Heavy lifting,
        // for example downloading or creating content etc, should be deferred to onDataSetChanged()
        // or getViewAt(). Taking more than 20 seconds in this call will result in an ANR.
        for (int i = 0; i < count; i++) {
            widgetItems.add(new WidgetItem(i + "!"));
        }
        ...
    }
...

 

ترجع دالة ()getViewAt  الخاصة بـ RemoteViewsFactory كائن RemoteViews المطابق للبيانات في الموضع المحدد في مجموعة البيانات.

في ما يلي مقتطف من تنفيذ RemoteViewsFactory لـ نموذج StackWidget:

Kotlin

override fun getViewAt(position: Int): RemoteViews {
    // Construct a remote views item based on the app 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)
    }
}

java

public RemoteViews getViewAt(int position) {

    // Construct a remote views item based on the app 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);

    ...
    // Return the remote views object.
    return rv;
}

 

 

 

 

تصميم ودجت التطبيق

 

إضافة السلوك إلى عناصر فردية

توضح لك المقاطع أعلاه كيفية ربط بياناتك بمجموعة ودجت التطبيق.

ولكن ماذا لو كنت تريد إضافة سلوك حيوي إلى العناصر الفردية في عرض “معاينة” المجموعة الخاصة بك؟

كما هو موضح في إستخدام فئة AppWidgetProvider، فإنك عادة تستخدم ()setOnClickPendingIntent لتعيين سلوك النقر للكائن ..

مثل جعل الزر يقوم بتشغيل أحد الأنشطة. ولكن هذا النهج غير مسموح به للمعاينات الفرعيه في عنصر التجميع الفردي..

(للتوضيح، يمكنك إستخدام ()setOnClickPendingIntent لإعداد زر عام في ودجت تطبيق Gmail والذي يقوم بتشغيل التطبيق..

كمثال، ولكن ليس على عناصر القائمة الفردية). بدلاً من ذلك، لإضافة سلوك النقر إلى عناصر فردية في مجموعة..

يمكنك إستخدام ()setOnClickFillInIntent . يستلزم ذلك إعداد قالب غرض معلق لمجموعتك، ثم إعداد غرض تعبئة..

على كل عنصر في المجموعة عبر RemoteViewsFactory الخاص بك.

يستخدم هذا القسم نموذج “ودجت الكومه” StackWidget لوصف كيفية إضافة سلوك إلى عناصر فردية.

في نموذج StackWidget، إذا لمس المستخدم العرض العلوي، سيعرض الودجت رسالة منبثقه “لمس العرض n”..

حيث يشير حرف n إلى الفهرس (الموضع)، في العرض الذي تم لمسه. هذه هي الطريقة التي يعمل بها:

  • يقوم StackWidgetProvider (فئة فرعية من AppWidgetProvider) بإنشاء غرض معلق يحتوي على إجراء مخصص يسمى TOAST_ACTION.

 

  • عندما يلمس المستخدم عرض “معاينه”، يتم تشغيل الغرض ويقوم ببث TOAST_ACTION.

 

  • يتم إعتراض هذا البث بواسطة دالة ()onReceive الخاصة بـ StackWidgetProvider ، ويعرض الودجت رسالة منبثقه للعرض الذي تم لمسه.

يتم توفير البيانات الخاصة بعناصر المجموعة بواسطة RemoteViewsFactory ، عبر RemoteViewsService.

ملاحظة: يستخدم نموذج StackWidget عملية بث، ولكن عادة ما يبدأ الودجت ببساطة بتشغيل نشاطاً في سيناريو كهذا.

 

 

 

 

تصميم ودجت التطبيق

 

إعداد قالب الغرض المعلق 

تقوم StackWidgetProvider (فئة فرعية AppWidgetProvider) بإعداد غرض معلق. لا يمكن للعناصر الفردية للمجموعة إعداد الأغراض المعلقة الخاصة بها.

بدلاً من ذلك، تقوم المجموعة ككل، بإعداد قالب غرض معلق، وتقوم العناصر الفردية بتعيين غرض تعبئة، بهدف إنشاء سلوك فريد على أساس كل عنصر على حدة.

تستقبل هذه الفئة أيضاً البث الذي يتم إرساله عندما يلمس المستخدم طريقة عرض. يعالج هذا الحدث في دالة ()onReceive الخاصة به.

إذا كان إجراء الغرض هو TOAST_ACTION، فسيعرض الودجت رسالة منبثقه للعرض الحالي.

Kotlin

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 to see whether the intent's action is TOAST_ACTION. If it is, the app 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
            )
            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 app widgets with the remote adapter
        appWidgetIds.forEach { appWidgetId ->

            // Sets up the intent that points to the StackViewService that will
            // provide 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 we need to embed the extras
                // into the data so that the extras will not be 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 should 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 cannot 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 will have 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)
    }
}

java

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 to see whether the intent's action is TOAST_ACTION. If it is, the app 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);
            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 app widgets with the remote adapter
        for (int i = 0; i < appWidgetIds.length; ++i) {

            // Sets up the intent that points to the StackViewService that will
            // provide 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 we need to embed the extras
            // into the data so that the extras will not be 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 should 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
            // cannot 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 will have 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 بتعيين غرض تعبئة على كل عنصر في المجموعة. هذا يجعل من الممكن تمييز إجراء النقر الفردي لعنصر معين.

ثم يتم دمج غرض التعبئة مع قالب PendingIntent لتحديد الغرض النهائي الذي سيتم تنفيذه عند النقر فوق العنصر.

Kotlin

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() you setup any connections / cursors to your data source. Heavy lifting,
        // for example downloading or creating content etc, should be deferred to onDataSetChanged()
        // or getViewAt(). Taking more than 20 seconds in this call will result 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 app 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)

            // Next, set a fill-intent, which will be used 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)
            ...
        }
    }
    ...
}

java

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() you set up any connections / cursors to your data source. Heavy lifting,
            // for example downloading or creating content etc, should be deferred to onDataSetChanged()
            // or getViewAt(). Taking more than 20 seconds in this call will result 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 app widget item XML file to construct a RemoteViews object.
        public RemoteViews getViewAt(int position) {
            // position will always range from 0 to getCount() - 1.

            // Construct a RemoteViews item based on the app 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);

            // Next, set a fill-intent, which will be used 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، وكيف يمكنك تشغيل التحديثات:

 

تتمثل إحدى ميزات ودجت التطبيقات التي تستخدم المجموعات في القدرة على تزويد المستخدمين بالمحتوى المحدّث.

مثال، ضع في إعتبارك ودجت أندرويد 3.0 Gmail، الذي يوفر للمستخدمين لقطة من البريد الوارد.

لجعل هذا ممكناً، يجب أن تكون قادراً على تشغيل RemoteViewsFactory وعرض التجميع لجلب وعرض البيانات الجديدة.

يمكنك تحقيق ذلك بإستدعاء AppWidgetManager

()notifyAppWidgetViewDataChanged .

ينتج عن هذا الطلب إستدعاء لدالة RemoteViewsFactory الخاصة بك على ()onDataSetChanged، والتي تمنحك الفرصة لجلب أي بيانات جديدة.

لاحظ أنه يمكنك إجراء عمليات المعالجة المكثفة بشكل متزامن داخل الإستدعاء ()onDataSetChanged.

ويضمن لك إكمال هذا الطلب قبل جلب البيانات الوصفية أو بيانات العرض من RemoteViewsFactory.

بالإضافة إلى ذلك، يمكنك إجراء عمليات المعالجة المكثفة ضمن دالة ()getViewAt . إذا أستغرق هذا الإستدعاء وقتاً طويلاً..

فإن عرض التحميل (المحدد بواسطة دالة ()getLoadingView الخاصة بـ RemoteViewsFactory سيتم عرضها في الموضع المقابل لعرض المجموعة حتى يتم إرجاعها.

 

راجع:

تصميم ودجت للتطبيق

 


للإطلاع على المقال باللغة الإنجليزية أضغط هنا.

الإعلانات

اترك رد