Android desktop widget

Create a desktop widget

  • Create an AppWidgetProvider class
    Create an AppWidgetProvider class that desktop widgets receive broadcasts when updating, enabling, disabling, and deleting application widgets. And AppWidgetProvider inherits BroadcastReceiver, and specifically performs certain broadcast filtering on widgets, so we need to create a custom AppWidgetProvider class to handle widget-related operations.
package com.example.widgetdemo

import android.appwidget.AppWidgetManager
import android.appwidget.AppWidgetProvider
import android.content.Context
import android.content.Intent
import android.os.Bundle
import android.util.Log

const val TAG = "MyWidgetProvider"

class MyWidgetProvider : AppWidgetProvider() {
    
    

   /**
    *  每次收到广播之后就会调用该函数,会在其他方法之前进行回调。一般可以不实现
    *  默认的 AppWidgetProvider 实现会过滤所有应用微件广播并视情况调用上述方法
    */
   override fun onReceive(context: Context?, intent: Intent?) {
    
    
       super.onReceive(context, intent)
       Log.d(TAG, "invoke onReceive......")
   }

   /**
    * 调用此方法可以按 AppWidgetProviderInfo 中的 updatePeriodMillis 属性定义的时间间隔来更新应用微件
    * 当用户添加应用微件时也会调用此方法,所以它应执行基本设置,如定义视图的事件处理脚本以及根据需要启动临时的 Service。
    * 不过,如果您已声明配置 Activity,则当用户添加应用微件时不会调用此方法,但会调用它来执行后续更新。
    * 由配置 Activity 负责在配置完成后执行首次更新。
    */
   override fun onUpdate(context: Context?, appWidgetManager: AppWidgetManager?, appWidgetIds: IntArray?) {
    
    
       super.onUpdate(context, appWidgetManager, appWidgetIds)
       Log.d(TAG, "invoke onUpdate......")
   }

   /**
    * 当首次放置微件时以及每当调整微件的大小时,会调用此方法。您可以使用此回调来根据微件的大小范围显示或隐藏内容
    */
   override fun onAppWidgetOptionsChanged(context: Context?, appWidgetManager: AppWidgetManager?, appWidgetId: Int, newOptions: Bundle?) {
    
    
       super.onAppWidgetOptionsChanged(context, appWidgetManager, appWidgetId, newOptions)
       Log.d(TAG, "invoke onAppWidgetOptionsChanged......")

   }

   /**
    * 每次从应用微件托管应用中删除应用微件时,都会调用此方法。
    */
   override fun onDeleted(context: Context?, appWidgetIds: IntArray?) {
    
    
       super.onDeleted(context, appWidgetIds)
       Log.d(TAG, "invoke onDeleted......")
   }

   /**
    * 首次创建应用微件的实例时,会调用此方法。例如,如果用户添加应用微件的两个实例,只有首次添加时会调用此方法。
    * 如果您需要打开一个新的数据库或执行只需要对所有应用微件实例执行一次的其他设置,则此方法非常合适。
    */
   override fun onEnabled(context: Context?) {
    
    
       super.onEnabled(context)
       Log.d(TAG, "invoke onEnabled......")
   }

   /**
    * 从应用微件托管应用中删除了应用微件的最后一个实例时,会调用此方法。
    * 您应使用此方法来清理在 onEnabled(Context) 中完成的所有工作,如删除临时数据库。
    */
   override fun onDisabled(context: Context?) {
    
    
       super.onDisabled(context)
       Log.d(TAG, "invoke onDisabled......")
   }

   override fun onRestored(context: Context?, oldWidgetIds: IntArray?, newWidgetIds: IntArray?) {
    
    
       super.onRestored(context, oldWidgetIds, newWidgetIds)
       Log.d(TAG, "invoke onRestored......")
   }
}
  • Register broadcast
    Register the receiver in the AndroidManifest.xml file, and specify the broadcast receiving class as the class we defined above, where the value of the label field indicates the name of the widget that can be displayed in the widget list, and specify the action as "android .appwidget.action.APPWIDGET_UPDATE", and add a file my_appwidget_info related to the configuration information of the widget
  <receiver
            android:name=".MyWidgetProvider"
            android:exported="false"
            android:label="我的卡片">
            <intent-filter>
                <action android:name="android.appwidget.action.APPWIDGET_UPDATE" />
            </intent-filter>
            <meta-data
                android:name="android.appwidget.provider"
                android:resource="@xml/my_appwidget_info" />
        </receiver>
  • my_appwidget_info.xml file
    initialLayout: the initial layout of the widget
    minWidth: the minimum width of the widget
    minHeight: the minimum height of the widget
    previewImage: the preview image of the widget in the application component list
    resizeMode: whether it can be resized, none|horizontal|vertical; fixed size |horizontal direction|vertical direction
    updatePeriodMillis: update interval, how often to update, unit MS, official limit 30 minutes, to reduce power consumption
    widgetCategory: where widgets can be displayed, generally the main interface
<?xml version="1.0" encoding="utf-8"?>
<appwidget-provider xmlns:android="http://schemas.android.com/apk/res/android"
    android:initialLayout="@layout/layout_my_card_widget"
    android:minWidth="80dp"
    android:minHeight="40dp"
    android:previewImage="@drawable/widget_icon"
    android:resizeMode="horizontal|vertical"
    android:updatePeriodMillis="86400000"
    android:widgetCategory="home_screen">
</appwidget-provider>

The first desktop widget is created, and the effect is as follows:
insert image description here

event interaction

  • Update widgets
    The onUpdate method will be called when the user adds a widget to the desktop for the first time, and the appWidgetIds attribute identifies several widgets managed by the current AppWidgetProvider, so the widget is updated for the first time in this method, and we can get the data here And update the relevant information of the widget.
    Layout preview:
    insert image description here
    We simulate a scenario, when the user wants to add a widget to the desktop for the first time, it needs to pull data from the network and update it to the widget, and set the click event of two Buttons. Since it is a demonstration, simply start a scheduled task to simulate time-consuming network operations. The actual situation is more complicated, and various cases need to be considered.
override fun onUpdate(context: Context?, appWidgetManager: AppWidgetManager?, appWidgetIds: IntArray?) {
    
    
        super.onUpdate(context, appWidgetManager, appWidgetIds)
        Log.d(TAG, "invoke onUpdate......")
        val timer = Timer()
        val timerTask: TimerTask = object : TimerTask() {
    
    
            override fun run() {
    
    //定时任务
                appWidgetIds?.forEach {
    
    
                    val pendingIntent: PendingIntent =
                        Intent(context, SecondActivity::class.java).let {
    
     intent ->
                            PendingIntent.getActivity(context, 0, intent, 0)
                        }
                    val views: RemoteViews =
                        RemoteViews(context?.packageName, R.layout.layout_my_card_widget).apply {
    
    
                            setTextViewText(R.id.name, "来自Code")//设置小组件TextView的值
                            //添加LeftButton的点击事件
                            setOnClickPendingIntent(R.id.btnLeft, getSelfPendingIntent(context, LEFT_BUTTON_CLICK))
                            //添加RightButton的点击事件
                            setOnClickPendingIntent(R.id.btnRight, getSelfPendingIntent(context, RIGHT_BUTTON_CLICK))
                        }
                        //执行更新
                    appWidgetManager?.updateAppWidget(appWidgetIds, views)
                }
            }
        }
        timer.schedule(timerTask, 1000)
    }

Because the interaction between the widget and the app is based on broadcasting, when we define the click event, we need to customize the intent and distinguish the click event through action

	private const val LEFT_BUTTON_CLICK = "widget_left_button"
	private const val RIGHT_BUTTON_CLICK = "widget_right_button"

    private fun getSelfPendingIntent(context: Context?, action: String): PendingIntent {
    
    
        val intent = Intent(context, javaClass)//javaClass = this.class
        intent.action = action
        return PendingIntent.getBroadcast(context, 0, intent, 0)
    }

The next step is when we click on the widget, and then call back to the onReceive method to process the event

override fun onReceive(context: Context?, intent: Intent?) {
    
    
        super.onReceive(context, intent)
        Log.d(TAG, "invoke onReceive......")
        intent?.action?.let {
    
     action ->
            context?.let {
    
    
                val manager = AppWidgetManager.getInstance(context)
                val remoteViews = RemoteViews(context.packageName, R.layout.layout_my_card_widget)
                if (action == LEFT_BUTTON_CLICK) {
    
    
                	//设置一个颜色
                    remoteViews.setTextColor(R.id.name, Color.parseColor("#FF03DAC5"))
                    manager.updateAppWidget(ComponentName(context, MyWidgetProvider::class.java), remoteViews)
                } else if (action == RIGHT_BUTTON_CLICK) {
    
    
                	//将颜色改回来
                    remoteViews.setTextColor(R.id.name, Color.parseColor("#FF000000"))
                    manager.updateAppWidget(ComponentName(context, MyWidgetProvider::class.java), remoteViews)
                    Toast.makeText(context, "点击右边按钮", Toast.LENGTH_SHORT).show()
                }
            }
        }
    }

The layout file is relatively simple, so the simple use of widgets has been completed.

Guess you like

Origin blog.csdn.net/weixin_42643321/article/details/127533983