一、概述。
RemoteViews是一种远程View,它提供了一组基础的操作用于跨进程更新它的界面。
一般RemoteViews在android中的应用场景有两种:一种是通知栏,另一种是桌面小部件。
二、应用。
(1)、通知栏。
首先使用系统默认的样式创建一个通知,
Intent intent = new Intent(this, NotificationActivity.class); PendingIntent pi = PendingIntent.getActivity(this,0,intent,0); //待定时刻发生的Intent NotificationManager manager = (NotificationManager)getSystemService(NOTIFICATION_SERVICE); Notification notification = new NotificationCompat.Builder(this) .setContentTitle("This is context title") // 标题 .setContentText("This is context text") // 内容 .setWhen(System.currentTimeMillis()) //创建通知的时间 .setSmallIcon(R.mipmap.ic_launcher) //状态栏图标 .setLargeIcon(BitmapFactory.decodeResource(getResources(),R.mipmap.ic_launcher)) //通知栏的图标 .setContentIntent(pi) //单击跳转 .setAutoCancel(true) // 点击后清除本身 // .setDefaults(NotificationCompat.DEFAULT_ALL) // .setStyle(new NotificationCompat.BigPictureStyle().bigPicture(BitmapFactory.decodeResource(getResources(), R.drawable.zz))) // .setStyle(new NotificationCompat.BigTextStyle().bigText("zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz"))
.build();
manager.notify( 1 ,notification) ; 上述代码会创建一个系统默认的通知栏,单击后会跳转至NotificationActivity,并且会清除本身。接下来,我们需要新建一个布局文件,然后使用RemoteViews加载这个布局文件,并且将那Notification的RemoteViews设置为新建的RemoteViews,即可改变通知样式。
布局文件remote_view.xml
xml version="1.0" encoding="utf-8"?>创建样式为RemoteView的通知:xmlns:android="http://schemas.android.com/apk/res/android" android:orientation="horizontal" android:layout_width="match_parent" android:layout_height="match_parent"> android:id="@+id/image" android:layout_width="100dp" android:layout_height="100dp" android:src="@drawable/back" /> android:id="@+id/content_text" android:layout_width="match_parent" android:textSize="20dp" android:layout_height="100dp" android:text="This is a aircraft" />
Intent intent = new Intent(this, NotificationActivity.class); PendingIntent pi = PendingIntent.getActivity(this,0,intent,0); //待定时刻发生的Intent NotificationManager manager = (NotificationManager)getSystemService(NOTIFICATION_SERVICE); RemoteViews remoteViews = new RemoteViews(getPackageName(),R.layout.remote_views); remoteViews.setTextViewText(R.id.content_text,"This is a airCraft, la la la !"); //设置TextView内容 remoteViews.setImageViewResource(R.id.image,R.drawable.back); // 设置ImageView 图片 Notification notification = new NotificationCompat.Builder(this) .setContentTitle("This is context title") // 标题 .setCustomContentView(remoteViews) .setWhen(System.currentTimeMillis()) //创建通知的时间 .setSmallIcon(R.mipmap.ic_launcher) //状态栏图标 .setContentIntent(pi) //单击跳转 .setAutoCancel(true) // 点击后取消通知 .build(); manager.notify(2,notification);上述需要注意的是RemoteViews的构造函数,参数1为包名,参数2为布局资源Id。
RemoteView无法像Activity那样直接更新View,为了跨进程更新界面,因此它提供了许多set方法。
setTextViewText中第一个参数为TextView的id,第二个参数为内容。
setImageViewResource中第一个参数也为ImageView的id,第二个参数即为要设置图片资源的id.
结果如下,底下为系统默认样式,
上边为RemoteViews样式。
(2)桌面小部件。
AppWidgetProvider是用于实现桌面小部件的类,本质是一个广播,继承自BroadcastReceiver。
本例实现一个桌面计数的小部件。
首先需要在layout下新建一个XML文件,命名widget.xml,内容自定义。
由于我们只需要计数,所以一个TextView即可。
xml version="1.0" encoding="utf-8"?>xmlns:android="http://schemas.android.com/apk/res/android" android:orientation="vertical" android:layout_width="match_parent" android:layout_height="match_parent"> android:id="@+id/high_low_wather" android:layout_gravity="center" android:layout_width="match_parent" android:layout_height="match_parent" />
其次需要定义小部件的配置信息。
新建文件夹res/xml,在此文件夹下新建weather.xml,添加内容如下:
xml version="1.0" encoding="utf-8"?>initialLayout 为初始化的布局。xmlns:android="http://schemas.android.com/apk/res/android" android:minWidth="294dp" android:minHeight="72dp" android:updatePeriodMillis="86400000" android:initialLayout="@layout/widget" android:resizeMode="horizontal|vertical">
minHeight和minWidth定义小工具的最小尺寸,
updatePeriodMillis自动更新周期,单位为毫秒,每隔一个周期,小工具自动更新就会触发。
新建一个服务用来每隔一秒发送一个广播(action为con.yang.widget.UPDATE_ALL)并且给NumProvider提供的数据加1。
public class NumUpdateService extends Service { private static final String TAG="WeatherUpdateService"; // 更新 widget 的广播对应的action private final String ACTION_UPDATE_ALL = "com.yang.widget.UPDATE_ALL"; // 周期性更新 widget 的周期 private static final int UPDATE_TIME = 1000; // 周期性更新 widget 的线程 private UpdateThread mUpdateThread; private Context mContext; private int temp = 0; @Override public void onCreate() { // 创建并开启线程UpdateThread mUpdateThread = new UpdateThread(); mUpdateThread.start(); mContext = this.getApplicationContext(); super.onCreate(); } @Override public void onDestroy(){ // 中断线程,即结束线程。 if (mUpdateThread != null) { mUpdateThread.interrupt(); } super.onDestroy(); } @Override public IBinder onBind(Intent intent) { return null; } /* * 服务开始时,即调用startService()时,onStartCommand()被执行。 */ @Override public int onStartCommand(Intent intent, int flags, int startId) { Log.d(TAG, "onStartCommand"); super.onStartCommand(intent, flags, startId); return START_STICKY; } private class UpdateThread extends Thread { @Override public void run() { super.run(); try { while (true) { Intent updateIntent=new Intent(ACTION_UPDATE_ALL); updateIntent.putExtra("temp",temp); temp++; // updateIntent.putExtra("high_low",32); mContext.sendBroadcast(updateIntent); Thread.sleep(UPDATE_TIME); } } catch (InterruptedException e) { // 将 InterruptedException 定义在while循环之外,意味着抛出 InterruptedException 异常时,终止线程。 e.printStackTrace(); } } } }
定义小部件的实现类NumProvider
public class NumProvider extends AppWidgetProvider { private static final String TAG = "WeatherProvider"; private boolean DEBUG = false; // 启动ExampleAppWidgetService服务对应的action private final Intent EXAMPLE_SERVICE_INTENT = new Intent("android.appwidget.action.EXAMPLE_APP_WIDGET_SERVICE"); // 更新 widget 的广播对应的action private final String ACTION_UPDATE_ALL = "com.yang.widget.UPDATE_ALL"; // 保存 widget 的id的HashSet,每新建一个 widget 都会为该 widget 分配一个 id。 private int appId; private AppWidgetManager mAppWidgetManager; private int mTemp; @Override public void onEnabled(Context context) { Log.d(TAG, "onEnabled"); Intent _intent = new Intent(context, NumUpdateService.class); context.startService(_intent); super.onEnabled(context); } @Override public void onDisabled(Context context) { Log.d(TAG, "onDisabled"); Intent _intent = new Intent(context, NumUpdateService.class); context.stopService(_intent); super.onDisabled(context); } @Override public void onUpdate(Context context, AppWidgetManager appWidgetManager, int[] appWidgetIds) { super.onUpdate(context, appWidgetManager, appWidgetIds); appId = appWidgetIds[0]; } @RequiresApi(api = Build.VERSION_CODES.JELLY_BEAN) @Override public void onReceive(Context context, Intent intent) { final String action = intent.getAction(); ComponentName thisWidget = new ComponentName(context,NumProvider.class);//定义容器 int temp = intent.getIntExtra("temp",0); AppWidgetManager appWidgetManager = AppWidgetManager .getInstance(context); if (ACTION_UPDATE_ALL.equals(action)) { // “更新”广播 Log.d(TAG, "update"); mTemp = temp; RemoteViews remoteView = new RemoteViews(context.getPackageName(), R.layout.widget); remoteView.setTextViewText(R.id.high_low_wather,String.valueOf(mTemp)); remoteView.setTextViewTextSize(R.id.high_low_wather,TypedValue.COMPLEX_UNIT_DIP,150); appWidgetManager.updateAppWidget(thisWidget, remoteView); } super.onReceive(context, intent); } }在配置文件AndroidManifest.xml声明小部件:
android:name=".NumProvider" >
android:name="android.appwidget.action.APPWIDGET_UPDATE" />
android:name="com.yang.widget.UPDATE_ALL"/>
android:name="android.appwidget.provider"
android:resource="@xml/weather" />
当广播到来时,AppWidgetProvider会根据广播的action通过onReceive方法进行自动分发广播。
通过新建一个RemoteViews,通过set方法设置TextView内容以及字体大小,然后通过AppWidgetManager的updateAppWidget方法更新界面,方法中第一个参数为容器,第二个参数为RemoteViews的实例。
结果如图所示
AppWidgetProvider包含如下几个方法,
onEnable:
当窗口小部件第一次添加到桌面时调用该方法,可添加多次但只在第一次调用。
onUpdate:
小部件被添加时或者每次小部件更新时都会调用一次该方法,小部件的更新时机由updatePeriodMillis来指定,每个周期小部件都会自动更新一次。
onDeleted:
每删除一次桌面小部件就调用一次。
onDisabled:
当最后一个该类型的桌面小部件被删除时调用该方法,注意是最后一个。
onReceive:
广播的内置方法,用来分发具体事件给其它方法。
这两个例子在我的github上,需要的可以直接下载
通知: https://github.com/wangchenyangzz/NotificationTest
小部件: https://github.com/wangchenyangzz/AppWidget
RemoteViews的应用就先说到这,下篇会分析RemoteViews的内部机制及原理。