Android Launcher開發之桌面小部件AppWidget詳解

openkk 12年前發布 | 56K 次閱讀 Android Android開發 移動開發

導讀:先簡單說說Widget的原理。Widget是在桌面上的一塊顯示信息的東西,也通過單擊Widget跳轉到一個程序里面。而系統自帶的程序,典型的Widget是music,這個Android內置的音樂播放小程序。先簡單說說Widget的原理。Widget是在桌面上的一塊顯示信息的東西,也通過單擊Widget跳轉到一個程序里面。而系統自帶的程序,典型的Widget是music,這個Android內置的音樂播放小程序。這個是典型的Widget+app應用。就是一個程序既可以通過Widget啟動,也可以通過App啟動。Widget就是一個AppWidgetProvider+一個UI界面顯示(預先綁定了好多Intent),界面上的信息可以通過程序控制而改變,單擊Widget,上的控件只能激發發送一個Intent,或發出一個Service的啟動通知。而AppWidgetProvider可以攔截這個Intent,而進行相應的處理(比如顯示新的信息)。Android開發里要大量的通過手動的方式配置好多xml文件。這對于.net開發來說不得不說是個夢靨呀。這也許也是一般的java程序員比.net程序的工資高的一個原因吧。畢竟做Java開發,特別是Android開發,確實很累。你可以看下源碼對照著源碼進行講解。我們先開發一個比較簡單的Widget應用,實現的主要功能是可以通過的不斷變化,而不斷的顯示當前時間。首先,要自己手動建一個名為xml的文件夾。建一個xml文件,加入如下代碼:

 <appwidget-providerxmlns:android="

     android:minHeight="72px"

     android:minWidth="72px"  

   android:updatePeriodMillis="3800000"android:initialLayout="@layout/main">

</appwidget-provider></pre>


這個是Widget的顯示設置,是對Widget屬性的一個配置文件這個android:minHeight是Widget的高,這個android:minWidth 是Widget的寬。這個android:updatePeriodMillis屬性是設置Widget頁面的 更新頁面的時間的頻率。而這個android:initialLayout屬性是表示的是初始化頁面的布局,Android里畫UI的地方都是通過xml文件,也可以通過代碼程序來畫,不過這樣畫的太麻煩了。 看下以下的文件系統,res文件夾是系統存放資源文件的目錄。以drawable開頭的文件夾是存放圖片資源的文件夾。而后面的hdpi和ldpi等,都是平常在不同的狀態如(橫屏與豎屏時)系統調用不同的圖片資源。Layout就是存放的一般都是xml,UI設計就是在這個layout文件夾里。Value里放的strings.xml就是從程序里分離的字符串,在實現國際化的時候可能會用到。 看看layout里的main.xml ,只有一個空間就是TextView,這個是用來顯示時間用的。 建一個類TestAppWidget繼承于AppWidgetProvider,而AppWidgetProvider繼承與android.content.BroadcastReceiver,所以TestAppWidget就是一個攔截處理Intent的BroadcastReceiver,這些Intent只能在Androidmainfest里設置來攔截處理。


public class TestAppWidget extends AppWidgetProvider {
  private static final String TAG="TestAppWidget";
  private static final String FRESH="com.sinxiao.app.fresh";
   private Context mContext ;
   private boolean run = true ;
   BroadcastReceiver mBroadcast =newBroadcastReceiver() {

  public void onReceive(Contextcontext, Intent intent) {   
   String action =intent.getAction();

   if(action.equals(Intent.ACTION_TIME_TICK)) {

   mContext.sendBroadcast(newIntent(FRESH));

}

}

};

/**

  • 通知Widget每個1秒刷新一次 */ Thread myThread = new Thread(){

    public void run() {

    while (run) {

    try {

    Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } mContext.sendBroadcast(newIntent(FRESH));//通知刷新Widget的Intent } }; };

    @Override public void onUpdate(Contextcontext, AppWidgetManager appWidgetManager,

    int[] appWidgetIds) { // 用來給Widget刷新界面顯示 Log.d(TAG,"onUpdate"); super.onUpdate(context,appWidgetManager, appWidgetIds); mContext = context; RemoteViews views = newRemoteViews(context.getPackageName(),R.layout.main); Calendar cal=Calendar.getInstance(); System.out.println(cal.getTime().toLocaleString()); views.setTextViewText(R.id.txttim,cal.getTime().toLocaleString()); appWidgetManager.updateAppWidget(appWidgetIds,views); myThread.start(); /**

  • 本類作為一個bracastReveiver能自己再,注冊個監聽器 (可以取消注釋,看報什么錯誤) */ // context.registerReceiver(mBroadcast,new IntentFilter(Intent.ACTION_TIME_TICK)); } @Override public void onReceive(Contextcontext, Intent intent) { Log.d(tag,"onReceive"); String action =intent.getAction(); Log.d(tag, "theaction is "+action); if (FRESH.equals(action)){ showTime(context); }elseif(Intent.ACTION_TIME_TICK.equals(action)){ showTime(context); } super.onReceive(context,intent); } private void showTime(Contextcontext) { RemoteViews views = newRemoteViews(context.getPackageName(),R.layout.main); Calendar cal=Calendar.getInstance(); views.setTextViewText(R.id.txttim,cal.getTime().toLocaleString()); ComponentName thisWidget =new ComponentName(context,TestAppWidget.class); AppWidgetManager.getInstance(context).updateAppWidget(thisWidget,views); } public void onDisabled(Contextcontext) { Log.d(tag,"onDisabled"); super.onDisabled(context); run = false ; } }</pre>

    以上代碼就是用來改變顯示時間和處理刷新FRESHIntent的主程序。 看AndroidMainifest里是如何將FRESH Intent綁定到這個TestAppWidget的,看TestAppWidget這個reciever 里有個 <action android:name="com.sinxiao.app.fresh" /> 這就是要攔截的Intent的一個標示。主程序里的重寫了父類里的OnReceive()方法在。  

            private static final StringFRESH="com.sinxiao.app.fresh";
    public void onReceive(Contextcontext, Intent intent) {
    String action=intent.getAction();
    Log.d(tag, "theaction is "+action);
    if (FRESH.equals(action)){
    showTime(context);
    }elseif(Inent.ACTION_TIME_TICK.equals(action)){
    showTime(context);
    }
    super.onReceive(context,intent);
    }


     

    這個FRESH就顯示的就是處理我們,剛才下面綁定的這個FRESH Intent。
     
      <?xml version="1.0"encoding="utf-8"?>
    <manifestxmlns:android="http://schemas.android.com/apk/res/android"
    package="com.sinxiao.widgetapp"
    android:versionCode="1"android:versionName="1.0.0">
    <applicationandroid:icon="@drawable/icon"android:label="@string/app_name">
    <receiver android:name=".setting.TestAppWidget">
    <intent-filter>
    <actionandroid:name="android.appwidget.action.APPWIDGET_UPDATE"/>
    <!-- 這個Intent不支持在配置文件里的注冊 所以這個是沒用的 -->
    <!-- <actionandroid:name ="android.intent.action.TIME_TICK"/> -->
    </intent-filter>
    <intent-filter>
    <!-- 將IntentAction 手動配置在 mainset文件上 -->
    <action android:name="com.sinxiao.app.fresh"/>
    </intent-filter>
    <meta-data android:name="android.appwidget.provider"
    android:resource="@xml/testwidget_setting" />
    </receiver>
    </application>
    </manifest>


    這樣就是Widget的簡單的實現思路。需要特別說明的是Android 的Widget里有好多潛規則呀。一不小心,就可能中招。這就說明了實踐是檢驗真理的唯一標準呀。 關于對Widget上控件如何賦值?如下所示:  
     
     RemoteViews views = newRemoteViews(context.getPackageName(),R.layout.main);
    Calendar
    cal=Calendar.getInstance();
    System.out.println(cal.getTime().toLocaleString());
    views.setTextViewText(R.id.txttim,cal.getTime().toLocaleString());

    appWidgetManager.updateAppWidget(appWidgetIds,views);</pre>

    為什么要通過RemoteViews來向Widget設置界面呢?我也不知道,那位高人可以告訴我呢?這就是潛規則呀。在一般app應用里,直接用 setContentView(R.layout.main); 來實現。這個RemoteViews更特殊,設置Text時,必須通過 views.setTextViewText(R.id.txttim,cal.getTime().toLocaleString()); 這種變態的方式才可以賦值,實在令人琢磨不透呀。 通過appWidgetManager來,更新了頁面,也就刷新了Widget界面。在獲得ACTION 為 com.sinxiao.app.fresh的Intent時,也是這樣更新頁面。在showTime();的方法里,


        ComponentName thisWidget = new ComponentName(context,TestAppWidget.class);
    AppWidgetManager.getInstance(context).updateAppWidget(thisWidget,views);


 本文由用戶 openkk 自行上傳分享,僅供網友學習交流。所有權歸原作者,若您的權利被侵害,請聯系管理員。
 轉載本站原創文章,請注明出處,并保留原始鏈接、圖片水印。
 本站是一個以用戶分享為主的開源技術平臺,歡迎各類分享!