var head = document.getElementsByTagName('head')[0], meta = document.createElement('meta'); meta.setAttribute('..._action_appwidget_enabled">
当前位置:   article > 正文

拜雨学Android之AppWidget_action_appwidget_enabled

action_appwidget_enabled

王侯不拜雨烟屐, 方知无欲是逍遥

AppWidget桌面小组件实战

基本介绍

桌面小组件一般不常用,使用也相对简单.桌面小组件主要是通过监听系统广播实现,创建一个桌面小组件,你需要了解下面两个类:

AppWidgetProviderInfo

组件配置信息, 主要描述了桌面小组件的元信息,例如布局文件,更新频率,配置页面等

AppWidgetProvider

一个广播接收者,监听用户创建桌面小组件的整个生命周期,主要接收如下几种广播

  • ACTION_APPWIDGET_UPDATE
  • ACTION_APPWIDGET_DELETED
  • ACTION_APPWIDGET_ENABLED
  • ACTION_APPWIDGET_DISABLED
  • ACTION_APPWIDGET_OPTIONS_CHANGED

对应的AppWidgetProvider会根据不同广播调用对应的方法

  • onUpdate()

该方法会按着用户设置的更新频率定时调用,在首次创建AppWidget时候也会调用,主要在这里更新布局

  • onDeleted()

在用户移除桌面小组件时候调用

  • onEnabled()

当用户在桌面小组件创建时候调用,可以初始化一些数据

  • onDisabled()

在删除AppWidget时候调用,一般用户回收资源,清除所有的数据请求

  • onAppWidgetOptionsChanged()

如果你设置了AppWidget可以修改大小,在用户修改桌面小组件大小时候调用

到这里对AppWidget有个大概的了解,下面会根据官方Demo进行实战讲解,更进一步学会使用AppWidget

实战训练

创建桌面组件

首选先创建一个简单的桌面小组件,用户可以将组件添加到桌面.

  1. 添加一个AppWidgetProviderInfo

AppWidgetProviderInfo定一个了一个AppWidget的基本信息,AppWidgetProviderInfo的创建需要在工程的res/xml中定一个一个<appwidget-provider>元素标签的xml文件

  1. <?xml version="1.0" encoding="utf-8"?>
  2. <appwidget-provider xmlns:android="http://schemas.android.com/apk/res/android"
  3. android:updatePeriodMillis="3600000"
  4. android:previewImage="@drawable/preview"
  5. android:initialLayout="@layout/desktop_widget_layout"
  6. android:minWidth="250dp"
  7. android:widgetCategory="home_screen|keyguard"
  8. android:minHeight="40dp">
  9. </appwidget-provider>

标签<appwidget-provider>对应的每个属性都对应AppWidgetProviderInfo类中的属性.这面对这些属性做一个简要说明

updatePeriodMillis

AppWidget刷新频率会回调onUpdate方法

initialLayout

AppWidget显示的布局文件

minWidthminHeight

AppWidget大小,这个的设计是有标准的,对应桌面格数,4x4等,更多可以参考WidgetDesignGuide

configure

指定一个Activity,在AppWidget创建前启动,来配置一些AppWidget的需要的数据

previewImage

设置预览图像,用户长按桌面,在桌面组件页面看到的应用AppWidget的预览图片,不设置默认是应用的启动图标

autoAdvanceViewId

指定的子View会自动更新,一般这里设置集合类的View,如ListView,GridView,StackView等,指定后在手机桌面每隔一段时间,对应的子View机会更新一次.不设置的话,只有手动滑动切换,才会更新

widgetCategory

设置AppWidget能够显示的屏幕,home_scree(主屏),keyguard(锁屏)或者同时

  1. 创建一个初始化的布局文件,App Widget创建时候显示页面
  1. <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
  2. android:layout_width="match_parent"
  3. android:layout_margin="@dimen/widget_margin" // (1)
  4. android:layout_height="match_parent">
  5. <TextView
  6. android:id="@+id/tv_text"
  7. android:layout_width="wrap_content"
  8. android:layout_height="wrap_content"
  9. android:text="This is Widget"
  10. android:textSize="20sp"
  11. android:textColor="#fff"/>
  12. <Button
  13. android:id="@+id/btn_open_main"
  14. android:layout_width="match_parent"
  15. android:layout_toRightOf="@+id/tv_text"
  16. android:layout_height="wrap_content"
  17. android:text="打开主界面"/>
  18. </RelativeLayout>

这里简单显示一个TextViewButton.AppWidget的布局是基于RemoteViews,所以仅仅支持一下布局和组件

支持的布局

  • FrameLayout
  • LinearLayout
  • RelativeLayout
  • GridLayout

支持的组件

  • AnalogClock
  • Button
  • Chronometer
  • ImageButton
  • ImageView
  • ProgressBar
  • TextView
  • ViewFlipper
  • ListView
  • GridView
  • StackView
  • AdapterViewFlipper

(1)对于4.0以上的Android系统,AppWidget支持自动设置边距,但是低版本则不可以,所以需要分开设置

res/values/dimens.xml

<dimen name="widget_margin">8dp</dimen>

res/values-v14/dimens.xml:

<dimen name="widget_margin">0dp</dimen>
  1. 创建一个类DesktopWidgetProvider.kt继承AppWidgetProvider,并覆盖对应的方法
  1. class DesktopWidgetProvider : AppWidgetProvider() {
  2. override fun onDeleted(context: Context?, appWidgetIds: IntArray?) {
  3. super.onDeleted(context, appWidgetIds)
  4. logI(" call fun onDeleted")
  5. }
  6. override fun onDisabled(context: Context?) {
  7. super.onDisabled(context)
  8. logI(" call fun onDisabled")
  9. }
  10. override fun onEnabled(context: Context?) {
  11. super.onEnabled(context)
  12. logI(" call fun onEnabled")
  13. }
  14. override fun onReceive(context: Context, intent: Intent) {
  15. super.onReceive(context, intent)
  16. logI(" call fun onReceive action: [${intent.action}], flags: [${intent.flags}]")
  17. }
  18. override fun onUpdate(context: Context, appWidgetManager: AppWidgetManager, appWidgetIds: IntArray) {
  19. super.onUpdate(context, appWidgetManager, appWidgetIds)
  20. logI(" call fun onUpdate")
  21. }
  22. }
  1. 将该类注册到清单文件里
  1. <receiver
  2. android:name=".DesktopWidgetProvider"
  3. android:label="SimpleAppWidget">
  4. <intent-filter>
  5. <!-- 固定模式,必须指定APPWIDGET_UPDATE广播 -->
  6. <action android:name="android.appwidget.action.APPWIDGET_UPDATE"/>
  7. </intent-filter>
  8. <!-- 指定上面创建的AppWidgetProviderInfo -->
  9. <meta-data
  10. android:name="android.appwidget.provider"
  11. android:resource="@xml/desktop_widget_info"/>
  12. </receiver>

到这里我们就实现了一个简单的桌面小组件,运行程序后,长按桌面,在弹出的选项中选择Widgets

01

滑动就可以看到我们添加的AppWidget

02

长按拖动到桌面就Ok了.

03

添加点击事件

上面创建了一个简单的AppWidget,没有任何效果,下面给按钮增加点击事件,拉起App进入主界面.这一步很简单,我们只需要在onUpdate方法中通过调用AppWidgetManagerupdateAppWidget方法更新下布局即可.

**updateAppWidget(int appWidgetId, RemoteViews views)**该方法有两个参数appWidgetId和RemoteViews,appWidgetId是AppWidget的唯一标识,每创建一个AppWidget都会生成一个appWIdgetId,AppWidget使用的是RemoteViews,所有需要给指定的AppWidget设置布局.

  1. override fun onUpdate(context: Context, appWidgetManager: AppWidgetManager, appWidgetIds: IntArray) {
  2. appWidgetIds.forEach { appWidgetId ->
  3. val pendingIntent: PendingIntent = Intent(context, MainActivity::class.java)
  4. .let { intent ->
  5. PendingIntent.getActivity(context, 0, intent, 0)
  6. }
  7. val views: RemoteViews = RemoteViews(
  8. context.packageName,
  9. R.layout.desktop_widget_layout
  10. ).apply {
  11. // 添加单击事件
  12. setOnClickPendingIntent(R.id.btn_open_main, pendingIntent)
  13. }
  14. appWidgetManager.updateAppWidget(appWidgetId, views)
  15. }
  16. super.onUpdate(context, appWidgetManager, appWidgetIds)
  17. logI(" call fun onUpdate")
  18. }

通过RemoteViews的setOnClickPendingIntent(R.id.btn_open_main, pendingIntent)我们给按钮设置点击事件,参数一就是按钮的id,参数二是执行的事件. 我们这里指定跳转到MainActivity

这样在运行App后,点击按钮就会拉起主界面

桌面组件中使用列表视图

在常规的Android布局中我们经常使用ListView, GridView等组件,在AppWidget中也可以使用(可以看看上面说过的RemoteViews支持的一种列表组件),这里我们参考官方使用StackView作为示例说明.

这里我们在创建AppWidget布局使用使用StackView

  1. <?xml version="1.0" encoding="utf-8"?>
  2. <FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
  3. android:layout_width="match_parent"
  4. android:layout_height="match_parent"
  5. android:layout_margin="@dimen/widget_margin">
  6. <StackView
  7. android:id="@+id/stack_view"
  8. android:layout_width="match_parent"
  9. android:layout_height="match_parent"
  10. android:gravity="center"
  11. android:loopViews="true"/>
  12. <TextView
  13. android:id="@+id/empty_view"
  14. android:layout_width="match_parent"
  15. android:layout_height="match_parent"
  16. android:gravity="center"
  17. android:background="@drawable/widget_item_background"
  18. android:textColor="#ffffff"
  19. android:textStyle="bold"
  20. android:text="@string/empty_view_text"
  21. android:textSize="20sp"/>
  22. </FrameLayout>

其余的和创建简单的AppWidget步骤一样,只是这里在初始化StackView时候有些不同,它的适配值不是我们常见的BaseAdapter或者RecyclerView.Adapter,这里的Adpater需要使用RemoteViewsService,该类调用onGetViewFactory方法返回一个RemoteViewsService.RemoteViewsFactory,而RemoteViewsService.RemoteViewsFactory接口需要我们自己集成实现,这个接口和Apdater有类似的方法

  1. class StackWidgetService : RemoteViewsService() {
  2. override fun onGetViewFactory(intent: Intent): RemoteViewsFactory =
  3. StackRemoteViewFactory(applicationContext)
  4. }
  5. class StackRemoteViewFactory(private val context: Context) : RemoteViewsService.RemoteViewsFactory {
  6. private lateinit var _widgetItemList: MutableList<String>
  7. // 初始化方法
  8. fun onCreate() {
  9. _widgetItemList = MutableList(REMOTE_VIEW_COUNT) { index ->
  10. logI(_tag, " call fun _widgetItemList index [$index]")
  11. "Item $index"
  12. }
  13. };
  14. // 初始化方法每次更新都会调用,这里可以初始化数据
  15. fun onDataSetChanged(){};
  16. // 移除组件时候会调用,释放一些组员
  17. fun onDestroy() {};
  18. // 列表组件中个数和BaseApdaterz中类似
  19. fun getCount(): Int = 10;
  20. // 返回列表视图中Item的View
  21. fun getViewAt(int position): RemoteViews {
  22. logI(_tag, " call fun getViewAt($position)")
  23. return RemoteViews(context.packageName, R.layout.stack_widget_item_layout).apply {
  24. setTextViewText(R.id.widget_item, _widgetItemList[position])
  25. setOnClickFillInIntent(R.id.widget_item, Intent().apply {
  26. putExtras(Bundle().apply {
  27. putInt(EXTRA_ITEM, position)
  28. })
  29. })
  30. }
  31. };
  32. // 预加载的View
  33. fun getLoadingView(): RemoteViews? = null;
  34. // 和BaseApdaterz中类似
  35. fun getViewTypeCount(): Int = 1;
  36. // 和BaseApdaterz中类似
  37. fun getItemId(int position): Long = position.toLong();
  38. }

我们在更新AppWidget时候初始化StackView布局

  1. override fun onUpdate(context: Context, appWidgetManager: AppWidgetManager, appWidgetIds: IntArray) {
  2. appWidgetIds.forEach { appWidgetId ->
  3. val intent = Intent(context, StackWidgetService::class.java).apply {
  4. // 为了保留setOnClickFillInIntent传递的数据不被忽略,setPendingIntentTemplate和setOnClickFillInIntent配合使用
  5. data = Uri.parse(toUri(Intent.URI_INTENT_SCHEME))
  6. }
  7. val remoteView = RemoteViews(context.packageName, R.layout.stack_widget_layout).apply {
  8. setRemoteAdapter(R.id.stack_view, intent)
  9. setEmptyView(R.id.stack_view, R.id.empty_view)
  10. }
  11. val toastPendingIntent: PendingIntent = Intent(context, StackWidgetProvider::class.java).run {
  12. action = TOAST_ACTION
  13. putExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, appWidgetId)
  14. data = Uri.parse(toUri(Intent.URI_INTENT_SCHEME))
  15. PendingIntent.getBroadcast(context, 0, this, PendingIntent.FLAG_UPDATE_CURRENT)
  16. }
  17. remoteView.setPendingIntentTemplate(R.id.stack_view, toastPendingIntent)
  18. appWidgetManager.updateAppWidget(appWidgetId, remoteView)
  19. }
  20. logI(_tag, "call fun onUpdate")
  21. super.onUpdate(context, appWidgetManager, appWidgetIds)
  22. }

创建一个Intent指定StackView的Adapter,通过RemoteViewssetRemoteAdapter给StackView设置Adapter,这样就完成了一个Stack效果的AppWidget,当然最后不要忘记在清单文件注册StackWidgetService

  1. <application
  2. android:allowBackup="true"
  3. android:icon="@mipmap/ic_launcher"
  4. android:label="@string/app_name"
  5. android:roundIcon="@mipmap/ic_launcher_round"
  6. android:supportsRtl="true"
  7. android:theme="@style/AppTheme"
  8. tools:ignore="GoogleAppIndexingWarning">
  9. ......
  10. <service
  11. android:name=".StackWidgetService"
  12. android:exported="false"
  13. android:permission="android.permission.BIND_REMOTEVIEWS"/>

动态创建AppWidget

通过程序代码创建AppWidget是在Android O以后提供的Api接口requestPinAppWidget

  1. val appWidgetManager: AppWidgetManager = getSystemService(AppWidgetManager::class.java)
  2. val myProvider = ComponentName(this, DesktopWidgetProvider::class.java)
  3. // 发送广播通知创建成功如果不需要则 pendingIntent可以 为空
  4. val successCallback: PendingIntent? = if (appWidgetManager.isRequestPinAppWidgetSupported) {
  5. Intent().let { intent ->
  6. intent.action = ACTION_CREATE_APPWIDGET
  7. PendingIntent.getBroadcast(this, 0, intent, PendingIntent.FLAG_UPDATE_CURRENT)
  8. }
  9. } else {
  10. null
  11. }
  12. successCallback?.also { pendingIntent ->
  13. appWidgetManager.requestPinAppWidget(myProvider, null, pendingIntent)
  14. }

如果我们需要接收系统是否创建AppWidget成功,则需要注册广播并且监听actionACTION_CREATE_APPWIDGET

  1. private const val ACTION_CREATE_APPWIDGET = "CreateAppWidget"
  2. class MainActivity : AppCompatActivity() {
  3. private val createAppWidgetReceiver = AppWidgetBroadcastReceiver()
  4. override fun onDestroy() {
  5. super.onDestroy()
  6. unregisterReceiver(createAppWidgetReceiver)
  7. }
  8. override fun onCreate(savedInstanceState: Bundle?) {
  9. super.onCreate(savedInstanceState)
  10. setContentView(R.layout.activity_main)
  11. val intentFilter = IntentFilter()
  12. intentFilter.addAction(ACTION_CREATE_APPWIDGET)
  13. registerReceiver(createAppWidgetReceiver, intentFilter)
  14. .....
  15. }
  16. }
  17. class AppWidgetBroadcastReceiver : BroadcastReceiver() {
  18. override fun onReceive(context: Context, intent: Intent) {
  19. logI("AppWidgetBroadcastReceiver", " call fun onReceive action: [${intent.action}], flags: [${intent.flags}]")
  20. toast(
  21. context, "Create Success ID : [${intent.getIntExtra(
  22. AppWidgetManager.EXTRA_APPWIDGET_ID,
  23. AppWidgetManager.INVALID_APPWIDGET_ID
  24. )}]"
  25. )
  26. }
  27. }

如果不需要则.requestPinAppWidget(myProvider, null, pendingIntent) 第三个参数可以传入null

添加配置页面

添加配置页面需要使用到上面介绍的android:config属性,新建一个Activity同时在清单文件中注册

  1. class WidgetConfigActivity : AppCompatActivity() {
  2. var mAppWidgetId = AppWidgetManager.INVALID_APPWIDGET_ID
  3. override fun onCreate(savedInstanceState: Bundle?) {
  4. super.onCreate(savedInstanceState)
  5. logI(TAG, " call fun onCreate")
  6. setContentView(R.layout.activity_widget_config)
  7. // Find the widget id from the intent.
  8. val intent = intent
  9. val extras = intent.extras
  10. if (extras != null) {
  11. mAppWidgetId = extras.getInt(
  12. AppWidgetManager.EXTRA_APPWIDGET_ID, AppWidgetManager.INVALID_APPWIDGET_ID
  13. )
  14. }
  15. // 在用户添加桌面小组件后会分配一个appWidgetId,如果获取失败则退出
  16. if (mAppWidgetId == AppWidgetManager.INVALID_APPWIDGET_ID) {
  17. finish()
  18. }
  19. btn_save.setOnClickListener {
  20. ConfigWidgetProvider.updateAppWidget(
  21. this,
  22. AppWidgetManager.getInstance(this),
  23. mAppWidgetId,
  24. et_appwidget_config_title.text.toString().trim()
  25. )
  26. // must set result otherwise the appwidget auto delete
  27. val resultValue = Intent()
  28. resultValue.putExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, mAppWidgetId)
  29. setResult(Activity.RESULT_OK)
  30. finish()
  31. }
  32. }
  33. }

布局res/layout/activity_widget_config.xml文件如下:

  1. <?xml version="1.0" encoding="utf-8"?>
  2. <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
  3. android:orientation="vertical"
  4. android:layout_width="match_parent"
  5. android:layout_height="match_parent">
  6. <TextView
  7. android:layout_width="match_parent"
  8. android:layout_height="45dp"
  9. android:id="@+id/textView2"
  10. android:gravity="center"
  11. android:text="@string/text_title"
  12. android:textColor="#FF5722"/>
  13. <EditText
  14. android:layout_width="match_parent"
  15. android:layout_height="59dp"
  16. android:inputType="textPersonName"
  17. android:ems="10"
  18. android:id="@+id/et_appwidget_config_title"
  19. android:hint="@string/text_input_value"/>
  20. <Button
  21. android:layout_width="match_parent"
  22. android:layout_height="wrap_content"
  23. android:id="@+id/btn_save"
  24. android:text="@string/label_save"/>
  25. </LinearLayout>

然后指定android:config值为WidgetConfigActivity

  1. <?xml version="1.0" encoding="utf-8"?>
  2. <appwidget-provider xmlns:android="http://schemas.android.com/apk/res/android"
  3. android:updatePeriodMillis="3600000"
  4. android:initialLayout="@layout/desktop_widget_layout"
  5. android:minWidth="250dp"
  6. android:configure="com.example.desktopwidget.WidgetConfigActivity"
  7. android:widgetCategory="home_screen|keyguard"
  8. android:minHeight="40dp">
  9. </appwidget-provider>

配置页面在用户添加桌面小组件后自动拉起页面,这里在配置完成后点击完成按钮,我们需要将配置信息应用到AppWidget并更新AppWidet,

  1. ConfigWidgetProvider.updateAppWidget(
  2. this,
  3. AppWidgetManager.getInstance(this),
  4. mAppWidgetId,
  5. et_appwidget_config_title.text.toString().trim()
  6. )
  7. ...
  8. fun updateAppWidget(
  9. context: Context,
  10. appWidgetManager: AppWidgetManager,
  11. appWidgetId: Int,
  12. text: String = "This is Widget"
  13. ) {
  14. // Create an Intent to launch ExampleActivity
  15. val pendingIntent: PendingIntent = Intent(context, MainActivity::class.java)
  16. .let { intent ->
  17. PendingIntent.getActivity(context, 0, intent, 0)
  18. }
  19. // Get the layout for the App Widget and attach an on-click listener
  20. // to the button
  21. val views: RemoteViews = RemoteViews(
  22. context.packageName,
  23. R.layout.desktop_widget_layout
  24. ).apply {
  25. setOnClickPendingIntent(R.id.btn_open_main, pendingIntent)
  26. setTextViewText(R.id.tv_text, text)
  27. }
  28. // Tell the AppWidgetManager to perform an update on the current app widget
  29. appWidgetManager.updateAppWidget(appWidgetId, views)
  30. }

同时需要setResult(Activity.RESULT_OK)并传递获取到的appWidgetId

  1. val resultValue = Intent()
  2. resultValue.putExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, mAppWidgetId)
  3. setResult(Activity.RESULT_OK)
  4. finish()

示例程序,欢迎star:star

参考:

  1. App Widgets 详解一 简单使用
  2. Google Appwidgets
声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/Monodyee/article/detail/313023
推荐阅读
相关标签
  

闽ICP备14008679号