安卓组件按钮点击事件

50

我有一个安卓小部件,每10分钟从服务器获取数据并显示在屏幕上。
我想要添加一个“刷新”按钮到这个小部件上。
当用户点击这个按钮时,我想运行从服务器获取信息的方法。
在应用程序中向按钮添加事件处理程序非常容易,但我找不到小部件的示例。
我想在小部件中为按钮点击添加一个函数,需要一些帮助。


1
这很有帮助 https://dev59.com/wl7Va4cB1Zd3GeqPLqW1#8635715 - StarsSky
7个回答

80

这里有另一个例子,可能会有所帮助:

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);
    }
}

2
非常感谢您提供这么好的示例,我已经使用了这段代码,我的应用程序运行良好...非常感谢。 - Butani Vijay
1
这段代码没有其他示例所呈现的混乱 - 这是开发指南信息的一个优秀而简洁的示例 - 谢谢! - headscratch
6
关于 PendingIntent.getBroadcast(context, 0, intent, 0) 的注释: 如果你有多个小部件的实例,则会导致一个bug。问题:每当你点击任何小部件实例时,只有最后一个小部件会更新。 解决方案:将 widgetId 传递给 getPendingSelfIntent,并尝试使用以下代码:PendingIntent.getBroadcast(context, widgetId, intent, 0) 详细说明 - Alireza Mirian

49

我已经知道如何做了。
<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

    }

1
感谢@Sharon Haim Pour! - Edmond Tamas
1
我认为值得注意的是,你可以(而且可能应该)使用显式意图(explicit intent)而不是隐式意图(implicit intent)。这意味着你不必在清单文件中定义动作,并且你应该像这样创建意图:Intent intent = new Intent(context, MyClass.class); - user936580
谢谢!这个小部件的按钮事件监听器帮了我很多忙。现在,是时候进行实验了,我需要更多地探索如何制作Android小部件。 - David Dimalanta
这段代码比上面的代码更好,因为它使用了显式意图,这使得从配置活动调用意图更容易。 - TanmayP
这是什么?views - Yousha Aleayoub
为什么包名需要前缀? - XxGoliathusxX

12

这里是另一个答案,具有以下优点:

  • 它处理所有的应用程序小部件实例(用户可能在屏幕上以不同的配置/大小拥有多个小部件实例)。官方文档建议编写所有实例的代码。请参阅指南 > 应用程序小部件 > 使用AppWidgetProvider类,向下滚动到“ExampleAppWidgetProvider”的代码示例。
  • 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);
        }
    }

}
组件看起来像这样 Widget update button example. Simple counting. 这基于@Kels,@SharonHaimPour和@Erti-ChrisEelmaa的getPendingSelfIntent工作。
它还建立在Android-er > 2010-10-19 > Update Widget in onReceive() method(不是我)的基础上,其中演示了如何以应用程序小部件实例为基础从onReceive调用onUpdate。 我将那段代码通用化并将其包装在callOnUpdate中。

11
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"));

还希望优先考虑URL:

如何正确处理小部件上的点击事件

如果您以不同的方式解决了它,请将其作为答案提供。


1
我不理解在哪里放置这段代码。你在 click 事件中调用函数吗? - Sharon Haim Pour
这是最好的答案。 - Mostafa Imran

1
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>

0

与其他答案使用onReceive()不同,我发现在onUpdate()中完成所有操作实际上更加简洁和简单。

官方的Android codelab 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操作,而不是自定义操作。

0

我尝试了Sharon Haim Pour在上面建议的解决方案,但我的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);

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接