听说郭大的此系列的第二本快要出版了。
首先我们要做一个app需要实现哪些功能?
- 省、市、县的显示
- 以上是可选的
- 查看某地区的天气
- 提供手动更新天气和自动更新
由于书中提供的天气地址已经过时,基本不能用。天气更新基本就失效了。 所以我找了一个 和风天气api 注册下有每天有3000次免费试用
省 市 县数据,整理了sql 项目地址
包结构
- activity 活动
- db 操作数据的
- model 数据模型
- util 工具类
- service 服务
- receiver 接收
初始化数据
- Province City County 三个sql脚本放入 assets 文件夹下
- 创建表结构
- 插入表数据
怎么玩?
- extends SQLiteOpenHelper
- onCreate() 方法中执行一系列操作
public class DbHelper extends SQLiteOpenHelper{
private static final String TAG = DbHelper.class.getSimpleName();
public static final String DB_NAME = "weather.db";
private Context mContext;
public DbHelper(Context context, String name, SQLiteDatabase.CursorFactory factory, int version) {
super(context, name, factory, version);
mContext = context;
}
/**
* Province表建表语句
*/
public static final String CREATE_PROVINCE = "create table Province ("
+ "id integer primary key autoincrement, "
+ "province_name text, "
+ "province_code text)";
/**
* City表建表语句
*/
public static final String CREATE_CITY = "create table City ("
+ "id integer primary key autoincrement, "
+ "city_name text, "
+ "city_code text, "
+ "province_code text)";
/**
* County表建表语句
*/
public static final String CREATE_COUNTY = "create table County ("
+ "id integer primary key autoincrement, "
+ "county_name text, "
+ "county_code text, "
+ "city_code text)";
@Override
public void onCreate(SQLiteDatabase db) {
// 创建表
db.execSQL(CREATE_PROVINCE); // 创建Province表
db.execSQL(CREATE_CITY); // 创建City表
db.execSQL(CREATE_COUNTY); // 创建County表
// 初始化数据
executeAssetsSQL(db, "Province.sql");
executeAssetsSQL(db, "City.sql");
executeAssetsSQL(db, "County.sql");
Log.d(TAG, "init city data success!!!");
}
/**
* 读取数据库文件(.sql),并执行sql语句
* */
private void executeAssetsSQL(SQLiteDatabase db, String sqlFileName) {
BufferedReader in = null;
try {
in = new BufferedReader(new InputStreamReader(mContext.getAssets().open(sqlFileName)));
String line;
while ((line = in.readLine()) != null) {
db.execSQL(line);
}
} catch (IOException e) {
Log.e(TAG, e.toString());
} finally {
try {
if (in != null)
in.close();
} catch (IOException e) {
Log.e(TAG, e.toString());
}
}
}
@Override
public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
}
}
读取sqlite数据
实体
set... get... 就省了
// 省
public class Province {
private int id;
private String provinceName;
private String provinceCode;
}
// 市
public class City {
private int id;
private String cityName;
private String cityCode;
private String provinceCode; // 对应省
}
// 县
public class County {
private int id;
private String countyName;
private String countyCode;
private String cityCode; //对应市
}
查询操作
封装到 WeatherDb
public class WeatherDb {
public static final int VERSION = 1;
private SQLiteDatabase db;
private static WeatherDb weatherDb;
// 单例
private WeatherDb(Context context) {
DbHelper dbHelper = new DbHelper(context, DbHelper.DB_NAME, null, VERSION);
db = dbHelper.getWritableDatabase();
}
/**
* 获取 实例
* @param context
* @return
*/
public synchronized static WeatherDb getInstance(Context context) {
if (weatherDb == null) {
weatherDb = new WeatherDb(context);
}
return weatherDb;
}
/**
* 从数据库读取全国所有的省份信息。
*/
public List<Province> loadProvinces() {
List<Province> list = new ArrayList<Province>();
Cursor cursor = db.query("Province", null, null, null, null, null, "province_code asc");
if (cursor.moveToFirst()) {
do {
Province province = new Province();
province.setId(cursor.getInt(cursor.getColumnIndex("id")));
province.setProvinceName(cursor.getString(cursor
.getColumnIndex("province_name")));
province.setProvinceCode(cursor.getString(cursor.getColumnIndex("province_code")));
list.add(province);
} while (cursor.moveToNext());
}
cursor.close();
return list;
}
/**
* 从数据库读取某省下所有的城市信息。
*/
public List<City> loadCities(String provinceCode) {
List<City> list = new ArrayList<City>();
Cursor cursor = db.query("City", null, "province_code = ?",
new String[] { provinceCode }, null, null, null);
if (cursor.moveToFirst()) {
do {
City city = new City();
city.setId(cursor.getInt(cursor.getColumnIndex("id")));
city.setCityName(cursor.getString(cursor
.getColumnIndex("city_name")));
city.setCityCode(cursor.getString(cursor
.getColumnIndex("city_code")));
city.setProvinceCode(provinceCode);
list.add(city);
} while (cursor.moveToNext());
}
cursor.close();
return list;
}
/**
* 从数据库读取某城市下所有的县信息。
*/
public List<County> loadCounties(String cityCode) {
List<County> list = new ArrayList<County>();
Cursor cursor = db.query("County", null, "city_code = ?",
new String[] { cityCode }, null, null, null);
if (cursor.moveToFirst()) {
do {
County county = new County();
county.setId(cursor.getInt(cursor.getColumnIndex("id")));
county.setCountyName(cursor.getString(cursor
.getColumnIndex("county_name")));
county.setCountyCode(cursor.getString(cursor
.getColumnIndex("county_code")));
county.setCityCode(cityCode);
list.add(county);
} while (cursor.moveToNext());
}
cursor.close();
return list;
}
}
HttpUtil
用 URL 也好 , HttpClient 也行。
选择地区
布局
choose_area.xml
- 显示标题 选择的地方 TextView
- 显示列表 ListView
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical" android:layout_width="match_parent"
android:layout_height="match_parent">
<RelativeLayout
android:layout_width="match_parent"
android:layout_height="50dp"
android:background="#484E61">
<TextView
android:id="@+id/title_text"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_centerInParent="true"
android:textColor="#fff"
android:textSize="24sp"/>
</RelativeLayout>
<ListView
android:id="@+id/list_view"
android:layout_width="match_parent"
android:layout_height="match_parent">
</ListView>
</LinearLayout>
地区活动
为了识别 在哪一层级,是 省呢,还是市呢,还是县呢? 定义三个常量:
public static final int LEVEL_PROVINCE = 0;
public static final int LEVEL_CITY = 1;
public static final int LEVEL_COUNTY = 2;
然后得知道当前到底是哪个被选择了呢?
扫描二维码关注公众号,回复:
436754 查看本文章
/**
* 当前选中的级别
*/
private int currentLevel;
/**
* 当前列表数据
*/
private List<String> dataList = new ArrayList<String>();
主要代码 ChooseAreaActivity
... 省略变量
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
requestWindowFeature(Window.FEATURE_NO_TITLE);
setContentView(R.layout.choose_area);
titleView = (TextView) findViewById(R.id.title_text);
listView = (ListView) findViewById(R.id.list_view);
adapter = new ArrayAdapter<>(this, android.R.layout.simple_list_item_1, dataList);
listView.setAdapter(adapter);
// 获取实例 ,初始化数据
weatherDb = WeatherDb.getInstance(this);
// 加载省级数据
queryProvinces();
}
/**
* 查询全部省,同时设置当前级别和列表数据
*/
private void queryProvinces() {
provinceList = weatherDb.loadProvinces();
if (provinceList != null && provinceList.size() > 0 ) {
dataList.clear();
for (Province province : provinceList) {
dataList.add(province.getProvinceName());
}
adapter.notifyDataSetChanged();
listView.setSelection(0);
titleView.setText("中国");
currentLevel = LEVEL_PROVINCE;
}
}
这样省的列表就展示出来了
点击切换省市县数据
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);
queryCities();
// 市
}else if (currentLevel == LEVEL_CITY){
selectedCity = cityList.get(position);
queryCounties();
// 县
}else if (currentLevel == LEVEL_COUNTY) {
String countyCode = countyList.get(position).getCountyCode();
// ...具体待下一步操作
}
}
});
// 根据省code加载市数据,同时设置当前级别和列表数据
private void queryCities() {
cityList = weatherDb.loadCities(selectedProvince.getProvinceCode());
if (cityList != null && cityList.size() > 0 ) {
dataList.clear();
for (City city : cityList) {
dataList.add(city.getCityName());
}
adapter.notifyDataSetChanged();
listView.setSelection(0);
titleView.setText(selectedProvince.getProvinceName());
currentLevel = LEVEL_CITY;
}
}
// 根据市code加载县数据,同时设置当前级别和列表数据
private void queryCounties() {
countyList = weatherDb.loadCounties(selectedCity.getCityCode());
if (countyList != null && countyList.size() > 0) {
dataList.clear();
for (County county : countyList) {
dataList.add(county.getCountyName());
}
adapter.notifyDataSetChanged();
listView.setSelection(0);
titleView.setText(selectedCity.getCityName());
currentLevel = LEVEL_COUNTY;
}
}
别忘了配置AndroidManifest
<activity
android:name="com.coolweather.app.activity.ChooseAreaActivity"
android:label="@string/app_name" >
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
写到这里基本 切换省市县的效果就出来了
到县就可以根据县的code去请求api 查询相关天气的数据,然后展示出来。
显示天气信息
封装http请求的天气数据
public class WeatherInfoUtil {
/**
* 解析服务器返回的JSON数据,并将解析出的数据存储到本地。
*/
public static void handleWeatherResponse(Context context, String response) {
try {
JSONObject jsonObject = new JSONObject(response);
JSONArray jsonArray = jsonObject.getJSONArray("HeWeather data service 3.0");
JSONObject weatherInfo = jsonArray.getJSONObject(0);
String status = weatherInfo.getString("status");
if ("ok".equals(status)) {
// 基础信息
JSONObject basic = weatherInfo.getJSONObject("basic");
// 现在天气
JSONObject now = weatherInfo.getJSONObject("now");
// 天气预报, 1 -7 天
JSONArray daily_forecast = weatherInfo.getJSONArray("daily_forecast");
// 小时预报 3小时
JSONArray hourly_forecast = weatherInfo.getJSONArray("hourly_forecast");
// 空气质量 有可能没有
JSONObject aqi = weatherInfo.optJSONObject("aqi");
// 提醒 有可能没有
JSONObject suggestion = weatherInfo.optJSONObject("suggestion");
// =================基础信息=================
// 城市
String cityName = basic.getString("city");
// ID
String weatherCode = basic.getString("id");
JSONObject update = basic.getJSONObject("update");
// 更新时间
String publishTime = update.getString("loc");
// =================现在天气===============
// 当前温度
String temp = now.getString("tmp");
// 当前天气描述
JSONObject cond = now.getJSONObject("cond");
String weatherDesp = cond.getString("txt");
JSONObject wind = now.getJSONObject("wind");
// 风向
String dir = wind.getString("dir");
// 风力
String sc = wind.getString("sc");
// =================天气预报===============
for (int i = 0; i < daily_forecast.length(); i++) {
JSONObject daily = daily_forecast.getJSONObject(i);
String date = daily.getString("date");
JSONObject condDaily = daily.getJSONObject("cond");
String txt_d = condDaily.getString("txt_d");
String txt_n = condDaily.getString("txt_n");
JSONObject tmpDaily = daily.getJSONObject("tmp");
String minTemp = tmpDaily.getString("min");
String maxTemp = tmpDaily.getString("max");
JSONObject windDaily = daily.getJSONObject("wind");
String dirDaily = windDaily.getString("dir");
String scDaily = windDaily.getString("sc");
}
saveWeatherInfo(context, cityName, weatherCode, temp, weatherDesp, publishTime, dir, sc);
}
if ("unknown city".equals(status)) {
saveWeatherInfo(context, null, null, null, "未知城市", null, null, null);
}
if ("no more requests".equals(status)) {
saveWeatherInfo(context, null, null, null, "超过访问次数", null , null, null);
}
} catch (JSONException e) {
e.printStackTrace();
}
}
/**
* 将服务器返回的所有天气信息存储到SharedPreferences文件中。
*/
private static void saveWeatherInfo(Context context, String cityName, String weatherCode, String temp, String weatherDesp, String publishTime,
String dir, String sc) {
SimpleDateFormat sdf = new SimpleDateFormat("yyyy年M月d日", Locale.CHINA);
SharedPreferences.Editor editor = PreferenceManager.getDefaultSharedPreferences(context).edit();
editor.putBoolean("city_selected", true);
editor.putString("city_name", cityName);
editor.putString("weather_code", weatherCode);
editor.putString("temp", temp + "℃");
editor.putString("weather_desp", weatherDesp);
editor.putString("publish_time", publishTime);
editor.putString("current_date", sdf.format(new Date()));
editor.putString("wind_dir", dir);
editor.putString("wind_sc", sc + "级");
editor.commit();
}
}
以上就是解析json 数据,然后存到SharedPreferences 中,key-value 形式。
天气布局
来想想我们要什么?
- 标题
- 县 居中
- 返回重新选择
- 刷新数据按钮
- 数据时间
- 天气信息
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical" android:layout_width="match_parent"
android:layout_height="match_parent">
<!--标题 城市名称-->
<RelativeLayout
android:layout_width="match_parent"
android:layout_height="50dp"
android:background="#484E61">
<Button
android:id="@+id/switch_city"
android:layout_width="30dp"
android:layout_height="30dp"
android:layout_centerVertical="true"
android:layout_marginLeft="10dp"
android:background="@drawable/home"/>
<TextView
android:id="@+id/city_name"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_centerInParent="true"
android:textColor="#fff"
android:textSize="24sp"
/>
<Button
android:id="@+id/refresh_weather"
android:layout_width="30dp"
android:layout_height="30dp"
android:layout_alignParentRight="true"
android:layout_centerVertical="true"
android:layout_marginRight="10dp"
android:background="@drawable/refresh"/>
</RelativeLayout>
<!--天气信息-->
<RelativeLayout
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_weight="1"
android:background="#27A5F9">
<TextView
android:id="@+id/publish_text"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentRight="true"
android:layout_marginRight="10dp"
android:layout_marginTop="10dp"
android:textColor="#fff"
android:textSize="24sp"
/>
<LinearLayout
android:id="@+id/weather_info_layout"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_centerInParent="true"
android:orientation="vertical">
<!--描述-->
<TextView
android:id="@+id/weather_desp"
android:layout_width="wrap_content"
android:layout_height="60dp"
android:layout_gravity="center_horizontal"
android:gravity="center"
android:textColor="#fff"
android:textSize="40sp"
/>
<!--温度-->
<TextView
android:id="@+id/temp"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center_horizontal"
android:gravity="center"
android:textColor="#FFF"
android:textSize="40sp"
/>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="horizontal"
>
<TextView
android:id="@+id/wind_dir"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textColor="#fff"
android:textSize="18sp"
android:gravity="center"
/>
<TextView
android:id="@+id/wind_sc"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textColor="#fff"
android:textSize="18sp"
android:gravity="center"
/>
</LinearLayout>
<!--日期-->
<TextView
android:id="@+id/current_date"
android:layout_width="wrap_content"
android:layout_height="40dp"
android:gravity="center"
android:textColor="#fff"
android:textSize="18sp"
/>
</LinearLayout>
</RelativeLayout>
</LinearLayout>
天气活动 WeatherActivity
首先 从 选择地区活动 到 天气活动 并把 code传递过来
ChooseAreaActivity
//
}else if (currentLevel == LEVEL_COUNTY) {
String countyCode = countyList.get(position).getCountyCode();
Intent intent = new Intent(ChooseAreaActivity.this, WeatherActivity.class);
intent.putExtra("county_code", countyCode);
startActivity(intent);
finish();
}
但是如果之前已经选中,那就不要再加载了,直接跳到天气。 这个可能一开始不太理解,可以后面再加。
isFromWeatherActivity = getIntent().getBooleanExtra("from_weather_
activity", false);
// 是否选中天气 , 选择就不用再加载了
SharedPreferences preferences = PreferenceManager.getDefaultSharedPreferences(this);
if (preferences.getBoolean("city_selected", false) && !isFromWeatherActivity) {
Intent intent = new Intent(this, WeatherActivity.class);
startActivity(intent);
finish();
return;
}
WeatherActivity
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
requestWindowFeature(Window.FEATURE_NO_TITLE);
setContentView(R.layout.weather_layout);
// 初始化各控件
weatherInfoLayout = (LinearLayout) findViewById(R.id.weather_info_layout);
cityNameText = (TextView) findViewById(R.id.city_name);
publishText = (TextView) findViewById(R.id.publish_text);
weatherDespText = (TextView) findViewById(R.id.weather_desp);
tempText = (TextView) findViewById(R.id.temp);
currentDateText = (TextView) findViewById(R.id.current_date);
switchCity = (Button) findViewById(R.id.switch_city);
refreshWeather = (Button) findViewById(R.id.refresh_weather);
windDir = (TextView) findViewById(R.id.wind_dir);
windSc = (TextView) findViewById(R.id.wind_sc);
switchCity.setOnClickListener(this);
refreshWeather.setOnClickListener(this);
// ChooseAreaActivity 传过来的值
String countyCode = getIntent().getStringExtra("county_code");
// 有代号显示 选择城市的天气,没有就本地已存储的天气
if (!TextUtils.isEmpty(countyCode)) {
publishText.setText("同步中...");
// 隐藏
weatherInfoLayout.setVisibility(View.INVISIBLE);
cityNameText.setVisibility(View.VISIBLE);
// 查天气
queryWeatherInfo(countyCode);
} else {
showWeather();
}
}
/**
* 显示天气,直接从 SharedPreferences 去取,取不到就存。
*/
private void showWeather() {
SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(this);
cityNameText.setText( prefs.getString("city_name", ""));
tempText.setText(prefs.getString("temp", ""));
weatherDespText.setText(prefs.getString("weather_desp", ""));
String publish_time = prefs.getString("publish_time", "");
publish_time = publish_time.split(" ").length > 1 ? publish_time.split(" ")[1] : "";
publishText.setText("今天" + publish_time + "发布");
windDir.setText(prefs.getString("wind_dir", ""));
windSc.setText(prefs.getString("wind_sc", ""));
currentDateText.setText(prefs.getString("current_date", ""));
// 显示
weatherInfoLayout.setVisibility(View.VISIBLE);
cityNameText.setVisibility(View.VISIBLE);
}
// 查天气信息
private void queryWeatherInfo(String weatherCode) {
String address = String.format("https://api.heweather.com/x3/weather?cityid=CN%s&key=%s", weatherCode, "填写api key");
Log.d(TAG, address);
queryFromServer(address);
}
/**
* 从服务器 获取 最新的天气数据
* @param address
*/
private void queryFromServer(String address) {
HttpUtil.sendHttpRequest(address, new HttpCallbackListener() {
@Override
public void onFinish(String response) {
WeatherInfoUtil.handleWeatherResponse(WeatherActivity.this, response);
runOnUiThread(new Runnable() {
@Override
public void run() {
showWeather();
}
});
}
@Override
public void onError(final Exception e) {
runOnUiThread(new Runnable() {
@Override
public void run() {
e.printStackTrace();
publishText.setText("同步失败");
}
});
}
});
}
// 点击返回 和 刷新效果
@Override
public void onClick(View v) {
switch (v.getId()) {
case R.id.switch_city:
// 选择城市,返回上一层
Intent intent = new Intent(this, ChooseAreaActivity.class);
intent.putExtra("from_weather_activity", true);
startActivity(intent);
finish();
break;
case R.id.refresh_weather:
// 刷新天气
publishText.setText("同步中...");
SharedPreferences preferences = PreferenceManager.getDefaultSharedPreferences(this);
String weatherCode = preferences.getString("weather_code", "");
// 获取id 重新去访问天气信息
if (!TextUtils.isEmpty(weatherCode)) {
queryWeatherInfo(weatherCode);
}
break;
default:
break;
}
}
加入权限和注册活动
<uses-permission android:name="android.permission.INTERNET"/>
<activity android:name=".activity.WeatherActivity"/>
后台定时更新活动
这部分功能不加也行,后续扩展吧。
public class AutoUpdateService extends Service{
@Nullable
@Override
public IBinder onBind(Intent intent) {
return null;
}
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
new Thread(new Runnable() {
@Override
public void run() {
updateWeather();
}
}).start();
AlarmManager alarmManager = (AlarmManager) getSystemService(ALARM_SERVICE);
// 8h
int anHour = 8 * 60 * 60 * 1000;
long triggerAtTime = SystemClock.elapsedRealtime() + anHour;
Intent i = new Intent(this, AutoUpdateReceiver.class);
PendingIntent pi = PendingIntent.getBroadcast(this, 0, i, 0);
alarmManager.set(AlarmManager.ELAPSED_REALTIME_WAKEUP, triggerAtTime, pi);
return super.onStartCommand(intent, flags, startId);
}
private void updateWeather() {
SharedPreferences preferences = PreferenceManager.getDefaultSharedPreferences(this);
String weatherCode = preferences.getString("weather_code", "");
String address = String.format("https://api.heweather.com/x3/weather?cityid=CN%s&key=%s", weatherCode, "填写app key");
HttpUtil.sendHttpRequest(address, new HttpCallbackListener() {
@Override
public void onFinish(String s) {
WeatherInfoUtil.handleWeatherResponse(AutoUpdateService.this, s);
}
@Override
public void onError(Exception e) {
e.printStackTrace();
}
});
}
}
来个定时接收
public class AutoUpdateReceiver extends BroadcastReceiver{
@Override
public void onReceive(Context context, Intent intent) {
Intent i = new Intent(context, AutoUpdateService.class);
context.startActivity(i);
}
}
别忘注册
<service android:name=".service.AutoUpdateService"/>
<receiver android:name=".service.AutoUpdateReceiver"/>
在哪里启动这个服务呢?
成功载入天气数据的时候: showWeather()方法内
// 启动服务
Intent intent = new Intent(this, AutoUpdateService.class);
startService(intent);
差不多就完了。
其实这个很简陋,只显示今天的,可以搞个折线图,把7天的天气预报也显示出来。
还有一些天气的图标,也可以获取到,这个api还是很全的。