使用AppWidgetProvider、RemoteViews、PendingIntent开发桌面小部件

AppWidgetProvider

  • android系统提供用于实现桌面小部件的类,继承BroadcastReceiver,所以其本质是一个广播,我们再使用的时候可以根据广播的使用方式来开发

RemoteViews

  • 用于通知栏和桌面小部件,提供了一系列的set方法来跨进程更新界面,并且这些方法只是View全部方法的子集,支持的View类型也有限,RemoteViews的内部机制这里不讨论

PendingIntent

  • 表示一种处于pending状态(待定、等待、即将发生)的意图,接下来将在某个特定的时刻发生,而Intent是立刻发生,PendingIntent的典型使用场景就是给RemoteViews设置点击事件
  • PendingIntent.genActivity()相当于Contex.startActivity(),PendingIntent.genSerivice()相当于Contex.startSerivice(),PendingIntent.genBroadcast()相当于Contex.sendBroadcast()

桌面小部件的开发步骤

  1. 在新建一个布局文件比如lcq_widget.xml放在res/layout下
    <?xml version="1.0" encoding="utf-8"?>
    <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:orientation="vertical">
    
        <ImageView
            android:id="@+id/lcq_img"
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            android:contentDescription="@string/todo"
            android:src="@drawable/lcq_img" />
    </LinearLayout>
  2. 定义小部件的配置信息比如lcq_appwidget_provider_info.xml,并放在res/xml下
    <?xml version="1.0" encoding="utf-8"?>
    <appwidget-provider xmlns:android="http://schemas.android.com/apk/res/android"
        android:initialLayout="@layout/lcq_widget"
        android:minWidth="100dp"
        android:minHeight="100dp"
        android:updatePeriodMillis="888000000" />

     initialLayout:桌面小工具使用的初始化布局;minHeight、minWidth为小工具的最小尺寸,updatePeriodMillis为自动更新的周期,单位毫秒

  3. 定义小部件的实现类比如LcqAppWidgetProvider
    package com.lcq.lcqappwidgetprovider;
    
    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.graphics.Bitmap;
    import android.graphics.BitmapFactory;
    import android.graphics.Matrix;
    import android.widget.RemoteViews;
    import android.widget.Toast;
    
    public class LcqAppWidgetProvider extends AppWidgetProvider {
        public static final String ACTION_ROTATE = "com.lcq.action.rotate.CLICK";
    
        @Override
        public void onDeleted(Context context, int[] appWidgetIds) {
            //每删除一次桌面小部件就调用一次
            super.onDeleted(context, appWidgetIds);
            Toast.makeText(context, "onDeleted", Toast.LENGTH_SHORT).show();
        }
    
        @Override
        public void onEnabled(Context context) {
            //当窗口小部件第一次添加到桌面是调用该方法,可添加多次但是只在第一次添加时调用
            super.onEnabled(context);
            Toast.makeText(context, "onEnabled", Toast.LENGTH_SHORT).show();
        }
    
        @Override
        public void onDisabled(Context context) {
            //当最后一个改类型的桌面小部件被删除时调用该方法,注意是最后一个
            super.onDisabled(context);
            Toast.makeText(context, "onDisabled", Toast.LENGTH_SHORT).show();
        }
    
        @Override
        public void onReceive(Context context, Intent intent) {
            //这是广播的内置方法,用于分发据图的事件给其他方法
            super.onReceive(context, intent);
            Toast.makeText(context, "onReceive:aciton:" + intent.getAction(), Toast.LENGTH_SHORT).show();
            if (intent.getAction().equals(ACTION_ROTATE)) {
                Toast.makeText(context, "点击了图片,开始旋转", Toast.LENGTH_SHORT).show();
                new Thread(new Runnable() {
                    @Override
                    public void run() {
                        Bitmap bitmap = BitmapFactory.decodeResource(context.getResources(), R.drawable.lcq_img);
                        AppWidgetManager appWidgetManager = AppWidgetManager.getInstance(context);
                        for (int i = 0; i < 37; i++) {//循环36次,将图片旋转360度
                            float degree = (i * 10) % 360;
                            RemoteViews remoteViews = obtainRemoteViews(context);
                            remoteViews.setImageViewBitmap(R.id.lcq_img, getBitmap(bitmap, degree));
                            appWidgetManager.updateAppWidget(new ComponentName(context, LcqAppWidgetProvider.class), remoteViews);
                            try {
                                Thread.sleep(30);
                            } catch (InterruptedException e) {
                                e.printStackTrace();
                            }
                        }
                    }
                }).start();
            }
        }
    
        //获取旋转后的bitmap,degree为旋转角度
        private Bitmap getBitmap(Bitmap bitmap, float degree) {
            Matrix matrix = new Matrix();
            matrix.reset();
            matrix.setRotate(degree);
            return Bitmap.createBitmap(bitmap, 0, 0, bitmap.getWidth(), bitmap.getHeight(), matrix, true);
        }
    
        @Override
        public void onUpdate(Context context, AppWidgetManager appWidgetManager, int[] appWidgetIds) {
            //小部件被添加时或者每次小部件更新时都会调用一次改方法,
            //小部件的更新实际由updatePeriodMillis来指定,每个周期小部件都会自动更新异常
            super.onUpdate(context, appWidgetManager, appWidgetIds);
            final int len = appWidgetIds.length;
            Toast.makeText(context, "onUpdate:len=" + len, Toast.LENGTH_SHORT).show();
            for (int i = 0; i < len; i++) {
                updateWidget(context, appWidgetManager, appWidgetIds[i]);
            }
        }
    
        private void updateWidget(Context context, AppWidgetManager appWidgetManager, int id) {
            appWidgetManager.updateAppWidget(id, obtainRemoteViews(context));
        }
    
        //创建使用布局RemoteViews,并给lcq_img设置点击事件,注意:clickIntent必须设置ComponentName,否则8.0以上机型会接收不到广播
        private RemoteViews obtainRemoteViews(Context context) {
            RemoteViews remoteViews = new RemoteViews(context.getPackageName(), R.layout.lcq_widget);
            Intent clickIntent = new Intent(ACTION_ROTATE);
            //在8.0以上广播机制有所变化,sendbroadcast前要指定下receiver的类
            clickIntent.setComponent(new ComponentName(context, LcqAppWidgetProvider.class));
            PendingIntent pendingIntent = PendingIntent.getBroadcast(context, 0, clickIntent, 0);
            remoteViews.setOnClickPendingIntent(R.id.lcq_img, pendingIntent);
            return remoteViews;
        }
    }
    

    这里主要实现了一个带有ImageView的小部件,点击小部件的ImageView对图片进行360度旋转特别注意clickIntent必须设置ComponentName,否则8.0以上机型会接收不到广播;AppWidgetProvider重要的几个重写方法,比如onDeleted、onEnabled、onDisabled、onReceive、onUpdate方法的作用已经在代码里面做了注释

  4. 在AndroidManifest.xml中声明小部件,因为其本质是一个广播,所以需要进行静态广播注册
            <receiver
                android:name="com.lcq.lcqappwidgetprovider.LcqAppWidgetProvider"
                android:exported="true">
                <meta-data
                    android:name="android.appwidget.provider"
                    android:resource="@xml/lcq_appwidget_provider_info" /><!--小部件的配信息-->
                <intent-filter>
                    <action android:name="com.lcq.action.rotate.CLICK" /><!--用于识别小部件的点击行为-->
                    <action android:name="android.appwidget.action.APPWIDGET_UPDATE" /><!--系统规范,必须存在,如果不加则在手机的小部件里面不会出现-->
                </intent-filter>
            </receiver>

    android:resource="@xml/lcq_appwidget_provider_info"小部件的配置信息;               <action android:name="com.lcq.action.rotate.CLICK" />:用于识别小部件的点击行为;
     <action android:name="android.appwidget.action.APPWIDGET_UPDATE" />:系统规范,必须存在,如果不加则在手机的小部件里面不会出现

  5. 编译代码生成apk文件,将其安装到手机
  6. 长按手机的桌面(不同品牌手机可能操作方式不一样)会弹出桌面设置界面,选择添加工具
  7. 然后定位到我们的应用,并选中
  8. 选中后回到桌面设置界面,可以看到我们开发的小工具已经被添加进来了,这个可以重复的操作,效果就是会添加多个小部件
  9. 我们回到桌面,然后点击我们开发的桌面小部件,可以看到已经转起来了
  •  具体的开发步骤和注意事项已经讲完了,你可以在这个基础之上做出更复杂更好看的效果和功能

猜你喜欢

转载自blog.csdn.net/qq_19942717/article/details/126923975