使用Android Studio 开发桌面小部件,闲来无事,自己动手做一个
AppWidget是应用程序窗口小部件(Widget)是微型的应用程序视图
官方文档链接:http://www.android-doc.com/guide/topics/appwidgets/index.html
目录结构(不是桌面小部件开发步骤)
- 通过Android Studio 创建Widget
- PendingIntent 详解
- Activity 显式 隐式 启动
- 播放器 小部件demo实现
此处可参考:https://blog.csdn.net/lgj860123/article/details/79219973
简单讲下步骤,先【创建Android 项目】,在项目【src-main 具体包名】 右键 new -> Widget 选中 然后配置一下具体参数,完成即可。最主要是桌面程序大小的选择,后面会讲到。
创建完后会自动生成以下文件
public class AppWidgetProvider extends BroadcastReceiver {
public AppWidgetProvider() {
}
// BEGIN_INCLUDE(onReceive)
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);
}
}
}
}
// END_INCLUDE(onReceive)
public void onUpdate(Context context, AppWidgetManager appWidgetManager, int[] appWidgetIds) {
}
public void onAppWidgetOptionsChanged(Context context, AppWidgetManager appWidgetManager,
int appWidgetId, Bundle newOptions) {
}
public void onDeleted(Context context, int[] appWidgetIds) {
}
public void onEnabled(Context context) {
}
public void onDisabled(Context context) {
}
public void onRestored(Context context, int[] oldWidgetIds, int[] newWidgetIds) {
}
}
可以看到,实际上是一个BroadcastReceiver(广播接收器),在onReceive()方法中接收桌面Widget发生变化时,系统发出的一系列广播,然后进行分类处理。最主要是下面五个方法:
此处可参考:https://blog.csdn.net/scry5566/article/details/37692995
该文件描述一个App Widget的元数据,比如布局,更新频率,大小等。后面需要在manifest中注册,下面介绍下属性:
在【res】目录下 layout 文件夹下会自动生成一个 布局文件(对应上述引用的布局资源文件),也就是widget的界面。简单做了个播放器的小部件
注意,Widget并不支持所有的控件跟布局,而仅仅只是支持Android布局和控件的一个子集
最后,会自动在manifest中生成 注册广播的代码
到此通过Android Studio 自动生成的代码基本就这些,一个简单的widget就做好了,在手机桌面长按空白处,添加小部件,就能找到该小部件了。
因为用到,所以记录,不要问我为什么。此处可参考:https://www.jianshu.com/p/4a8fc0b78094
PendingIntent 是 Android 提供的一种用于外部程序调起自身程序的能力,生命周期不与主程序相关。
1,外部程序通过 PendingIntent 只能调用起三种组件
2,使用场景
3,获取方式
// 获取 Broadcast 关联的 PendingIntent
PendingIntent.getBroadcast(Context context, int requestCode, Intent intent, int flags)
// 获取 Activity 关联的 PendingIntent
PendingIntent.getActivity(Context context, int requestCode, Intent intent, int flags)
PendingIntent.getActivity(Context context, int requestCode, Intent intent, int flags, Bundle options)
// 获取 Service 关联的 PendingIntent
PendingIntent.getService(Context context, int requestCode, Intent intent, int flags)
4,参数意义
这里主要说明 PendingIntent.getXXX() 第四个参数 flags
//如果新请求的 PendingIntent 发现已经存在时,取消已存在的,用新的 PendingIntent 替换
int FLAG_CANCEL_CURRENT
//如果新请求的 PendingIntent 发现已经存在时,忽略新请求的,继续使用已存在的。日常开发中很少使用
int FLAG_NO_CREATE
//表示 PendingIntent 只能使用一次,如果已使用过,那么 getXXX(...) 将会返回 NULL
//也就是说同类的通知只能使用一次,后续的通知单击后无法打开。
int FLAG_ONE_SHOT
//如果新请求的 PendingIntent 发现已经存在时, 如果 Intent 有字段改变了,这更新已存在的 PendingIntent
int FLAG_UPDATE_CURRENT
1,Activity 直接跳转(应用内部直接跳转)
Intent intent = new Intent(LoginActivity.this, MainActivity.class);
intent.putExtra("key","value");//传递参数
startActivity(intent);
2,通过包名,类名(应用内部+应用外部)
Intent intent = new Intent();
//通过包名,类名进行跳转
intent.setClassName("com.example.mvptest","com.example.mvptest.MainActivity");
intent.putExtra("key","value");//传递参数
startActivity(intent);
3,ComponentName跳转(应用内部+应用外部)
Intent intent = new Intent();
intent.setComponent(newComponentName("com.example.mvptest","com.example.mvptest.MainActivity"));
intent.putExtra("key","value");//传递参数
startActivity(intent);
在AndroidManifest中定义某个Activity的 intent-filter
在Intent中通过 设置 action,添加category来进行匹配
Intent intent = new Intent();
intent.setAction("com.example.widget");
intent.addCategory("android.intent.category.DEFAULT");//默认,可以不用写
intent.putExtra("key","value");//传递参数
startActivity(intent);
this.finish();
隐式启动,在启动的时候是不明确的。 如果intent-filter中还设置了 data 属性,则必须 action,category,data 完全匹配才能完成跳转
1,先通过Android studio创建一个Widget
2,小部件布局,上面介绍时,已经贴出
3,播放器用广播进行模拟,点击界面按钮,发送一个广播通知播放器播放。 播放器进行相应操作,发送一个广播模拟回调通知,接收到这个通知,更新桌面小部件的界面UI。这里切记需要判断下remoteViews是否为空,更新方式下面onReceive()中有贴出
package com.example.mvptest.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.os.Bundle;
import android.util.Log;
import android.widget.RemoteViews;
import com.example.mvptest.R;
/**
* Implementation of App Widget functionality.
*/
public class AppWidget extends AppWidgetProvider {
private static final String TAG = "AppWidget";
public static final String START_ACT_ACTION = "com.example.widget";
public static final String RECEIVER_ACTION_STATUS = "com.widget.STATUS_CHANGED";
public static final int OPEN_ACT_CODE = 111;
private static RemoteViews remoteViews;
/**
* 接收到任意广播时触发
*
* @param context
* @param intent
*/
@Override
public void onReceive(Context context, Intent intent) {
super.onReceive(context, intent);
String action = intent.getAction();
Log.e(TAG, "onReceive: " + action);
if (RECEIVER_ACTION_STATUS.equals(action)) {
if (null == remoteViews) {
remoteViews = new RemoteViews(context.getPackageName(), R.layout.app_widget);
}
if (WidgetReciver.status == 0) {
remoteViews.setTextViewText(R.id.widget_title, "播放中");
remoteViews.setImageViewResource(R.id.widget_pause, R.drawable.btn_player_play_normal);
} else {
remoteViews.setTextViewText(R.id.widget_title, "电台桌面插件");
remoteViews.setImageViewResource(R.id.widget_pause, R.drawable.btn_player_pause_normal);
}
ComponentName componentName = new ComponentName(context, AppWidget.class);
AppWidgetManager.getInstance(context).updateAppWidget(componentName, remoteViews);
}
}
/**
* 当 widget 更新时触发
* 用户首次添加时也会被调用
* 如果定义了widget的configure属性,首次添加时不会被调用
*
* @param context
* @param appWidgetManager
* @param appWidgetIds
*/
@Override
public void onUpdate(Context context, AppWidgetManager appWidgetManager, int[] appWidgetIds) {
for (int appWidgetId : appWidgetIds) {
updateAppWidget(context, appWidgetManager, appWidgetId);
}
}
/**
* 当 widget 首次添加或者大小被改变时触发
*
* @param context
* @param appWidgetManager
* @param appWidgetId
* @param newOptions
*/
@Override
public void onAppWidgetOptionsChanged(Context context, AppWidgetManager appWidgetManager, int appWidgetId, Bundle newOptions) {
super.onAppWidgetOptionsChanged(context, appWidgetManager, appWidgetId, newOptions);
Log.e(TAG, "onAppWidgetOptionsChanged: ");
}
/**
* 当第1个 widget 的实例被创建时触发。也就是说,如果用户对同一个 widget 增加了两次(两个实例),
* 那么onEnabled()只会在第一次增加widget时触发
*
* @param context
*/
@Override
public void onEnabled(Context context) {
}
/**
* 当最后1个 widget 的实例被删除时触发
*
* @param context
*/
@Override
public void onDisabled(Context context) {
}
/**
* 当 widget 被删除时
*
* @param context
* @param appWidgetIds
*/
@Override
public void onDeleted(Context context, int[] appWidgetIds) {
super.onDeleted(context, appWidgetIds);
}
/*******************************************************************************************************************/
/**
* 更新
*
* @param context
* @param appWidgetManager
* @param appWidgetId
*/
static void updateAppWidget(Context context, AppWidgetManager appWidgetManager, int appWidgetId) {
Log.e(TAG, "updateAppWidget: ");
remoteViews = new RemoteViews(context.getPackageName(), R.layout.app_widget);
remoteViews.setTextViewText(R.id.widget_title, "电台桌面插件");
openAct(context);
radioCtrl(context);
appWidgetManager.updateAppWidget(appWidgetId, remoteViews);
}
/**
* 点击标题 打开activity
* 通过PendingIntent 添加一个跳转activity
*
* @param context
*/
private static void openAct(Context context) {
Intent intent = new Intent();
intent.setAction(START_ACT_ACTION);
PendingIntent pi = PendingIntent.getActivity(context, OPEN_ACT_CODE, intent, PendingIntent.FLAG_CANCEL_CURRENT);
remoteViews.setOnClickPendingIntent(R.id.widget_title, pi);
}
/**
* 控制 播放(模拟)
* 通过PendingIntent 添加一个Broadcast
*
* @param context
*/
private static void radioCtrl(Context context) {
Intent intent = new Intent(WidgetReciver.ACTION_WIDGET_CTRL);
intent.putExtra(WidgetReciver.INTENT_EXTRAS, WidgetReciver.ACTION_WIDGET_PAUSE);//暂停 播放
remoteViews.setOnClickPendingIntent(R.id.widget_pause, PendingIntent.getBroadcast(context, 11, intent, PendingIntent.FLAG_CANCEL_CURRENT));
intent.putExtra(WidgetReciver.INTENT_EXTRAS, WidgetReciver.ACTION_WIDGET_NEXT);//下一首
remoteViews.setOnClickPendingIntent(R.id.widget_next, PendingIntent.getBroadcast(context, 12, intent, PendingIntent.FLAG_CANCEL_CURRENT));
intent.putExtra(WidgetReciver.INTENT_EXTRAS, WidgetReciver.ACTION_WIDGET_LAST);//上一首
remoteViews.setOnClickPendingIntent(R.id.widget_last, PendingIntent.getBroadcast(context, 13, intent, PendingIntent.FLAG_CANCEL_CURRENT));
}
}
添加模拟播放器的广播接收类,不要忘记在AndroidManifest中注册
package com.example.mvptest.widget;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.util.Log;
public class WidgetReciver extends BroadcastReceiver {
private static final String TAG = "WidgetReciver";
public static final String INTENT_EXTRAS = "Widget_Reciver";
public static final String ACTION_WIDGET_CTRL = "com.example.widget.CTRL";
public static final int ACTION_WIDGET_PAUSE = 0;
public static final int ACTION_WIDGET_LAST = 1;
public static final int ACTION_WIDGET_NEXT = 2;
public static int status = 1;//0_播放 1_暂停
@Override
public void onReceive(Context context, Intent intent) {
String action = intent.getAction();
Log.e(TAG, "onReceive: " + action);
if (action.equals(ACTION_WIDGET_CTRL)) {
int e = intent.getIntExtra(INTENT_EXTRAS, 0);
Log.e(TAG, "onReceive: " + e);
switch (e) {
case ACTION_WIDGET_LAST:
Log.e(TAG, "onReceive: 播放上一首");
break;
case ACTION_WIDGET_NEXT:
Log.e(TAG, "onReceive: 播放下一首");
break;
case ACTION_WIDGET_PAUSE:
Log.e(TAG, "onReceive: 暂停/开始 播放");
status = status == 0 ? 1 : 0;
context.sendBroadcast(new Intent(AppWidget.RECEIVER_ACTION_STATUS));
break;
}
}
}
}
注册广播
简单demo到此结束,后续更新复杂数据,list数据的刷新跟显式。