美文网首页
Android 管理桌面控件

Android 管理桌面控件

作者: gaookey | 来源:发表于2023-01-30 22:05 被阅读0次
    image.png

    桌面控件是通过 BroadcastReceiver 的形式来进行控制的,因此每个桌面控件都对应于一个 BroadcastReceiver。 为了简化桌面控件的开发,Android 系统提供了一个 AppWidgetProvider 类,它就是 BroadcastReceiver 的子类。也就是说,开发者开发桌面控件只要缴承 AppWidgetProvider 类即可。

    public class DesktopApp extends AppWidgetProvider {
        @Override
        public void onUpdate(Context context, AppWidgetManager appWidgetManager, int[] appWidgetIds) {
            // 加载指定界面布局文件,创建RemoteViews对象
            RemoteViews remoteViews = new RemoteViews(context.getPackageName(), R.layout.my_widget);  // ①
            // 为show ImageView设置图片
            remoteViews.setImageViewResource(R.id.show, R.drawable.image);   // ②
            // 将AppWidgetProvider的子类实例包装成ComponentName对象
            ComponentName componentName = new ComponentName(context, DesktopApp.class);  // ③
            // 调用AppWidgetManager将remoteViews添加到ComponentName中
            appWidgetManager.updateAppWidget(componentName, remoteViews);  // ④
        }
    }
    

    layout/my_widget.xml

    <?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/show"
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            android:gravity="center"
            android:src="@drawable/logo" />
    </LinearLayout>
    

    xml/appwidget_provider.xml

    <?xml version="1.0" encoding="utf-8"?>
    <!-- 指定该桌面控件的基本配置信息:
        minWidth:桌面控件的最小宽度。
        minWidth:桌面控件的最小高度。
        updatePeriodMillis:更新频率
        initialLayout:初始时显示的布局 -->
    <appwidget-provider xmlns:android="http://schemas.android.com/apk/res/android"
        android:initialLayout="@layout/my_widget"
        android:minWidth="150dip"
        android:minHeight="70dip"
        android:updatePeriodMillis="1000" />
    

    AndroidManifest.xml

    <?xml version="1.0" encoding="utf-8"?>
    <manifest xmlns:android="http://schemas.android.com/apk/res/android"
        xmlns:tools="http://schemas.android.com/tools">
    
        <application
            android:allowBackup="true"
            android:dataExtractionRules="@xml/data_extraction_rules"
            android:fullBackupContent="@xml/backup_rules"
            android:icon="@mipmap/ic_launcher"
            android:label="@string/app_name"
            android:roundIcon="@mipmap/ic_launcher_round"
            android:supportsRtl="true"
            android:theme="@style/Theme.MyApplication"
            tools:targetApi="31">
            
            <receiver
                android:name=".DesktopApp"
                android:exported="true"
                android:label="@string/app_name"
                tools:ignore="IntentFilterExportedReceiver">
                <!-- 将该BroadcastReceiver当成桌面控件 -->
                <intent-filter>
                    <action android:name="android.appwidget.action.APPWIDGET_UPDATE" />
                </intent-filter>
                <!-- 指定桌面控件的meta-data -->
                <meta-data
                    android:name="android.appwidget.provider"
                    android:resource="@xml/appwidget_provider" />
            </receiver>
    
        </application>
    
    </manifest>
    

    把应用安装到 Android 系统上,然后桌面长按,选择 Widgets

    image.png

    拖动控件到桌面

    image.png image.png

    实例:液晶时钟

    LedClock

    public class LedClock extends AppWidgetProvider {
        private Timer timer = new Timer();
        private AppWidgetManager appWidgetManager;
        private Context context;
        // 将0~9的液晶数字图片定义成数组
        private int[] digits = new int[]{R.drawable.su01, R.drawable.su02,
                R.drawable.su03, R.drawable.su04, R.drawable.su05,
                R.drawable.su06, R.drawable.su07, R.drawable.su08,
                R.drawable.su09, R.drawable.su10};
        // 将显示小时、分钟、秒钟的ImageView定义成数组
        private int[] digitViews = new int[]{R.id.img01, R.id.img02, R.id.img04,
                R.id.img05, R.id.img07, R.id.img08};
    
        static class MyHandler extends Handler {
            private WeakReference<LedClock> ledClock;
    
            public MyHandler(WeakReference<LedClock> ledClock) {
                this.ledClock = ledClock;
            }
    
            @Override
            public void handleMessage(Message msg) {
                if (msg.what == 0x123) {
                    RemoteViews views = new RemoteViews(ledClock.get().context.getPackageName(), R.layout.clock);
                    // 定义SimpleDateFormat对象
                    SimpleDateFormat df = new SimpleDateFormat("HHmmss");
                    // 将当前时间格式化成HHmmss的形式
                    String timeStr = df.format(new Date());
                    for (int i = 0; i < timeStr.length(); i++) {
                        // 将第i个数字字符转换为对应的数字
                        int num = timeStr.charAt(i) - 48;
                        // 将第i个图片设为对应的液晶数字图片
                        views.setImageViewResource(ledClock.get().digitViews[i],
                                ledClock.get().digits[num]);
                    }
                    // 将AppWidgetProvider子类实例包装成ComponentName对象
                    ComponentName componentName = new ComponentName(ledClock.get().context,
                            LedClock.class);
                    // 调用AppWidgetManager将remoteViews添加到ComponentName中
                    ledClock.get().appWidgetManager.updateAppWidget(
                            componentName, views);
                }
                super.handleMessage(msg);
            }
        }
    
        private Handler handler = new MyHandler(new WeakReference<>(this));
    
        @Override
        public void onUpdate(Context context,
                             AppWidgetManager appWidgetManager, int[] appWidgetIds) {
            System.out.println("--onUpdate--");
            this.appWidgetManager = appWidgetManager;
            this.context = context;
            // 定义计时器
            timer = new Timer();
            // 启动周期性调度
            timer.schedule(new TimerTask() {
                @Override
                public void run() {
                    // 发送空消息,通知界面更新
                    handler.sendEmptyMessage(0x123);
                }
            }, 0, 1000);
        }
    }
    

    layout/clock.xml

    <?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="horizontal">
        <!-- 定义8个ImageView来显示液晶数字 -->
        <ImageView
            android:id="@+id/img01"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content" />
    
        <ImageView
            android:id="@+id/img02"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content" />
    
        <ImageView
            android:id="@+id/img03"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:src="@drawable/su00" />
    
        <ImageView
            android:id="@+id/img04"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content" />
    
        <ImageView
            android:id="@+id/img05"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content" />
    
        <ImageView
            android:id="@+id/img06"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:src="@drawable/su00" />
    
        <ImageView
            android:id="@+id/img07"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content" />
    
        <ImageView
            android:id="@+id/img08"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content" />
    </LinearLayout>
    

    xml/my_clock.xml

    <?xml version="1.0" encoding="utf-8"?><!-- 指定该桌面组件的基本配置信息:
        minWidth:桌面控件的最小宽度。
        minWidth:桌面控件的最小高度。
        updatePeriodMillis:更新频率
        initialLayout:初始时显示的布局 -->
    <appwidget-provider xmlns:android="http://schemas.android.com/apk/res/android"
        android:initialLayout="@layout/clock"
        android:minWidth="200dp"
        android:minHeight="20dp"
        android:updatePeriodMillis="1000" />
    

    AndroidManifest.xml

            <receiver
                android:name=".LedClock"
                android:exported="true"
                android:label="@string/name"
                tools:ignore="IntentFilterExportedReceiver">
                <!-- 将该BroadcastReceiver当成桌面控件 -->
                <intent-filter>
                    <action android:name="android.appwidget.action.APPWIDGET_UPDATE" />
                </intent-filter>
                <!-- 指定桌面控件的meta-data -->
                <meta-data
                    android:name="android.appwidget.provider"
                    android:resource="@xml/my_clock" />
            </receiver>
    
    image.png

    显示带数据集的桌面控件

    StackWidgetService

    public class StackWidgetService extends RemoteViewsService {
        // 重写该方法,该方法返回一个RemoteViewsFactory对象
        // RemoteViewsFactory对象的作用类似于Adapter
        // 它负责为RemoteView中的指定组件提供多个列表项
        @Override
        public RemoteViewsFactory onGetViewFactory(Intent intent) {
            return new StackRemoteViewsFactory(this.getApplicationContext(), intent);  // ①
        }
    
        class StackRemoteViewsFactory implements RemoteViewsFactory {
            private Context mContext;
            private Intent intent;
    
            StackRemoteViewsFactory(Context mContext, Intent intent) {
                this.mContext = mContext;
                this.intent = intent;
            }
    
            // 定义一个数组来保存该组件生成的多个列表项
            private int[] items;
    
            @Override
            public void onCreate() {
                // 初始化items数组
                items = new int[]{R.drawable.bomb5, R.drawable.bomb6,
                        R.drawable.bomb7, R.drawable.bomb8, R.drawable.bomb9,
                        R.drawable.bomb10, R.drawable.bomb11, R.drawable.bomb12,
                        R.drawable.bomb13, R.drawable.bomb14, R.drawable.bomb15,
                        R.drawable.bomb16};
            }
    
            @Override
            public void onDestroy() {
                items = null;
            }
    
            // 该方法的返回值控制该对象包含多少个列表项
            @Override
            public int getCount() {
                return items.length;
            }
    
            // 该方法的返回值控制各位置所显示的RemoteViews
            @Override
            public RemoteViews getViewAt(int position) {
                // 创建RemoteViews对象,加载/res/layout目录下的widget_item.xml文件
                RemoteViews rv = new RemoteViews(mContext.getPackageName(), R.layout.widget_item);
                // 更新widget_item.xml布局文件中的widget_item组件
                rv.setImageViewResource(R.id.widget_item, items[position]);
                // 创建Intent,用于传递数据
                Intent fillInIntent = new Intent();
                fillInIntent.putExtra(StackWidgetProvider.EXTRA_ITEM, position);
                // 设置当单击该RemoteViews时传递fillInIntent包含的数据
                rv.setOnClickFillInIntent(R.id.widget_item, fillInIntent);
                // 此处让线程暂停0.2秒来模拟加载该组件
                System.out.println("加载【" + position + "】位置的组件");
                try {
                    Thread.sleep(200);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                return rv;
            }
    
            @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 true;
            }
    
            @Override
            public void onDataSetChanged() {
            }
        }
    }
    

    layout/widget_item.xml

    <?xml version="1.0" encoding="utf-8"?>
    <ImageView xmlns:android="http://schemas.android.com/apk/res/android"
        android:id="@+id/widget_item"
        android:layout_width="120dp"
        android:layout_height="120dp"
        android:gravity="center" />
    

    StackWidgetProvider

    public class StackWidgetProvider extends AppWidgetProvider {
        public static final String TOAST_ACTION = "org.crazyit.desktop.TOAST_ACTION";
        public static final String EXTRA_ITEM = "org.crazyit.desktop.EXTRA_ITEM";
    
        @Override
        public void onUpdate(Context context,
                             AppWidgetManager appWidgetManager, int[] appWidgetIds) {
            // 创建RemoteViews对象,加载/res/layout目录下的widget_layout.xml文件
            RemoteViews rv = new RemoteViews(context.getPackageName(), R.layout.widget_layout);
            Intent intent = new Intent(context, StackWidgetService.class);
            // 使用intent更新rv中的stack_view组件(StackView)
            rv.setRemoteAdapter(R.id.stack_view, intent);  // ①
            // 设置当StackWidgetService提供的列表项为空时,直接显示empty_view组件
            rv.setEmptyView(R.id.stack_view, R.id.empty_view);
            // 创建启动StackWidgetProvider组件(作为BroadcastReceiver)的Intent
            Intent toastIntent = new Intent(context, StackWidgetProvider.class);
            // 为该Intent设置Action属性
            toastIntent.setAction(StackWidgetProvider.TOAST_ACTION);
            // 将Intent包装成PendingIntent
            PendingIntent toastPendingIntent = PendingIntent.getBroadcast(context,
                    0, toastIntent, PendingIntent.FLAG_UPDATE_CURRENT);
            // 将PendingIntent与stack_view进行关联
            rv.setPendingIntentTemplate(R.id.stack_view, toastPendingIntent);
            // 使用AppWidgetManager通过RemoteViews更新AppWidgetProvider
            appWidgetManager.updateAppWidget(new ComponentName(context,
                    StackWidgetProvider.class), rv);  // ②
            super.onUpdate(context, appWidgetManager, appWidgetIds);
        }
    
        // 重写该方法,将该组件当成BroadcastReceiver使用
        @Override
        public void onReceive(Context context, Intent intent) {
            if (TOAST_ACTION.equals(intent.getAction())) {
                // 获取Intent中的数据
                int viewIndex = intent.getIntExtra(EXTRA_ITEM, 0);
                // 显示Toast提示
                Toast.makeText(context, "点击第" + viewIndex + "个列表项",
                        Toast.LENGTH_SHORT).show();
            }
            super.onReceive(context, intent);
        }
    }
    

    layout/widget_layout.xml

    <?xml version="1.0" encoding="utf-8"?>
    <FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:layout_margin="8dp">
    
        <StackView
            android:id="@+id/stack_view"
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            android:gravity="center"
            android:loopViews="true" />
    
        <TextView
            android:id="@+id/empty_view"
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            android:background="#ff0f"
            android:gravity="center"
            android:text="@string/no_item"
            android:textColor="#ffffff"
            android:textSize="20sp"
            android:textStyle="bold" />
    </FrameLayout>
    

    AndroidManifest.xml

            <!-- 配置AppWidgetProvider,即配置桌面控件 -->
            <receiver android:name=".StackWidgetProvider"
                android:exported="true">
                <!-- 通过该intent-filter指定该Receiver作为桌面控件 -->
                <intent-filter>
                    <action android:name="android.appwidget.action.APPWIDGET_UPDATE" />
                </intent-filter>
                <!-- 为桌面控件指定meta-data -->
                <meta-data
                    android:name="android.appwidget.provider"
                    android:resource="@xml/stackwidgetinfo" />
            </receiver>
            <!-- 配置RemoteViewsService
            必须指定权限为android.permission.BIND_REMOTEVIEWS
             -->
            <service
                android:name=".StackWidgetService"
                android:exported="false"
                android:permission="android.permission.BIND_REMOTEVIEWS" />
    
    image.gif

    摘抄至《疯狂Android讲义(第4版)》

    相关文章

      网友评论

          本文标题:Android 管理桌面控件

          本文链接:https://www.haomeiwen.com/subject/rtlvhdtx.html