Criar um widget de área de trabalho
- Criar uma classe AppWidgetProvider
Crie uma classe AppWidgetProvider para que os widgets da área de trabalho recebam transmissões ao atualizar, habilitar, desabilitar e excluir widgets de aplicativos. E AppWidgetProvider herda BroadcastReceiver e executa especificamente certa filtragem de transmissão em widgets, portanto, precisamos criar uma classe AppWidgetProvider personalizada para lidar com operações relacionadas a widgets.
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
Registre o receptor no arquivo AndroidManifest.xml e especifique a classe de recebimento de broadcast como a classe que definimos acima, onde o valor do campo label indica o nome do widget que pode ser exibido na lista de widgets e especifique o action como "android .appwidget.action.APPWIDGET_UPDATE" e adicione um arquivo my_appwidget_info relacionado às informações de configuração do 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>
- arquivo my_appwidget_info.xml
initialLayout: o layout inicial do widget
minWidth: a largura mínima do widget
minHeight: a altura mínima do widget
previewImage: a imagem de visualização do widget na lista de componentes do aplicativo
resizeMode: se pode ser redimensionado, nenhum |horizontal|vertical; tamanho fixo |direção horizontal|direção vertical
updatePeriodMillis: intervalo de atualização, frequência de atualização, unidade MS, limite oficial de 30 minutos, para reduzir o consumo de energia
widgetCategory: onde os widgets podem ser exibidos, geralmente a interface principal
<?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>
O primeiro widget da área de trabalho é criado e o efeito é o seguinte:
interação do evento
- Widgets de atualização
O método onUpdate será chamado quando o usuário adicionar um widget ao desktop pela primeira vez, e o atributo appWidgetIds identifica vários widgets gerenciados pelo AppWidgetProvider atual, então o widget é atualizado pela primeira vez neste método, e nós pode obter os dados aqui e atualizar as informações relevantes do widget.
Visualização do layout:
simulamos um cenário. Quando o usuário deseja adicionar um widget à área de trabalho pela primeira vez, ele precisa extrair dados da rede e atualizá-los no widget e definir o evento de clique de dois botões. Como é uma demonstração, basta iniciar uma tarefa agendada para simular operações de rede demoradas. A situação real é mais complicada e vários casos precisam ser considerados.
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)
}
Como a interação entre o widget e o aplicativo é baseada na transmissão, quando definimos o evento de clique, precisamos personalizar a intenção e distinguir o evento de clique por meio da ação
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)
}
A próxima etapa é quando clicamos no widget e chamamos de volta o método onReceive para processar o evento
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()
}
}
}
}
O arquivo de layout é relativamente simples, portanto, o uso simples de widgets foi concluído.
- Nota: O layout dos widgets suporta apenas RemoteViews, como segue:
Consulte a documentação oficial: Google Development Documentation