【发布时间】:2013-01-25 17:23:00
【问题描述】:
我有一个 android 小部件,它每 10 分钟从服务器获取数据并将其显示在屏幕上。
我想为该小部件添加一个“刷新”按钮。
当用户单击该按钮时,我想运行从服务器获取信息的方法。
向应用程序中的按钮添加事件处理程序非常简单,但是我找不到小部件的示例。
我想获得一些关于向小部件中的按钮单击添加功能的帮助。
【问题讨论】:
我有一个 android 小部件,它每 10 分钟从服务器获取数据并将其显示在屏幕上。
我想为该小部件添加一个“刷新”按钮。
当用户单击该按钮时,我想运行从服务器获取信息的方法。
向应用程序中的按钮添加事件处理程序非常简单,但是我找不到小部件的示例。
我想获得一些关于向小部件中的按钮单击添加功能的帮助。
【问题讨论】:
这里还有一个应该有帮助的例子:
package com.automatic.widget;
import android.app.PendingIntent;
import android.appwidget.AppWidgetManager;
import android.appwidget.AppWidgetProvider;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.widget.RemoteViews;
public class Widget extends AppWidgetProvider {
private static final String SYNC_CLICKED = "automaticWidgetSyncButtonClick";
@Override
public void onUpdate(Context context, AppWidgetManager appWidgetManager, int[] appWidgetIds) {
RemoteViews remoteViews;
ComponentName watchWidget;
remoteViews = new RemoteViews(context.getPackageName(), R.layout.widget_layout);
watchWidget = new ComponentName(context, Widget.class);
remoteViews.setOnClickPendingIntent(R.id.sync_button, getPendingSelfIntent(context, SYNC_CLICKED));
appWidgetManager.updateAppWidget(watchWidget, remoteViews);
}
@Override
public void onReceive(Context context, Intent intent) {
// TODO Auto-generated method stub
super.onReceive(context, intent);
if (SYNC_CLICKED.equals(intent.getAction())) {
AppWidgetManager appWidgetManager = AppWidgetManager.getInstance(context);
RemoteViews remoteViews;
ComponentName watchWidget;
remoteViews = new RemoteViews(context.getPackageName(), R.layout.widget_layout);
watchWidget = new ComponentName(context, Widget.class);
remoteViews.setTextViewText(R.id.sync_button, "TESTING");
appWidgetManager.updateAppWidget(watchWidget, remoteViews);
}
}
protected PendingIntent getPendingSelfIntent(Context context, String action) {
Intent intent = new Intent(context, getClass());
intent.setAction(action);
return PendingIntent.getBroadcast(context, 0, intent, 0);
}
}
【讨论】:
PendingIntent.getBroadcast(context, 0, intent, 0) 的注释:如果您有多个小部件实例,这将导致错误。问题:当您单击任何小部件实例时,只有最后一个小部件会更新。解决方案:将 widgetId 传递给 getPendingSelfIntent 并试试这个:PendingIntent.getBroadcast(context, widgetId, intent, 0) Detailed description
我发现了如何做到这一点。
在><receiver><intent-filter>标签中的AndroidManifest.xml文件中添加一个动作:
<action android:name="MY_PACKAGE_NAME.WIDGET_BUTTON" />
在提供者中添加一个与动作名称匹配的常量:
public static String WIDGET_BUTTON = "MY_PACKAGE_NAME.WIDGET_BUTTON";
在onUpdate() 方法中添加与操作匹配的待处理意图:
Intent intent = new Intent(WIDGET_BUTTON);
PendingIntent pendingIntent = PendingIntent.getBroadcast(context, 0, intent, PendingIntent.FLAG_UPDATE_CURRENT);
views.setOnClickPendingIntent(R.id.MY_BUTTON_ID, pendingIntent );
最后,在 onRecieve() 方法中,检查动作名称:
if (WIDGET_BUTTON.equals(intent.getAction())) {
//your code here
}
【讨论】:
views
这是另一个具有以下好处的答案:
onReceive 中的主力代码实际上调用了onUpdate(这样可以减少代码重复)。onUpdate(Context context) 中的代码是通用的,因此可以放入任何 AppWidgetProvider 子类中。代码:
public class MyWidget extends AppWidgetProvider {
private static final String ACTION_UPDATE_CLICK =
"com.example.myapp.action.UPDATE_CLICK";
private static int mCount = 0;
private static String getMessage() {
return String.valueOf(mCount++);
}
private PendingIntent getPendingSelfIntent(Context context, String action) {
// An explicit intent directed at the current class (the "self").
Intent intent = new Intent(context, getClass());
intent.setAction(action);
return PendingIntent.getBroadcast(context, 0, intent, 0);
}
@Override
public void onUpdate(Context context, AppWidgetManager appWidgetManager,
int[] appWidgetIds) {
super.onUpdate(context, appWidgetManager, appWidgetIds);
String message = getMessage();
// Loop for every App Widget instance that belongs to this provider.
// Noting, that is, a user might have multiple instances of the same
// widget on
// their home screen.
for (int appWidgetID : appWidgetIds) {
RemoteViews remoteViews = new RemoteViews(context.getPackageName(),
R.layout.my_widget);
remoteViews.setTextViewText(R.id.textView_output, message);
remoteViews.setOnClickPendingIntent(R.id.button_update,
getPendingSelfIntent(context,
ACTION_UPDATE_CLICK)
);
appWidgetManager.updateAppWidget(appWidgetID, remoteViews);
}
}
/**
* A general technique for calling the onUpdate method,
* requiring only the context parameter.
*
* @author John Bentley, based on Android-er code.
* @see <a href="http://android-er.blogspot.com
* .au/2010/10/update-widget-in-onreceive-method.html">
* Android-er > 2010-10-19 > Update Widget in onReceive() method</a>
*/
private void onUpdate(Context context) {
AppWidgetManager appWidgetManager = AppWidgetManager.getInstance
(context);
// Uses getClass().getName() rather than MyWidget.class.getName() for
// portability into any App Widget Provider Class
ComponentName thisAppWidgetComponentName =
new ComponentName(context.getPackageName(),getClass().getName()
);
int[] appWidgetIds = appWidgetManager.getAppWidgetIds(
thisAppWidgetComponentName);
onUpdate(context, appWidgetManager, appWidgetIds);
}
@Override
public void onReceive(Context context, Intent intent) {
super.onReceive(context, intent);
if (ACTION_UPDATE_CLICK.equals(intent.getAction())) {
onUpdate(context);
}
}
}
小部件看起来像这样
这建立在@Kels、@SharonHaimPour 和@Erti-ChrisEelmaa 的getPendingSelfIntent 工作之上。
它还建立在Android-er > 2010-10-19 > Update Widget in onReceive() method(不是我)的基础上,其中演示了如何在 App Widget 实例的基础上从 onReceive 调用 onUpdate。我将该代码通用化并将其包装在 callOnUpdate 中。
【讨论】:
protected PendingIntent getPendingSelfIntent(Context context, String action) {
Intent intent = new Intent(context, getClass());
intent.setAction(action);
return PendingIntent.getBroadcast(context, 0, intent, 0);
}
views.setOnClickPendingIntent(R.id.Timm, getPendingSelfIntent(context,
"ham"));
也喜欢网址:
How to correctly handle click events on Widget
如果您以其他方式解决了问题,请提供此作为答案
【讨论】:
在pendingIntent中,我们还可以添加额外的属性appWidgetId以便稍后在onReceive中重用它来更新小部件点击的小部件实例
class ExampleAppWidgetProvider : AppWidgetProvider() {
override fun onUpdate(context: Context, appWidgetManager: AppWidgetManager, appWidgetIds: IntArray {
appWidgetIds.forEach { appWidgetId ->
Log.e("TAG", "onUpdate $appWidgetId")
val pendingRefreshClickIntent: PendingIntent = Intent(context, javaClass).let {
it.action = ACTION_REFRESH_CLICK
it.putExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, appWidgetId)
return@let PendingIntent.getBroadcast(
context,
appWidgetId, // click in all instances widget will work well (base on Alireza Mirian comment in the top answer)
it,
PendingIntent.FLAG_UPDATE_CURRENT
)
}
val views = RemoteViews(
context.packageName,
R.layout.example_appwidget
)
views.setOnClickPendingIntent(R.id.button_refresh, pendingRefreshClickIntent)
appWidgetManager.updateAppWidget(appWidgetId, views)
}
}
override fun onReceive(context: Context?, intent: Intent?) {
super.onReceive(context, intent)
Log.i("TAG", "onReceive " + intent?.action)
if (intent?.action == ACTION_REFRESH_CLICK) {
val appWidgetId = intent.extras?.getInt(AppWidgetManager.EXTRA_APPWIDGET_ID) ?: return
Log.i("TAG", "onReceive appWidgetId $appWidgetId")
val appWidgetManager = AppWidgetManager.getInstance(context)
val views = RemoteViews(context!!.packageName, R.layout.example_appwidget)
views.setTextViewText(R.id.text_data, "a " + (Math.random() * 9).roundToInt())
appWidgetManager.updateAppWidget(appWidgetId, views)
}
}
companion object {
private const val ACTION_REFRESH_CLICK = "com.example.androidwidgetbuttonclick.action.ACTION_REFRESH_CLICK"
}
}
小部件初始布局
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical">
<TextView
android:id="@+id/text_data"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="AA"
android:textSize="20sp" />
<Button
android:id="@+id/button_refresh"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Refresh" />
</LinearLayout>
【讨论】:
我尝试了 Sharon Haim Pour above 建议的解决方案,但我在 AppWidgetProvider 类中的 onReceive() 方法从未在按钮按下时被调用。
Intent intent = new Intent(WIDGET_BUTTON);
PendingIntent pendingIntent = PendingIntent.getBroadcast(context, 0, intent,
PendingIntent.FLAG_UPDATE_CURRENT);
views.setOnClickPendingIntent(R.id.MY_BUTTON_ID, pendingIntent );
经过一番研究,我可以通过更新以下代码来解决问题:
Intent intent = new Intent(context, MY_APPWIDGETPROVIDER_CLASS.class);
intent.setAction(WIDGET_BUTTON);
PendingIntent pendingIntent = PendingIntent.getBroadcast(context, 0, intent,
PendingIntent.FLAG_UPDATE_CURRENT);
views.setOnClickPendingIntent(R.id.MY_BUTTON_ID, pendingIntent );
别忘了在下面放:
appWidgetManager.updateAppWidget(appWidgetId, views);
【讨论】:
与此处使用onReceive() 的其他答案不同,我发现在onUpdate() 中执行所有操作实际上要干净得多。
Android 官方代码实验室Advanced Android 02.1: App widgets 提供了这个解决方案。 Java 中的示例代码。这里我介绍一下 Kotlin 中的解决方案。
class MyAppWidgetProvider : AppWidgetProvider() {
override fun onUpdate(
context: Context?,
appWidgetManager: AppWidgetManager?,
appWidgetIds: IntArray?
) {
appWidgetIds?.forEach { appWidgetId ->
val views = RemoteViews(
context?.packageName,
R.layout.appwidget
)
// Coroutine to perform background IO task.
GlobalScope.launch(Dispatchers.IO) {
// Suspend function.
val apiData = Api.retrofitService.getData()
updateWidgetUI(views, apiData)
context?.let {
views.setOnClickPendingIntent(
R.id.widget_button,
getUpdatePendingIntent(it, appWidgetId)
)
}
appWidgetManager?.updateAppWidget(appWidgetId, views)
}
}
}
private fun updateWidgetUI(views: RemoteViews, apiData: ApiData){
views.apply {
setTextViewText(R.id.widget_value_textview, apiData.value)
setTextViewText(
R.id.widget_last_updated_value_textview,
DateFormat.getTimeInstance(DateFormat.MEDIUM).format(Date())
)
}
}
private fun getUpdatePendingIntent(context: Context, appWidgetId: Int): PendingIntent {
val intent = Intent(context, MyAppWidgetProvider::class.java).also {
it.action = AppWidgetManager.ACTION_APPWIDGET_UPDATE
// It's very important to use intArrayOf instead of arrayOf,
// as a primitive int array is expected.
it.putExtra(AppWidgetManager.EXTRA_APPWIDGET_IDS, intArrayOf(appWidgetId))
}
// Set the immutability flag for Android 12.
val flags = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
PendingIntent.FLAG_IMMUTABLE or PendingIntent.FLAG_UPDATE_CURRENT
} else {
PendingIntent.FLAG_UPDATE_CURRENT
}
return PendingIntent.getBroadcast(
context,
appWidgetId,
intent,
flags
)
}
// No need for onReceive().
}
这里的关键是使用内置的 AppWidgetManager.ACTION_APPWIDGET_UPDATE 动作而不是自定义动作。
【讨论】: