本篇文章將分享兩個VIEW組件,一個天氣組件和一個日期組件,這兩個組件本來是一個App Widget 後來,我看著好玩,將他們弄成一個VIEW的組件,可以像使用Windows Phone 7 的用戶控件一樣拖放到你想要的項目中。本篇將演示這兩個組件的編寫過程,工程文件如下:
包名介紹:
- com.terry.weather 程序的入口包
- com.yaomei.adapter 天氣預報組件使用到的數據源
- com.yaomei.model 天氣預報使用到的模型包
- com.yaomei.util 獲取天氣信息的工具包
- com.yaomei.widget 天氣預報組件、日期組件的存放位置
從包名可以看出,編寫一個天氣預報所需要的代碼量比編寫一個日期VIEW所需要的代碼量要多得多 ,那麼我們先把天氣預報的一些實現思路跟大家講講。
首先,本實例使用的天氣預報是一個可以自己國際化的天氣組件VIEW,可以看上圖,將所需要的URL都放入ANDROID 自己的國際化文件夾裡面,比如中文的話就這樣寫:
<string name="googleWeatherApi">
<![CDATA[http://www.google.com/ig/api?hl=zh-cn&weather=]]>
</string>
那麼是英語環境的就只需要在默認的VALUES裡面的string.xml這樣寫即可:
<string name="googleWeatherApi">
<![CDATA[http://www.google.com/ig/api?hl=en&weather=]]>
</string>
這是本篇一個要注意的一點,另外還有需要注意的是,這個天氣組件提供可供用戶選擇更新頻率,這裡比如我們使用3個小時更新一次,那麼當用戶退出程序時,再打開是否還要再去Google 上面讀天氣呢?答案是NO,因為既然用戶選擇了更新頻率,那麼在一定的時間內,我們最好不要自動去更新,除非用戶自己點擊更新才去執行。那麼要如何得到之前的數據呢?
這裡使用到的是SharePreference 將一些天氣的信息保存進去,連同天氣的圖片也一並保存。保存天氣圖片是將google 天氣的圖片使用Base64轉成字符串,然後保存進Sharepreference ,如果更新頻率條件未滿足則進去SharePrference 將天氣預報數據取出來 。因為Android 並未提供將圖片轉成字符串的API,這裡使用到的是apache 的一個Jar包,可在這裡下載:點擊這裡
思路上面給出了,下面給出天氣預報組件VIEW的核心代碼,其他附屬代碼可在後面的附件下載得到,代碼如下:
package com.yaomei.widget;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.List;
import java.util.Timer;
import java.util.TimerTask;
import org.apache.commons.codec.binary.Base64;
import android.app.Activity;
import android.content.Context;
import android.content.SharedPreferences;
import android.content.res.TypedArray;
import android.graphics.Bitmap.CompressFormat;
import android.graphics.drawable.BitmapDrawable;
import android.graphics.drawable.Drawable;
import android.os.Handler;
import android.os.Message;
import android.text.Html;
import android.util.AttributeSet;
import android.view.LayoutInflater;
import android.view.View;
import android.widget.GridView;
import android.widget.ImageView;
import android.widget.LinearLayout;
import android.widget.TextView;
import android.widget.Toast;
import com.terry.weather.R;
import com.yaomei.adapter.weatherAdapter;
import com.yaomei.model.WeatherMdoel;
import com.yaomei.util.strHelpeUtil;
public class WeatherView extends LinearLayout {
private static final String Hour_COMPARE = "hour_compare";
private static final String DAY_OF_WEEK = "day_of_week";
private static final String LOW = "low";
private static final String HIGH = "high";
private static final String CONDITION = "condition";
private static final String IMAGE = "image";
private static final String DATE_COMPARE = "date_compare";
private static final String CITYNAE_SHARE = "cityNameShare";
private ImageView iv_weather;
private TextView tv_state, tv_position, tv;
WeatherMdoel model;
private List<WeatherMdoel> weatherList = null;
GridView gv;
Timer timer;
Handler handler = new Handler() {
public void handleMessage(Message msg) {
if (msg.arg1 == 1) {
if (weatherList.size() > 0) {
gv
.setAdapter(new weatherAdapter(getContext(),
weatherList));
init();
} else {
Toast.makeText(getContext(), "查詢不到數據", 1000).show();
}
// msg.recycle();
}
};
};
/**
* 自動加載天氣
*/
private boolean autoLoad = false;
public boolean getAutoLoad() {
return autoLoad;
}
public void setAutoLoad(boolean isLoad) {
this.autoLoad = isLoad;
}
/**
* 城市名稱
*/
private String cityName = "";
public String getCityName() {
return cityName;
}
public void setCityName(String cityName) {
this.cityName = cityName;
}
/**
* 設置每幾小時更新一次
*/
private int updateHour;
public int getUpdateHour() {
return updateHour;
}
public void setUpdateHour(int hour) {
this.updateHour = hour;
}
public WeatherView(Context context) {
this(context, null);
// TODO Auto-generated constructor stub
}
public WeatherView(Context context, AttributeSet attrs) {
super(context, attrs);
int resouceID = -1;
TypedArray tyedArray = context.obtainStyledAttributes(attrs,
R.styleable.WeatherView);
int N = tyedArray.getIndexCount();
for (int i = 0; i < N; i++) {
int attr = tyedArray.getIndex(i);
switch (attr) {
case R.styleable.WeatherView_AutoLoad:
setAutoLoad(tyedArray.getBoolean(
R.styleable.WeatherView_AutoLoad, false));
break;
case R.styleable.WeatherView_CityName:
resouceID = tyedArray.getResourceId(
R.styleable.WeatherView_CityName, 0);
setCityName(resouceID > 0 ? tyedArray.getResources().getText(
resouceID).toString() : tyedArray
.getString(R.styleable.WeatherView_CityName));
break;
case R.styleable.WeatherView_UpdateHour:
setUpdateHour(tyedArray.getInteger(
R.styleable.WeatherView_UpdateHour, 3));
break;
}
}
View view = LayoutInflater.from(getContext()).inflate(
R.layout.weather_layout, this);
tv = (TextView) view.findViewById(R.id.tv_temperature);
gv = (GridView) view.findViewById(R.id.grid);
iv_weather = (ImageView) view.findViewById(R.id.iv_weather);
tv_state = (TextView) view.findViewById(R.id.tv_state);
tv_position = (TextView) view.findViewById(R.id.tv_position);
timer = new Timer();
if (getAutoLoad()) {
startLoadWeather();
}
tyedArray.recycle();
}
/**
* 開始加載
*/
public void startLoadWeather() {
timer.schedule(new TimerTask() {
@Override
public void run() {
SharedPreferences share = getContext().getSharedPreferences(
"weather", Activity.MODE_PRIVATE);
long time = System.currentTimeMillis();
final Calendar mCalendar = Calendar.getInstance();
mCalendar.setTimeInMillis(time);
String tempDate = mCalendar.get(Calendar.YEAR) + "-"
+ mCalendar.get(Calendar.MONTH) + "-"
+ mCalendar.get(Calendar.DAY_OF_MONTH);
if (share.contains(DATE_COMPARE)) {
if (share.getString(CITYNAE_SHARE, "").equals(cityName)) {
int time_cop = mCalendar.get(Calendar.HOUR)
- share.getInt(Hour_COMPARE, 0);
String date = share.getString(DATE_COMPARE, "");
if (time_cop >= getUpdateHour()
|| !date.equals(tempDate)) {
saveWeatherList(mCalendar.get(Calendar.HOUR),
tempDate);
} else if (time_cop < getUpdateHour()) {
weatherList = new ArrayList<WeatherMdoel>();
for (int i = 0; i < 4; i++) {
WeatherMdoel model = new WeatherMdoel();
model.setWeek(share.getString(DAY_OF_WEEK + i,
""));
model.setLowTemp(share.getString(LOW + i, ""));
model
.setHighTemp(share.getString(HIGH + i,
""));
model.setConditions(share.getString(CONDITION
+ i, ""));
String image = share.getString(IMAGE + i, "");
byte[] base64Bytes = Base64.decodeBase64(image
.getBytes());
ByteArrayInputStream bais = new ByteArrayInputStream(
base64Bytes);
model.setImageUrl("");
model
.setImageDrawable(Drawable
.createFromStream(bais,
"weather_image"));
weatherList.add(model);
}
}
} else {
saveWeatherList(mCalendar.get(Calendar.HOUR), tempDate);
}
} else {
saveWeatherList(mCalendar.get(Calendar.HOUR), tempDate);
}
// 把必要的操作放在於線程中執行,不阻塞UI
if (handler.hasMessages(1))
handler.obtainMessage().recycle();
else {
Message msg = handler.obtainMessage();
msg.arg1 = 1;
msg.sendToTarget();
}
}
}, 0, getUpdateHour() * 3600 * 1000);
}
/**
* 第一次或者另外重新加載
*/
void saveWeatherList(int hour, String day) {
weatherList = new ArrayList<WeatherMdoel>();
weatherList = strHelpeUtil.searchWeather(Html.fromHtml(
getContext().getResources()
.getString(R.string.googleWeatherApi)).toString(),
getCityName());
SharedPreferences.Editor shareEditor = getContext()
.getSharedPreferences("weather", Activity.MODE_PRIVATE).edit();
shareEditor.clear();
int i = 0;
for (WeatherMdoel model : weatherList) {
shareEditor.putString(DAY_OF_WEEK + i, model.getWeek());
shareEditor.putString(LOW + i, model.getLowTemp());
shareEditor.putString(HIGH + i, model.getHighTemp());
shareEditor.putString(CONDITION + i, model.getConditions());
/**
* 將圖片存入
*/
ByteArrayOutputStream baos = new ByteArrayOutputStream();
((BitmapDrawable) strHelpeUtil.loadImage(model.getImageUrl()))
.getBitmap().compress(CompressFormat.JPEG, 50, baos);
String ImageBase64 = new String(Base64.encodeBase64(baos
.toByteArray()));
shareEditor.putString(IMAGE + i, ImageBase64);
i++;
}
shareEditor.putString(DATE_COMPARE, day);
shareEditor.putInt(Hour_COMPARE, hour);
shareEditor.putString(CITYNAE_SHARE, cityName);
shareEditor.commit();
}
/**
* 初始化組件 信息
*/
void init() {
model = weatherList.get(0);
iv_weather.setImageDrawable(model.getImageUrl() == "" ? model
.getImageDrawable() : strHelpeUtil.loadImage(model
.getImageUrl()));
tv_state.setText(model.getConditions());
tv_position.setText(getCityName());
tv.setText(getContext().getResources().getString(R.string.temp_format,
model.getLowTemp(), model.getHighTemp()));
}
/**
* 釋放對象
*/
public void releaseTimer() {
timer.cancel();
weatherList = null;
}
}
學習這個類,你能夠學到的知識點為:為應用程序添加屬性,編寫組件,SharePreference 的使用,Timer和Handler 異步處理UI等知識點。
日期VIEW顯示VIEW組件,是一個顯示當前系統時間的組件,當第一次運行時,得到當前的秒數在以60秒減去當前秒,得到第一次運行時下一次運行需要的秒數,當這一次更新完畢後,下一次每次60秒更新一次時間,這個組件也是以分更新UI的操作,學習本類,你可以學到兩個Handler 是如何協作處理UI,代碼如下:
package com.yaomei.widget;
import java.util.Calendar;
import java.util.Date;
import android.content.Context;
import android.os.Handler;
import android.util.AttributeSet;
import android.view.LayoutInflater;
import android.view.View;
import android.widget.FrameLayout;
import android.widget.TextView;
import com.terry.weather.R;
import com.yaomei.util.strHelpeUtil;
public class DateView extends FrameLayout {
private TextView tv_date_time, tv_week, tv_date;
int second;
Handler handler = new Handler() {
public void handleMessage(android.os.Message msg) {
init();
handler.sendMessageDelayed(handler.obtainMessage(), 60 * 1000);
};
};
public DateView(Context context) {
this(context, null);
}
public DateView(Context context, AttributeSet attrs) {
super(context, attrs);
//this.setBackgroundDrawable(getContext().getResources().getDrawable(
// R.drawable.date_background));
View view = LayoutInflater.from(getContext()).inflate(
R.layout.date_layout, this);
tv_date_time = (TextView) view.findViewById(R.id.tv_date_time);
tv_week = (TextView) view.findViewById(R.id.tv_week);
tv_date = (TextView) view.findViewById(R.id.tv_date);
init();
final Calendar calendar = Calendar.getInstance();
second = calendar.get(Calendar.SECOND);
handler.sendMessageDelayed(handler.obtainMessage(),
(60 - second) * 1000);
}
void init() {
java.text.DateFormat df = new java.text.SimpleDateFormat("HH:mm");
tv_date_time.setText(df.format(new Date()));
tv_week.setText(strHelpeUtil.getWeekOfDate(new Date()));
strHelpeUtil str = new strHelpeUtil(getContext());
tv_date.setText(str.toString());
}
}
上篇運行效果如下:
由於沒有為其提供背景顏色,使用的同學可以自己為它們加上一個好看的背景顏色,效果會更加。
上面的天氣組件,其實可以使用AsyncTask也是起到同樣的效果,AsyncTask使用起來會覺得優雅一點,這裡也順便把一些AsyncTask在使用上一些注意事項跟大家談一談:
- 在doInBackground 裡面不要直接操作UI,比如設置UI的可見性操作。
- 在doInBackground 所在的操作只負責幫你得到數據,然後把UI處理都放在onPostExecute 裡面。
- 同時啟動幾個AsyncTask 注意線程加鎖,使用synchronized
- 必須每次都創建一個新的AsyncTask 對象,否則會提示“a task can be executed only once” 的錯誤信息。
本篇的所有源碼下載地址:組件