确定网站建设的目的,装饰网站建设重要性,头像制作免费,渭南网站制作Android widget 小部件使用指南强化版 一、简单UI的小部件二、含集合的小部件三、可配置的小部件四、可控制的小部件五、Android 12 Widget 更新 小部件是主屏幕定制的一个重要方面。您可以将它们视为应用程序最重要的数据和功能的“概览”视图#xff0c;这些数据和功能可以直… Android widget 小部件使用指南强化版 一、简单UI的小部件二、含集合的小部件三、可配置的小部件四、可控制的小部件五、Android 12 Widget 更新 小部件是主屏幕定制的一个重要方面。您可以将它们视为应用程序最重要的数据和功能的“概览”视图这些数据和功能可以直接在用户的主屏幕上访问。用户可以在主屏幕面板上移动小部件如果支持的话还可以调整它们的大小以根据自己的喜好定制小部件中的信息量。
一、简单UI的小部件
此类小部件通常仅显示关键信息元素布局简单。小部件属于RemoteViews常用的控件是支持的如TextView、Images但是不支持自定义的控件具体参考创建应用微件布局
声明AppWidgetProviderInfoXML 定义了小部件的基本品质。AppWidgetProviderInfo使用单个元素在 XML 资源文件中 定义对象appwidget-provider并将其保存在项目的res/xml/文件夹中。
?xml version1.0 encodingutf-8?
appwidget-provider xmlns:androidhttp://schemas.android.com/apk/res/androidandroid:initialLayoutlayout/all_note_appwidget_layoutandroid:minWidth150dpandroid:minHeight54dpandroid:previewImagedrawable/todo_appwidget_previewandroid:resizeModenoneandroid:updatePeriodMillis86400000android:widgetCategoryhome_screen /initialLayout指向定义小部件布局的布局资源previewImage在选择应用小部件时看到的预览图updatePeriodMillis小部件更新频率实际更新不完全按时进行widgetCategory声明您的小部件是否可以显示在主屏home_screen、锁定屏幕keyguard 或两者上
在清单中声明一个小部件AndroidManifest.xml中声明小部件信息
receiverandroid:name.widget.AllNoteAppWidgetProviderandroid:labelstring/all_notesandroid:exportedtrueintent-filteraction android:nameandroid.appwidget.action.APPWIDGET_UPDATE / !-- 必须 --action android:namecom.android.note.widget.UPDATE_ALL_NOTE/ !-- 自定义 --/intent-filtermeta-dataandroid:nameandroid.appwidget.providerandroid:resourcexml/all_note_appwidget_info /
/receiverreceiver指定AppWidgetProvider小部件intent-filter指定接受 AppWidgetProvider广播。ACTION_APPWIDGET_UPDATE 这是您必须显式声明的唯一广播。根据需要自动 AppWidgetManager 将所有其他小部件广播发送到AppWidgetProvider。还可以自定义其他广播用于更新小部件的数据视图meta-dataandroid:name指定元数据名称用于 android.appwidget.provider将数据标识为 AppWidgetProviderInfo描述符。android:resource指定AppWidgetProviderInfo资源位置。
使用AppWidgetProvider来处理小部件广播 类AppWidgetProvider扩展 BroadcastReceiver为一个便利类来处理小部件广播它仅接收与小部件相关的事件广播例如小部件何时更新、删除、启用和禁用。所以我们重写AppWidgetProvider处理小部件广播并更新小部件以响应小部件生命周期事件。
public class AllNoteAppWidgetProvider extends AppWidgetProvider {public static final String UPDATE_ALL_NOTE_COUNT_ACTION com.android.note.widget.UPDATE_ALL_NOTE;Overridepublic void onUpdate(Context context, AppWidgetManager appWidgetManager, int[] appWidgetIds) {// 当添加相同的小部件时需要全部遍历更新for (int appWidgetId : appWidgetIds) {updateAppWidget(context, appWidgetManager, appWidgetId);}}Overridepublic void onReceive(Context context, Intent intent) {// 接收到自定义的广播用于做处理后更新小部件if (UPDATE_ALL_NOTE_COUNT_ACTION.equals(intent.getAction())) {final AppWidgetManager appWidgetManager AppWidgetManager.getInstance(context);final int[] appWidgetIds appWidgetManager.getAppWidgetIds(new ComponentName(context,this.getClass()));for (int appWidgetId : appWidgetIds) {updateAppWidget(context, appWidgetManager, appWidgetId);}} else {super.onReceive(context, intent);}}static void updateAppWidget(Context context, AppWidgetManager appWidgetManager, int appWidgetId) {// 小部件的布局实际就是RemoteViewsRemoteViews views new RemoteViews(context.getPackageName(), R.layout.all_note_appwidget_layout);//做数据处理NoteDataManager manager NoteDataManagerImpl.getInstance(context);int size manager.getNotesForPresetFolder(NoteDataManagerImpl.ALL_NOTE_FOLDER).size();// 更新视图作为RemoteViews只能使用其通过的方法来设置值views.setTextViewText(R.id.size, context.getString(R.string.widget_note_count, size));// 添加一个跳转应用的意图Intent composeIntent new Intent(context, NoteEditorActivity.class);composeIntent.putExtra(NoteEditorPresenter.OPEN_TYPE, 1);composeIntent.putExtra(FolderUtil.KEY_ID, 0);composeIntent.putExtra(FolderUtil.KEY_FILTER_TYPE, 0);PendingIntent composeNoteIntent PendingIntent.getActivity(context, WidgetUtil.getUniqueCode(),composeIntent, PendingIntent.FLAG_IMMUTABLE);views.setOnClickPendingIntent(R.id.btn_add, composeNoteIntent);Intent intent new Intent(context, MainActivity.class);intent.putExtra(Constants.KEY_DEFAULT_VIEW, Constants.TAG_NOTE);PendingIntent pendingIntent PendingIntent.getActivity(context, WidgetUtil.getUniqueCode(),intent, PendingIntent.FLAG_MUTABLE);views.setOnClickPendingIntent(R.id.header, pendingIntent);// 通过 appWidgetManager 更新视图appWidgetManager.updateAppWidget(appWidgetId, views);}// 用于外部调用更新小部件public static void updateCount(Context context) {context.sendBroadcast(new Intent(UPDATE_ALL_NOTE_COUNT_ACTION, null, context, AllNoteAppWidgetProvider.class));}
}onReceive(Context, Intent)接收来自小部件的广播或者自定义的广播以便来做一些操作。onUpdate()更新小部件调用此方法。当用户添加小部件时也会调用此方法因此它应该执行基本设置例如为 View对象定义事件处理程序或启动作业来加载要在小部件中显示的数据。
需要更新小部件时的更新方法
AllNoteAppWidgetProvider.updateCount(context);效果参考
二、含集合的小部件
集合小部件专门用于显示相同类型的许多元素例如来自图库应用程序的图片集合、来自新闻应用程序的文章或来自通信应用程序的消息。集合小部件通常专注于两个用例浏览集合以及将集合的元素打开到其详细视图。集合小部件可以垂直滚动。小部件使用以下视图类型之一呈现数据这些视图类型称为集合视图
ListView显示垂直滚动列表中的项目的视图。GridView显示二维滚动网格中的项目的视图。StackView堆叠卡片视图有点像名片盒用户可以向上或向下轻拂前面的卡片以分别查看上一张或下一张卡片。AdapterViewFlipper一个由适配器支持的简单动画 ViewAnimator可以在两个或多个视图之间进行动画处理。一次只显示一个孩子。
由于这些集合视图由适配器支持因此 Android 框架必须包含额外的架构来支持它们在小部件中的使用。在小部件的上下文中Adapter被替换为 RemoteViewsFactory它是界面的薄包装Adapter。当请求集合中的特定项目时RemoteViewsFactory将创建集合的项目并将其作为对象返回 RemoteViews。要在您的小部件中包含集合视图请实现RemoteViewsService和 RemoteViewsFactory。
关于创建小部件的大部分配置同简单UI小部件操作关于集合视图的小部件其他操作步骤如下:
小部件的配置的initialLayout中含有集合视图如ListView
RelativeLayout xmlns:androidhttp://schemas.android.com/apk/res/androidandroid:layout_widthmatch_parentandroid:layout_heightwrap_contentandroid:backgroundcolor/app_widget_backgroundandroid:orientationverticalandroid:padding16dpListViewandroid:idid/note_listandroid:layout_widthmatch_parentandroid:layout_heightwrap_contentandroid:layout_belowid/widget_headerandroid:listSelectordrawable/transparent_selectorandroid:scrollbarsnone //RelativeLayout在AndroidManifest.xml中声明RemoteViewsService
serviceandroid:name.widget.NoteListWidgetServiceandroid:exportedtrueandroid:permissionandroid.permission.BIND_REMOTEVIEWS /permission通过在清单文件中使用 权限 声明该服务来执行此操作 BIND_REMOTEVIEWS可以防止其他应用程序随意访问您的小部件的数据。
实现RemoteViewsService和 RemoteViewsFactory
public class NoteListWidgetService extends RemoteViewsService {private static final String TAG NoteListWidgetService;public NoteListWidgetService() {super();}Overridepublic IBinder onBind(Intent intent) {return super.onBind(intent);}Overridepublic RemoteViewsFactory onGetViewFactory(Intent intent) {// 创建 RemoteViewsFactoryreturn new ListViewRemoteViewsFactory(this, intent);}private static class ListViewRemoteViewsFactory implements RemoteViewsService.RemoteViewsFactory {private Context mContext;private int mAppWidgetId;private ArrayListNoteItem mItems new ArrayList();private AppWidgetManager mAppWidgetManager;public ListViewRemoteViewsFactory(Context context, Intent intent) {mContext context;// 获取传递来的 AppWidgetIdmAppWidgetId intent.getIntExtra(AppWidgetManager.EXTRA_APPWIDGET_ID,AppWidgetManager.INVALID_APPWIDGET_ID);}Overridepublic void onCreate() {// 加载数据NoteDataManager mManager NoteDataManagerImpl.getInstance(mContext);mItems (ArrayList)mManager.getNotesForPresetFolder(2);}Overridepublic void onDataSetChanged() {// 更新数据NoteDataManager mManager NoteDataManagerImpl.getInstance(mContext);mItems (ArrayList) mManager.getNotesForPresetFolder(2);}Overridepublic void onDestroy() {mItems.clear();}Overridepublic int getCount() {return Constants.WIDGET_LIST_COUNT;}Overridepublic RemoteViews getViewAt(int i) {int size mItems.size();if (i size) {NoteItem item mItems.get(i);Date date new Date(item.getLongDate());// 创建的ListView项的第一种布局类型RemoteViews itemView new RemoteViews(mContext.getPackageName(), R.layout.widget_listview_item_note);itemView.setTextViewText(R.id.note_time, DateUtil.getDisplayNoteTime(mContext, date));itemView.setTextViewText(R.id.note_title, NoteUtils.parseContent(mContext, item.getContent(), true));// 点击跳转应用及传递参数这里没有使用PendingIntent,而是在AppWidgetProvider中//通过setPendingIntentTemplate创建了模板这里只需要Intent即可Intent fillInIntent new Intent();fillInIntent.putExtra(FolderUtil.KEY_ID, item.getId());fillInIntent.putExtra(NoteEditorPresenter.OPEN_TYPE, NoteEditorPresenter.TYPE_EDIT_NOTE);itemView.setOnClickFillInIntent(R.id.listview_linearlayout, fillInIntent);return itemView;}//这里属于ListView项的第二种布局类型return new RemoteViews(mContext.getPackageName(), R.layout.app_widget_note_placeholder);}Overridepublic RemoteViews getLoadingView() {return null;}Overridepublic int getViewTypeCount() {// 布局类型的数目return 2;}Overridepublic long getItemId(int i) {return i;}Overridepublic boolean hasStableIds() {return false;}}
}关于 RemoteViewsService.RemoteViewsFactory类似于Adapter的使用
onCreate首次加载提供数据onDataSetChanged后续更新提供新数据onDestroy清除数据getCountListView显示数据的条数getViewAt返回ListView每项的视图数据和界面更新的操作都在这里getViewTypeCountListView项的视图种类的个数用于实现不同的布局至少返回1否则有数据也不显示显示正在加载中getItemId返回当前项的位置
在AppWidgetProvider更新时候调用
// 通过 NoteListWidgetService 更新需要传递appWidgetId
Intent serviceIntent new Intent(context, NoteListWidgetService.class);
serviceIntent.putExtra(AppWidgetManager.EXTRA_APPWIDGET_ID,appWidgetIds[i]);
views.setRemoteAdapter(R.id.note_list, serviceIntent);// 创建 PendingIntent 模板
// 但是不支持添加多个PendingIntent 模板那就意味着所有数据交互都只能走这一个PendingIntent
Intent itemIntent new Intent(context, NoteEditorActivity.class);
PendingIntent listPendingIntent PendingIntent.getActivity(context, 1,itemIntent, PendingIntent.FLAG_UPDATE_CURRENT | PendingIntent.FLAG_MUTABLE);
views.setPendingIntentTemplate(R.id.note_list, listPendingIntent);// 通过appWidgetManager更新 ListView调用 RemoteViewsFactory 的 onDataSetChanged
appWidgetManager.notifyAppWidgetViewDataChanged(appWidgetIds[i], R.id.note_list);效果参考默认显示3条数据若无则使用空白占位
三、可配置的小部件
如果您想让用户配置您的小部件的设置请创建小部件配置Activity此活动由应用程序小部件主机在创建小部件时或稍后自动启动具体取决于您指定的配置选项。
应用程序小部件宿主调用配置活动并且配置活动必须始终返回结果。结果必须包括启动该活动的意图传递的应用程序小部件 ID - 在意图附加中保存为 EXTRA_APPWIDGET_ID.启动配置活动时系统不会发送广播 这意味着创建小部件时ACTION_APPWIDGET_UPDATE 不会调用该方法。onUpdate()配置活动有责任在AppWidgetManager第一次创建小部件时请求更新。但是 onUpdate()会在后续更新中调用 - 仅在第一次时跳过。 此类小部件可以根据配置来显示不同用的小部件内容。 关于创建小部件的大部分配置同简单UI小部件操作关于可配置的小部件其他操作步骤如下:
声明配置Activity在 AndroidManifest.xml 中将配置活动声明为普通Activity
activity android:name.widget.FolderAppWidgetConfigureandroid:exportedtrueintent-filteraction android:nameandroid.appwidget.action.APPWIDGET_CONFIGURE//intent-filter
/activityintent-filter应用程序小部件主机通过 ACTION_APPWIDGET_CONFIGURE 操作启动它因此活动需要接受此意图
在AppWidgetProviderInfo.xml使用属性声明配置的Activity
?xml version1.0 encodingutf-8?
appwidget-provider xmlns:androidhttp://schemas.android.com/apk/res/androidandroid:configurecom.android.note.widget.FolderAppWidgetConfigureandroid:initialLayoutlayout/folder_appwidget_layoutandroid:minWidth250dpandroid:minHeight110dpandroid:previewImagedrawable/todo_appwidget_previewandroid:resizeModenoneandroid:updatePeriodMillis86400000android:widgetCategoryhome_screen /android:configure必须配置才可以生效
小部件Activity配置的具体实现
public class ExampleAppWidgetConfigure extends Activity {static final String TAG ExampleAppWidgetConfigure;private static final String PREFS_NAME com.example.android.apis.appwidget.ExampleAppWidgetProvider;private static final String PREF_PREFIX_KEY prefix_;int mAppWidgetId AppWidgetManager.INVALID_APPWIDGET_ID;EditText mAppWidgetPrefix;public ExampleAppWidgetConfigure() {super();}Overridepublic void onCreate(Bundle icicle) {super.onCreate(icicle);// 设置Activity布局可以当作一个普通Activity来操作即可例如设置文本、设置点击事件等setContentView(R.layout.appwidget_configure);mAppWidgetPrefix (EditText)findViewById(R.id.appwidget_prefix);findViewById(R.id.save_button).setOnClickListener(mOnClickListener);// 必须从intent获取 widget idIntent intent getIntent();Bundle extras intent.getExtras();if (extras ! null) {mAppWidgetId extras.getInt(AppWidgetManager.EXTRA_APPWIDGET_ID, AppWidgetManager.INVALID_APPWIDGET_ID);}// 如果 widget id 无效则放弃if (mAppWidgetId AppWidgetManager.INVALID_APPWIDGET_ID) {finish();}mAppWidgetPrefix.setText(loadTitlePref(ExampleAppWidgetConfigure.this, mAppWidgetId));}View.OnClickListener mOnClickListener new View.OnClickListener() {public void onClick(View v) {final Context context ExampleAppWidgetConfigure.this;// 点击确定按钮时根据AppWidgetId保存一些我们需要的信息到prefs中// 你可以保存更多您需要的信息用于后续更新小部件的内容String titlePrefix mAppWidgetPrefix.getText().toString();saveTitlePref(context, mAppWidgetId, titlePrefix);// 使用新设置的前缀将小部件更新推送到视图上AppWidgetManager appWidgetManager AppWidgetManager.getInstance(context);ExampleAppWidgetProvider.updateAppWidget(context, appWidgetManager,mAppWidgetId, titlePrefix);// 必须需要返回接受到的原始的appWidgetId// 你可以传递更多的值用于后续更新小部件的内容Intent resultValue new Intent();resultValue.putExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, mAppWidgetId);setResult(RESULT_OK, resultValue);finish();}};// 将此前缀写入该小部件的 SharedPreferences 对象static void saveTitlePref(Context context, int appWidgetId, String text) {SharedPreferences.Editor prefs context.getSharedPreferences(PREFS_NAME, 0).edit();prefs.putString(PREF_PREFIX_KEY appWidgetId, text);prefs.commit();}// 从该小部件的 SharedPreferences 对象中读取前缀。如果没有保存首选项则从资源获取默认值static String loadTitlePref(Context context, int appWidgetId) {SharedPreferences prefs context.getSharedPreferences(PREFS_NAME, 0);String prefix prefs.getString(PREF_PREFIX_KEY appWidgetId, null);if (prefix ! null) {return prefix;} else {return context.getString(R.string.appwidget_prefix_default);}}
}EXTRA_APPWIDGET_ID必须接收从intent中的AppWidgetId最后完成配置后也必须返回此AppWidgetIdSharedPreferences存值我们需要用appWidgetId 作为key以便添加多个小部件时不混淆
允许用户重新配置放置的小部件
默认情况下应用程序小部件主机仅在用户将小部件添加到主屏幕后立即启动配置活动一次。但是您可以指定选项使用户能够重新配置现有小部件或通过提供默认小部件配置来跳过初始小部件配置。
要让用户重新配置现有小部件需要在widgetFeatures中指定标志 reconfigurable
appwidget-providerandroid:configurecom.myapp.ExampleAppWidgetConfigurationActivityandroid:widgetFeaturesreconfigurable
/appwidget-providerreconfigurable标志在 Android 9API 级别 28中引入的但直到 Android 12 才得到广泛支持。
四、可控制的小部件
控制小部件的主要目的是显示常用功能以便用户可以从主屏幕触发它们而无需打开应用程序。例如待办事项的复选框的与元素交互将它们标记为完成由于从Android 12API 级别 31才开始支持这种复合按钮CheckBox、Switch、RadioButton。 而在 Android 12 之前我们也可以实现没有可以通过使用ImageView来达到这种效果RemoteViews 提供了 setImageViewResource 用于更新ImageView资源我们只需要准备2张图片资源即可。
RemoteViews itemView new RemoteViews(mContext.getPackageName(), R.layout.widget_listview_item_todo);
itemView.setImageViewResource(R.id.todo_check, item.isComplete() ? R.drawable.todo_on : R.drawable.todo_off);然而这仅仅是更新了小部件的UI我们如何更新UI背后的数据逻辑呢? 这里是有方案但是有缺陷由于待办事项属于ListView布局而本身具备点击事件点击跳转到对应的待办内容详情页Activity因此需要在AppWidgetProvider中设置PendingIntent 模板但在我们需要更新待办完成数据时就还需要利用这个PendingIntent 模板如下
Intent intent new Intent();
intent.setAction(TodoAppWidgetProvider.CHECK_TODO_ACTION);
intent.putExtra(TodoAppWidgetProvider.KEY_ITEM_CHECK_ID, item.getId());
itemView.setOnClickFillInIntent(R.id.todo_check, intent);这个时候可以在Activity中完成数据的更新操作但是会跳转到Activity中这个是我们不想要的所以跳转后就需要finish此Activity但实际测试仍会出现闪一下Activity的现象这就是缺陷使用子线程仍会出现。
if (action ! null action.equals(TodoAppWidgetProvider.CHECK_TODO_ACTION)) {updateTodoWidget(intent);finish();return;
}private void updateTodoWidget(Intent intent) {new Thread(new Runnable() {Overridepublic void run() {int id intent.getIntExtra(TodoAppWidgetProvider.KEY_ITEM_CHECK_ID, 0);TodoItem todoItem mNoteDataManager.getTodoItem(id);// 这里省略了对此todoItem的一些数据操作使其完成或未完成if (mNoteDataManager.updateTodo(todoItem) 0) {TodoAppWidgetProvider.updateWidget(mContext);}}}).start();
}五、Android 12 Widget 更新
Google官方文档[1] Android 12 微件改进 [2] AppWidget 示例 - Android 12 - Kotlin
实现圆角Android 12 中的微件采用圆角设计。将应用微件用在搭载 Android 12 或更高版本的设备上时启动器会自动识别微件的背景并将其剪裁成具有圆角。添加设备主题微件可以为按钮、背景及其他组件使用设备主题颜色包括浅色主题和深色主题。使微件的个性化设置更容易使用 appwidget-provider 的 configure 属性指定配置 activity添加了一些新选项添加新的复合按钮CheckBox、Switch、RadioButton使用改进的 API 设置微件大小和布局改进应用的微件选择器体验实现更流畅的过渡使用简化的 RemoteViews 集合Android 12 添加了 setRemoteAdapter(int viewId, RemoteViews.RemoteCollectionItems items) 方法可让您的应用在填充 ListView 时直接传递集合。使用 RemoteViews 的运行时修改Android 12 添加了几个 RemoteViews 方法可用于在运行时修改 RemoteViews 属性。 相关参考 [1] https://developer.android.google.cn/develop/ui/views/appwidgets/overview [2] https://android.googlesource.com/platform/development//master/samples/ApiDemos/src/com/example/android/apis/appwidget