天氣預報APP(1)
一個天氣預報APP至少應該具備以下功能:
*可以羅列出全國所有的省、市、縣;
*可以查看全國任意城市的天氣信息;
*可以自由的切換城市,去查看其他城市的天氣;
*提供手動更新以及後臺自動更新天氣的功能;
這裏使用和風天氣作為天氣預報來源,全國省市縣的數據信息這裏使用的是《第一行代碼》的作者郭霖大佬架設的服務器。
具體就是:想要羅列出中國所有的省份,只需要訪問這個地址:http://guolin.tech/api/china,服務器會返回一段JSON格式的數據,其中包括了中國所有省份的名稱及省份的id:
[{"id":1,"name":"北京"},{"id":2,"name":"上海"},{"id":3,"name":"天津"},
{"id":4,"name":"重慶"},{"id":5,"name":"香港"},{"id":6,"name":"澳門"},
{"id":7,"name":"臺灣"},{"id":8,"name":"黑龍江"},{"id":9,"name":"吉林"},
{"id":10,"name":"遼寧"},{"id":11,"name":"內蒙古"},{"id":12,"name":"河北"},
{"id":13,"name":"河南"},{"id":14,"name":"山西"},{"id":15,"name":"山東"},
{"id":16,"name":"江蘇"},{"id":17,"name":"浙江"},{"id":18,"name":"福建"},
{"id":19,"name":"江西"},{"id":20,"name":"安徽"},{"id":21,"name":"湖北"},
{"id":22,"name":"湖南"},{"id":23,"name":"廣東"},{"id":24,"name":"廣西"},
{"id":25,"name":"海南"},{"id":26,"name":"貴州"},{"id":27,"name":"雲南"},
{"id":28,"name":"四川"},{"id":29,"name":"西藏"},{"id":30,"name":"陜西"},
{"id":31,"name":"寧夏"},{"id":32,"name":"甘肅"},{"id":33,"name":"青海"},
{"id":34,"name":"新疆"}]
如果想要河北省有那些城市,就把id加上:http://guolin.tech/api/china/12
[{"id":57,"name":"石家莊"},{"id":58,"name":"保定"},{"id":59,"name":"張家口"},
{"id":60,"name":"唐山"},{"id":61,"name":"廊坊"},{"id":62,"name":"滄州"},
{"id":63,"name":"衡水"},{"id":64,"name":"邢臺"},{"id":65,"name":"邯鄲"},
{"id":66,"name":"秦皇島"}]
如果想要知道秦皇島有那些縣,就再加上id:http://guolin.tech/api/china/12/66
[{"id":557,"name":"秦皇島","weather_id":"CN101091101"},{"id":558,"name":"青龍","weather_id":"CN101091102"},
{"id":559,"name":"昌黎","weather_id":"CN101091103"},{"id":560,"name":"撫寧","weather_id":"CN101091104"},
{"id":561,"name":"盧龍","weather_id":"CN101091105"},{"id":562,"name":"北戴河","weather_id":"CN101091106"}]
以上是全國省市縣的數據,然後是天氣數據的api的使用:
https://free-api.heweather.com/v5/weather?city=yourcity&key=yourkey
這是一個免費的使用方法,一天可以訪問4000次,不過要先申請。yourcity部分就填之前獲取到的id即可,key的話申請就有了。
https://free-api.heweather.com/v5/weather?city=CN101091101&key=32d1c829ed7d483086f4f5b4d5947cef
這樣就能查詢到秦皇島的天氣情況,返回的數據就比較復雜了,官方的例子就是:
{ "HeWeather5": [ { "alarms": [ { "level": "藍色", "stat": "預警中", "title": "山東省青島市氣象臺發布大風藍色預警", "txt": "青島市氣象臺2016年08月29日15時24分繼續發布大風藍色預警信號:預計今天下午到明天,我市北風風力海上6到7級陣風9級,陸地4到5陣風7級,請註意防範。", "type": "大風" } ], "aqi": { "city": { "aqi": "60", "co": "0", "no2": "14", "o3": "95", "pm10": "67", "pm25": "15", "qlty": "良", //共六個級別,分別:優,良,輕度汙染,中度汙染,重度汙染,嚴重汙染 "so2": "10" } }, "basic": { "city": "青島", "cnty": "中國", "id": "CN101120201", "lat": "36.088000", "lon": "120.343000", "prov": "山東" //城市所屬省份(僅限國內城市) "update": { "loc": "2016-08-30 11:52", "utc": "2016-08-30 03:52" } }, "daily_forecast": [ { "astro": { "mr": "03:09", "ms": "17:06", "sr": "05:28", "ss": "18:29" }, "cond": { "code_d": "100", "code_n": "100", "txt_d": "晴", "txt_n": "晴" }, "date": "2016-08-30", "hum": "45", "pcpn": "0.0", "pop": "8", "pres": "1005", "tmp": { "max": "29", "min": "22" }, "vis": "10", "wind": { "deg": "339", "dir": "北風", "sc": "4-5", "spd": "24" } } ], "hourly_forecast": [ { "cond": { "code": "100", "txt": "晴" }, "date": "2016-08-30 12:00", "hum": "47", "pop": "0", "pres": "1006", "tmp": "29", "wind": { "deg": "335", "dir": "西北風", "sc": "4-5", "spd": "36" } } ], "now": { "cond": { "code": "100", "txt": "晴" }, "fl": "28", "hum": "41", "pcpn": "0", "pres": "1005", "tmp": "26", "vis": "10", "wind": { "deg": "330", "dir": "西北風", "sc": "6-7", "spd": "34" } }, "status": "ok", "suggestion": { "comf": { "brf": "較舒適", "txt": "白天天氣晴好,您在這種天氣條件下,會感覺早晚涼爽、舒適,午後偏熱。" }, "cw": { "brf": "較不宜", "txt": "較不宜洗車,未來一天無雨,風力較大,如果執意擦洗汽車,要做好蒙上汙垢的心理準備。" }, "drsg": { "brf": "熱", "txt": "天氣熱,建議著短裙、短褲、短薄外套、T恤等夏季服裝。" }, "flu": { "brf": "較易發", "txt": "雖然溫度適宜但風力較大,仍較易發生感冒,體質較弱的朋友請註意適當防護。" }, "sport": { "brf": "較適宜", "txt": "天氣較好,但風力較大,推薦您進行室內運動,若在戶外運動請註意防風。" }, "trav": { "brf": "適宜", "txt": "天氣較好,風稍大,但溫度適宜,是個好天氣哦。適宜旅遊,您可以盡情地享受大自然的無限風光。" }, "uv": { "brf": "強", "txt": "紫外線輻射強,建議塗擦SPF20左右、PA++的防曬護膚品。避免在10點至14點暴露於日光下。" } } } ] }數據返回示例
首先是這次項目需要依賴的庫的聲明:
compile ‘org.litepal.android:core:1.6.0‘ compile ‘com.squareup.okhttp3:okhttp:3.9.0‘ compile ‘com.google.code.gson:gson:2.8.0‘ compile ‘com.github.bumptech.glide:glide:4.0.0‘
Litepal用於數據庫操作。OkHttp用於進行網絡請求,GSON用於解析獲得的JSON數據,Glide用於加載展示圖片
1、建立數據庫和表
先建三張表,分別是省,市,縣:
Province:
public class Province extends DataSupport { private int id; private String provinceName;//省的名字 private int provinceCode;//省的代號 public int getId() { return id; } public void setId(int id) { this.id = id; } public String getProvinceName() { return provinceName; } public void setProvinceName(String provinceName) { this.provinceName = provinceName; } public int getProvinceCode() { return provinceCode; } public void setProvinceCode(int provinceCode) { this.provinceCode = provinceCode; } }省的數據信息
City:
public class City extends DataSupport { private int id; private String cityName;//城市的名字 private int cityCode;//城市的代號 private int provinceId;//城市所在省的id值 public int getId() { return id; } public void setId(int id) { this.id = id; } public String getCityName() { return cityName; } public void setCityName(String cityName) { this.cityName = cityName; } public int getCityCode() { return cityCode; } public void setCityCode(int cityCode) { this.cityCode = cityCode; } public int getProvinceId() { return provinceId; } public void setProvinceId(int provinceId) { this.provinceId = provinceId; } }市的數據信息
County:
public class County extends DataSupport { private int id; private String countyName;//縣的名字 private String weatherId;//天氣的id private int cityId;//所屬市的id public int getId() { return id; } public void setId(int id) { this.id = id; } public String getCountyName() { return countyName; } public void setCountyName(String countyName) { this.countyName = countyName; } public String getWeatherId() { return weatherId; } public void setWeatherId(String weatherId) { this.weatherId = weatherId; } public int getCityId() { return cityId; } public void setCityId(int cityId) { this.cityId = cityId; } }縣的數據信息
然後是Litepal的建表操作,在app/src/main目錄下新建assets目錄,在其中新建Litepal.xml文件
<?xml version="1.0" encoding="utf-8"?> <litepal> <dbname value = "cool_weather"></dbname> <version value = "1"></version> <list> <mapping class = "xbt.exp20.db.Province"></mapping> <mapping class = "xbt.exp20.db.City"></mapping> <mapping class = "xbt.exp20.db.County"></mapping> </list> </litepal>
再然後是配置LitepalApplication:
<manifest xmlns:android="http://schemas.android.com/apk/res/android" package="xbt.exp20"> ... <application android:name="org.litepal.LitePalApplication" ... </application> </manifest>
這樣三個類加上Litepal的配置,數據庫和表會在首次執行任意數據庫操作的時候自動創建。
2、遍歷全國省市縣數據
首先是新建一個HttpUtil類:
/** * 發起一條HTTP請求,傳入地址,並註冊一個回調來處理服務器響應 */ public class HttpUtil { public static void sendOkHttpRequest(String address, okhttp3.Callback callback){ OkHttpClient client = new OkHttpClient(); Request request = new Request.Builder().url(address).build(); client.newCall(request).enqueue(callback); } }
然後是一個Utility類:
public class Utility { /** *解析和處理服務器返回的省級數據 */ public static boolean handleProvinceResponse(String response){ if(!TextUtils.isEmpty(response)){ //如果字符序列不為空或長度為0 try{ JSONArray allProvinces = new JSONArray(response); for(int i = 0; i < allProvinces.length(); i++){ JSONObject provinceObject = allProvinces.getJSONObject(i); Province province = new Province(); province.setProvinceName(provinceObject.getString("name")); province.setProvinceCode(provinceObject.getInt("id")); province.save(); } return true; }catch (JSONException e){ e.printStackTrace(); } } return false; } /** *解析和處理服務器返回的市級數據 */ public static boolean handleCityResponse(String response, int provinceId){ if(!TextUtils.isEmpty(response)){ //如果字符序列不為空或長度為0 try{ JSONArray allCities = new JSONArray(response); for(int i = 0; i < allCities.length(); i++){ JSONObject CityObject = allCities.getJSONObject(i); City city = new City(); city.setCityName(CityObject.getString("name")); city.setCityCode(CityObject.getInt("id")); city.setProvinceId(provinceId); city.save(); } return true; }catch (JSONException e){ e.printStackTrace(); } } return false; } /** *解析和處理服務器返回的縣級數據 */ public static boolean handleCountyResponse(String response, int cityId){ if(!TextUtils.isEmpty(response)){ //如果字符序列不為空或長度為0 try{ JSONArray allCounties = new JSONArray(response); for(int i = 0; i < allCounties.length(); i++){ JSONObject countyObject = allCounties.getJSONObject(i); County county = new County(); county.setCountyName(countyObject.getString("name")); county.setWeatherId(countyObject.getString("weather_id")); county.setCityId(cityId); county.save(); } return true; }catch (JSONException e){ e.printStackTrace(); } } return false; } }解析處理數據
因為遍歷全國省市的功能經常復用,所以寫在碎片裏面:
先是一個choose_area.xml作為碎片fragment的布局:
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:orientation="vertical" android:layout_width="match_parent" android:layout_height="match_parent" android:background="#fff"> <RelativeLayout android:layout_width="match_parent" android:layout_height="?attr/actionBarSize" android:background="?attr/colorPrimary"> <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="20sp"/> <Button android:id="@+id/back_button" android:layout_width="25dp" android:layout_height="25dp" android:layout_marginLeft="10dp" android:layout_alignParentLeft="true" android:layout_centerVertical="true" android:background="@drawable/ic_back"/> </RelativeLayout> <ListView android:id="@+id/list_view" android:layout_width="match_parent" android:layout_height="match_parent"></ListView> </LinearLayout>fragment的布局
一個標題欄和一個滾動控件ListView,標題欄是一個RelativeLayout,擁有一個文字標題,一個按鈕
然後是fragment的java代碼
/** * Created by xbt on 2017/9/8. * 用於遍歷省市縣數據的碎片 */ public class ChooseAreaFragment extends android.support.v4.app.Fragment { public static final int LEVEL_PROVINCE = 0; public static final int LEVEL_CITY = 1; public static final int LEVEL_COUNTY = 2; private ProgressBar progressDialog; private TextView titleText; private Button backButton; private ListView listView; private ArrayAdapter<String> adapter; private List<String> dataList = new ArrayList<>(); /** * 省列表 */ private List<Province> provinceList; /** * 市列表 */ private List<City> cityList; /** * 縣列表 */ private List<County> countyList; /** * 選中的省份 */ private Province selectedProvince; /** * 選中的城市 */ private City selectedCity; /** * 選中的縣 */ private County selectedCounty; /** * 當前被選中的級別 */ private int currentLevel; @Nullable @Override public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container, Bundle savedInstanceState) { View view = inflater.inflate(R.layout.choose_area,container,false); titleText = (TextView) view.findViewById(R.id.title_text); backButton = (Button) view.findViewById(R.id.back_button); listView = (ListView) view.findViewById(R.id.list_view); adapter = new ArrayAdapter<>(getContext(), android.R.layout.simple_list_item_1,dataList); listView.setAdapter(adapter); return view; } public void onActivityCreated( Bundle savedInstanceState) { super.onActivityCreated(savedInstanceState); listView.setOnItemClickListener(new AdapterView.OnItemClickListener() { @Override public void onItemClick(AdapterView<?> adapterView, 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(); } } }); backButton.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View view) { if(currentLevel == LEVEL_COUNTY){ queryCities(); }else if(currentLevel == LEVEL_CITY){ queryProvinces(); } } }); queryProvinces(); } /** * 查詢全國所有的省,優先從數據庫查詢,如果沒有查到再去服務器上查詢 */ private void queryProvinces(){ titleText.setText("中國"); backButton.setVisibility(View.GONE); 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"); } } /** * 查詢全國所有的市,優先從數據庫查詢,如果沒有查到再去服務器上查詢 */ private void queryCities(){ titleText.setText(selectedProvince.getProvinceName()); backButton.setVisibility(View.VISIBLE); cityList = DataSupport.where("provinceid = ?", String.valueOf(selectedProvince.getId())).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"); } } /** * 查詢全國所有的縣,優先從數據庫查詢,如果沒有查到再去服務器上查詢 */ private void queryCounties(){ titleText.setText(selectedCity.getCityName()); backButton.setVisibility(View.VISIBLE); countyList = DataSupport.where("cityid = ?", 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 cityCode = selectedCity.getCityCode(); int provinceCode = selectedProvince.getProvinceCode(); String address = "http://guolin.tech/api/china/" + provinceCode + "/" + cityCode; queryFromServer(address, "county"); } } /** * 根據傳入的地址和類型從服務器上查詢省市縣的數據 */ private void queryFromServer(String address, final String type){ HttpUtil.sendOkHttpRequest(address, new Callback() { @Override public void onFailure(Call call, IOException e) { //通過runOnUiThread回到主線程處理邏輯 getActivity().runOnUiThread(new Runnable() { @Override public void run() { Toast.makeText(getContext(),"加載失敗",Toast.LENGTH_SHORT).show(); } }); } @Override public void onResponse(Call call, Response response) throws IOException { String responseText = response.body().string(); boolean result = false; if("province".equals(type)){ result = Utility.handleProvinceResponse(responseText); }else if("city".equals(type)){ result = Utility.handleCityResponse(responseText, selectedProvince.getId()); }else if("county".equals(type)){ result = Utility.handleCountyResponse(responseText,selectedCity.getId()); } if(result){ getActivity().runOnUiThread(new Runnable() { @Override public void run() { if("province".equals(type)){ queryProvinces(); }else if("city".equals(type)){ queryCities(); }else if("county".equals(type)){ queryCounties(); } } }); } } }); } }遍歷全國省市縣的碎片java代碼
這段代碼的大概邏輯就是其中的這個方法:註釋寫的格外仔細了些
public void onActivityCreated( Bundle savedInstanceState) { super.onActivityCreated(savedInstanceState); //列表的點擊事件響應 listView.setOnItemClickListener(new AdapterView.OnItemClickListener() { @Override public void onItemClick(AdapterView<?> adapterView, 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();//查詢全國所有的省,優先從數據庫查詢,如果沒有查到再去服務器上查詢 } } }); //返回按鈕響應 backButton.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View view) { //如果當前級別是市,點擊返回就是想重新選省,縣同理,想重新選市 if(currentLevel == LEVEL_COUNTY){ queryCities();//查詢全國所有的省,優先從數據庫查詢,如果沒有查到再去服務器上查詢 }else if(currentLevel == LEVEL_CITY){ queryProvinces();//查詢全國所有的省,優先從數據庫查詢,如果沒有查到再去服務器上查詢 } } }); queryProvinces();//活動剛啟動,沒有選取省市縣就直接展示全國34個省 }
結果:
天氣預報APP(1)