Android weidget小組件 概述與簡單widget

轉(zhuǎn)載請注明原創(chuàng)出處,謝謝!

ps:
因業(yè)務(wù)需求,需要給app增加Widget小組件,網(wǎng)上找了很多文檔,文檔零零散散,也比較老,所以總結(jié)一下

應(yīng)用 widget 概覽

小組件是什么

個(gè)人任務(wù)小組件就是,自定義在主屏幕上“一目了然”的視圖。讓用戶在主屏幕即可進(jìn)行訪問,查看重要數(shù)據(jù)和功能。豐富手機(jī)可玩性。

小組件類型

  • 普通
  • 列表(ListView)
  • 其他

桌面小組件和負(fù)一屏小部件的區(qū)別

已小米手機(jī)為例,普通開發(fā)的小組件是不能放到負(fù)一屏的,在開發(fā)階段我們需要對小米進(jìn)行適配,然后將app上傳到小米小部件開發(fā)者平臺才能進(jìn)行調(diào)試,在正式環(huán)境,還需要去審核。

創(chuàng)建簡單的widget

聲明 AppWidgetProviderInfo XML

AppWidgetProviderInfo 對象定義了 widget 的基本特性。您可以使用單個(gè) <appwidget-provider> 元素在 XML 資源文件中定義 AppWidgetProviderInfo 對象,并將其保存在項(xiàng)目的 res/xml/ 文件夾中。

<appwidget-provider xmlns:android="http://schemas.android.com/apk/res/android"
    android:minWidth="40dp"
    android:minHeight="40dp"
    android:targetCellWidth="1"
    android:targetCellHeight="1"
    android:maxResizeWidth="250dp"
    android:maxResizeHeight="120dp"
    android:updatePeriodMillis="86400000"
    android:description="@string/example_appwidget_description"
    android:previewLayout="@layout/example_appwidget_preview"
    android:initialLayout="@layout/example_loading_appwidget"
    android:configure="com.example.android.ExampleAppWidgetConfigurationActivity"
    android:resizeMode="horizontal|vertical"
    android:widgetCategory="home_screen"
    android:widgetFeatures="reconfigurable|configuration_optional">
</appwidget-provider>
  • minWidth 和 minHeight:指定 widget 的默認(rèn)大小,如果微件的最小寬度或高度的值與單元格的尺寸不匹配,則這些值會(huì)向上舍入到最接近的單元格大小。
  • updatePeriodMillis:定義微件框架通過調(diào)用 onUpdate() 回調(diào)方法來從 AppWidgetProvider 請求更新的頻率。不能保證實(shí)際更新按此值正好準(zhǔn)時(shí)發(fā)生,google建議盡可能降低更新頻率(不超過每小時(shí)一次),以節(jié)省電池電量。不支持小于 30 分鐘的值,也就是最小設(shè)置為1800000
  • initialLayout:指向用于定義微件布局的布局資源。

使用 AppWidgetProvider 類處理微件廣播

AppWidgetProvider 類會(huì)處理 widget 廣播,并響應(yīng) widget 生命周期事件來更新 widget。

在清單中聲明微件

首先,在應(yīng)用的 AndroidManifest.xml 文件中聲明 AppWidgetProvider 類

<receiver android:name="ExampleAppWidgetProvider"
                 android:lable="小組件"
                 android:exported="false">
    <intent-filter>
        <action android:name="android.appwidget.action.APPWIDGET_UPDATE" />
    </intent-filter>
    <meta-data android:name="android.appwidget.provider"
               android:resource="@xml/example_appwidget_info" />
</receiver>
  • android:lable:調(diào)起在 widget 選擇器中顯示時(shí),widget 需要具有唯一的名稱。

實(shí)現(xiàn) AppWidgetProvider 類

AppWidgetProvider 類擴(kuò)展了 BroadcastReceiver 作為一個(gè)輔助類來處理微件廣播。它僅接收與微件相關(guān)的事件廣播,例如當(dāng)更新、刪除、啟用和停用微件時(shí)發(fā)出的廣播。

  • onUpdate:調(diào)用此方法可以按 AppWidgetProviderInfo 中的 updatePeriodMillis 屬性定義的時(shí)間間隔來更新微件。
  • onAppWidgetOptionsChanged:當(dāng)首次放置微件時(shí)以及每當(dāng)調(diào)整微件的大小時(shí),系統(tǒng)都會(huì)調(diào)用此方法。
  • onDeleted:每次從微件托管應(yīng)用中刪除微件時(shí),都會(huì)調(diào)用此方法。
  • onEnabled:首次創(chuàng)建微件實(shí)例時(shí),系統(tǒng)會(huì)調(diào)用此方法。例如,如果用戶添加 widget 的兩個(gè)實(shí)例,只有首次添加時(shí)會(huì)調(diào)用此方法。
  • onDisabled:從微件托管應(yīng)用中刪除了微件的最后一個(gè)實(shí)例時(shí),會(huì)調(diào)用此方法。
  • onReceive:針對每個(gè)廣播調(diào)用此方法,并且是在上述各個(gè)回調(diào)方法之前調(diào)用。
public class TestWidget extends AppWidgetProvider {

    /**
     * 調(diào)用此方法可以按 AppWidgetProviderInfo 中的 updatePeriodMillis 屬性定義的時(shí)間間隔來更新微件。
     */
    @Override
    public void onUpdate(Context context, AppWidgetManager appWidgetManager, int[] appWidgetIds) {
        for (int appWidgetId : appWidgetIds) {
            updateAppWidget(context, appWidgetManager, appWidgetId);
        }
    }

    /**
     * 當(dāng)首次放置微件時(shí)以及每當(dāng)調(diào)整微件的大小時(shí),系統(tǒng)都會(huì)調(diào)用此方法。
     */
    @Override
    public void onAppWidgetOptionsChanged(Context context, AppWidgetManager appWidgetManager, int appWidgetId, Bundle newOptions) {
        super.onAppWidgetOptionsChanged(context, appWidgetManager, appWidgetId, newOptions);
        Bundle appWidgetOptions = appWidgetManager.getAppWidgetOptions(appWidgetId);
        // 包含微件實(shí)例寬度的下限
        Log.i("TAG", "onAppWidgetOptionsChanged: 包含微件實(shí)例寬度的下限 " + appWidgetOptions.getInt(AppWidgetManager.OPTION_APPWIDGET_MIN_WIDTH));
        // 包含微件實(shí)例的高度下限
        Log.i("TAG", "onAppWidgetOptionsChanged: 包含微件實(shí)例的高度下限 " + appWidgetOptions.getInt(AppWidgetManager.OPTION_APPWIDGET_MIN_HEIGHT));
        // 包含微件實(shí)例的寬度上限
        Log.i("TAG", "onAppWidgetOptionsChanged: 包含微件實(shí)例的寬度上限 " + appWidgetOptions.getInt(AppWidgetManager.OPTION_APPWIDGET_MAX_WIDTH));
        // 包含微件實(shí)例的高度上限
        Log.i("TAG", "onAppWidgetOptionsChanged: 包含微件實(shí)例的高度上限 " + appWidgetOptions.getInt(AppWidgetManager.OPTION_APPWIDGET_MAX_HEIGHT));
    }

    /**
     * 每次從微件托管應(yīng)用中刪除微件時(shí),都會(huì)調(diào)用此方法。
     */
    @Override
    public void onDeleted(Context context, int[] appWidgetIds) {
    }

    /**
     * 首次創(chuàng)建微件實(shí)例時(shí),系統(tǒng)會(huì)調(diào)用此方法。例如,如果用戶添加 widget 的兩個(gè)實(shí)例,只有首次添加時(shí)會(huì)調(diào)用此方法。
     */
    @Override
    public void onEnabled(Context context) {
    }

    /**
     * 從微件托管應(yīng)用中刪除了微件的最后一個(gè)實(shí)例時(shí),會(huì)調(diào)用此方法。
     */
    @Override
    public void onDisabled(Context context) {
    }

    /**
     * 針對每個(gè)廣播調(diào)用此方法,并且是在上述各個(gè)回調(diào)方法之前調(diào)用。
     * 要注意,實(shí)測?。?!會(huì)收到appwidgetId為0,從而導(dǎo)致組件更新失敗
     */
    @Override
    public void onReceive(Context context, Intent intent) {
        super.onReceive(context, intent);
        int appWidgetId = intent.getIntExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, AppWidgetManager.INVALID_APPWIDGET_ID);
        if (appWidgetId != AppWidgetManager.INVALID_APPWIDGET_ID) {
            updateAppWidget(context, AppWidgetManager.getInstance(context), appWidgetId);
        } else {
            // 要注意,實(shí)測!??!會(huì)收到appwidgetId為0,從而導(dǎo)致組件更新失敗
            Log.i("TAG", "onReceive: 收到appwidgetId為0的廣播");
            int[] appWidgetIds = AppWidgetManager.getInstance(context).getAppWidgetIds(new ComponentName(context, TestWidget.class));
            for (int id : appWidgetIds) {
                updateAppWidget(context, AppWidgetManager.getInstance(context), id);
            }
        }
    }

    private void updateAppWidget(Context context, AppWidgetManager appWidgetManager, int appWidgetId) {
        RemoteViews views = new RemoteViews(context.getPackageName(), R.layout.widget_test);
        // ...
        // Instruct the widget manager to update the widget
        appWidgetManager.updateAppWidget(appWidgetId, views);
    }
}

創(chuàng)建 widget 布局

RemoteViews 僅限于支持以下布局:
AdapterViewFlipper、FrameLayout、GridLayout、GridView、LinearLayout、ListView、RelativeLayout、StackView、ViewFlipper
以及以下小部件:
AnalogClock、Button、Chronometer、ImageButton、ImageView、ProgressBar、TextClock、TextView
自 API 31 起,以下小部件和布局也可以使用:
CheckBox、RadioButton、RadioGroup、Switch
這些類的子類不受支持。

<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:id="@android:id/background"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:background="#FFFFFF"
    android:gravity="center"
    android:orientation="vertical">

    <LinearLayout
        android:id="@+id/ll_root"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:minHeight="@dimen/dp_80"
        android:orientation="vertical"
        android:padding="@dimen/dp_16">

    </LinearLayout>
</FrameLayout>
  • android:id="@android:id/background"加上這行代碼,手機(jī)會(huì)自動(dòng)圓角,但是還是建議你給background設(shè)置上圓角,解決適配部分手機(jī)、低版本沒有圓角的問題(三星)
  • android:padding="@dimen/dp_16" 更具設(shè)計(jì)規(guī)范,要是是有邊距的
  • android:minHeight="@dimen/dp_80"更具我結(jié)合三方app、并且多輪測試下來,強(qiáng)烈介意采用這種規(guī)則適配,要不然你在平板上看很完美,但是手機(jī)上會(huì)被拖拽拉升的特別大,很難看的。

應(yīng)用主動(dòng)刷新刷新

<intent-filter>里加一個(gè)action,例如“aaa.test.refresh”;

    public static void refreshWidget(Context context) {
        try {
            Intent intent = new Intent(context, TestWidget.class);
            intent.setAction("aaa.test.refresh");
            int[] appWidgetIds = AppWidgetManager.getInstance(context).getAppWidgetIds(new ComponentName(context, TestWidget.class));
            for (int appWidgetId : appWidgetIds) {
                intent.putExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, appWidgetId);
                PendingIntent broadcast = PendingIntent.getBroadcast(context, appWidgetId, intent, PendingIntent.FLAG_ONE_SHOT);
                try {
                    broadcast.send();
                } catch (PendingIntent.CanceledException e) {
                    e.printStackTrace();
                }
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

應(yīng)用主動(dòng)添加小組件

AppWidgetManager appWidgetManager = AppWidgetManager.getInstance(context);
ComponentName myProvider = new ComponentName(context, ExampleAppWidgetProvider.class);

if (appWidgetManager.isRequestPinAppWidgetSupported()) {
    PendingIntent successCallback = PendingIntent.getBroadcast(
            context,  0, new Intent(...), PendingIntent.FLAG_UPDATE_CURRENT);
    appWidgetManager.requestPinAppWidget(myProvider, null, successCallback);
}
  • 各大廠商的手機(jī)好像有兼容問題,不能保證都成功
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容