RemoteView从名字看即为远程的View,RemoteViews表示一个View结构,它可以在其他进程中显示,为了能够更新它的界面,RemoteViews提供了一组基础的操作用于跨进程更新它的界面。
RemoteViews在Android的使用场景有两种:通知栏和桌面小控件。通知栏是通过NotificationManager的notify方法实现,它除了默认效果,还可以自定义布局。桌面小部件则是通过AppWidgetProvider来实现,AppWidget本质是一个广播。这两个都无法像在Activity中那样直接更新小部件。因为两者都是运行在系统的SystemServer进程。
创建一个Notification对象,并进行设置
Notification notification = new NotificationCompat.Builder(content) //创建
.setContentTitle("冰炭不投day") //设置标题
.setContentText("啦啦啦啦啦") //设置正文
.setWhen(System.currentTimeMillis()) //指定被创建时间
.setSmallIcon(R.drawable.ic_launcher_foreground) //设置通知的小图标
.setLargeIcon(BitmapFactory.decodeResource(getResources(),R.drawable.icon)) //设置大图标
.build();
NotificationManager manager = (NotificationManager)getSystemService(NOTIFICATION_SERVICE);
manager.notify(1,notification); //启动该通知
Intent intent = new Intent(this,NotificationActiviy.class);
PendingIntent pi = PendingIntent.getActivity(this,0,intent,0);
Notification notification = new NotificationCompat.Builder(this) //创建
.setContentTitle("冰炭不投day") //设置标题
.setContentText("啦啦啦啦啦") //设置正文
.setWhen(System.currentTimeMillis()) //指定被创建时间
.setSmallIcon(R.drawable.ic_launcher_foreground) //设置通知的小图标
.setLargeIcon(BitmapFactory.decodeResource(getResources(),R.drawable.icon)) //设置大图标
.setContentIntent(pi)
.setAutoCancel(true) //点击图标后,通知消失
.build();
NotificationManager manager = (NotificationManager)getSystemService(NOTIFICATION_SERVICE);
manager.notify(1,notification);
.setSound(Uri.fromFile(new File("/system/media/audio/ringtones/luna.agg"))) //通知时播放音频
.setVibrate(new long[]{0,1000,1000,1000})//通知使手机震动 数组中的参赛分别表示静止和震动的时长 需要添加权限
.setLights(Color.GREEN,1000,1000) //通知使led灯亮 三个参数分别表示led等的颜色,led等亮起来的时长 led灯灭起来的时常
.setDefaults(NotificationCompat.DEFAULT_ALL ) //使用系统默认效果
.setStyle(new NotificationCompat.BigTextStyle().bigText(",,,,,,,,,")) //设置大段文字
.setStyle(new NotificationCompat.BigPictureStyle().bigPicture(BitmapFactory.decodeResource(getResources(),R.drawable.big_image))) //设置大图片
在res/layout下新建一个XML文件,命名为widget
内容如下:
桌面小部件的布局不同于普通的布局,它是基于RemoteViews它不支持所有类型的布局或视图窗口小部件。
RemoteViews对象(以及App Widget)可以支持以下布局类:
FrameLayout
LinearLayout
RelativeLayout
GridLayout
__
AnalogClock
Button
Chronometer
ImageButton
ImageView
ProgressBar
TextView
ViewFlipper
ListView
GridView
StackView
AdapterViewFlipper
在res/xml下新建appwidget_provider_info.xml(名称随意),添加如下内容
以下是appwidget-provider属性摘要:
更多属性可查看官方文档,链接下文章最后。
public class MyWidgetProvider extends AppWidgetProvider {
public static final String CLICK_ACTION = "com.heshucheng.remoteviews.click_action";
private static final String TAG = "MyWidgetProvider";
public MyWidgetProvider() {
super();
}
@Override
public void onReceive(final Context context, Intent intent) {
super.onReceive(context, intent);
Log.d(TAG, "onReceive: " + intent.getAction());
//这里判断是自己的action,做自己的事情,比如小部件被点击了要干什么
if (intent.getAction().equals(CLICK_ACTION)) {
Toast.makeText(context, "click it", Toast.LENGTH_SHORT).show();
new Thread(new Runnable() {
@Override
public void run() {
Bitmap srcbBitmap = BitmapFactory.decodeResource(context.getResources(), R.drawable.icon);
AppWidgetManager appWidgetManager = AppWidgetManager.getInstance(context);
for (int i = 0; i < 37; i++){
float degree = (i*10)%360;
RemoteViews remoteViews = new RemoteViews(context.getPackageName(),R.layout.widget);
remoteViews.setImageViewBitmap(R.id.image, rotateBitmap(context,srcbBitmap,degree));
Intent intentClick = new Intent();
intentClick.setAction(CLICK_ACTION);
PendingIntent pendingIntent = PendingIntent.getBroadcast(context,0,intentClick,0);
remoteViews.setOnClickPendingIntent(R.id.image,pendingIntent);
appWidgetManager.updateAppWidget(new ComponentName(context,MyWidgetProvider.class),remoteViews);
SystemClock.sleep(30);
}
}
}).start();
}
}
//每次更新都会调用
@Override
public void onUpdate(Context context, AppWidgetManager appWidgetManager, int[] appWidgetIds) {
super.onUpdate(context, appWidgetManager, appWidgetIds);
Log.i(TAG, "onUpdate");
RemoteViews remoteViews = new RemoteViews(context.getPackageName(), R.layout.widget);
Intent intentClick = new Intent(CLICK_ACTION);
PendingIntent pendingIntent = PendingIntent.getBroadcast(context, 0, intentClick, 0);
remoteViews.setOnClickPendingIntent(R.id.image, pendingIntent);
appWidgetManager.updateAppWidget(new ComponentName(context, MyWidgetProvider.class), remoteViews);
}
//动画 旋转一周
private Bitmap rotateBitmap(Context context,Bitmap srcbitmap,float degree ) {
Matrix matrix = new Matrix();
matrix.reset();
matrix.setRotate(degree);
Bitmap temBitmap = Bitmap.createBitmap(srcbitmap,0,0,srcbitmap.getWidth(),srcbitmap.getHeight(),matrix,true);
return temBitmap;
}
}
AppWidgetProvider类广播接收器作为一个方便的类来处理应用的Widget广播延伸。AppWidgetProvider仅接收与App Widget相关的事件广播,例如更新,删除,启用和禁用App Widget时。当这些广播事件发生时,AppWidgetProvider会收到以下方法调用:
上面代码中的两个Action,第一个是用来识别小部件的单击行为,第二个是作为小部件的标识而必须存在的。
PendingIntent表示一种处于Pending状态的Intent,pending表示的是即将发生的意思,它是在将来的某个不确定的时刻放生,而Intent是立刻发生。
PendingIntent支持三种待定意图:启动Activity(getActivity),启动Service(getService),发送广播(getBroadcast)。
如图中所示,这三个方法的参数都是一样的,主要理解的是第二个参数requstCode和第四个参数flags,code代表的是发送码,多数情况下为0,而且code会影响到flag,flag常见的有几种我们下面会说,其实最主要是理解匹配规则。
PendingIntent的匹配规则为:如果两个PendingIntent他们内部的Intent相同并且requstCode也相同的话,那么PendingIntent就是相同的,code比较好理解,那什么情况下Intent相同呢,Intent的匹配规则是:如果两个Intent的ComponentName的匹配过程,只要Intent之间的ComponentName和intent-filter相同,那么这两个intent就相同,需要注意的是Extras不参与匹配过程,只要intent之间的name和intent-filter相同就行,我们再来说下flags的参数含义
FLAG_ONE_SHOT
当前描述的PendingIntent只能被使用一次,然后他就会被cancel,如果后续还有相同的PendingIntent,那么他的send方法就会失败,对于通知栏的消息来说,如果采用此标记位,那么同类的通知只能使用一次,后续将无法打开
FLAG_NO_CREATE
当前描述的PendingIntent不会主动去创建,如果当前PendingIntent之前不存在,那么getActivity等方法都会直接返回null,即获取PendingIntent失败,这个标记位很少见,他无法单独使用,因此在日常开发当中,并没有太多的意义,这里就不过多的介绍了
FLAG_CANCEL_CURRENT
当前描述的PendingIntent如果已经存在,那么就会被cancel,然后系统创建一个新的PendingIntent,对于通知栏来说,那些被cancel的消息将无法被打开
FLAG_UPDATE_CURRENT
当前描述的PendingIntent如果已经存在的话,那么他们就会被更新,他们的intent中的extras会被替换成新的
从上面的分析看还是不太好理解这四个标记位,下面结合实际的项目来,这里分析两种情况,如下代码,如果notify的第一个参数id是常量,那么多次通知就只能弹出一个通知,后续会把前面的替换掉,如果每次不一样,就会多弹出几个通知
如果notify方法的id是常量,那么不管PendingIntent是否匹配,后面的通知会替换前面的通知,这个很好理解
如果notify方法的id每次不同,那么当PendingIntent不匹配时,这里的匹配是指PendingIntent中Intent相同切requstCode相同,在这种情况下不管采用了何种标记位,这些通知之间不互相干扰。如果PendingIntent处于匹配状态,这个时候要分情况讨论,如果采用FLAG_ONE_SHOT标记位,那么后续通知中,PendingIntent会和第一条通知保持一致,包括其中的Extras,单击任何一条通知,剩余的都无法打开当所有的通知被清除后,会重复这个过程,如果采用FLAG_CANCEL_CURRENT标记位,那么只有最新的通知可以打开,之前弹出的所有通知均无法打开,如果采用FLAG_UPDATE_CURRENT标记位。那么之前弹出的通知PendingIntent会被更新,最终他们和最新的一条通知保持一致,包括其中的Extras,那么这些通知都可以被打开
《第一行代码》
《android艺术开发探索》
官方文档