AppWidget不知道大家使用這個多不多,這個在手機上也叫做掛件,掛件也就是放在桌面方便用戶進行使用的,從android1.6開始掛件支持一些簡單的lauout和view,到了android4.0之後谷歌在掛件上也是加上了更為豐富的view支持,下面我們就從頭開始來介紹一下這些掛件吧。
如何添加一個簡單的AppWidget掛件
添加一個掛件很簡單,分為四部,只要按照這四部來弄就很容易添加上一個掛件:
(1)添加AppWidgetProviderInfo信息,這個信息是一個以xml文件形式出現的,這個文件是描述的是這個掛件的屬性信息,比如大小和布局文件等等。那麼這個xml文件定義在哪裡呢?它就定義在res目錄在xml目錄中,看下圖
看上面的圖片中在xml目錄下創建一個itchq_info.xml文件,這個文件裡面就是描述掛件的屬性,來看看這個裡面的代碼:
復制代碼
<?xml version="1.0" encoding="utf-8"?>
<appwidget-provider
xmlns:android="http://schemas.android.com/apk/res/android"
android:minWidth="70dp"
android:minHeight="120dp"
android:initialLayout="@layout/activity_main"
>
</appwidget-provider>
復制代碼
上面的都是基本的屬性,android:minWidth和android:minHeight這個兩個分別是表示掛件的寬和高,android:initialLayout這個就是設置這個掛件的布局文件,這個布局文件就是在layout下的。
(2)添加布局信息,從上面中已經看到掛件的布局文件是Activity_main,看一下這個布局文件,這一步估計就很簡單的,
復制代碼
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:paddingBottom="@dimen/activity_vertical_margin"
android:paddingLeft="@dimen/activity_horizontal_margin"
android:paddingRight="@dimen/activity_horizontal_margin"
android:paddingTop="@dimen/activity_vertical_margin"
tools:context=".MainActivity" >
<TextView
android:id="@+id/txt"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/hello_world"
android:layout_centerHorizontal="true"
/>
<Button
android:layout_below="@id/txt"
android:id="@+id/btn"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="button"
android:layout_centerHorizontal="true"
/>
</RelativeLayout>
復制代碼
上面的布局很簡單,就是顯示一個簡單的TextView和Button,這個就不多做介紹了。
(3)創建一個類繼承於AppWidgetProvider,這個類類似於我們的Activty類,定義Appwidget的生命周期,我們來看看系統中AppWidgetProvider是一個啥樣的類
從上面的圖片我們就可以看出這個AppWidgetProvider是繼承於廣播的類,所以這個AppwidgetProvider就是一個廣播,當是它又有自己的生命周期函數,來看看這個我們定義這個類的代碼:
復制代碼
package com.itchq.appwidgetactivity;
import android.appwidget.AppWidgetManager;
import android.appwidget.AppWidgetProvider;
import android.content.Context;
import android.content.Intent;
public class itchqAppWidget extends AppWidgetProvider{
@Override
public void onReceive(Context context, Intent intent) {
// TODO Auto-generated method stub
super.onReceive(context, intent);
}
@Override
public void onUpdate(Context context, AppWidgetManager appWidgetManager,
int[] appWidgetIds) {
// TODO Auto-generated method stub
super.onUpdate(context, appWidgetManager, appWidgetIds);
}
@Override
public void onDeleted(Context context, int[] appWidgetIds) {
// TODO Auto-generated method stub
super.onDeleted(context, appWidgetIds);
}
@Override
public void onEnabled(Context context) {
// TODO Auto-generated method stub
super.onEnabled(context);
}
@Override
public void onDisabled(Context context) {
// TODO Auto-generated method stub
super.onDisabled(context);
}
}
復制代碼
上面這五個方法就是AppWidgetProvider經常使用到的一些函數,它們分別的意義是:
onReceive(Context context, Intent intent):
這個大家都知道是接受廣播的方法,因為我們的AppWidgetProvider就是一個廣播類,每一次對掛件的添加和刪除這個方法都會接受到一個廣播,都會跑一次這個方法
onEnabled(Context context)
這個是剛開始添加的掛件的時候跑的方法,注意在android機制中同一個掛件可以添加多次,這個只有第一次添加該掛件時才會跑的方法,
onUpdate(Context context, AppWidgetManager appWidgetManager,int[] appWidgetIds)
根據AppWidgetProviderInfo 中updatePeriodMillis 屬性描述的時間間隔來調用此方法對 Widget 進行更新和每一次添加掛件時都會調用這個方法,注意這個方法和onEnabled()區別是每一次添加掛件都會調用,而onEnabled()只有第一次添加掛件的時候才會調用,還有一個是關於android:updatePeriodMillis這個屬性好像沒有作用,就算你設置了1分鐘更新一次沒過一分鐘也不會取調用這個方法也就是沒有更新(這個好像一直都是這樣的,不知道是為什麼)。
onDeleted(Context context, int[] appWidgetIds)
這個方法是每刪除一次掛件就會調用,注意前面有說過同一個掛件可以添加多次,所以當有多個掛件在桌面上時每刪除一個掛件就就會調用上面的方法。
onDisabled(Context context)
這個是刪除桌面上最後一個掛件的時候才會調用,注意不要和onDeleted()方法搞混淆了,onDisabled()表示的是一個桌面上添加了多個相同的掛件當我們刪除完最後一個時就會調用這個方法。
看上面的log,第一次添加掛件時先是跑了onEnabled()方法之後就接受到android.appwidget.action.APPWIDGET_ENABLED的廣播,之後又調用了opUpdate()方法以及接受到android.appwidget.action.APPWIDGET_UPDATE的廣播,這些廣播都是系統發的,我們再看下面的log,在添加第二次相同的掛件的時候我們發現直接就調用onUpdate()以及接受到android.appwidget.action.APPWIDGET_UPDATE的廣播,沒有調用了onEnabled()方法了,這個就是因為該掛件不是第一次添加所以就不會再調用onEnabled()方法了。
接下來我們看看刪除的時候,在第一次刪除時調用了onDeleted()方法以及接受到android.appwidget.action.APPWIDGET_DELETED的廣播,但是沒有調用onDisabled()這個方法,因為該掛件添加了兩次,我們在刪除第一個的時候還有一個在桌面上,當我們再把最後一個給刪除的時候就發現先調用onDeleted()方法之後就調用onDisabled()方法,
上面每一次調用方法之後都會接受相應的廣播,這個就是android系統發來的。
(4)最後一步是在AndroidManifest.xml中聲明,AppWidgetProvider本身是一個廣播,那麼既然是廣播我們就需要在AndroidManifest.xm進行注冊,來看看這個是如何注冊的:
復制代碼
<receiver
android:name="com.itchq.appwidgetactivity.itchqAppWidget"
>
<intent-filter>
<action android:name="android.appwidget.action.APPWIDGET_UPDATE"/>
</intent-filter>
<meta-data android:name="android.appwidget.provider" android:resource="@xml/itchq_info"/>
</receiver>
復制代碼
看上面的代碼,這個廣播要記得需要靜態的注冊android.appwidget.action.APPWIDGET_UPDATE廣播,如果你不添加這個就無法在掛件欄裡面看到這個掛件,也就無法添加新的掛件,在meta-data標簽裡面android:name是固定的,來看看android:resource這個就是我們在第一步添加AppWidgetProviderInfo信息
我們來看看界面的顯示效果
上面就是顯示效果,一個textView和一個button
掛件的交互事件
我們在上面定義一個按鈕和一個文本,那邊在掛件中如何設置按鈕的點擊事件以及給文本框設置文本呢?這個跟我們在Activity的處理是完全不一樣的,這個涉及到一個RemoteViews類的使用,RemoteViews類描述了一個View對象能夠顯示在其他進程中,可以融合從一個 layout資源文件實現布局。雖然該類在android.widget.RemoteViews而不是appWidget下面但在Android Widgets開發中會經常用到它,我們先來看看按鈕的點擊事件是如何設置的,
復制代碼
@Override
public void onUpdate(Context context, AppWidgetManager appWidgetManager,
int[] appWidgetIds) {
// TODO Auto-generated method stub
super.onUpdate(context, appWidgetManager, appWidgetIds);
RemoteViews remoteViews=new RemoteViews(context.getPackageName(), R.layout.activity_main);
Intent intent=new Intent();
intent.setAction("btn.itchq.com");
PendingIntent pendingIntent=PendingIntent.getBroadcast(context, 0, intent, 0);
remoteViews.setOnClickPendingIntent(R.id.btn, pendingIntent);
appWidgetManager.updateAppWidget(appWidgetIds, remoteViews);
Log.i("cheng", "onUpdate");
}
復制代碼
我們看到這個RemoteViews對象的構造函數中第二個參數就是我們的初始化布局文件,即就是在第一步中添加AppWidgetProviderInfo信息的中的 android:initialLayout這個布局,當然如何我們在這裡指定其它布局也可以,如果在這裡指定其它布局那麼這個布局就會調換掉默認的初始化布局文件,上面的代碼可以看到按鈕的點擊事件通過設置想要的intent來實現的,在設置好的intent中通過PendingIntent包裝之後,remoteViews.setOnClickPendingIntent()就是設置的按鈕的點擊事件,這個方法中第一個參數指定的是這個button的id,第二個方法指定的就是已經設置好的PengingIntent,我們可以看到這個是一個廣播事件,當然我們也可以設置按鈕的來開啟一個Activity和一個服務,如何修改看下面的代碼:
復制代碼
RemoteViews remoteViews=new RemoteViews(context.getPackageName(), R.layout.activity_main);
Intent intent=new Intent(context,MainActivity.class);
//intent.setAction("btn.itchq.com");
PendingIntent pendingIntent=PendingIntent.getActivity(context, 0, intent, 0);
remoteViews.setOnClickPendingIntent(R.id.btn, pendingIntent);
appWidgetManager.updateAppWidget(appWidgetIds, remoteViews);
復制代碼
很容易了,說白了就是這個PendingIntent的使用方法了,好了我們再來看看還有最好一句話appWidgetManager.updateAppWidget(appWidgetIds, remoteViews);
這個是掛件的更新,這個很重要,我們要記住每一個使用RemoteViews設置完相應的動作之後一定要更新一下掛件,如果不更新那麼我們所有設置都是無效的,比如上面沒有更新的話那麼我們點擊按鈕是沒有任何效果的,由於這個是在onUpdate()方法裡面設置的,所以更新這個直接那appWidgetManager這個對象來更新就行,那麼如果不是在onUpdate()裡面更新的話在外面又是如何來更新的呢?,我們直接來看下面的代碼
ComponentName thisName=new ComponentName(context, itchqAppWidget.class);
AppWidgetManager manager=AppWidgetManager.getInstance(context);
manager.updateAppWidget(thisName, remoteViews);
這個就是直接獲取的AppWidgetManager對象,通過這個就可以更新掛件,itchqAppWidget.class是繼承AppWidgetProvider的類名,這裡還有另外一個方法:
AppWidgetManager manager=AppWidgetManager.getInstance(context);
int[] appids=manager.getAppWidgetIds(new ComponentName(context, itchqAppWidget.class));
manager.updateAppWidget(appids, remoteViews);
這兩個更新的方法都是一樣的,很簡單吧,好了回到按鈕的點擊事件,上面中我們設置一個按鈕的點擊事件,其實是一個發送廣播的方式,這個廣播是btn.itchq.com,那要讓一個掛件接受這個廣播我們就必須進行注冊,這個和廣播的機制是一樣的
<intent-filter>
<action android:name="android.appwidget.action.APPWIDGET_UPDATE"/>
<action android:name="btn.itchq.com"/>
</intent-filter>
看上面的代碼就很容易理解了,那麼我們再看看接受廣播之後做哪些事件呢
復制代碼
public void onReceive(Context context, Intent intent) {
// TODO Auto-generated method stub
super.onReceive(context, intent);
Log.i("cheng", "onReceive="+intent.getAction());
if(intent.getAction().equals("btn.itchq.com")){
RemoteViews remoteViews=new RemoteViews(context.getPackageName(), R.layout.activity_main);
remoteViews.setTextViewText(R.id.txt, "Success");
ComponentName thisName=new ComponentName(context, itchqAppWidget.class);
AppWidgetManager manager=AppWidgetManager.getInstance(context);
manager.updateAppWidget(thisName, remoteViews);
}
復制代碼
remoteViews.setTextViewText()就是更新文本,第一個參數就是文本的id,第二個參數就是要更新的文件內容,很容易理解吧,最後不要忘了更新一些掛件
其實在RomteViews類裡面有一系列的setXXX()方法,我們可以 這些set設置相應的功能,比如可以切換ImageView的圖片
android:configure
復制代碼
<appwidget-provider
xmlns:android="http://schemas.android.com/apk/res/android"
android:minWidth="70dp"
android:minHeight="120dp"
android:initialLayout="@layout/activity_main"
android:configure="com.itchq.appwidgetactivity.MainActivity"
>
</appwidget-provider>
復制代碼
這個是在appwidgetproviderInfo中定義的(就是我們的res目錄下xml文件中),如果用戶在添加一個App Widget時需要配置設置,那麼可以創建一個app widget configuration Activity。這個Activity由App Widget host自動啟動,並且允許用戶在創建的時候配置可能的設置,不如掛件的大小呀,一些需要的參數呀等等,這個Activity和我們普通的Avtivity是一樣需要在在Android manifest文件中進行聲明,但是要注意這個activity必須能接收這樣的Intent “android.appwidget.action.APPWIDGET_CONFIGURE“,因為只有接受這個才會被它會被App Widget host中的ACTION_APPWIDGET_CONFIGUREaction啟動
<activity android:name="com.itchq.appwidgetactivity.MainActivity" >
<intent-filter>
<action android:name="android.appwidget.action.APPWIDGET_CONFIGURE" />
</intent-filter>
</activity>
我們再看看這個Avtivity中的代碼
復制代碼
package com.itchq.appwidgetactivity;
import android.os.Bundle;
import android.app.Activity;
import android.appwidget.AppWidgetManager;
import android.content.Intent;
import android.util.Log;
import android.view.Menu;
import android.view.View;
import android.view.View.OnClickListener;
import android.widget.Button;
import android.widget.EditText;
import android.widget.RemoteViews;
import android.widget.TextView;
public class MainActivity extends Activity {
private int mAppWidgetId = AppWidgetManager.INVALID_APPWIDGET_ID;
private EditText text;
private Button btn;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setResult(RESULT_CANCELED);
setContentView(R.layout.main);
Intent intent = getIntent();
Bundle extras = intent.getExtras();
if (extras != null) {
mAppWidgetId = extras.getInt(
AppWidgetManager.EXTRA_APPWIDGET_ID, AppWidgetManager.INVALID_APPWIDGET_ID);
}
if (mAppWidgetId == AppWidgetManager.INVALID_APPWIDGET_ID) {
Log.i("cheng", "aaaaa");
finish();
}
text=(EditText) findViewById(R.id.test);
btn=(Button) findViewById(R.id.ok);
btn.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
// TODO Auto-generated method stub
click();
}
});
}
public void click(){
RemoteViews remoteViews=new RemoteViews(getPackageName(), R.layout.activity_main);
remoteViews.setTextViewText(R.id.txt, text.getText());
AppWidgetManager appWidgetManager = AppWidgetManager.getInstance(this);
appWidgetManager.updateAppWidget(mAppWidgetId, remoteViews);
Intent resultValue = new Intent();
resultValue.putExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, mAppWidgetId);
setResult(RESULT_OK, resultValue);
finish();
}
@Override
public boolean onCreateOptionsMenu(Menu menu) {
// Inflate the menu; this adds items to the action bar if it is present.
getMenuInflater().inflate(R.menu.main, menu);
return true;
}
}
復制代碼
這個MainActivity中布局中顯示一個EditText和Button,setResult(RESULT_CANCELED);這句話說明的就是配置Activity第一次打開時,設置Activity result為RESULT_CANCLED。這樣,用戶在未完成配置設置而退出時,App Widget被告知配置取消,將不會添加App Widget。
Intent intent = getIntent();
Bundle extras = intent.getExtras();
if (extras != null) {
mAppWidgetId = extras.getInt(
AppWidgetManager.EXTRA_APPWIDGET_ID, AppWidgetManager.INVALID_APPWIDGET_ID);
}
這段代碼從啟動Activity的Intent中獲取App Widget ID。我們後面會通過這個ID來更新掛件
if (mAppWidgetId == AppWidgetManager.INVALID_APPWIDGET_ID) {
finish();
}
這句話表示的就是這個給定的app widget ID無效的時候就直接結束這個Activity,最後我們來看看這個按鈕的點擊事件中的代碼:
RemoteViews remoteViews=new RemoteViews(getPackageName(), R.layout.activity_main);
remoteViews.setTextViewText(R.id.txt, text.getText());
AppWidgetManager appWidgetManager = AppWidgetManager.getInstance(this);
appWidgetManager.updateAppWidget(mAppWidgetId, remoteViews);
這段代碼表示當我們點擊了按鈕之後,掛件外面的TextView就顯示成我們在這裡設置的Text,之後還不要忘了要更新一下掛件
Intent resultValue = new Intent();
resultValue.putExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, mAppWidgetId);
setResult(RESULT_OK, resultValue);
finish();
最後,創建返回Intent,設置給Activity返回結果,並且結束Activity。
這個整體的效果就是在添加掛件之後就會先彈出一個Activity界面,設置好Text之後點擊按鈕就返回掛件外面,掛件的TextView控件顯示的就是我們剛剛開始設置的Text。