本篇文章将分享两个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” 的错误信息。
本篇的所有源码下载地址:组件