Android之AppWidget 开发浅析

news2024/11/26 10:49:42

什么是AppWidget

AppWidget 即桌面小部件,也叫桌面控件,就是能直接显示在Android系统桌面上的小程序,先看图:

          

     

 

  图中我用黄色箭头指示的即为AppWidget,一些用户使用比较频繁的程序,可以做成AppWidget,这样能方便地使用。典型的程序有时钟、天气、音乐播放器等。AppWidget 是Android 系统应用开发层面的一部分,有着特殊用途,使用得当的化,的确会为app 增色不少,它的工作原理是把一个进程的控件嵌入到别外一个进程的窗口里的一种方法。长按桌面空白处,会出现一个 AppWidget 的文件夹,在里面找到相应的 AppWidget ,长按拖出,即可将 AppWidget 添加到桌面

如何开发AppWidget

AppWidget 是通过 BroadCastReceiver 的形式进行控制的,开发 AppWidget 的主要类为 AppWidgetProvider, 该类继承自 BroadCastReceiver。为了实现桌面小部件,开发者只要开发一个继承自 AppWidgetProvider 的子类,并重写它的 onUpdate() 方法即可。重写该方法,一般来说可按如下几个步骤进行:

  1、创建一个 RemoteViews 对象,这个对象加载时指定了桌面小部件的界面布局文件。

  2、设置 RemoteViews 创建时加载的布局文件中各个元素的属性。

  3、创建一个 ComponentName 对象

  4、调用 AppWidgetManager 更新桌面小部件。

下面来看一个实际的例子,用 Android Studio 自动生成的例子来说。(注:我用的是最新版的 AS 2.2.3,下面简称 AS。)

  新建了一个 HelloWorld 项目,然后新建一个 AppWidget ,命名为 MyAppWidgetProvider,按默认下一步,就完成了一个最简单的AppWidget的开发。运行程序之后,将小部件添加到桌面。操作步骤和默认效果如下:  

我们看看 AS 为我们自动生成了哪些代码呢?对照着上面说的的步骤我们来看看。

  首先,有一个 MyAppWidgetProvider 的类。

package com.example.joy.remoteviewstest;

import android.appwidget.AppWidgetManager;
import android.appwidget.AppWidgetProvider;
import android.content.Context;
import android.widget.RemoteViews;

/**
 * Implementation of App Widget functionality.
 */
public class MyAppWidgetProvider extends AppWidgetProvider {

    static void updateAppWidget(Context context, AppWidgetManager appWidgetManager,
                                int appWidgetId) {

        CharSequence widgetText = context.getString(R.string.appwidget_text);  
        // Construct the RemoteViews object
        RemoteViews views = new RemoteViews(context.getPackageName(), R.layout.my_app_widget_provider);
        views.setTextViewText(R.id.appwidget_text, widgetText);

        // Instruct the widget manager to update the widget
        appWidgetManager.updateAppWidget(appWidgetId, views);
    }

    @Override
    public void onUpdate(Context context, AppWidgetManager appWidgetManager, int[] appWidgetIds) {
        // There may be multiple widgets active, so update all of them
        for (int appWidgetId : appWidgetIds) {
            updateAppWidget(context, appWidgetManager, appWidgetId);
        }
    }

    @Override
    public void onEnabled(Context context) {
        // Enter relevant functionality for when the first widget is created
    }

    @Override
    public void onDisabled(Context context) {
        // Enter relevant functionality for when the last widget is disabled
    }
}

 该类继承自 AppWidgetProvider ,AS默认帮我们重写 onUpdate() 方法,遍历 appWidgetIds, 调用了 updateAppWidget() 方法。再看 updateAppWidget() 方法,很简单,只有四行:

  第一行, CharSequence widgetText = context.getString(R.string.appwidget_text);

     声明了一个字符串;

  第二行, RemoteViews views = new RemoteViews(context.getPackageName(), R.layout.my_app_widget_provider);

    创建了一个 RemoteViews 对象,第一个参数传应用程序包名,第二个参数指定了,RemoteViews 加载的布局文件。这一行对应上面步骤中说的第一点。可以看到在 res/layout/ 目录下面 AS 自动生成了一个 my_app_widget_provider.xml 文件,内容如下:

<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:background="#09C"
    android:padding="@dimen/widget_margin">

    <TextView
        android:id="@+id/appwidget_text"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_centerHorizontal="true"
        android:layout_centerVertical="true"
        android:layout_margin="8dp"
        android:background="#09C"
        android:contentDescription="@string/appwidget_text"
        android:text="@string/appwidget_text"
        android:textColor="#ffffff"
        android:textStyle="bold|italic" />

</RelativeLayout>

 这个文件就是我们最后看到的桌面小部件的样子,布局文件中只有一个TextView。这是你可能会问,想要加图片可以吗?可以,就像正常的Activity布局一样添加 ImageView 就行了,聪明的你可能开始想自定义小部件的样式了,添加功能强大外观漂亮逼格高的自定义控件了,很遗憾,不可以。小部件布局文件可以添加的组件是有限制的,详细内容在下文介绍RemoteViews 时再说。

  第三行, views.setTextViewText(R.id.appwidget_text, widgetText);

    将第一行声明的字符串赋值给上面布局文件中的 TextView,注意这里赋值时,指定TextView的 id,要对应起来。这一行对于了上面步骤中的第二点。

  第四行,  appWidgetManager.updateAppWidget(appWidgetId, views);

    这里调用了 appWidgetManager.updateAppWidget() 方法,更新小部件。这一行对应了上面步骤中的第四点。

  这时,你可能有疑问了,上面明明说了四个步骤,其中第三步,创建一个 ComponentName 对象,明明就不需要。的确,这个例子中也没有用到。如果我们手敲第四步代码,AS的智能提示会告诉你,appWidgetManager.updateAppWidget() 有三个重载的方法。源码中三个方法没有写在一起,为了方便,这里我复制贴出官方 API 中的介绍

 void     updateAppWidget(ComponentName provider, RemoteViews views)

          Set the RemoteViews to use for all AppWidget instances for the supplied AppWidget provider.

   void    updateAppWidget(int[] appWidgetIds, RemoteViews views)

          Set the RemoteViews to use for the specified appWidgetIds.

   void    updateAppWidget(int appWidgetId, RemoteViews views)

          Set the RemoteViews to use for the specified appWidgetId.

   这个三个方法都接收两个参数,第二个参数都是 RemoteViews 对象。其中第一个方法的第一个参数就是 ComponentName 对象,更新所有的 AppWidgetProvider 提供的所有的 AppWidget 实例,第二个方法时更新明确指定 Id 的 AppWidget 的对象集,第三个方法,更新明确指定 Id 的某个 AppWidget 对象。所以一般我们使用第一个方法,针对所有的 AppWidget 对象,我们也可以根据需要选择性地去更新。

  到这里,所有步骤都结束了,就完了?还没。前面说了,自定义的 MyAppWidgetProvider 继承自 AppWidgetProvider,而 AppWidgetProvider 又是继承自 BroadCastReceiver,

所以说 MyAppWidgetProvider 本质上是一个广播接受者,属于四大组件之一,需要我们的清单文件中注册。打开AndroidManifest.xml文件可以看到,的确是注册了小部件的,内容如下:

 <receiver android:name=".MyAppWidgetProvider">
     <intent-filter>
         <action android:name="android.appwidget.action.APPWIDGET_UPDATE" />
      </intent-filter>

      <meta-data
            android:name="android.appwidget.provider"
            android:resource="@xml/my_app_widget_provider_info" />
  </receiver>

  上面代码中有一个 Action,这个 Action 必须要加,且不能更改,属于系统规范,是作为小部件的标识而存在的。如果不加,这个 Receiver 就不会出现在小部件列表里面。然后看到小部件指定了 @xml/my_app_widget_provider_info 作为meta-data,细心的你发现了,在 res/ 目录下面建立了一个 xml 文件夹,下面新建了一个 my_app_widget_provider_info.xml 文件,内容如下:

<?xml version="1.0" encoding="utf-8"?>
<appwidget-provider xmlns:android="http://schemas.android.com/apk/res/android"
    android:initialKeyguardLayout="@layout/my_app_widget_provider"
    android:initialLayout="@layout/my_app_widget_provider"
    android:minHeight="40dp"
    android:minWidth="40dp"
    android:previewImage="@drawable/example_appwidget_preview"
    android:resizeMode="horizontal|vertical"
    android:updatePeriodMillis="86400000"
    android:widgetCategory="home_screen">
</appwidget-provider>

 这里配置了一些小部件的基本信息,常用的属性有 initialLayout 就是小部件的初始化布局, minHeight 定义了小部件的最小高度,previewImage 指定了小部件在小部件列表里的预览图,updatePeriodMillis 指定了小部件更新周期,单位为毫秒。更多属性,可以查看API文档。

  到这里,上面这个极简单的小部件开发过程就真的结束了。为了开发出更强大一点小部件,我们还需要进一步了解 RemoteViews 和 AppWidgetProvider。

AppWidget的妆容——RemoteViews

  下面简单说说 RemoteViews 相关的几个类。

  1.1 RemoteViews

  RemoteViews,从字面意思理解为它是一个远程视图。是一种远程的 View,它在其它进程中显示,却可以在另一个进程中更新。RemoteViews 在Android中的使用场景主要有:自定义通知栏和桌面小部件。

  在RemoteViews 的构造函数中,第二个参数接收一个 layout 文件来确定 RemoteViews 的视图;然后,我们调用RemoteViews 中的 set 方法对 layout 中的各个组件进行设置,例如,可以调用 setTextViewText() 来设置 TextView 组件的文本。

  前面提到,小部件布局文件可以添加的组件是有限制的,它可以支持的 View 类型包括四种布局:FrameLayout、LinearLayout、RelativeLayout、GridLayout 和 13 种View: AnalogClock、Button、Chronometer、ImageButton、ImageView、ProgressBar、TextView、ViewFlipper、ListView、GridView、StackView、AdapterViewFlipper、ViewSub。注意:RemoteViews 也并不支持上述 View 的子类。

  RemoteViews 提供了一系列 setXXX() 方法来为小部件的子视图设置属性。具体可以参考 API 文档。

  1.2 RemoteViewsService

  RemoteViewsService,是管理RemoteViews的服务。一般,当AppWidget 中包含 GridView、ListView、StackView 等集合视图时,才需要使用RemoteViewsService来进行更新、管理。RemoteViewsService 更新集合视图的一般步骤是:

  (01) 通过 setRemoteAdapter() 方法来设置 RemoteViews 对应 RemoteViewsService 。

  (02) 之后在 RemoteViewsService 中,实现 RemoteViewsFactory 接口。然后,在 RemoteViewsFactory 接口中对集合视图的各个子项进行设置,例如 ListView 中的每一Item。

  1.3 RemoteViewsFactory

  通过RemoteViewsService中的介绍,我们知道RemoteViewsService是通过 RemoteViewsFactory来具体管理layout中集合视图的,RemoteViewsFactory是RemoteViewsService中的一个内部接口。RemoteViewsFactory提供了一系列的方法管理集合视图中的每一项。例如:

  RemoteViews getViewAt(int position)

  通过getViewAt()来获取“集合视图”中的第position项的视图,视图是以RemoteViews的对象返回的。

  int getCount()

  通过getCount()来获取“集合视图”中所有子项的总数。

AppWidget的美貌——AppWidgetProvider

  我们说一位女同事漂亮,除了因为她穿的衣服、化的妆漂亮以外,我想最主要的原因还是她本人长的漂亮吧。同样,小部件之所以有附着在桌面,跨进程更新 View 的能力,主要是因为AppWidgetProvider 是一个广播接收者。我们发现,上面的例子中,AS 帮我们自动生成的代码中,除了 onUpdate() 方法被我们重写了,还有重写 onEnable() 和 onDisable() 两个方法,但都是空实现,这两个方法什么时候会被调用?还有,我们说自定义的 MyAppWidgetProvider,继承自 AppWidgetProvider,而 MyAppWidgetProvider 又是BroadCastReceiver 的子类,而我们却没有向写常规广播接收者一样重写 onReceiver() 方法?下面跟进去 AppWidgetProvider 源码,一探究竟。

  这个类代码并不多,其实,AppWidgetProvider 出去构造方法外,总共只有下面这些方法:

  onEnable() :当小部件第一次被添加到桌面时回调该方法,可添加多次,但只在第一次调用。对用广播的 Action 为 ACTION_APPWIDGET_ENABLE。

  onUpdate():  当小部件被添加时或者每次小部件更新时都会调用一次该方法,配置文件中配置小部件的更新周期 updatePeriodMillis,每次更新都会调用。对应广播 Action 为:ACTION_APPWIDGET_UPDATE 和 ACTION_APPWIDGET_RESTORED 。

  onDisabled(): 当最后一个该类型的小部件从桌面移除时调用,对应的广播的 Action 为 ACTION_APPWIDGET_DISABLED。

  onDeleted(): 每删除一个小部件就调用一次。对应的广播的 Action 为: ACTION_APPWIDGET_DELETED 。

  onRestored(): 当小部件从备份中还原,或者恢复设置的时候,会调用,实际用的比较少。对应广播的 Action 为 ACTION_APPWIDGET_RESTORED。

  onAppWidgetOptionsChanged(): 当小部件布局发生更改的时候调用。对应广播的 Action 为 ACTION_APPWIDGET_OPTIONS_CHANGED。

  最后就是 onReceive() 方法了,AppWidgetProvider 重写了该方法,用于分发具体的时间给上述的方法。看看源码:

public void onReceive(Context context, Intent intent) {
        // Protect against rogue update broadcasts (not really a security issue,
        // just filter bad broacasts out so subclasses are less likely to crash).
        String action = intent.getAction();
        if (AppWidgetManager.ACTION_APPWIDGET_UPDATE.equals(action)) {
            Bundle extras = intent.getExtras();
            if (extras != null) {
                int[] appWidgetIds = extras.getIntArray(AppWidgetManager.EXTRA_APPWIDGET_IDS);
                if (appWidgetIds != null && appWidgetIds.length > 0) {
                    this.onUpdate(context, AppWidgetManager.getInstance(context), appWidgetIds);
                }
            }
        } else if (AppWidgetManager.ACTION_APPWIDGET_DELETED.equals(action)) {
            Bundle extras = intent.getExtras();
            if (extras != null && extras.containsKey(AppWidgetManager.EXTRA_APPWIDGET_ID)) {
                final int appWidgetId = extras.getInt(AppWidgetManager.EXTRA_APPWIDGET_ID);
                this.onDeleted(context, new int[] { appWidgetId });
            }
        } else if (AppWidgetManager.ACTION_APPWIDGET_OPTIONS_CHANGED.equals(action)) {
            Bundle extras = intent.getExtras();
            if (extras != null && extras.containsKey(AppWidgetManager.EXTRA_APPWIDGET_ID)
                    && extras.containsKey(AppWidgetManager.EXTRA_APPWIDGET_OPTIONS)) {
                int appWidgetId = extras.getInt(AppWidgetManager.EXTRA_APPWIDGET_ID);
                Bundle widgetExtras = extras.getBundle(AppWidgetManager.EXTRA_APPWIDGET_OPTIONS);
                this.onAppWidgetOptionsChanged(context, AppWidgetManager.getInstance(context),
                        appWidgetId, widgetExtras);
            }
        } else if (AppWidgetManager.ACTION_APPWIDGET_ENABLED.equals(action)) {
            this.onEnabled(context);
        } else if (AppWidgetManager.ACTION_APPWIDGET_DISABLED.equals(action)) {
            this.onDisabled(context);
        } else if (AppWidgetManager.ACTION_APPWIDGET_RESTORED.equals(action)) {
            Bundle extras = intent.getExtras();
            if (extras != null) {
                int[] oldIds = extras.getIntArray(AppWidgetManager.EXTRA_APPWIDGET_OLD_IDS);
                int[] newIds = extras.getIntArray(AppWidgetManager.EXTRA_APPWIDGET_IDS);
                if (oldIds != null && oldIds.length > 0) {
                    this.onRestored(context, oldIds, newIds);
                    this.onUpdate(context, AppWidgetManager.getInstance(context), newIds);
                }
            }
        }
    }

AppWidget 练习

  下面再自己写个例子,学习 RemoteViews 中的其它知识点,这个例子中小部件布局中用到 button 和 listview。上代码:

  小部件的布局文件 mul_app_widget_provider.xml 如下:

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:orientation="horizontal"
    android:layout_width="match_parent"
    android:layout_height="match_parent">

    <LinearLayout
        android:layout_width="100dp"
        android:layout_height="200dp"
        android:orientation="vertical">
        <ImageView
            android:id="@+id/iv_test"
            android:layout_width="match_parent"
            android:layout_height="100dp"
            android:src="@mipmap/ic_launcher"/>
        <Button
            android:id="@+id/btn_test"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:text="点击跳转"/>
    </LinearLayout>

    <TextView
        android:layout_width="1dp"
        android:layout_height="200dp"
        android:layout_marginLeft="5dp"
        android:layout_marginRight="5dp"
        android:background="#f00"/>

    <ListView
        android:id="@+id/lv_test"
        android:layout_width="100dp"
        android:layout_height="200dp">
    </ListView>

</LinearLayout>

  小部件的配置信息 mul_app_widget_provider_info.xml 如下:

<?xml version="1.0" encoding="utf-8"?>
<appwidget-provider xmlns:android="http://schemas.android.com/apk/res/android"
    android:initialLayout="@layout/mul_app_widget_provider"
    android:minHeight="200dp"
    android:minWidth="200dp"
    android:previewImage="@mipmap/a1"
    android:updatePeriodMillis="86400000">
</appwidget-provider>

  MulAppWidgetProvider.java:

package com.example.joy.remoteviewstest;

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.os.Bundle;
import android.text.TextUtils;
import android.widget.RemoteViews;
import android.widget.Toast;

public class MulAppWidgetProvider extends AppWidgetProvider {

    public static final String CHANGE_IMAGE = "com.example.joy.action.CHANGE_IMAGE";

    private RemoteViews mRemoteViews;
    private ComponentName mComponentName;

    private int[] imgs = new int[]{
            R.mipmap.a1,
            R.mipmap.b2,
            R.mipmap.c3,
            R.mipmap.d4,
            R.mipmap.e5,
            R.mipmap.f6
    };


    @Override
    public void onUpdate(Context context, AppWidgetManager appWidgetManager, int[] appWidgetIds) {
        mRemoteViews = new RemoteViews(context.getPackageName(), R.layout.mul_app_widget_provider);
        mRemoteViews.setImageViewResource(R.id.iv_test, R.mipmap.ic_launcher);
        mRemoteViews.setTextViewText(R.id.btn_test, "点击跳转到Activity");
        Intent skipIntent = new Intent(context, MainActivity.class);
        PendingIntent pi = PendingIntent.getActivity(context, 200, skipIntent, PendingIntent.FLAG_CANCEL_CURRENT);
        mRemoteViews.setOnClickPendingIntent(R.id.btn_test, pi);

        // 设置 ListView 的adapter。
        // (01) intent: 对应启动 ListViewService(RemoteViewsService) 的intent
        // (02) setRemoteAdapter: 设置 ListView 的适配器
        // 通过setRemoteAdapter将 ListView 和ListViewService关联起来,
        // 以达到通过 GridWidgetService 更新 gridview 的目的
        Intent lvIntent = new Intent(context, ListViewService.class);
        mRemoteViews.setRemoteAdapter(R.id.lv_test, lvIntent);
        mRemoteViews.setEmptyView(R.id.lv_test,android.R.id.empty);

        // 设置响应 ListView 的intent模板
        // 说明:“集合控件(如GridView、ListView、StackView等)”中包含很多子元素,如GridView包含很多格子。
        // 它们不能像普通的按钮一样通过 setOnClickPendingIntent 设置点击事件,必须先通过两步。
        // (01) 通过 setPendingIntentTemplate 设置 “intent模板”,这是比不可少的!
        // (02) 然后在处理该“集合控件”的RemoteViewsFactory类的getViewAt()接口中 通过 setOnClickFillInIntent 设置“集合控件的某一项的数据”
        
        /*
         * setPendingIntentTemplate 设置pendingIntent 模板
         * setOnClickFillInIntent   可以将fillInIntent 添加到pendingIntent中
         */
        Intent toIntent = new Intent(CHANGE_IMAGE);
        PendingIntent pendingIntent = PendingIntent.getBroadcast(context, 200, toIntent, PendingIntent.FLAG_UPDATE_CURRENT);
        mRemoteViews.setPendingIntentTemplate(R.id.lv_test, pendingIntent);


        mComponentName = new ComponentName(context, MulAppWidgetProvider.class);
        appWidgetManager.updateAppWidget(mComponentName, mRemoteViews);
    }

    @Override
    public void onReceive(Context context, Intent intent) {
        super.onReceive(context, intent);
        if(TextUtils.equals(CHANGE_IMAGE,intent.getAction())){
            Bundle extras = intent.getExtras();
            int position = extras.getInt(ListViewService.INITENT_DATA);
            mRemoteViews = new RemoteViews(context.getPackageName(), R.layout.mul_app_widget_provider);
            mRemoteViews.setImageViewResource(R.id.iv_test, imgs[position]);
            mComponentName = new ComponentName(context, MulAppWidgetProvider.class);
            AppWidgetManager.getInstance(context).updateAppWidget(mComponentName, mRemoteViews);
        }
    }
}

MainActivity.java:

package com.example.joy.remoteviewstest;

import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;

public class MainActivity extends AppCompatActivity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
    }
}

  下面重点是 ListView 在小部件中的用法:

package com.example.joy.remoteviewstest;

import android.content.Context;
import android.content.Intent;
import android.os.Bundle;
import android.widget.RemoteViews;
import android.widget.RemoteViewsService;

import java.util.ArrayList;
import java.util.List;

public class ListViewService extends RemoteViewsService {
    public static final String INITENT_DATA = "extra_data";

    @Override
    public RemoteViewsFactory onGetViewFactory(Intent intent) {
        return new ListRemoteViewsFactory(this.getApplicationContext(), intent);
    }

    private class ListRemoteViewsFactory implements RemoteViewsService.RemoteViewsFactory {

        private Context mContext;

        private List<String> mList = new ArrayList<>();

        public ListRemoteViewsFactory(Context context, Intent intent) {
            mContext = context;
        }

        @Override
        public void onCreate() {
            mList.add("一");
            mList.add("二");
            mList.add("三");
            mList.add("四");
            mList.add("五");
            mList.add("六");
        }

        @Override
        public void onDataSetChanged() {

        }

        @Override
        public void onDestroy() {
            mList.clear();
        }

        @Override
        public int getCount() {
            return mList.size();
        }

        @Override
        public RemoteViews getViewAt(int position) {
            RemoteViews views = new RemoteViews(mContext.getPackageName(), android.R.layout.simple_list_item_1);
            views.setTextViewText(android.R.id.text1, "item:" + mList.get(position));

            Bundle extras = new Bundle();
            extras.putInt(ListViewService.INITENT_DATA, position);
            Intent changeIntent = new Intent();
            changeIntent.setAction(MulAppWidgetProvider.CHANGE_IMAGE);
            changeIntent.putExtras(extras);

            /* android.R.layout.simple_list_item_1 --- id --- text1
             * listview的item click:将 changeIntent 发送,
             * changeIntent 它默认的就有action 是provider中使用 setPendingIntentTemplate 设置的action*/
            views.setOnClickFillInIntent(android.R.id.text1, changeIntent);
            return views;
        }

        /* 在更新界面的时候如果耗时就会显示 正在加载... 的默认字样,但是你可以更改这个界面
         * 如果返回null 显示默认界面
         * 否则 加载自定义的,返回RemoteViews
         */
        @Override
        public RemoteViews getLoadingView() {
            return null;
        }

        @Override
        public int getViewTypeCount() {
            return 1;
        }

        @Override
        public long getItemId(int position) {
            return position;
        }

        @Override
        public boolean hasStableIds() {
            return false;
        }
    }
}

 最后看看清单文件:

复制代码
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="com.example.joy.remoteviewstest">

    <application
        android:allowBackup="true"
        android:icon="@mipmap/ic_launcher"
        android:label="@string/app_name"
        android:supportsRtl="true"
        android:theme="@style/AppTheme">
        <activity android:name=".MainActivity">
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />

                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>

        <receiver android:name=".MulAppWidgetProvider"
            android:label="@string/app_name">
            <intent-filter>
                <action android:name="com.example.joy.action.CHANGE_IMAGE"/>
                <action android:name="android.appwidget.action.APPWIDGET_UPDATE"/>
            </intent-filter>
            <meta-data
                android:name="android.appwidget.provider"
                android:resource="@xml/mul_app_widget_provider_info">
            </meta-data>
        </receiver>

        <service android:name=".ListViewService"
            android:permission="android.permission.BIND_REMOTEVIEWS"
            android:exported="false"
            android:enabled="true"/>

    </application>

</manifest>

 这个小部件添加到桌面后有一个 ImageView 显示小机器人,下面有一个 Button ,右边有一个ListView。

  这里主要看看,Button 和 ListView 在 RemoteViews中如何使用。、

  Button 设置 Text 和 TextView 一样,因为 Button 本身继承自 TextView,Button 设置点击事件如下:

  Intent skipIntent = new Intent(context, MainActivity.class);
        PendingIntent pi = PendingIntent.getActivity(context, 200, skipIntent, PendingIntent.FLAG_CANCEL_CURRENT);
        mRemoteViews.setOnClickPendingIntent(R.id.btn_test, pi);

 用到方法 setOnClickPendingIntent,PendingIntent 表示延迟的 Intent , 与通知中的用法一样。这里点击之后跳转到了 MainActivity。

  关于 ListView 的用法就复杂一些了。首先需要自定义一个类继承自 RemoteViewsServices ,并重写 onGetViewFactory 方法,返回 RemoteViewsService.RemoteViewsFactory 接口的对象。这里定义了一个内部类实现该接口,需要重写多个方法,与 ListView 的多布局适配很类似。重点方法是

public RemoteViews getViewAt(int position){} 

这个方法中指定了 ListView 的每一个 item 的布局以及内容,同时通过  setOnClickFillInIntent() 或者 setOnClickPendingIntent() 给 item 设置点击事件。这里我实现的点击 item,替换左边的 ImageView 的图片。重写了 MulAppWidgetProvider 类的 onReceiver 方法,处理替换图片的逻辑。

  程序运行效果如下图: 

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/423979.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

分布式系统概念和设计-分布式对象和远程调用

分布式系统概念和设计 分布式对象和远程调用 能够接收远程方法调用的对象称为远程对象&#xff0c;远程对象实现一个远程接口。 调用者和被调用对象分别存在不同的失败可能性&#xff0c;RMI和本地调用有不同的语义。 中间件 在进程和消息传递等基本构造模块之上提供编程模型的…

PDCA循环模型——如何用同样的时间做更多的事?【No.1 】

PDCA循环模型&#xff0c;又称戴明环&#xff0c;是一个持续改进模型。PDCA循环包括以下内容&#xff1a; Plan阶段&#xff1a;确认目标&#xff0c;制定计划Do阶段&#xff1a;执行措施和计划Check阶段&#xff1a;检查验证&#xff0c;评估效果Action阶段&#xff1a;有效措…

健哥MYSQL私房菜 - 基础与介绍

前言 从今天开始, 健哥就带各位小伙伴学习数据库技术。数据库技术是Java开发中必不可少的一部分知识内容。也是非常重要的技术。本系列教程由浅入深, 全面讲解数据库体系。 非常适合零基础的小伙伴来学习。 ------------------------------前戏已做完&#xff0c;精彩即开始---…

docker-compose详讲

一、概述 docker-compose 项目是docker官方的开源项目&#xff0c; 负责实现对docker容器集群的快速编排&#xff0c;来轻松高效的管理容器&#xff0c;定义运行多个容器。 docker-compose将所管理的容器分为三层&#xff0c; 分别是工程&#xff08;project&#xff09;&#…

C#,码海拾贝(19)——一般实矩阵的QR分解(QR Decomposition)方法之C#源代码,《C#数值计算算法编程》源代码升级改进版

1 实矩阵 实矩阵&#xff0c;指的是矩阵中所有的数都是实数的矩阵。如果一个矩阵中含有除实数以外的数&#xff0c;那么这个矩阵就不是实矩阵。 2 QR&#xff08;正交三角&#xff09;分解法 QR&#xff08;正交三角&#xff09;分解法是求一般矩阵全部特征值的最有效并广泛应…

基于Java+SpringBoot制作一个宿舍报修小程序

制作一个宿舍报修小程序&#xff0c;让学生实现快速报修&#xff0c;将流程进行精简&#xff0c; 便于管理部门有效响应。 微信小程序实战开发专栏 一、小程序1.1 项目创建1.2 首页iconfont图标引入1.3 报修管理报修提交报修记录报修溯源1.4 来访登记1.5 公告通知二、API2.1 Sp…

windows日志捕获工具-DebugView使用教程

debugview 是一款捕获windows桌面系统程序中由TRACE(debug版本)和OutputDebugString输出的信息。 1、双击打开DebugView.exe工具&#xff0c;看到如下界面&#xff1a; 其中这里 Include代表过滤想要的关键字&#xff0c;一般不会找自己想要的关键字日志就设置成星号*&#xf…

GDOUCTF WEB

web hate eat snake 游戏题找js&#xff0c;将判断语句删掉即可 ezweb 看源码找到/src路由&#xff0c;找到源码 import flaskapp flask.Flask(__name__)app.route(/, methods[GET]) def index():return flask.send_file(index.html)app.route(/src, methods[GET]) def so…

如何快速开发软件?这篇文章说明白了

随着经济迅速发展&#xff0c;传统软件开发模式存在研发周期长、需求转化困难、投入成本高等问题&#xff0c;无法适应当前业务发展速度,市场需要快速开发工具。快速开发软件可分为代码生成类、少代码类、零代码功能配置类。代码生成类相对灵活&#xff0c;但对用户要求高&…

MySQL group_concat设置group_concat_max_len

GROUP_CONCAT函数用于将多个字符串连接成一个字符串&#xff0c;在拼接成字符串时就会存在拼接长度的问题&#xff0c;mysql 默认的拼接最大长度为1024 个字节&#xff0c;由于1024个字节会出现不够用的情况&#xff0c;所以有时需要去根据情况进行修改&#xff0c;方式如下。 …

nginx启动、配置、测试(全网最全)

目录 一、要求 1.配置不同IP访问 2.配置不同端口访问 3.配置域名访问 二、前期准备 1.安装gcc g的依赖库 2.安装 pcre的依赖库 3.安装zlib的依赖库 4.安装openssl的依赖库 5.解压nginx的安装包 6.进入到解压的nginx安装目录里面 7.将nginx安装到/usr/local/下 8.编译 9.进入到…

从 Python 中的字典列表中删除重复项

要从字典列表中删除重复项&#xff1a; 使用字典推导来遍历列表。使用每个 id 属性的值作为键&#xff0c;使用字典作为值。使用 dict.values() 方法只获取唯一的字典。使用 list() 类将结果转换为列表。 list_of_dictionaries [{id: 1, site: jiyik.com},{id: 2, site: goo…

5G NR调制阶数与EVM关系以及对系统SNR要求分析

移动通信技术对数据传输速率要求越来越高。一种提高传输速率的思路是使用更高阶的QAM 调制方式&#xff0c;例如5G NR 的256QAM PDSCH&#xff0c;微波的1024QAM&#xff0c;2048QAM和4096QAM 调制。更高阶的QAM 调制方式对系统也提出了更高的要求。例如某个系统的EVM 测试结果…

微服务+springcloud+springcloud alibaba学习笔记【Hystrix(豪猪哥)的使用】(6/9)

Hystrix&#xff08;豪猪哥&#xff09;的使用 6/91、Hystrix熔断器概述2、HyStrix重要概念3、hystrix案例3.1 新建模块 Cloud-provider-hystrix-payment80013.2 创建带降级的order模块 Cloud-comsumer-feign-hystrix-order803.3 配置服务降级:3.3.1 服务降级 Cloud-provider-h…

企业做体系认证要警惕的8大问题,别再被不良认证机构忽悠啦!

企业资质认证要警惕这八大问题 企业资质真的很管用。我们都知道从事任何一个行业都需要准入证明&#xff0c;尤其是招投标企业&#xff0c;企业资质更是投标的准入门槛&#xff0c;并且在投标中还可以为企业加分。 有些资质如ISO三体系是企业必备的资质之一&#xff0c;也是常…

麻了,不要再动不动就BeanUtil.copyProperties

前言 最近项目上要求升级一个工具包hutool的版本&#xff0c;以解决安全漏洞问题&#xff0c;这不升级还好&#xff0c;一升级反而捅出了更大的篓子&#xff0c;究竟是怎么回事呢&#xff1f; 事件回顾 我们项目原先使用的hutool版本是5.7.2&#xff0c;在代码中&#xff0c…

Hudi集成Flink-写入方式

文章目录一、CDC 入湖1.1、[开启binlog](https://blog.csdn.net/wuxintdrh/article/details/130142601)1.2、创建测试表1.2.1、创建mysql表1.2.2、将 binlog 日志 写入 kafka1、使用 mysql-cdc 监听 binlog2、kafka 作为 sink表3、写入sink 表1.2.3、将 kakfa 数据写入hudi1、k…

ERTEC200P-2 PROFINET设备完全开发手册(4-2)

4.2 XHIF接口实验 4.2.1写入单片机固件 首先按照下图连接设备 用JLINK 20Pin JTAG连接4 Pin SWD可以采用转接板 单片机的参考程序是用ST的CubeIDE生成的&#xff0c;目前的版本是1.7.0。打开安装后的CubeIDE&#xff0c;在菜单中选择“File->Import“ 选择“Existing Proj…

企业信息化建设都包括哪些方面?

随着大数据技术的发展&#xff0c;时代的发展要求企业转变管理模式、建立信息化管理机制&#xff0c;同时也是提高工作、管理效率&#xff0c;促进企业战略性发展的重要保障。 企业信息化是将信息技术应用于企业发展实践中的一个动态过程&#xff0c;即通过挖掘先进的管理理念…

高可靠多层板制造服务再获认可!华秋荣获创想三维优秀质量奖

4月10日&#xff0c;创想三维2023年度战略供应商大会在惠州成功举办&#xff0c;高可靠多层板制造商华秋出席了本次活动并取得了《优秀质量奖》一奖项。 大会现场&#xff0c;创想三维董事长陈春指出公司的持续发展与供应链高质量的交付息息相关。作为创想三维主力PCB供应商&am…