项目地址: https://github.com/devallever/MyCoolWeather

下载apk

我的博客: https://devallever.github.io/

简介

极简天气, 天气应用,是我看完Android第一行代码后, 参考并修改的练习项目, 以巩固所学知识

感谢开源

  • OkHttp
  • Retrofit
  • Glide
  • RxJava/RxAndroid
  • LitePal
  • EventBus

    感谢郭神提供天气数据接口

功能

第一版

  • 显示实时天气, 三天预报, 空气质量, 温馨提示
  • 获取Bing每日图片
  • 滑动切换城市
  • 城市管理: 增加,删除
  • 下拉刷新
  • 缓存省市县, 天气信息








第二版

  • 后台自动刷新
  • 自动下载壁纸
  • 天气提醒
  • 查看历史天气
  • 关于
  • 设置刷新频,全局提醒,背景图片
  • 检查更新

功能实现细节

第二版功能细节

后台自动更新天气信息

使用服务, 启动服务后通过AlarmManager设置一个定时任务,每隔一小时更新天气信息, 在退出程序时候启动该服务,在服务中主要执行了两个方法分别更新天气信息和壁纸, 请求网络后把数据保存到Weather表中

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
Log.d(TAG, "onStartCommand: ()");
updateWeather();
updateImage();
int anHour = 1 * 60 * 60 * 1000; //1小时
//int anHour = 10* 1000; //10秒
long triggerAtTime = SystemClock.elapsedRealtime() + anHour;
AlarmManager alarmManager = (AlarmManager) getSystemService(ALARM_SERVICE);
Intent serviceIntent = new Intent(this, AutoUpdateService.class);
//Intent receiverIntent = new Intent(this, AutoUpdateReceiver.class);
PendingIntent pendingIntent = PendingIntent.getService(this,0,serviceIntent,0);
alarmManager.cancel(pendingIntent);
alarmManager.set(AlarmManager.ELAPSED_REALTIME_WAKEUP, triggerAtTime,pendingIntent);
return super.onStartCommand(intent, flags, startId);
}

启动服务

1
2
3
4
5
6
7
@Override
protected void onDestroy() {
//启动后台更新服务
Intent serviceIntent = new Intent(this, AutoUpdateService.class);
startService(serviceIntent);
super.onDestroy();
}

天气提醒功能

每次后台获取更新后,判断该城市是否显示通知, 是的话就对天气信息进一步分析:

昼夜温度大于10度时提醒,

1
2
3
4
5
6
7
int max = Integer.valueOf(newWeather.getDaily_forecast().get(0).getTmp().getMax());
int min = Integer.valueOf(newWeather.getDaily_forecast().get(0).getTmp().getMin());
if ((max-min) >=10 ){
//显示通知
builder.setContentText( county + ": 昼夜温差较大,请预防感冒!");
notificationManager.notify(id,builder.build());
}

今明天气相差5度时提醒

1
2
3
4
5
6
7
8
9
10
11
12
13
//2.比较今天明天气温相差5度提示
Daily_forecast today = newWeather.getDaily_forecast().get(0);
Daily_forecast tomorrow = newWeather.getDaily_forecast().get(1);
int todayTmp = Integer.valueOf(today.getTmp().getMin());
int tomorrowTmp = Integer.valueOf(tomorrow.getTmp().getMin());
if ( Math.abs(todayTmp-tomorrowTmp) >= 5){
if (tomorrowTmp > todayTmp){
builder.setContentText( county + ": 明天将大幅度升温!");
}else {
builder.setContentText( county + ": 明天将大幅度降温温!");
}
notificationManager.notify(id+1000,builder.build());
}

有降水提示带伞

1
2
3
4
5
//3. 有雨的要带伞
if (tomorrow.getCond().getTxt_d().contains("雨")){
builder.setContentText( county + ": 明天将有" + tomorrow.getCond().getTxt_d() + ", 出门记得带伞.");
notificationManager.notify(id+1001,builder.build());
}

当实时天气与上一次的天气有不同时候,提醒

1
2
3
4
5
6
7
//4. 比较实时天气
String old = oldWeather.getNow().getCond().getTxt();
String newInfo = newWeather.getNow().getCond().getTxt();
if (!old.equals(newInfo)){
builder.setContentText( county + ": " + newWeather.getNow().getCond().getTxt());
notificationManager.notify(id+1002,builder.build());
}

查看历史天气

创建一个数据库表HistoryWeather用来保存历史天气信息
id, weatherId, countyName, date, weatehr, min, max
每次后台自动更新天气数据时候,解析json数据,获取所需数据封装成HIstoryWeather,然后保存,根据date和weatherId字段获取要保存的记录,如果存在的就更新,不存在的就天机记录

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
private void saveHistoryWeather(String result){
Gson gson = new Gson();
HeWeather5 heWeather5 = gson.fromJson(result,Root.class).getHeWeather5().get(0);
List<Daily_forecast> daily_forecastList = heWeather5.getDaily_forecast();
//for (Daily_forecast daily_forecast :daily_forecastList){
Daily_forecast today = daily_forecastList.get(0);
String date = today.getDate();
String weather = today.getCond().getTxt_d();
String weatherId = heWeather5.getBasic().getId();
String countyName = heWeather5.getBasic().getCity();
String min = today.getTmp().getMin();
String max = today.getTmp().getMax();
HistoryWeather historyWeather = new HistoryWeather();
historyWeather.setDate(date);
historyWeather.setWeatherId(weatherId);
historyWeather.setCountyName(countyName);
historyWeather.setWeather(weather);
historyWeather.setMin(min);
historyWeather.setMax(max);
historyWeather.saveOrUpdate("weatherId=? and date=?", weatherId, date);
//}
}

查找历史天气信息时候,默认是查找前30条记录,并且按date降序排列

1
2
3
4
5
6
7
8
9
10
List<HistoryWeather> historyWeatherList;
historyWeatherList = DataSupport.where("weatherId=? order by date desc", weatherId).find(HistoryWeather.class);
for (HistoryWeather historyWeather: historyWeatherList){
HistoryWeatherItem historyWeatherItem = new HistoryWeatherItem();
historyWeatherItem.setWeather(historyWeather.getWeather());
historyWeatherItem.setDate(historyWeather.getDate());
historyWeatherItem.setMax(historyWeather.getMax());
historyWeatherItem.setMin(historyWeather.getMin());
historyWeatherItemList.add(historyWeatherItem);
}

第一版功能细节

选择城市 ChooseCItyActivity

首先访问数据库中是否有所有省份数据,有则获取,没有则请求服务器,然后得到全国省份的json数据,保存到数据库中

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
private void queryProvince(){
toolbar.setTitle("中国");
provinceList = DataSupport.findAll(Province.class);
if (provinceList.size() > 0){
dataList.clear();
for (Province province:provinceList){
dataList.add(province.getProvinceName());
}
adapter.notifyDataSetChanged();
listView.setSelection(0);
currentLevel = LEVEL_PROVINCE;
}else {
String address = "http://guolin.tech/api/china/";
queryFromServer(address, "province");
}
}

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
public static boolean handleProvinceResponse(String response){
if (!TextUtils.isEmpty(response)){
try {
JSONArray allProvince = new JSONArray(response);
for (int i = 0; i<allProvince.length(); i++){
JSONObject provinceObject = allProvince.getJSONObject(i);
Province province = new Province();
province.setProvinceCode(provinceObject.getInt("id"));
province.setProvinceName(provinceObject.getString("name"));
province.save();
}
return true;
}catch (JSONException je){
je.printStackTrace();
}
}
return false;
}

然后解析json数据,设置到Listview中,点击时记录省份id, 然后根据这个id访问数据库中是否有该省份的城市信息,有则获取,没有则请求服务器,然后得到该省份所有城市的json数据,保存到数据库中

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
private void queryCity(){
toolbar.setTitle(selectedProvince.getProvinceName());
cityList = DataSupport.where("provinceCode = ? ", String.valueOf(selectedProvince.getProvinceCode())).find(City.class);
if (cityList.size() > 0){
dataList.clear();
for (City city: cityList){
dataList.add(city.getCityName());
}
adapter.notifyDataSetChanged();
listView.setSelection(0);
currentLevel = LEVEL_CITY;
}else {
int provinceCode = selectedProvince.getProvinceCode();
String address = "http://guolin.tech/api/china/" + provinceCode;
queryFromServer(address,"city");
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
public static boolean handleCityResponse(String response, int provinceCode){
if (!TextUtils.isEmpty(response)){
try {
Log.d(TAG, "handleCityResponse: \n response = " + response);
JSONArray allCity = new JSONArray(response);
for (int i = 0; i< allCity.length(); i++){
JSONObject cityObject = allCity.getJSONObject(i);
City city = new City();
city.setCityCode(cityObject.getInt("id"));
city.setCityName(cityObject.getString("name"));
city.setProvinceCode(provinceCode);
city.save();
}
return true;
}catch (JSONException je){
je.printStackTrace();
}
}
return false;
}

然后解析json数据,清空listview数据源, 然后加载该省的所有城市数据,设置到ListView中,点击时记录该市的id, 然后根据这个id访问数据库中是否存在该市的所有县数据,有则获取, 没有则请求服务器,然后得到该市所有县的json数据,保存到数据库中

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
private void queryCounty(){
toolbar.setTitle(selectedCity.getCityName());
Toast.makeText(this,"cityCode = "+ selectedCity.getCityCode() + "\n" +
"id = " + selectedCity.getId(),Toast.LENGTH_LONG ).show();
countyList = DataSupport.where("cityCode = ?", String.valueOf(selectedCity.getId())).find(County.class);
if (countyList.size() > 0 ){
dataList.clear();
for (County county: countyList){
dataList.add(county.getCountyName());
}
adapter.notifyDataSetChanged();
listView.setSelection(0);
currentLevel = LEVEL_COUNTY;
}else {
int provinceCode = selectedProvince.getProvinceCode();
int cityCode = selectedCity.getCityCode();
String address = "http://guolin.tech/api/china/" + provinceCode + "/" + cityCode;
queryFromServer(address,"county");
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
public static boolean handleCountyResponse(String response, int cityCode){
if (!TextUtils.isEmpty(response)) {
try {
JSONArray allCounty = new JSONArray(response);
for (int i = 0; i < allCounty.length(); i++) {
JSONObject countyObject = allCounty.getJSONObject(i);
County county = new County();
county.setCountyName(countyObject.getString("name"));
county.setWeatherId(countyObject.getString("weather_id"));
county.setCityCode(cityCode);
boolean successed = county.save();
if (successed) Log.d(TAG, "handleCountyResponse: save success");
else Log.d(TAG, "handleCountyResponse: save fail");
}
return true;
} catch (JSONException je) {
je.printStackTrace();
}
}
return false;
}

选择其中某一项, 获取weatherId和县名称countyName, 通过setResult返回父Activity中,
在父Activity中onActivityResult方法中, 把weatherId和countyName保存到Weather数据表中, 然后重新获取weather表中数据.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
listView.setOnItemClickListener(new AdapterView.OnItemClickListener() {
@Override
public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
if (currentLevel == LEVEL_PROVINCE){
selectedProvince = provinceList.get(position);
queryCity();
}else if (currentLevel == LEVEL_CITY){
selectedCity = cityList.get(position);
Toast.makeText(ChooseCityActivity.this,"id = " + selectedCity.getId() + "\n"+
"cityName = " + selectedCity.getCityName() + "\n" +
"cityCode = " + selectedCity.getCityCode() + "\n" +
"provinceCode = " + selectedCity.getProvinceCode(),Toast.LENGTH_SHORT).show();
queryCounty();
}else if (currentLevel == LEVEL_COUNTY){
selectedCounty = countyList.get(position);
Toast.makeText(ChooseCityActivity.this,"id = " + selectedCounty.getId() + "\n"+
"countyName = " + selectedCounty.getCountyName() + "\n" +
"weather_id = " + selectedCounty.getWeatherId(),Toast.LENGTH_SHORT).show();
Intent intent = new Intent();
intent.putExtra("weather_id", selectedCounty.getWeatherId());
intent.putExtra("county_name",selectedCounty.getCountyName());
setResult(RESULT_OK,intent);
finish();
}
}
});

回到父Activity进行操作

1
2
3
4
5
6
7
8
9
10
11
12
13
String weather_id = data.getStringExtra("weather_id");
List<Weather> weatherList = DataSupport.where("weatherId = ? ", weather_id).find(Weather.class);
if (weatherList != null && weatherList.size()==0){
Weather weather = new Weather();
weather.setCountyName(data.getStringExtra("county_name"));
weather.setWeatherId(data.getStringExtra("weather_id"));
weather.setIsShow("1");
weather.save();
titleList.add(weather.getCountyName());
WeatherFragment weatherFragment = new WeatherFragment(weather.getWeatherId());
weatherFragmentList.add(weatherFragment);
weatherPageAdapter.notifyDataSetChanged();
}

构造ViewPager天气页面数据源

在MainActivity中,访问数据库weather表, (条件:isShow=1,表示显示是否显示在主界面), 把查询结果存到List中,如果list大小为0, 则打开选择城市界面,如果存在数据,则遍历每个weather对象,创建WeatherFragment,并把weather对象中的weatherId传到Fragment的构造方法中,并添加到fragmengList中, WeatherFragment会根据这个weatherId获取天气信息,同时把weather的countyName添加到titleLists中用于设置每个pager的标题,,,然后通知adapter数据更新了,
pagerAdapter是通过fragmengList和titleList绑定数据的

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
private void showWeather(){
weatherList.clear();
weatherFragmentList.clear();
titleList.clear();
weatherList = DataSupport.where("isShow=?", "1").find(Weather.class);
if (weatherList.size() ==0){
//转到选择城市Activity
Intent intent = new Intent(this, ChooseCityActivity.class);
startActivityForResult(intent, REQUEST_CODE_CHOOSE_CITY);
}else {
for (Weather weather: weatherList) {
titleList.add(weather.getCountyName());
WeatherFragment weatherFragment = new WeatherFragment(weather.getWeatherId());
weatherFragmentList.add(weatherFragment);
}
weatherPageAdapter.notifyDataSetChanged();
}
}

选择城市后返回的操作

1
2
3
4
5
6
7
8
9
10
11
12
13
String weather_id = data.getStringExtra("weather_id");
List<Weather> weatherList = DataSupport.where("weatherId = ? ", weather_id).find(Weather.class);
if (weatherList != null && weatherList.size()==0){
Weather weather = new Weather();
weather.setCountyName(data.getStringExtra("county_name"));
weather.setWeatherId(data.getStringExtra("weather_id"));
weather.setIsShow("1");
weather.save();
titleList.add(weather.getCountyName());
WeatherFragment weatherFragment = new WeatherFragment(weather.getWeatherId());
weatherFragmentList.add(weatherFragment);
weatherPageAdapter.notifyDataSetChanged();
}

城市管理-CityManageActivity

该页面有一个RecyclerView,和一个FloatingActionButton, 其中recyclerView的item是一个CardView, 显示了该城市粗略的天气信息,如温度和天气,fab用于添加城市,可以通过左右滑动每一个卡片来删除数据,
请求数据库, 访问weather表的所有数据, 抽取其中所需的信息封装到CityItem中,然后添加到cityItemList中,作为RecyclerView的数据源,当成功选择一个城市并返回后,根据返回的weatherId访问数据库是否存在该数据,有则不操作,没有则添加到weather表中.以免产生冗余数据.当滑动删除城市后,会从数据表中删除掉这条记录,(改进的做法是滑动删除后显示一个Snackbar来确认操作)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
weatherList = DataSupport.findAll(Weather.class);
Gson gson = new Gson();
Root root;
for (Weather weather: weatherList){
CityItem cityItem = new CityItem();
cityItem.setIsShow(weather.getIsShow());
root = gson.fromJson(weather.getWeatherInfo(),Root.class);
if (root!=null){
cityItem.setTmp(root.getHeWeather5().get(0).getNow().getTmp());
cityItem.setWeather(root.getHeWeather5().get(0).getNow().getCond().getTxt());
cityItem.setCounty(weather.getCountyName());
cityItem.setWeatherId(weather.getWeatherId());
cityItemList.add(cityItem);
}
}
cityRecyclerAdapter.notifyDataSetChanged();

滑动删除后的操作-我还在进一步研究,不是很懂,因为是复制别人的代码
关联

1
2
3
4
//关联ItemTouchHelper和RecyclerView
ItemTouchHelper.Callback callback = new ItemTouchHelperCallback(cityRecyclerAdapter);
ItemTouchHelper mItemTouchHelper = new ItemTouchHelper(callback);
mItemTouchHelper.attachToRecyclerView(recyclerView);

ItemTouchHelperCallback:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
public class ItemTouchHelperCallback extends ItemTouchHelper.Callback {
private OnMoveAndSwipedListener moveAndSwipedListener;
public ItemTouchHelperCallback(OnMoveAndSwipedListener listener) {
this.moveAndSwipedListener = listener;
}
//设置拖动方向以及侧滑方向
@Override
public int getMovementFlags(RecyclerView recyclerView, RecyclerView.ViewHolder viewHolder) {
if (recyclerView.getLayoutManager() instanceof LinearLayoutManager) {
//单列的RecyclerView支持上下拖动和左右侧滑
final int dragFlags = ItemTouchHelper.UP | ItemTouchHelper.DOWN;
final int swipeFlags = ItemTouchHelper.START | ItemTouchHelper.END;
return makeMovementFlags(dragFlags, swipeFlags);
} else {
//多列的RecyclerView支持上下左右拖动和不支持左右侧滑
final int dragFlags = ItemTouchHelper.UP | ItemTouchHelper.DOWN | ItemTouchHelper.LEFT | ItemTouchHelper.RIGHT;
final int swipeFlags = 0;
return makeMovementFlags(dragFlags, swipeFlags);
}
}
//拖动item时会调用此方法
@Override
public boolean onMove(RecyclerView recyclerView, RecyclerView.ViewHolder viewHolder, RecyclerView.ViewHolder target) {
//如果两个item不是同一个类型的,不让他拖拽
if (viewHolder.getItemViewType() != target.getItemViewType()) {
return false;
}
moveAndSwipedListener.onItemMove(viewHolder.getAdapterPosition(), target.getAdapterPosition());
return true;
}
//侧滑item时会调用此方法
@Override
public void onSwiped(RecyclerView.ViewHolder viewHolder, int direction) {
moveAndSwipedListener.onItemDismiss(viewHolder.getAdapterPosition());
}
}

OnMoveAndSwipedListener:

1
2
3
4
5
6
7
public interface OnMoveAndSwipedListener {
boolean onItemMove(int fromPosition, int toPosition);
void onItemDismiss(int position);
}

监听到滑动删除执行以下方法

1
2
3
4
5
6
7
8
9
10
11
12
@Override
public boolean onItemMove(int fromPosition, int toPosition) {
notifyItemMoved(fromPosition, toPosition);
return true;
}
@Override
public void onItemDismiss(int position) {
DataSupport.deleteAll(Weather.class,"weatherId=?",cityItemList.get(position).getWeatherId());
cityItemList.remove(position);
notifyItemRemoved(position);
}

显示天气-WeatherFragmeng

显示天气信息是在WeatherFragment中完成的, 也就是每个页面. 从构造方法中获取到该所显示城市的天气数据, 根据这个weatherId访问数据库中该weatherId所在记录是否有weatherInfo信息,如果有则获取该天气信息的json数据, 没有则向服务器请求数据,获取天气信息,成功获取信息之后保存到数据库中.

1
2
3
4
5
6
7
8
weatherList = DataSupport.where("weatherId = ? ", weather_id).find(Weather.class);
if (weatherList != null && weatherList.size()>0){
if (TextUtils.isEmpty(weatherList.get(0).getWeatherInfo())) sendWeatherInfoRequest();
else handleWeatherInfoResponse(weatherList.get(0).getWeatherInfo());
}else {
//get dada from internet
sendWeatherInfoRequest();
}

获取后保存到数据库

1
2
3
4
5
6
7
8
9
10
String responseText = response.body().string();
Weather weather = new Weather();
weather.setWeatherInfo(responseText);
weather.updateAll("weatherId=?", weather_id);
getActivity().runOnUiThread(new Runnable() {
@Override
public void run() {
handleWeatherInfoResponse(responseText);
}
});


持续更新……..
欢迎大家共同学习共同成长…..