Android weidget小组件 概述与简单widget

转载请注明原创出处,谢谢!

ps:
因业务需求,需要给app增加Widget小组件,网上找了很多文档,文档零零散散,也比较老,所以总结一下

应用 widget 概览

小组件是什么

个人任务小组件就是,自定义在主屏幕上“一目了然”的视图。让用户在主屏幕即可进行访问,查看重要数据和功能。丰富手机可玩性。

小组件类型

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

桌面小组件和负一屏小部件的区别

已小米手机为例,普通开发的小组件是不能放到负一屏的,在开发阶段我们需要对小米进行适配,然后将app上传到小米小部件开发者平台才能进行调试,在正式环境,还需要去审核。

创建简单的widget

声明 AppWidgetProviderInfo XML

AppWidgetProviderInfo 对象定义了 widget 的基本特性。您可以使用单个 <appwidget-provider> 元素在 XML 资源文件中定义 AppWidgetProviderInfo 对象,并将其保存在项目的 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 的默认大小,如果微件的最小宽度或高度的值与单元格的尺寸不匹配,则这些值会向上舍入到最接近的单元格大小。
  • updatePeriodMillis:定义微件框架通过调用 onUpdate() 回调方法来从 AppWidgetProvider 请求更新的频率。不能保证实际更新按此值正好准时发生,google建议尽可能降低更新频率(不超过每小时一次),以节省电池电量。不支持小于 30 分钟的值,也就是最小设置为1800000
  • initialLayout:指向用于定义微件布局的布局资源。

使用 AppWidgetProvider 类处理微件广播

AppWidgetProvider 类会处理 widget 广播,并响应 widget 生命周期事件来更新 widget。

在清单中声明微件

首先,在应用的 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:调起在 widget 选择器中显示时,widget 需要具有唯一的名称。

实现 AppWidgetProvider 类

AppWidgetProvider 类扩展了 BroadcastReceiver 作为一个辅助类来处理微件广播。它仅接收与微件相关的事件广播,例如当更新、删除、启用和停用微件时发出的广播。

  • onUpdate:调用此方法可以按 AppWidgetProviderInfo 中的 updatePeriodMillis 属性定义的时间间隔来更新微件。
  • onAppWidgetOptionsChanged:当首次放置微件时以及每当调整微件的大小时,系统都会调用此方法。
  • onDeleted:每次从微件托管应用中删除微件时,都会调用此方法。
  • onEnabled:首次创建微件实例时,系统会调用此方法。例如,如果用户添加 widget 的两个实例,只有首次添加时会调用此方法。
  • onDisabled:从微件托管应用中删除了微件的最后一个实例时,会调用此方法。
  • onReceive:针对每个广播调用此方法,并且是在上述各个回调方法之前调用。
public class TestWidget extends AppWidgetProvider {

    /**
     * 调用此方法可以按 AppWidgetProviderInfo 中的 updatePeriodMillis 属性定义的时间间隔来更新微件。
     */
    @Override
    public void onUpdate(Context context, AppWidgetManager appWidgetManager, int[] appWidgetIds) {
        for (int appWidgetId : appWidgetIds) {
            updateAppWidget(context, appWidgetManager, appWidgetId);
        }
    }

    /**
     * 当首次放置微件时以及每当调整微件的大小时,系统都会调用此方法。
     */
    @Override
    public void onAppWidgetOptionsChanged(Context context, AppWidgetManager appWidgetManager, int appWidgetId, Bundle newOptions) {
        super.onAppWidgetOptionsChanged(context, appWidgetManager, appWidgetId, newOptions);
        Bundle appWidgetOptions = appWidgetManager.getAppWidgetOptions(appWidgetId);
        // 包含微件实例宽度的下限
        Log.i("TAG", "onAppWidgetOptionsChanged: 包含微件实例宽度的下限 " + appWidgetOptions.getInt(AppWidgetManager.OPTION_APPWIDGET_MIN_WIDTH));
        // 包含微件实例的高度下限
        Log.i("TAG", "onAppWidgetOptionsChanged: 包含微件实例的高度下限 " + appWidgetOptions.getInt(AppWidgetManager.OPTION_APPWIDGET_MIN_HEIGHT));
        // 包含微件实例的宽度上限
        Log.i("TAG", "onAppWidgetOptionsChanged: 包含微件实例的宽度上限 " + appWidgetOptions.getInt(AppWidgetManager.OPTION_APPWIDGET_MAX_WIDTH));
        // 包含微件实例的高度上限
        Log.i("TAG", "onAppWidgetOptionsChanged: 包含微件实例的高度上限 " + appWidgetOptions.getInt(AppWidgetManager.OPTION_APPWIDGET_MAX_HEIGHT));
    }

    /**
     * 每次从微件托管应用中删除微件时,都会调用此方法。
     */
    @Override
    public void onDeleted(Context context, int[] appWidgetIds) {
    }

    /**
     * 首次创建微件实例时,系统会调用此方法。例如,如果用户添加 widget 的两个实例,只有首次添加时会调用此方法。
     */
    @Override
    public void onEnabled(Context context) {
    }

    /**
     * 从微件托管应用中删除了微件的最后一个实例时,会调用此方法。
     */
    @Override
    public void onDisabled(Context context) {
    }

    /**
     * 针对每个广播调用此方法,并且是在上述各个回调方法之前调用。
     * 要注意,实测!!!会收到appwidgetId为0,从而导致组件更新失败
     */
    @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 {
            // 要注意,实测!!!会收到appwidgetId为0,从而导致组件更新失败
            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);
    }
}

创建 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"加上这行代码,手机会自动圆角,但是还是建议你给background设置上圆角,解决适配部分手机、低版本没有圆角的问题(三星)
  • android:padding="@dimen/dp_16" 更具设计规范,要是是有边距的
  • android:minHeight="@dimen/dp_80"更具我结合三方app、并且多轮测试下来,强烈介意采用这种规则适配,要不然你在平板上看很完美,但是手机上会被拖拽拉升的特别大,很难看的。

应用主动刷新刷新

<intent-filter>里加一个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();
        }
    }

应用主动添加小组件

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);
}
  • 各大厂商的手机好像有兼容问题,不能保证都成功
©著作权归作者所有,转载或内容合作请联系作者
【社区内容提示】社区部分内容疑似由AI辅助生成,浏览时请结合常识与多方信息审慎甄别。
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

相关阅读更多精彩内容

友情链接更多精彩内容