待機窗口小部件的編寫流程---時間小部件

時間小部件在很多android智能機上都很常見,本篇主要通過介紹時間部件的編寫流程,介紹在待機上顯示小部件的方法,先來張圖。

時間小部件


在不瞭解Launcher上如何顯示窗口小部件之前,我們暫時分步驟完成小部件的編寫,完成後再找機會做深入研究。

第一步、配置AndroidManifest.xml。

我們需要爲小部件編寫一個TimeWidgetProvider繼承自AppWidgetProvider,從AppWidgetProvider的父類BroadcastReceiver看出,該類實際上是一個用來接受widget廣播消息的類。那麼在AndroidManifest.xml中需要一個receiver標籤,如下:

        <receiver android:name="com.blackhill.mytimewidget.TimeWidgetProvider">
            <meta-data android:name="android.appwidget.provider"
                android:resource="@xml/time_widget_provider"/>
            <intent-filter>
                <action android:name="android.appwidget.action.APPWIDGET_UPDATE" />
                <action android:name="android.appwidget.action.APPWIDGET_DISABLED"/>                
            </intent-filter>
        </receiver>
其中meta-data標籤中android:resource="@xml/time_widget_provider",指定了我們要完成的小部件的屬性,我們需要在工程的res下創建一個xml文件夾,並在其中創建一個time_widget_provider.xml文件,類似如下:

<?xml version="1.0" encoding="utf-8"?>
<appwidget-provider xmlns:android="http://schemas.android.com/apk/res/android"
    android:minWidth="300dp"
    android:minHeight="200dp"
    android:updatePeriodMillis="0"
    android:initialLayout="@layout/activity_time_widget"
    >
</appwidget-provider>
以上各項屬性逐一介紹如下:

minWidth:widget所佔的屏幕寬度

minHeight:widget所佔的屏幕高度

updatePeriodMillis:更新時間頻率。據說從android 1.6以後,默認更新時間爲30分鐘。所以我們需要另外增加一個service來定時刷新。

initialLayout:UI佈局

第二步、創建widget的UI佈局

在layout中增加布局文件activity_time_widget.xml,類似如下:

<FrameLayout 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=".TimeWidgetActivity" >

    <ImageView android:id="@+id/id_bg"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:src="@drawable/bg"
        android:contentDescription="@string/descrip_bg"/>
    
    <RelativeLayout android:id="@+id/id_time_and_date"
        android:layout_width="fill_parent"
        android:layout_height="fill_parent">
	    <RelativeLayout
	        android:id="@+id/id_time_area"
	        android:layout_width="fill_parent"
	        android:layout_height="116dp"
	        android:layout_marginLeft="0dp"
	        android:layout_marginTop="0dp" >
	        
			<RelativeLayout android:id="@+id/id_hour_area"
	    		android:layout_width="118dp"
	    		android:layout_height="92dp"
	    		android:layout_marginTop="18dp"
	    		android:layout_marginLeft="26dp">
				
			    <ImageView android:id="@+id/id_hour_num_one"
			        android:src="@drawable/num_0"
			        android:contentDescription="@string/descrip_digit"
			        android:layout_width="wrap_content"
			        android:layout_height="wrap_content"
					android:layout_centerVertical="true"
					android:layout_marginLeft="20dp"
					/>
			    
			    <ImageView android:id="@+id/id_hour_num_two"
			        android:src="@drawable/num_0"
			        android:contentDescription="@string/descrip_digit"
			        android:layout_width="wrap_content"
			        android:layout_height="wrap_content"		        
			        android:layout_toRightOf="@id/id_hour_num_one"
			        android:layout_centerVertical="true"
			        />
			</RelativeLayout>
			
			<RelativeLayout android:id="@+id/id_minute_area"
	    		android:layout_width="118dp"
	    		android:layout_height="92dp"
	    		android:layout_marginTop="18dp"
	    		android:layout_toRightOf="@id/id_hour_area">
	    
			    <ImageView android:id="@+id/id_minute_num_1"
			        android:src="@drawable/num_0"
			        android:contentDescription="@string/descrip_digit"
			        android:layout_width="wrap_content"
			        android:layout_height="wrap_content"
					android:layout_centerVertical="true"
					android:layout_marginLeft="20dp"	        
			        />
			    
			    <ImageView android:id="@+id/id_minute_num_2"
			        android:src="@drawable/num_0"
			        android:contentDescription="@string/descrip_digit"
			        android:layout_width="wrap_content"
			        android:layout_height="wrap_content"		        
			        android:layout_toRightOf="@id/id_minute_num_1"
			        android:layout_centerVertical="true"		        
			        />		    
			</RelativeLayout>
					
	    </RelativeLayout>
	
	    <RelativeLayout android:id="@+id/id_date_area"
	        android:layout_below="@id/id_time_area"
	        android:layout_width="fill_parent"
	        android:layout_height="44dp">
			<RelativeLayout android:id="@+id/id_date"
			    android:layout_width="150dp"
			    android:layout_height="fill_parent">
		        <TextView android:id="@+id/id_date_text"
		            android:text="@string/str_date_text"
		            android:layout_centerVertical="true"
		            android:layout_centerHorizontal="true"
				    android:layout_width="wrap_content"
				    android:layout_height="wrap_content"/>
	        </RelativeLayout>
	        
			<RelativeLayout android:id="@+id/id_lunar"
			    android:layout_width="150dp"
			    android:layout_height="fill_parent"
			    android:layout_toRightOf="@id/id_date">
		        <TextView android:id="@+id/id_lunar_text"
		            android:text="@string/str_lunar_text"
				    android:layout_width="wrap_content"
				    android:layout_height="wrap_content"
				    android:layout_centerHorizontal="true"
		            android:layout_centerVertical="true" />
	        </RelativeLayout>
	    </RelativeLayout>
	    
	</RelativeLayout>
</FrameLayout>

以上佈局內容看起來比較複雜,建議將部分內容分開寫,用include標籤加進來會好看很多。那到這步,就能看到初步效果了。接下來,我們需要編寫AndroidManifest.xml中的TimeWidgetProvider了。

第三步、編寫XXXWidgetProvider

package com.blackhill.mytimewidget.ui;

import java.util.ArrayList;

import android.appwidget.AppWidgetManager;
import android.appwidget.AppWidgetProvider;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.text.format.DateFormat;
import android.util.Log;
import android.widget.RemoteViews;

import com.blackhill.mytimewidget.R;
import com.blackhill.mytimewidget.lunar.DateAndTimeModel;

public class TimeWidgetProvider extends AppWidgetProvider {
	private final String TAG = "TimeWidgetProvider";
	private final String ACTION_UPDATE_WIDGET = "com.blackhill.mytimewidget.UPDATE_WIDGET";
	private static final int[] timePic = {R.drawable.num_0, R.drawable.num_1, R.drawable.num_2, R.drawable.num_3, R.drawable.num_4,
		R.drawable.num_5, R.drawable.num_6, R.drawable.num_7, R.drawable.num_8, R.drawable.num_9};
	private static final int[] weekdayString = {R.string.Sunday, R.string.Monday, R.string.Tuesday, R.string.Wednesday, 
		R.string.Thursday, R.string.Friday, R.string.Saturday};
	private static ArrayList<Integer> widgetIds = new ArrayList<Integer>();
	private Intent mUpdateService = null;
	
	@Override
	public void onDeleted(Context context, int[] appWidgetIds) {
		// TODO Auto-generated method stub
		Log.d(TAG, "onDeleted");
		releaseAppWidgetIds(appWidgetIds);
		super.onDeleted(context, appWidgetIds);
	}

	@Override
	public void onUpdate(Context context, AppWidgetManager appWidgetManager,
			int[] appWidgetIds) {
		// TODO Auto-generated method stub
		Log.d(TAG, "onUpdate");
		addAppWidgetIds(appWidgetIds);
		updateDateAndTimeWidget(context, appWidgetManager, getAppWidgetIds(widgetIds));
		// start service for updating widget per minute.
		startService(context);
		
		super.onUpdate(context, appWidgetManager, appWidgetIds);		
	}
	
    @Override
	public void onDisabled(Context context) {
		// TODO Auto-generated method stub
    	stopService(context);
		super.onDisabled(context);
	}

	@Override
	public void onEnabled(Context context) {
		// TODO Auto-generated method stub
		startService(context);
		super.onEnabled(context);
	}

	static ComponentName getComponentName(Context context) {
        return new ComponentName(context, TimeWidgetProvider.class);
    }
    
    public void startService(Context context) {
    	Log.d(TAG, "startService");
    	mUpdateService = new Intent(context, TimeWidgetService.class);
    	context.startService(mUpdateService);
    }
    
    public void stopService(Context context) {
    	Log.d(TAG, "stop service");
    	if (mUpdateService != null) {
    		context.stopService(mUpdateService);
    	}
    }
    
	@Override
	public void onReceive(Context context, Intent intent) {
		// TODO Auto-generated method stub
		Log.d(TAG, "onReceive");
		final String action = intent.getAction();
		Log.d(TAG, "onReceive: action = " + action);
		if (action.equals("com.blackhill.mytimewidget.UPDATE_WIDGET")) {
			Log.d(TAG, "onReceive: updateDateAndTimeWidget!!!");
			AppWidgetManager appWidgetManager = AppWidgetManager.getInstance(context);
			updateDateAndTimeWidget(context, appWidgetManager, appWidgetManager.getAppWidgetIds(getComponentName(context)));
		}else {
			Log.d(TAG, "onReceive: super.onReceive");
			super.onReceive(context, intent);
		}
	}

	void updateDateAndTimeWidget(Context context, AppWidgetManager appWidgetManager,
			int[] appWidgetIds) {
		boolean b24HourFormat = DateFormat.is24HourFormat(context);
		DateAndTimeModel mDateTime = new DateAndTimeModel();
		
		int hour = mDateTime.getCurrentHour(b24HourFormat);
		int minute = mDateTime.getCurrentMin();
		
//		String format = context.getString(R.string.lunar_date_formatter);
//		SimpleDateFormat timeFormat = new SimpleDateFormat(format);
//		String date = timeFormat.format(mDateTime.getCurrentDate());
		String date = mDateTime.getCurrentDate(context);
		
		int weekdayId = getWeekdayStringId(mDateTime.getCurrentWeekDay());
		String weekday = context.getString(weekdayId);
		
		Log.d(TAG, "updateDateAndTimeWidget:length = " + appWidgetIds.length);
		Log.d(TAG, "updateDateAndTimeWidget:appWidgetIds = " + appWidgetIds.toString());
		Log.d(TAG, "updateDateAndTimeWidget:date = " + date);
		Log.d(TAG, "updateDateAndTimeWidget:weekday = " + weekday);        
		
		for (int appWidgetId: appWidgetIds) {
			Log.d(TAG, "updateDateAndTimeWidget:appWidgetId = " + appWidgetId);
			
			RemoteViews views = new RemoteViews(context.getPackageName(), R.layout.activity_time_widget);
			views.setImageViewResource(R.id.id_hour_num_one, getTimeStringId(hour/10));
			views.setImageViewResource(R.id.id_hour_num_two, getTimeStringId(hour%10));
			views.setImageViewResource(R.id.id_minute_num_1, getTimeStringId(minute/10));
			views.setImageViewResource(R.id.id_minute_num_2, getTimeStringId(minute%10));
			views.setTextViewText(R.id.id_date_text, date);
			views.setTextViewText(R.id.id_lunar_text, weekday);
			appWidgetManager.updateAppWidget(appWidgetId, views);
		}
	}
	
	int getTimeStringId(int num) {
		if (num < timePic.length) {
			return timePic[num];
		}else {
			return timePic[0];
		}
	}
	
	int getWeekdayStringId(int weekday) {
		if (weekday < weekdayString.length) {
			return weekdayString[weekday - 1];
		}else {
			return weekdayString[0];
		}
	}
	
	public void addAppWidgetIds(int[] appWidgetIds) {
		for (int widgetId: appWidgetIds) {
			if (!widgetIds.contains(widgetId)) {
				widgetIds.add(widgetId);
			}
		}
	}
	
	public void releaseAppWidgetIds(int[] appWidgetIds) {
		for (int widgetId:appWidgetIds) {
			if (!widgetIds.contains(widgetId)) {
				widgetIds.remove(widgetId);
			}
		}
	}
	
	public int[] getAppWidgetIds(ArrayList<Integer> widgetIds) {		
		int[] appWidgets = new int[widgetIds.size()];
		
		try {
			for (int i = 0; i < widgetIds.size(); i++) {
				appWidgets[i] = widgetIds.get(i);
			}
		}catch (Exception excep) {
			
		}
		return appWidgets;
	}
}

其中必須要重載onReceive函數以及onUpdate函數,如果有資源需要釋放,需要再重載onDeleted, onDisable等。TimeWidgetProvider繼承自AppWidgetProvider,從AppWidgetProvider的源碼可以看出,onEnable, onDisable, onDeleted, onUpdate都是在AppWidgetProvider的onReceive中調用的,如:

    public void onReceive(Context context, Intent intent) {
        // Protect against rogue update broadcasts (not really a security issue,
        // just filter bad broacasts out so subclasses are less likely to crash).
        String action = intent.getAction();
        if (AppWidgetManager.ACTION_APPWIDGET_UPDATE.equals(action)) {
            Bundle extras = intent.getExtras();
            if (extras != null) {
                int[] appWidgetIds = extras.getIntArray(AppWidgetManager.EXTRA_APPWIDGET_IDS);
                if (appWidgetIds != null && appWidgetIds.length > 0) {
                    this.onUpdate(context, AppWidgetManager.getInstance(context), appWidgetIds);
                }
            }
        }
        else if (AppWidgetManager.ACTION_APPWIDGET_DELETED.equals(action)) {
            Bundle extras = intent.getExtras();
            if (extras != null && extras.containsKey(AppWidgetManager.EXTRA_APPWIDGET_ID)) {
                final int appWidgetId = extras.getInt(AppWidgetManager.EXTRA_APPWIDGET_ID);
                this.onDeleted(context, new int[] { appWidgetId });
            }
        }
        else if (AppWidgetManager.ACTION_APPWIDGET_OPTIONS_CHANGED.equals(action)) {
            Bundle extras = intent.getExtras();
            if (extras != null && extras.containsKey(AppWidgetManager.EXTRA_APPWIDGET_ID)
                    && extras.containsKey(AppWidgetManager.EXTRA_APPWIDGET_OPTIONS)) {
                int appWidgetId = extras.getInt(AppWidgetManager.EXTRA_APPWIDGET_ID);
                Bundle widgetExtras = extras.getBundle(AppWidgetManager.EXTRA_APPWIDGET_OPTIONS);
                this.onAppWidgetOptionsChanged(context, AppWidgetManager.getInstance(context),
                        appWidgetId, widgetExtras);
            }
        }
        else if (AppWidgetManager.ACTION_APPWIDGET_ENABLED.equals(action)) {
            this.onEnabled(context);
        }
        else if (AppWidgetManager.ACTION_APPWIDGET_DISABLED.equals(action)) {
            this.onDisabled(context);
        }
    }

所以,可以選擇在onReceive或者onUpdate中加入service,以便更新時間。這裏,我們加入了一個TimeWidgetService.java來實現時間的更新。

第四步、後臺Service

package com.blackhill.mytimewidget.ui;

import android.app.Service;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.os.IBinder;
import android.util.Log;

public class TimeWidgetService extends Service {
	private final static String TAG = "TimeWidgetService";
	
	@Override
	public IBinder onBind(Intent arg0) {
		// TODO Auto-generated method stub
		return null;
	}

	private BroadcastReceiver mTimeWidgetReceiver = new BroadcastReceiver() {

		@Override
		public void onReceive(Context context, Intent intent) {
			// TODO Auto-generated method stub
			String action = intent.getAction();
			Log.d(TAG, "onReceive:action name = " + action);
			if (action.equals(Intent.ACTION_TIMEZONE_CHANGED)
					|| action.equals(Intent.ACTION_TIME_CHANGED)
					|| action.equals(Intent.ACTION_TIME_TICK)
					|| action.equals(Intent.ACTION_DATE_CHANGED)) {
				Log.d(TAG, "onReceive: update the widget");
				updateWidget();
			}
		}
		
	};
	
	@Override
	public void onCreate() {
		// TODO Auto-generated method stub
		super.onCreate();
		Log.d(TAG, "onCreate");
		IntentFilter filter = new IntentFilter();
		filter.addAction(Intent.ACTION_TIME_CHANGED);
		filter.addAction(Intent.ACTION_TIME_TICK);
		filter.addAction(Intent.ACTION_TIMEZONE_CHANGED);
		filter.addAction(Intent.ACTION_DATE_CHANGED);
		registerReceiver(mTimeWidgetReceiver, filter);
	}

	@Override
	public void onDestroy() {
		// TODO Auto-generated method stub
		super.onDestroy();
		Log.d(TAG, "onDestroy");
		unregisterReceiver(mTimeWidgetReceiver);
	}

	@Override
	public int onStartCommand(Intent intent, int flags, int startId) {
		// TODO Auto-generated method stub
		Log.d(TAG, "onStartCommand");
		return super.onStartCommand(intent, flags, startId);
	}

	public void updateWidget() {
		Intent broadcastIntent = new Intent("com.blackhill.mytimewidget.UPDATE_WIDGET");
		broadcastIntent.addCategory("com.blackhill.mytimewidget.widgetcategory");
		sendBroadcast(broadcastIntent);
	}
}

在Service的生命週期中,onCreate()註冊系統廣播,時間更新我們需要增加ACTION_TIME_CHANGED, ACTION_TIME_TICK, ACTION_TIME_DATE_CHANGED等。其中系統時間每到整一分鐘就會發送ACTION_TIME_TICK出來,收到系統廣播後,我們就可以updateWidget()。這裏我們採用發廣播的方式,發送自定義的廣播com.blackhill.mytimewidget.UPDATE_WIDGET

,當然,這個需要在AndroidManifest.xml中聲明。

        <receiver android:name="com.blackhill.mytimewidget.ui.TimeWidgetProvider">
            <meta-data android:name="android.appwidget.provider"
                android:resource="@xml/time_widget_provider"/>
            <intent-filter>
                <action android:name="android.appwidget.action.APPWIDGET_UPDATE" />
                <action android:name="android.appwidget.action.APPWIDGET_DISABLED"/>
                <action android:name="android.intent.action.DATE_CHANGED"/>
                <action android:name="android.intent.action.TIME_CHANGED"/>
                <action android:name="android.intent.action.TIME_SET"/>
                <action android:name="android.intent.action.TIME_TICK"/>                 
                <action android:name="android.intent.action.TIMEZONE_CHANGED"/>
                <action android:name="android.intent.action.CONFIGURATION_CHANGED"/>
                
                <action android:name="com.blackhill.mytimewidget.UPDATE_WIDGET" />
                <category android:name="com.blackhill.mytimewidget.widgetcategory" />
            </intent-filter>
        </receiver>

在TimeWidgetProvider收到UPDATE_WIDGET後,調用updateDateAndTimeWidget並刷新UI組件。

另外,再介紹獲取時間日期的文件DateAndTimeModel.java,以供參考。

package com.blackhill.mytimewidget.lunar;

import java.util.Calendar;
import java.util.Date;
import java.util.GregorianCalendar;
import java.util.TimeZone;

import android.content.Context;
import android.util.Log;

import com.blackhill.mytimewidget.R;

public class DateAndTimeModel {
	private static final String TAG = "DateAndTimeModel";
	private static final String TIMEZONE_PROPERTY = "persist.sys.timezone";
	
	private static final int HOUR_FORMAT = 12;
	private static final int MONTH_ONE_YEAR = 12;
	private static final int DAYS_ONE_WEEK = 7;
	private GregorianCalendar mCalendar;
	private TimeZone timeZone;
	
	public DateAndTimeModel() {
		Log.d(TAG, "DateAndTimeModle()");
		timeZone = TimeZone.getDefault();
		mCalendar = new GregorianCalendar(timeZone);
		mCalendar.setTimeInMillis(getSystemTimeMillis());
	}
	
	public long getSystemTimeMillis() {
		return System.currentTimeMillis();
	}
	
	public Date getCurrentDate() {		
		return new Date();
	}
	
	public String getCurrentDate(Context context) {
		StringBuffer dateStrBuffer = new StringBuffer();
		dateStrBuffer.append(getCurrentYear());
		dateStrBuffer.append(context.getString(R.string.lunar_year));
		dateStrBuffer.append(getCurrentMonth());
		dateStrBuffer.append(context.getString(R.string.lunar_month));
		dateStrBuffer.append(getCurrentDay());
		dateStrBuffer.append(context.getString(R.string.lunar_day));		
		return dateStrBuffer.toString();
	}
	
	public int getCurrentYear() {
		Log.d(TAG, "getCurrentHour: YEAR = " + mCalendar.get(Calendar.YEAR));
		return mCalendar.get(Calendar.YEAR);
	}
	
	public int getCurrentMonth() {
		Log.d(TAG, "getCurrentHour:month = " + mCalendar.get(Calendar.MONTH));
		// calculate from 0, as January equals to 0, February equals to 1.
		int indexOfMonth = mCalendar.get(Calendar.MONTH) + 1;
		return indexOfMonth % MONTH_ONE_YEAR;
	}
	
	public int getCurrentDay() {
		Log.d(TAG, "getCurrentHour:day of month = " + mCalendar.get(Calendar.DAY_OF_MONTH));
		return mCalendar.get(Calendar.DAY_OF_MONTH);
	}
	
	public int getCurrentWeekDay() {
		Log.d(TAG, "getCurrentHour:day of week = " + mCalendar.get(Calendar.DAY_OF_WEEK));
		return mCalendar.get(Calendar.DAY_OF_WEEK);
	}
	
	public int getCurrentHour(boolean b24HourFormat) {
		Log.d(TAG, "getCurrentHour:Hour = " + mCalendar.get(Calendar.HOUR_OF_DAY));
		Log.d(TAG, "getCurrentHour:b24HourFormat = " + b24HourFormat);
		if (b24HourFormat) {
			return mCalendar.get(Calendar.HOUR_OF_DAY);
		}else {
			return  mCalendar.get(Calendar.HOUR_OF_DAY) % HOUR_FORMAT;
		}
		
	}
	
	public int getCurrentMin() {
		Log.d(TAG, "getCurrentHour:Minute = " + mCalendar.get(Calendar.MINUTE));
		return mCalendar.get(Calendar.MINUTE);
	}
	
}

時間日期的獲取採用GregorianCalendar來取得,GregorianCalendar 是java.util包中 Calendar 的一個具體子類,有興趣可以度娘哦~




發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章