待机窗口小部件的编写流程---时间小部件

时间小部件在很多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 的一个具体子类,有兴趣可以度娘哦~




  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值