一、RemoteViews的應用
RemoteViews在實際開發中,主要用在通知欄和桌面小部件的開發過程中。通知欄主要是通過NotificationManager的notify方法來實作的,圖除了預設效果外,還可以另外定義布局。桌面小部件是通過AppWidgetProvider來實作的,AppWidgetProvider本質上是一個廣播。通知欄和桌面小部件的開發過程中都會用到RemoteViews,他們在更新界面時無法像在activity中那樣直接去更新view,這是因為二者的界面都運作在其他程序中,确切來說是在系統的SyestemServer程序。為了跨程序更新界面,RemoteViews提供了一系列的set方法并且這些方法隻是view全部方法的子集,另外RemoteViews支援的view類型是有限的。
1.RemoteViews在通知欄中的應用
Notification notification = new Notification();
notification.icon = R.mipmap.ic_launcher_round;
notification.tickerText = "hello world";
notification.when = System.currentTimeMillis();
notification.flags = Notification.FLAG_AUTO_CANCEL;
Intent intent = new Intent(this, LoginActivity.class);
PendingIntent pendingIntent = PendingIntent.getActivity(this,
, intent, PendingIntent.FLAG_UPDATE_CURRENT);
System.out.println(pendingIntent);
RemoteViews remoteViews = new RemoteViews(getPackageName(), R.layout.remote);
remoteViews.setTextViewText(R.id.msg, "chapter_5: ");
remoteViews.setImageViewResource(R.id.icon, R.mipmap.ic_launcher);
PendingIntent openActivity2PendingIntent = PendingIntent.getActivity(this,
, new Intent(this, LoginActivity.class), PendingIntent.FLAG_UPDATE_CURRENT);
remoteViews.setOnClickPendingIntent(R.id.msg, openActivity2PendingIntent);
notification.contentView = remoteViews;
notification.contentIntent = pendingIntent;
NotificationManager manager = (NotificationManager)getSystemService(Context.NOTIFICATION_SERVICE);
manager.notify(, notification);
RemoteViews的使用很簡單,重要提供相應的包名和布局檔案的id即可以建立一個RemoteViews對象,但是如何更新RemoteViews呢?這一點和更新view有很大不同,更新RemoteViews時,無法直接通路裡面的view,必須通過RemoteViews提供的一系列方法來更新view:
- remoteViews.setTextViewText(R.id.msg, “chapter_5: “);//更新文字
- remoteViews.setImageViewResource(R.id.icon, R.mipmap.ic_launcher);//更新圖檔
- remoteViews.setOnClickPendingIntent(R.id.msg, openActivity2PendingIntent);設定點選事件。
2.RemoteViews在桌面小部件上的應用
桌面小控件的開發步驟:
1、定義小部件界面:
在res/layout下建立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/imageView1"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:src="@drawable/icon1" />
</LinearLayout>
2.定義小部件配置資訊
在res/xml下建立xml檔案
<?xml version="1.0" encoding="utf-8"?>
<appwidget-provider xmlns:android="http://schemas.android.com/apk/res/android"
android:initialLayout="@layout/widget"//初始化的布局
android:minHeight="84dp"
android:minWidth="84dp"
android:updatePeriodMillis="86400000" >//自動更新周期,毫秒為機關
</appwidget-provider>
3.定義小部件的實作類
public class MyAppWidgetProvider extends AppWidgetProvider {
public static final String TAG = "MyAppWidgetProvider";
public static final String CLICK_ACTION = "com.ryg.chapter_5.action.CLICK";
public MyAppWidgetProvider() {
super();
}
@Override
public void onReceive(final Context context, Intent intent) {
super.onReceive(context, intent);
Log.i(TAG, "onReceive : action = " + intent.getAction());
// 這裡判斷是自己的action,做自己的事情,比如小工具被點選了要幹啥,這裡是做一個動畫效果
if (intent.getAction().equals(CLICK_ACTION)) {
Toast.makeText(context, "clicked it", Toast.LENGTH_SHORT).show();
new Thread(new Runnable() {
@Override
public void run() {
Bitmap srcbBitmap = BitmapFactory.decodeResource(
context.getResources(), R.drawable.icon1);
AppWidgetManager appWidgetManager = AppWidgetManager.getInstance(context);
for (int i = ; i < ; i++) {
float degree = (i * ) % ;
RemoteViews remoteViews = new RemoteViews(context
.getPackageName(), R.layout.widget);
remoteViews.setImageViewBitmap(R.id.imageView1,
rotateBitmap(context, srcbBitmap, degree));
Intent intentClick = new Intent();
intentClick.setAction(CLICK_ACTION);
PendingIntent pendingIntent = PendingIntent
.getBroadcast(context, , intentClick, );
remoteViews.setOnClickPendingIntent(R.id.imageView1, pendingIntent);
appWidgetManager.updateAppWidget(new ComponentName(
context, MyAppWidgetProvider.class),remoteViews);
SystemClock.sleep();
}
}
}).start();
}
}
/**
* 每次視窗小部件被點選更新都調用一次該方法
*/
@Override
public void onUpdate(Context context, AppWidgetManager appWidgetManager,
int[] appWidgetIds) {
super.onUpdate(context, appWidgetManager, appWidgetIds);
Log.i(TAG, "onUpdate");
final int counter = appWidgetIds.length;
Log.i(TAG, "counter = " + counter);
for (int i = ; i < counter; i++) {
int appWidgetId = appWidgetIds[i];
onWidgetUpdate(context, appWidgetManager, appWidgetId);
}
}
/**
* 視窗小部件更新
*
* @param context
* @param appWidgeManger
* @param appWidgetId
*/
private void onWidgetUpdate(Context context,
AppWidgetManager appWidgeManger, int appWidgetId) {
Log.i(TAG, "appWidgetId = " + appWidgetId);
RemoteViews remoteViews = new RemoteViews(context.getPackageName(),
R.layout.widget);
// "視窗小部件"點選事件發送的Intent廣播
Intent intentClick = new Intent();
intentClick.setAction(CLICK_ACTION);
PendingIntent pendingIntent = PendingIntent.getBroadcast(context, ,
intentClick, );
remoteViews.setOnClickPendingIntent(R.id.imageView1, pendingIntent);
appWidgeManger.updateAppWidget(appWidgetId, remoteViews);
}
private Bitmap rotateBitmap(Context context, Bitmap srcbBitmap, float degree) {
Matrix matrix = new Matrix();
matrix.reset();
matrix.setRotate(degree);
Bitmap tmpBitmap = Bitmap.createBitmap(srcbBitmap, , ,
srcbBitmap.getWidth(), srcbBitmap.getHeight(), matrix, true);
return tmpBitmap;
}
}
4.在AndroidManifest.xml中聲明小部件
<receiver android:name=".MyAppWidgetProvider" >
<meta-data
android:name="android.appwidget.provider"
android:resource="@xml/appwidget_provider_info" >
</meta-data>
<intent-filter>
<action android:name="com.ryg.chapter_5.action.CLICK" />//小部件的單擊行為
<action android:name="android.appwidget.action.APPWIDGET_UPDATE" />//系統規範,必須有
</intent-filter>
</receiver>
AppWidgetProvider 中幾個方法的調用時機
@Override
public void onEnabled(Context context) {
super.onEnabled(context);
//當該視窗小部件第一次添加到桌面時調用該方法,
//可添加多次但隻在地題詞調用
}
@Override
public void onUpdate(Context context, AppWidgetManager appWidgetManager, int[] appWidgetIds) {
super.onUpdate(context, appWidgetManager, appWidgetIds);
//小部件被添加時或者每次小部件更新時都會調用一個該方法
//小部件更新時機由updatePeriodMillis來指定
//每個周期小部件都會自動更新一次
}
@Override
public void onDeleted(Context context, int[] appWidgetIds) {
super.onDeleted(context, appWidgetIds);
//每删除一次桌面小部件就調用一次
}
@Override
public void onDisabled(Context context) {
super.onDisabled(context);
//當最後一個該類型的桌面小部件被删除時調用該方法,注意是最後一個。
}
@Override
public void onReceive(Context context, Intent intent) {
super.onReceive(context, intent);
//這是廣播的内置方法,用于分發具體的事件給其他方法
}
3. PendingIntent概述
顧名思義:PendingIntent表示一種處于Pending狀态的意圖,而Pending狀态表示的是一種待定,等待,即将發生的意思,就是說接下來有一個意圖将在某個待定的事件發生。可以看出PendingIntent和Intent的差別在于,PendingIntent是在将來某個不确定的時刻發生,而Intent是立即發生。PendingIntent典型的使用場景是給RemoteViews添加點選事件,因為RemoteViews運作在遠端程序中,是以RemoteViews不同于普通的view,是以無法直接向view那樣通過setOnClickListener方法來設定單擊事件。要給RemoteViews設定單擊事件,就必須使用PendingIntent,PendingIntent通過send和cancel放來發送和取消特定的待定的Intent。
PendingIntent支援三種特定意圖:啟動Activity,啟動Service和發送廣播。
- getActivity(Context context,int requestCode,Intent intent,int flags)//獲得一個PendingIntent,該待定意圖發送時,相當于context.startActivity(Intent);
- getService(Context context,int requestCode,Intent intent,int flags);//獲得一個PendingIntent,該待定意圖發送時,相當于context.startService(Intent);
- getBroadcast(Context context,int requestCode,Intent intent,int flags));
- //獲得一個PendingIntent,該待定意圖發送時,相當于context.sendBroadcast(Intent);
flags 的類型:
- FLAG_ONE_SHOT: PendingIntent隻能使用一次
- FLAG_NO_CREATE:不怎麼用
- FLAG_CANCEL_CURRENT:PendingIntent如果已經存在,那麼他們都會被cancel,然後系統會建立一個新的PendingIntent
- FLAG_UPDATE_CURRENT:PendingIntent如果已經存在,那麼會被更新,即它們intent中的Extras會被替換成最新的。
二、RemoteViews的内部機制
RemoteViews的隻主要作用是在其他程序中顯示并更新View界面,其構造方法中有兩個參數,一個是目前包名,一個是布局檔案
支援的Layout:
- FrameLayout
- LinearLayout
- RelativeLayout
- GridLayout
支援的View:
- AnalogClock,Botton,Chronometer,ImageBottom,ImageView,ProgressBar
- TextView,ViewFlipper,ListView,GridView,StackView,AdapterViewFlipper,ViewStub
三、RemoteViews的意義
public void onButtonClick(View v) {
RemoteViews remoteViews = new RemoteViews(getPackageName(), R.layout.layout_simulated_notification);
remoteViews.setTextViewText(R.id.msg, "msg from process:" + Process.myPid());
remoteViews.setImageViewResource(R.id.icon, R.drawable.icon1);
PendingIntent pendingIntent = PendingIntent.getActivity(this,
, new Intent(this, DemoActivity_1.class), PendingIntent.FLAG_UPDATE_CURRENT);
PendingIntent openActivity2PendingIntent = PendingIntent.getActivity(
this, , new Intent(this, DemoActivity_2.class), PendingIntent.FLAG_UPDATE_CURRENT);
remoteViews.setOnClickPendingIntent(R.id.item_holder, pendingIntent);
remoteViews.setOnClickPendingIntent(R.id.open_activity2, openActivity2PendingIntent);
Intent intent = new Intent(MyConstants.REMOTE_ACTION);
intent.putExtra(MyConstants.EXTRA_REMOTE_VIEWS, remoteViews);
sendBroadcast(intent);
}
public class MainActivity extends Activity {
private static final String TAG = "MainActivity";
private LinearLayout mRemoteViewsContent;
private BroadcastReceiver mRemoteViewsReceiver = new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
RemoteViews remoteViews = intent
.getParcelableExtra(MyConstants.EXTRA_REMOTE_VIEWS);
if (remoteViews != null) {
updateUI(remoteViews);
}
}
};
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
initView();
}
private void initView() {
mRemoteViewsContent = (LinearLayout) findViewById(R.id.remote_views_content);
IntentFilter filter = new IntentFilter(MyConstants.REMOTE_ACTION);
registerReceiver(mRemoteViewsReceiver, filter);
}
private void updateUI(RemoteViews remoteViews) {
// View view = remoteViews.apply(this, mRemoteViewsContent);
int layoutId = getResources().getIdentifier("layout_simulated_notification", "layout", getPackageName());
View view = getLayoutInflater().inflate(layoutId, mRemoteViewsContent, false);
remoteViews.reapply(this, view);
mRemoteViewsContent.addView(view);
}