簡(jiǎn)介
參考《第一行代碼》捕虽,開發(fā)出一款全國(guó)省市縣的天氣預(yù)報(bào)app.
創(chuàng)建數(shù)據(jù)庫(kù)和表
使用LitePal對(duì)數(shù)據(jù)庫(kù)進(jìn)行操作罢防,創(chuàng)建三個(gè)實(shí)體類分別是Province空入、City和County。
1. 添加依賴項(xiàng)
compile 'org.litepal.android:core:1.3.2'
2. 創(chuàng)建實(shí)體類
package com.example.stardream.coolweather.db;
import org.litepal.crud.DataSupport;
/**
* Created by StarDream on 2018/8/22.
*/
//LitePal中的每一個(gè)實(shí)體類都應(yīng)該繼承DataSupport
public class Province extends DataSupport {
private int id; //實(shí)體類具有的id
private String provinceName; //省份的名字
private int provinceCode; //省的代號(hào)
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實(shí)體類和County實(shí)體類同理钢猛。每個(gè)實(shí)體類代表一張表,實(shí)體類中的屬性代表表中的每一列衍慎。
3. 配置litepal.xml文件
<litepal>
<!--dbname 為數(shù)據(jù)庫(kù)的名字-->
<dbname value="cool_weather"/>
<!--數(shù)據(jù)庫(kù)版本指定為1-->
<version value="1"/>
<!--將實(shí)體類添加到映射列表中-->
<list>
<mapping class="com.example.stardream.coolweather.db.Province"/>
<mapping class="com.example.stardream.coolweather.db.City"/>
<mapping class="com.example.stardream.coolweather.db.County"/>
</list>
</litepal>
4. 修改AndroidManifest.xml文件
將項(xiàng)目的application配置為org.litepal.LitePalApplication
android:name="org.litepal.LitePalApplication"
關(guān)于LitePal的具體內(nèi)容詳見LitePal詳解
遍歷全國(guó)省市縣數(shù)據(jù)
1. 客戶端與服務(wù)器的交互
package com.example.stardream.coolweather.util;
import okhttp3.OkHttpClient;
import okhttp3.Request;
/**
* Created by StarDream on 2018/8/22.
*/
//采用OkHttp與服務(wù)器進(jìn)行通信
public class HttpUtil {
//與服務(wù)器進(jìn)行交互發(fā)起一條http請(qǐng)求只需要調(diào)用sendOkHttpRequest()即可
//傳入要請(qǐng)求的地址,注冊(cè)一個(gè)回調(diào)來(lái)處理服務(wù)器的響應(yīng)
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);
}
}
發(fā)起http請(qǐng)求只需調(diào)用sendOkHttprequest()這個(gè)方法皮钠,傳入一個(gè)地址稳捆,并且注冊(cè)一個(gè)回調(diào)來(lái)處理服務(wù)器的響應(yīng)。
2. 處理服務(wù)器返回的Json格式的數(shù)據(jù)
新建一個(gè)Utility類處理和解析Json數(shù)據(jù)麦轰。
package com.example.stardream.coolweather.util;
import android.text.TextUtils;
import com.example.stardream.coolweather.db.City;
import com.example.stardream.coolweather.db.County;
import com.example.stardream.coolweather.db.Province;
import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;
/**
* Created by StarDream on 2018/8/22.
*/
public class Utility {
//處理和解析省份的數(shù)據(jù)
public static boolean hanldeProvinceResponse(String response){
if(!TextUtils.isEmpty(response)){
try{
//json的對(duì)象的數(shù)組乔夯,用來(lái)接收傳回的多個(gè)省份的數(shù)據(jù)
JSONArray allProvinces = new JSONArray(response);
for(int i=0;i<allProvinces.length();i++){
//取出每一個(gè)省份
JSONObject provinceObject = allProvinces.getJSONObject(i);
Province province = new Province();
//解析出省份的id并將其賦值給province對(duì)象
province.setProvinceCode(provinceObject.getInt("id"));
//解析出省份的name并將其賦值給province對(duì)象
province.setProvinceName(provinceObject.getString("name"));
//將這一個(gè)省份保存到表中
province.save();
}
//處理成功返回真
return true;
}catch(JSONException e){
e.printStackTrace();
}
}
//處理失敗返回假
return false;
}
//處理和解析市的數(shù)據(jù)
public static boolean handleCityResponse(String response,int provinceId){
if(!TextUtils.isEmpty(response)){
try{
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.setProvinceId(provinceId);
city.save();
}
}catch(JSONException e){
e.printStackTrace();
}
return true;
}
return false;
}
//處理和解析縣的數(shù)據(jù)
public static boolean handleCountyResponse(String response,int cityId){
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.setCityId(cityId);
county.setWeatherId(countyObject.getInt("weather_id"));
county.save();
}
}catch (JSONException e){
e.printStackTrace();
}
return true;
}
return false;
}
}
省級(jí)、市級(jí)和縣級(jí)對(duì)服務(wù)器發(fā)來(lái)的數(shù)據(jù)的處理解析方式都是類似的款侵,使用JSONArray和JSONObject進(jìn)行解析末荐,然后組裝成實(shí)體類對(duì)象,再調(diào)用save()方法存儲(chǔ)到數(shù)據(jù)庫(kù)中新锈。
3. 遍歷省市縣
遍歷省市縣功能.xml
因?yàn)楸闅v省市縣的功能會(huì)用到多次甲脏,因此將其寫為碎片的形式而不是寫在活動(dòng)里面,這樣復(fù)用的時(shí)候可以直接在布局文件中調(diào)用碎片妹笆。
<?xml version="1.0" encoding="utf-8"?>
<!--頭布局作為標(biāo)題欄-->
<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">
<!--布局高度為actionBar高度块请,背景色為colorPrimary-->
<RelativeLayout
android:layout_width="match_parent"
android:layout_height="?attr/actionBarSize"
android:background="?attr/colorPrimary">
<!--用于顯示標(biāo)題內(nèi)容-->
<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"/>
<!--省市縣的數(shù)據(jù)顯示在這里-->
<!--listView會(huì)自動(dòng)給每個(gè)子項(xiàng)之間增加一條分割線-->
<ListView
android:layout_width="match_parent"
android:layout_height="match_parent">
android:id="@+id/list_view/>
</RelativeLayout>
</LinearLayout>
一般情況下標(biāo)題欄可以采用ActionBar,但是碎片中最好不用ActionBar或Toolbar,否則會(huì)有問題拳缠。
遍歷省市縣碎片
1. 新建一個(gè)ChooseAreaFragment用于展示查詢界面和實(shí)現(xiàn)基本查詢功能墩新,定義一些量值。
public class ChooseAreaFragment extends Fragment {
public static final int LEVEL_PROVINCE = 0;
public static final int LEVEL_CITY = 1;
public static final int LEVEL_COUNTY =2;
private ProgressDialog progressDialog;
private TextView titleText;
private Button backButton;
private ListView listView;
private ArrayAdapter<String> adapter;//適配器窟坐,與ListView配合使用
private List<String> dataList = new ArrayList<>();//動(dòng)態(tài)數(shù)組
//省列表
private List<Province> provinceList;
//市列表
private List<City> cityList;
//縣列表
private List<County> countyList;
//選中的省份
private Province selectedProvince;
//選中的城市
private City selectedCity;
//當(dāng)前選中的級(jí)別
private int currentLevel;
}
2. onCreateView()方法中獲取到一些控件的實(shí)例海渊,初始化了ArrayAdapter,將其設(shè)置為L(zhǎng)istView的適配器哲鸳。
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
/*
* 【LayoutInflater】其實(shí)是在res/layout/下找到xml布局文件臣疑,并且將其實(shí)例化,
* 對(duì)于一個(gè)沒有被載入或者想要?jiǎng)討B(tài)載入的界面徙菠,都需要使用LayoutInflater.inflate()來(lái)載入朝捆;
* */
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
listView.setAdapter(adapter);
return view;
}
3. 在onActivityCreated()方法中設(shè)置ListView和Button的點(diǎn)擊事件,在這里完成了基本的初始化操作懒豹,調(diào)用queryProvinces()方法芙盘,加載省級(jí)數(shù)據(jù)。
@Override
public void onActivityCreated(@Nullable Bundle savedInstanceState) {
super.onActivityCreated(savedInstanceState);
//對(duì)列表設(shè)置監(jiān)聽事件
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);
//顯示出省份對(duì)應(yīng)下city的界面
queryCities();
}else if(currentLevel == LEVEL_CITY){
//記住選中的City
selectedCity = cityList.get(position);
//切換到相應(yīng)的county界面
queryCounties();
}
}
});
//為返回按鈕注冊(cè)監(jiān)聽事件
backButton.setOnClickListener(new View.OnClickListener(){
public void onClick(View v){
//若在county切換到City
if(currentLevel == LEVEL_COUNTY){
queryCities();
}else if(currentLevel == LEVEL_CITY){
//若在City切換到province
queryProvinces();
}
}
});
//初始狀態(tài)下顯示province
queryProvinces();
}
4. 在queryProvinces()方法中脸秽,設(shè)置頭布局的標(biāo)題儒老,返回按鈕。調(diào)用LitePal查詢結(jié)構(gòu)讀取省級(jí)數(shù)據(jù)记餐,若讀取到則將其顯示在界面上驮樊,若沒有則調(diào)用queryServer()方法從服務(wù)器查詢數(shù)據(jù)。queryCities()方法和queryCounty()方法同理。
- 查詢省級(jí)數(shù)據(jù)
/*查詢?nèi)珖?guó)所有的省囚衔,先從數(shù)據(jù)庫(kù)查挖腰,沒有的話去服務(wù)器查詢
* */
private void queryProvinces(){
//設(shè)置標(biāo)題欄
titleText.setText("中國(guó)");
//隱藏返回按鈕
backButton.setVisibility(View.GONE);
//在數(shù)據(jù)庫(kù)中查詢所有省份
provinceList = DataSupport.findAll(Province.class);
if(provinceList.size()>0){
dataList.clear();
for(Province province:provinceList){
dataList.add(province.getProvinceName());
}
//更新適配器中的內(nèi)容,變?yōu)槭》輸?shù)據(jù)
adapter.notifyDataSetChanged();
listView.setSelection(0);
currentLevel = LEVEL_PROVINCE;
}else{
//從服務(wù)器中查詢
String address = "http://guolin.tech/api/china";
queryFromServer(address,"province");
}
}
- 查詢市級(jí)數(shù)據(jù)
/*查詢對(duì)應(yīng)省的City數(shù)據(jù)练湿,優(yōu)先從數(shù)據(jù)庫(kù)查猴仑,若沒有,則去服務(wù)器查詢
* */
private void queryCities(){
//設(shè)置標(biāo)題欄
titleText.setText(selectedProvince.getProvinceName());
//設(shè)置返回按鈕可見
backButton.setVisibility(View.VISIBLE);
//在數(shù)據(jù)庫(kù)中查詢對(duì)應(yīng)的City數(shù)據(jù)
cityList = DataSupport.findAll(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{
String address = "http://guolin.tech/api/china/"+selectedProvince.getProvinceCode();
queryFromServer(address,"city");
}
}
- 查詢縣級(jí)數(shù)據(jù)
/*查詢選中的市內(nèi)的所有縣肥哎,優(yōu)先從數(shù)據(jù)庫(kù)查辽俗,若沒有則去服務(wù)器查詢
* */
private void queryCounties(){
titleText.setText(selectedCity.getCityName());
backButton.setVisibility(View.VISIBLE);
countyList = DataSupport.findAll(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{
String address = "http://guolin.tech/api/china/"+
selectedProvince.getProvinceCode()+"/"+selectedCity.getCityCode();
queryFromServer(address,"county");
}
}
5. 在queryFromServer()方法中,調(diào)用HttpUtil中的sendOkHttpRequest()方法向服務(wù)器發(fā)送請(qǐng)求篡诽,響應(yīng)的數(shù)據(jù)回調(diào)到onResponse()方法中崖飘,然后調(diào)用Utility中的handleProvincesResponse()方法解析和處理服務(wù)器返回的數(shù)據(jù),然后將其存儲(chǔ)到數(shù)據(jù)庫(kù)中杈女。最后再次調(diào)用queryProvinces()方法重新加載省級(jí)數(shù)據(jù)朱浴,因?yàn)?strong>queryProvinces()涉及UI操作,則須在主線程中調(diào)用达椰,因此借助runOnUiThread()方法從子線程切換到主線程赊琳。
- (問題:queryFromServer()在哪里說(shuō)明是是在子線程中執(zhí)行的???)
/*根據(jù)傳入的地址和類型從服務(wù)器上獲取數(shù)據(jù)
* */
private void queryFromServer(String address,final String type){
//未查出之前顯示出進(jìn)度條框
showProgressDialog();
HttpUtil.sendOkHttpRequest(address, new Callback() {
@Override
public void onFailure(Call call, IOException e) {
//通過runOnUiThread回到主線程處理邏輯
getActivity().runOnUiThread(new Runnable() {
@Override
public void run() {
closeProgressDialog();
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(type.equals("province")){
result = Utility.hanldeProvinceResponse(responseText);
}else if(type.equals("city")){
result = Utility.handleCityResponse(responseText,selectedProvince.getId());
}else if(type.equals("county")){
result = Utility.handleCountyResponse(responseText,selectedCity.getId());
}
if(result){
getActivity().runOnUiThread(new Runnable() {
@Override
public void run() {
closeProgressDialog();
if(type.equals("province")){
queryProvinces();
}else if(type.equals("city")){
queryCities();
}else if(type.equals("county")){
queryCounties();
}
}
});
}
}
});
}
6. 涉及到的進(jìn)度條框
- 進(jìn)度條框的顯示
//顯示進(jìn)度條框
private void showProgressDialog(){
if(progressDialog == null){
progressDialog = new ProgressDialog(getActivity());
progressDialog.setMessage("正在加載…");
progressDialog.setCanceledOnTouchOutside(false);
}
progressDialog.show();
}
- 進(jìn)度條框的關(guān)閉
//關(guān)閉進(jìn)度框
private void closeProgressDialog(){
if(progressDialog != null){
progressDialog.dismiss();
}
}
將碎片添加到活動(dòng)
1. 修改activity_main.xml中的代碼
定義一個(gè)FrameLayout,將ChooseAreaFragment加入砰碴,并讓其充滿整個(gè)布局躏筏。
<FrameLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent">
<fragment
android:id="@+id/choose_area_fragment"
android:name="com.example.stardream.coolweather.activity.ChooseAreaFragment"
android:layout_width="match_parent"
android:layout_height="match_parent" />
</FrameLayout>
2. 刪除原生的ActionBar
在style.xml中,
<style name="AppTheme" parent="Theme.AppCompat.Light.NoActionBar">
3. 權(quán)限問題
在AndroidManifest.xml中添加網(wǎng)絡(luò)權(quán)限呈枉。
<uses-permission android:name="android.permission.INTERNET"/>
顯示天氣信息
因?yàn)楹惋L(fēng)天氣返回的天氣信息的JSON數(shù)據(jù)結(jié)構(gòu)非常復(fù)雜趁尼,若使用JSONObject解析比較麻煩,因此使用GSON對(duì)天氣信息進(jìn)行解析猖辫。
在這里應(yīng)該加入JSONObject和GSON解析和處理數(shù)據(jù)的區(qū)別
1. 定義GSON實(shí)體類
返回天氣信息的格式:
要為basic酥泞、aqi、now啃憎、suggestion和daily_forecase定義實(shí)體類芝囤。
basic
在gson包下建立Basic類
package com.example.stardream.coolweather.gson;
import com.google.gson.annotations.SerializedName;
/**
* Created by StarDream on 2018/8/24.
*/
/*由于JSON中的一些字段不太適合直接作為Java字段命名,
這里使用@SerializedName朱姐的方式讓JSON字段和java字段建立映射關(guān)系
* */
public class Basic {
//"city"與cityName建立映射關(guān)系
@SerializedName("city")
public String cityName;
//"id"與weatherId建立映射關(guān)系
@SerializedName("id")
public String weatherId;
@SerializedName("update")
public Update update;
public class Update{
//"loc"與updateTime建立映射關(guān)系
@SerializedName("loc")
public String updateTime;
}
}
aqi
package com.example.stardream.coolweather.gson;
import com.google.gson.annotations.SerializedName;
/**
* Created by StarDream on 2018/8/24.
*/
public class AQI {
public AQICity city;
public class AQICity{
@SerializedName("aqi")
String aqi;
@SerializedName("pm25")
String pm25;
}
}
為什么這里的“aqi”與“pm25”沒有使用SerilizedName???
now
package com.example.stardream.coolweather.gson;
import com.google.gson.annotations.SerializedName;
/**
* Created by StarDream on 2018/8/24.
*/
public class Now {
@SerializedName("tmp")
public String temperature;
@SerializedName("cond")
public More more;
public class More{
@SerializedName("txt")
public String info;
}
}
suggestion
package com.example.stardream.coolweather.gson;
import com.google.gson.annotations.SerializedName;
/**
* Created by StarDream on 2018/8/24.
*/
public class Suggestion {
@SerializedName("comf")
public Comfort comfort;
@SerializedName("cw")
public CarWash carWash;
@SerializedName("sport")
public Sport sport;
public class Comfort{
@SerializedName("txt")
public String info;
}
public class CarWash{
@SerializedName("txt")
public String info;
}
public class Sport{
@SerializedName("txt")
public String info;
}
}
daily_forecast
daily_forecase內(nèi)包含的是一個(gè)數(shù)組辛萍,只定義出單日天氣的實(shí)體類悯姊,在聲明實(shí)體類引用的時(shí)候使用集合類型來(lái)進(jìn)行聲明。
package com.example.stardream.coolweather.gson;
import com.google.gson.annotations.SerializedName;
/**
- Created by StarDream on 2018/8/24.
*/
public class Forecast {
@SerializedName("date")
public String date;
@SerializedName("tmp")
public Temperature temperature;
@SerializedName("cond")
public More more;
public class Temperature{
@SerializedName("max")
public String max;
@SerializedName("min")
public String min;
}
public class More{
@SerializedName("txt_d")
public String info;
}
}
總實(shí)體類Weather
創(chuàng)建一個(gè)總的實(shí)體類來(lái)引用印上的各個(gè)實(shí)體類贩毕。
package com.example.stardream.coolweather.gson;
import com.google.gson.annotations.SerializedName;
import java.util.List;
/**
* Created by StarDream on 2018/8/24.
*/
public class Weather {
public String status;
public Basic basic;
public AQI aqi;
public Now now;
public Suggestion suggestion;
//由于daily_forecase中包含的是一個(gè)數(shù)組悯许,
//這里使用List集合來(lái)引用Forecast類
@SerializedName("daily_forecast")
public List<Forecast> forecastList;
}
2. 編寫天氣界面
天氣信息界面,activity_weather.xml
為了讓代碼相對(duì)整齊辉阶,采用引用布局技術(shù)先壕,將界面的不同部分寫在不同的文件中瘩扼,再通過引入布局的方式集成到activity_weather.xml中。
title.xml頭布局
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="?attr/actionBarSize">
<TextView
android:id="@+id/title_city"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_centerInParent="true"
android:textColor="#fff"
android:textSize="20sp"/>
<TextView
android:id="@+id/title_update_time"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginRight="10dp"
android:layout_alignParentRight="true"
android:layout_centerVertical="true"
android:textColor="#fff"
android:textSize="16sp"/>
</RelativeLayout>
其中垃僚,一個(gè)居中顯示城市名集绰,一個(gè)居右顯示更新時(shí)間。
now.xml當(dāng)前天氣信息布局
<?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="wrap_content"
android:layout_margin="15dp">
<TextView
android:id="@+id/degree_text"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="end"
android:textColor="#fff"
android:textSize="60sp"/>
<TextView
android:id="@+id/weather_info_text"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="end"
android:textColor="#fff"
android:textSize="20sp"/>
</LinearLayout>
其中谆棺,放置了兩個(gè)textView,一個(gè)用來(lái)顯示氣溫栽燕,另外一個(gè)用于顯示天氣概況。
forecast.xml未來(lái)幾天天氣信息的布局
<?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="wrap_content"
android:layout_margin="15dp"
android:background="#8000">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginLeft="15dp"
android:layout_marginTop="15dp"
android:text="預(yù)報(bào)"
android:textColor="#fff"
android:textSize="20sp"/>
<LinearLayout
android:id="@+id/forecast_layout"
android:layout_width="match_parent"
android:orientation="vertical"
android:layout_height="wrap_content">
</LinearLayout>
</LinearLayout>
布局使用了半透明背景包券,TextView定義了標(biāo)題“預(yù)報(bào)”,使用LinearLayout定義了一個(gè)顯示未來(lái)幾天天氣的布局炫贤,但未放入內(nèi)容溅固,要根據(jù)服務(wù)器返回的數(shù)據(jù)在代碼中動(dòng)態(tài)添加。
forecast_item.xml未來(lái)天氣信息的子項(xiàng)布局
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_margin="15dp">
<TextView
android:id="@+id/date_text"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_gravity="center_vertical"
android:layout_weight="2"
android:textColor="#fff"/>
<TextView
android:id="@+id/info_text"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_gravity="center_vertical"
android:layout_weight="1"
android:gravity="center"
android:textColor="#fff"/>
<TextView
android:id="@+id/max_text"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_gravity="center_vertical"
android:layout_weight="1"
android:gravity="right"
android:textColor="#fff"/>
<TextView
android:id="@+id/min_text"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_gravity="center_vertical"
android:layout_weight="1"
android:gravity="left"
android:textColor="#fff"/>
</LinearLayout>
子項(xiàng)布局中放置了4個(gè)textView,分別用于顯示天氣預(yù)報(bào)日期兰珍,天氣概況侍郭,最高溫度和最低溫度。
aqi空氣質(zhì)量信息的布局
<?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="wrap_content"
android:layout_margin="15dp"
android:background="#8000">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginLeft="15dp"
android:layout_marginTop="15dp"
android:text="空氣質(zhì)量"
android:textColor="#fff"
android:textSize="20sp"/>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_margin="15dp">
<RelativeLayout
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1">
<LinearLayout
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_centerInParent="true">
<TextView
android:id="@+id/aqi_text"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:textColor="#fff"
android:textSize="40sp"/>
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:textColor="#fff"
android:text="AQI指數(shù)"/>
</LinearLayout>
</RelativeLayout>
<RelativeLayout
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1">
<LinearLayout
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_centerInParent="true">
<TextView
android:id="@+id/pm25_text"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:textColor="#fff"
android:textSize="40sp"/>
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:text="PM2.5指數(shù)"
android:textColor="#fff"/>
</LinearLayout>
</RelativeLayout>
</LinearLayout>
</LinearLayout>
使用半透明背景掠河,最上方定義了“空氣質(zhì)量”標(biāo)題亮元,然后實(shí)現(xiàn)了左右平分且居中對(duì)齊的布局,分別用于顯示AQI指數(shù)和PM2.5指數(shù)唠摹。
suggestion.cml作為生活建議信息的布局
<?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="wrap_content"
android:layout_margin="15sp"
android:background="#8000">
<TextView
android:layout_marginLeft="15dp"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="15dp"
android:text="生活建議"
android:textColor="#fff"
android:textSize="20sp"/>
<TextView
android:id="@+id/comfort_text"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_margin="15dp"
android:textColor="#fff"/>
<TextView
android:id="@+id/car_wash_text"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_margin="15dp"
android:textColor="#fff"/>
<TextView
android:id="@+id/sport_text"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_margin="15dp"
android:textColor="#fff"/>
</LinearLayout>
同樣使用半透明背景和一個(gè)標(biāo)題爆捞,用三個(gè)textView顯示舒適度、洗車指數(shù)和運(yùn)動(dòng)建議的相關(guān)數(shù)據(jù)勾拉。
將以上布局文件引入activity_weather.xml中
<?xml version="1.0" encoding="utf-8"?>
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/activity_weather"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="@color/colorPrimary"
tools:context="com.example.stardream.coolweather.activity.WeatherActivity">
<ScrollView
android:id="@+id/weahter_layout"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:scrollbars="none"
android:overScrollMode="never">
<LinearLayout
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="wrap_content">
<include layout="@layout/title"/>
<include layout="layout/now"/>
<include layout="@layout/forecast"/>
<include layout="@layout/aqi"/>
<include layout="@layout/suggestion"/>
</LinearLayout>
</ScrollView>
</FrameLayout>
最外層布局使用了FrameLayout煮甥,然后嵌套了一個(gè)ScrollView,可以滾動(dòng)查看屏幕之外的內(nèi)容藕赞。因?yàn)?strong>ScrollView內(nèi)部只允許存在一個(gè)子布局成肘,因此嵌入垂直方向的Lin,將其余所有布局引入斧蜕。
將天氣顯示在界面
解析天氣JSON數(shù)據(jù)
//將返回的JSON數(shù)據(jù)解析成Weather實(shí)體類
public static Weather handleWeatherResponse(String response){
try{
JSONObject jsonObject = new JSONObject(response);
JSONArray jsonArray = jsonObject.getJSONArray("HeWeahter");
String weatherContent = jsonArray.getJSONObject(0).toString();
return new Gson().fromJson(weatherContent,Weather.class);
}catch (Exception ex){
ex.printStackTrace();
}
return null;
}
在Utility中添加解析JSON數(shù)據(jù)的方法双霍,handleWeatherResponse()方法中先通過JSONObject和JSONArray將天氣中的主體內(nèi)容解析出來(lái),之后可通過調(diào)用dromJson()方法將JSON轉(zhuǎn)換成Weather對(duì)象批销。
編寫WeatherActivity()中的代碼
定義控件的變量
public class WeatherActivity extends AppCompatActivity {
private ScrollView weatherLayout;
private TextView titleCity;
private TextView titleUpdateTime;
private TextView degreeText;
private TextView weatherInfoText;
private LinearLayout forecastLayout;
private TextView aqiText;
private TextView pm25Text;
private TextView comfortText;
private TextView carWashText;
private TextView sportText;
……
}
onCreate()方法
獲取控件的實(shí)例洒闸,從本地緩存讀取天氣數(shù)據(jù),若沒有均芽,則會(huì)從Intent中讀出天氣Id顷蟀,然后調(diào)用requestWeahter()方法請(qǐng)求服務(wù)器上的數(shù)據(jù)。
注意:這里緩存中保存數(shù)據(jù)采用SharedPreferences的方式骡技,具體用法見 SharedPreferences存儲(chǔ)
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_weather);
//初始化控件
weatherLayout = (ScrollView)findViewById(R.id.weahter_layout);
titleCity = (TextView)findViewById(R.id.title_city);
titleUpdateTime = (TextView)findViewById(R.id.title_update_time);
degreeText = (TextView)findViewById(R.id.degree_text);
weatherInfoText = (TextView)findViewById(R.id.weather_info_text);
forecastLayout = (LinearLayout)findViewById(R.id.forecast_layout);
aqiText = (TextView)findViewById(R.id.aqi_text);
pm25Text = (TextView)findViewById(R.id.pm25_text);
comfortText = (TextView)findViewById(R.id.comfort_text);
carWashText = (TextView)findViewById(R.id.car_wash_text);
sportText = (TextView)findViewById(R.id.sport_text);
SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(this);
String weatherString = prefs.getString("weather",null);
if(weatherString != null){
//若有緩存直接解析天氣數(shù)據(jù)
Weather weather = Utility.handleWeatherResponse(weatherString);
showWeatherInfo(weather);
}else{
//無(wú)緩存時(shí)去服務(wù)器查詢天氣
String weatherId = getIntent().getStringExtra("weather_id");
weatherLayout.setVisibility(View.INVISIBLE);
requestWeather(weatherId);
}
}
向服務(wù)器請(qǐng)求天氣信息
/*
* 根據(jù)天氣的Id向服務(wù)器請(qǐng)求天氣信息*/
public void requestWeather(final String weatherId){
String weatherUrl = "http://guolin.tech/api/weather?cityid="+
weatherId+"&key=";
HttpUtil.sendOkHttpRequest(weatherUrl, new Callback() {
@Override
public void onFailure(Call call, IOException e) {
e.printStackTrace();
runOnUiThread(new Runnable(){
public void run(){
Toast.makeText(WeatherActivity.this,"獲取天氣信息失敗",Toast.LENGTH_SHORT).show();
}
});
}
@Override
public void onResponse(Call call, Response response) throws IOException {
final String responseText = response.body().string();
final Weather weather = Utility.handleWeatherResponse(responseText);
runOnUiThread(new Runnable() {
@Override
public void run() {
if(weather !=null && "ok".equals(weather.status)){
SharedPreferences.Editor editor = PreferenceManager.getDefaultSharedPreferences(WeatherActivity.this).edit();
editor.putString("weather",responseText);
editor.apply();
showWeatherInfo(weather);
}else{
Toast.makeText(WeatherActivity.this,"獲取天氣信息失敗",Toast.LENGTH_SHORT).show();
}
}
});
}
});
}
requestWeather()方法先使用傳入的天氣id和APIKEY拼裝出接口地址鸣个,然后調(diào)用HttpUtil.sendOkHttpRequest()方法向該地址發(fā)送請(qǐng)求羞反,服務(wù)器會(huì)以JSON格式返回天氣信息。然后在onResponse()回調(diào)中調(diào)用Utility.handleWeatherResponse()將返回的JSON數(shù)據(jù)轉(zhuǎn)換成Weather對(duì)象囤萤,將線程切換到主線程昼窗。若服務(wù)器返回的status狀態(tài)是ok,則將返回?cái)?shù)據(jù)緩存到SharedPreferences中涛舍,并進(jìn)行顯示澄惊。
showWeatherInfo()顯示天氣信息
/*處理冰戰(zhàn)士Weather實(shí)體類中的數(shù)據(jù)
* */
private void showWeatherInfo(Weather weather){
String cityName = weather.basic.cityName;
String updateTime = weather.basic.update.updateTime.split(" ")[1];
String degree = weather.now.temperature+"℃";
String weatherInfo = weather.now.more.info;
titleCity.setText(cityName);
titleUpdateTime.setText(updateTime);
degreeText.setText(degree);
weatherInfoText.setText(weatherInfo);
forecastLayout.removeAllViews();
for(Forecast forecast:weather.forecastList){
View view = LayoutInflater.from(this).inflate(R.layout.forecas_item,forecastLayout,false);
TextView dateText = (TextView)view.findViewById(R.id.date_text);
TextView infoText = (TextView)view.findViewById(R.id.info_text);
TextView maxText = (TextView)view.findViewById(R.id.max_text);
TextView minText = (TextView)view.findViewById(R.id.min_text);
dateText.setText(forecast.date);
infoText.setText(forecast.more.info);
maxText.setText(forecast.temperature.max);
minText.setText(forecast.temperature.min);
forecastLayout.addView(view);
}
if(weather.aqi != null){
aqiText.setText(weather.aqi.city.aqi);
pm25Text.setText(weather.aqi.city.pm25);
}
String comfort = "舒適度:"+weather.suggestion.comfort.info;
String carwash = "洗車指數(shù):"+weather.suggestion.carWash.info;
String sport = "運(yùn)動(dòng)建議:"+weather.suggestion.sport.info;
comfortText.setText(comfort);
carWashText.setText(carwash);
sportText.setText(sport);
weatherLayout.setVisibility(View.VISIBLE);
}
從Weather對(duì)象獲取數(shù)據(jù),然后顯示到相應(yīng)的空間上富雅。
從縣列表跳轉(zhuǎn)到天氣界面
public class ChooseAreaFragment extends Fragment {
……
@Override
public void onActivityCreated(@Nullable Bundle savedInstanceState) {
super.onActivityCreated(savedInstanceState);
//對(duì)列表設(shè)置監(jiān)聽事件
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);
//顯示出省份對(duì)應(yīng)下city的界面
queryCities();
}else if(currentLevel == LEVEL_CITY){
//記住選中的City
selectedCity = cityList.get(position);
//切換到相應(yīng)的county界面
queryCounties();
}else if(currentLevel == LEVEL_COUNTY){
String weatherId = countyList.get(position).getWeatherId();
Intent intent = new Intent(getActivity(),WeatherActivity.class);
intent.putExtra("weather_id",weatherId);
startActivity(intent);
getActivity().finish();
}
}
});
//為返回按鈕注冊(cè)監(jiān)聽事件
backButton.setOnClickListener(new View.OnClickListener(){
public void onClick(View v){
//若在county切換到City
if(currentLevel == LEVEL_COUNTY){
queryCities();
}else if(currentLevel == LEVEL_CITY){
//若在City切換到province
queryProvinces();
}
}
});
//初始狀態(tài)下顯示province
queryProvinces();
}
……
}
在點(diǎn)擊縣級(jí)列表之后掸驱,將選中的縣的天氣id傳遞出去,啟動(dòng)WeatherActivity没佑。
#### 修改MainActivity
若在緩存中存在天氣信息時(shí)毕贼,不再進(jìn)行選擇而是直接跳轉(zhuǎn)到天氣界面。
package com.example.stardream.coolweather.activity;
import android.content.Intent;
import android.content.SharedPreferences;
import android.preference.PreferenceManager;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import com.example.stardream.coolweather.R;
public class MainActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(this);
if(prefs.getString("weather",null) != null){
Intent intent = new Intent(this,WeatherActivity.class);
startActivity(intent);
finish();
}
}
}
###獲取每日一圖改變天氣背景
####修改activity_weather.xml中的代碼
<?xml version="1.0" encoding="utf-8"?>
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/activity_weather"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="@color/colorPrimary"
tools:context="com.example.stardream.coolweather.activity.WeatherActivity">
<ImageView
android:id="@+id/bing_pic_img"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:scaleType="centerCrop"/>
<ScrollView
android:id="@+id/weahter_layout"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:scrollbars="none"
android:overScrollMode="never">
<LinearLayout
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="wrap_content">
<include layout="@layout/title"/>
<include layout="layout/now"/>
<include layout="@layout/forecast"/>
<include layout="@layout/aqi"/>
<include layout="@layout/suggestion"/>
</LinearLayout>
</ScrollView>
</FrameLayout>
增加ImageView作為背景圖片蛤奢。
####修改WeatherActivity中的代碼
##### ImageView控件的定義
private ImageView bingPicImg;
##### 在onCreate()中
bingPicImg = (ImageView)findViewById(R.id.bing_pic_img);
String bingPic = prefs.getString("bing_pic",null);
if(bingPic !=null){
Glide.with(this).load(bingPic).into(bingPicImg);
}else{
loadBingPic();
}
···
若緩存中有圖片鬼癣,則直接調(diào)用Glide方式取出,若沒有啤贩,向服務(wù)器請(qǐng)求待秃。
loadBingPic()方法
/*
* 加載必應(yīng)圖片,每日一圖*/
private void loadBingPic(){
String requestBingPic = "http://guolin.tech/api/bing_pic";
HttpUtil.sendOkHttpRequest(requestBingPic, new Callback() {
@Override
public void onFailure(Call call, IOException e) {
e.printStackTrace();
}
@Override
public void onResponse(Call call, Response response) throws IOException {
final String bingPic = response.body().string();
SharedPreferences.Editor editor = PreferenceManager.getDefaultSharedPreferences(WeatherActivity.this).edit();
editor.putString("bing_pic",bingPic);
editor.apply();
runOnUiThread(new Runnable() {
@Override
public void run() {
Glide.with(WeatherActivity.this).load(bingPic).into(bingPicImg)
}
});
}
});
}
在requestWeather()方法中
loadBingPic();
每次請(qǐng)求天氣信息時(shí)也會(huì)刷新背景圖片
解決背景圖片和狀態(tài)欄沒有融合的問題
在onCreate()方法中
//Android5.0及以上系統(tǒng)才支持痹屹,即版本號(hào)大于等于21
if(Build.VERSION.SDK_INT>=21){
//調(diào)用getWindow().getDecorView()拿到當(dāng)前活動(dòng)的DecorView
View decorView = getWindow().getDecorView();
//改變系統(tǒng)的UI顯示章郁,傳入的兩個(gè)值表示活動(dòng)的布局會(huì)顯示在狀態(tài)欄上面
decorView.setSystemUiVisibility(View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN|View.SYSTEM_UI_FLAG_LAYOUT_STABLE);
//將狀態(tài)欄設(shè)置成透明色
getWindow().setStatusBarColor(Color.TRANSPARENT);
}
頭布局和系統(tǒng)狀態(tài)欄緊貼到了一起,修改activity_weather.xml中的代碼志衍,在ScrollView的LinearLayout中增加了android:fitsSystemWindows屬性驱犹,設(shè)置成true表示會(huì)為系統(tǒng)狀態(tài)欄留出空間。
<LinearLayout
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:fitsSystemWindows="true">
<include layout="@layout/title"/>
<include layout="layout/now"/>
<include layout="@layout/forecast"/>
<include layout="@layout/aqi"/>
<include layout="@layout/suggestion"/>
</LinearLayout>
手動(dòng)更新天氣和切換城市
手動(dòng)更新天氣
采用下拉刷新方式手動(dòng)更新天氣
修改activity_weather.xml
<!--SwipeRefreshLayout具有下拉刷新功能-->
<android.support.v4.widget.SwipeRefreshLayout
android:id="@+id/swipe_refresh"
android:layout_width="match_parent"
android:layout_height="match_parent">
<ScrollView
……
</ScrollView>
</android.support.v4.widget.SwipeRefreshLayout>
修改WeatherActivity中的代碼
定義刷新控件
private SwipeRefreshLayout swipeRefresh;
加載控件足画,設(shè)置下拉進(jìn)度條顏色
swipeRefresh = (SwipeRefreshLayout)findViewById(R.id.swipe_refresh);
swipeRefresh.setColorSchemeResources(R.color.colorPrimary);
//定義一個(gè)weatherId
final String weatherId;
實(shí)現(xiàn)更新
if(weatherString != null){
//若有緩存直接解析天氣數(shù)據(jù)
Weather weather = Utility.handleWeatherResponse(weatherString);
//若有緩存得到weatherId
weatherId = weather.basic.weatherId;
showWeatherInfo(weather);
}else{
//無(wú)緩存時(shí)去服務(wù)器查詢天氣
weatherId = getIntent().getStringExtra("weather_id");
weatherLayout.setVisibility(View.INVISIBLE);
requestWeather(weatherId);
}
swipeRefresh.setOnRefreshListener(new SwipeRefreshLayout.OnRefreshListener(){
public void onRefresh(){
requestWeather(weatherId);
}
});
隱藏刷新進(jìn)度條
/*
* 根據(jù)天氣的Id向服務(wù)器請(qǐng)求天氣信息*/
public void requestWeather(final String weatherId){
String weatherUrl = "http://guolin.tech/api/weather?cityid="+
weatherId+"&key=";
HttpUtil.sendOkHttpRequest(weatherUrl, new Callback() {
@Override
public void onFailure(Call call, IOException e) {
e.printStackTrace();
runOnUiThread(new Runnable(){
public void run(){
Toast.makeText(WeatherActivity.this,"獲取天氣信息失敗",Toast.LENGTH_SHORT).show();
//刷新事件結(jié)束雄驹,隱藏刷新進(jìn)度條
swipeRefresh.setRefreshing(false);
}
});
}
@Override
public void onResponse(Call call, Response response) throws IOException {
final String responseText = response.body().string();
final Weather weather = Utility.handleWeatherResponse(responseText);
runOnUiThread(new Runnable() {
@Override
public void run() {
if(weather !=null && "ok".equals(weather.status)){
SharedPreferences.Editor editor = PreferenceManager.getDefaultSharedPreferences(WeatherActivity.this).edit();
editor.putString("weather",responseText);
editor.apply();
showWeatherInfo(weather);
}else{
Toast.makeText(WeatherActivity.this,"獲取天氣信息失敗",Toast.LENGTH_SHORT).show();
}
//刷新事件結(jié)束,隱藏刷新進(jìn)度條
swipeRefresh.setRefreshing(false);
}
});
}
});
loadBingPic();//每次請(qǐng)求天氣信息時(shí)會(huì)刷新背景圖片
}
切換城市
在title.xml標(biāo)題欄設(shè)置按鈕
<Button
android:id="@+id/nav_button"
android:layout_width="30dp"
android:layout_height="30dp"
android:layout_marginLeft="10dp"
android:layout_alignParentLeft="true"
android:layout_centerVertical="true"
android:background="@drawable/ic_home"/>
修改activity_weather.xml布局加入滑動(dòng)菜單功能
<?xml version="1.0" encoding="utf-8"?>
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/activity_weather"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="@color/colorPrimary"
tools:context="com.example.stardream.coolweather.activity.WeatherActivity">
<ImageView
android:id="@+id/bing_pic_img"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:scaleType="centerCrop"/>
<!--SwipeRefreshLayout具有下拉刷新功能-->
<!--fitsSystemWindows為系統(tǒng)狀態(tài)欄留出空間-->
<android.support.v4.widget.DrawerLayout
android:id="@+id/drawer_layout"
android:layout_width="match_parent"
android:layout_height="match_parent"
>
<android.support.v4.widget.SwipeRefreshLayout
android:id="@+id/swipe_refresh"
android:layout_width="match_parent"
android:layout_height="match_parent">
<ScrollView
android:id="@+id/weahter_layout"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:scrollbars="none"
android:overScrollMode="never">
<LinearLayout
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:fitsSystemWindows="true">
<include layout="@layout/title"/>
<include layout="layout/now"/>
<include layout="@layout/forecast"/>
<include layout="@layout/aqi"/>
<include layout="@layout/suggestion"/>
</LinearLayout>
</ScrollView>
</android.support.v4.widget.SwipeRefreshLayout>
<fragment
android:id="@+id/choose_area_fragment"
android:name="com.example.stardream.coolweather.activity.ChooseAreaFragment"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_gravity="start"/>
</android.support.v4.widget.DrawerLayout>
</FrameLayout>
DrawerLayout中的第一個(gè)子控件用于作為主屏幕中顯示的內(nèi)容淹辞;
第二個(gè)子控件用于作為滑動(dòng)菜單中顯示的內(nèi)容医舆,添加了用于遍歷省市縣數(shù)據(jù)的碎片。
修改WeatherActivity中的代碼加入滑動(dòng)菜單的邏輯控制
定義Button和DrawerLayout
public DrawerLayout drawerLayout;
private Button navButton;
onCreate()
drawerLayout = (DrawerLayout)findViewById(R.id.drawer_layout);
navButton = (Button)findViewById(R.id.nav_button);
navButton.setOnClickListener(new View.OnClickListener(){
public void onClick(View v){
drawerLayout.openDrawer(GravityCompat.START);
}
});
在onCreate()中獲取新增的DrawerLayout和Button的實(shí)例象缀,然后在Button點(diǎn)擊事件中調(diào)用DrawerLayout的openDrawer()方法打開活動(dòng)菜單即可蔬将。
請(qǐng)求新選擇的城市的天氣信息
因?yàn)樵瓉?lái)的跳轉(zhuǎn)是從MainActivity中跳轉(zhuǎn)過去的,現(xiàn)在就在WeatherActivity中央星,所以就關(guān)閉滑動(dòng)菜單霞怀,顯示下拉刷新進(jìn)度條,請(qǐng)求新城市的天氣信息莉给。
在ChooseAreaFragment中的onActivityCreated()中毙石,
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);
//顯示出省份對(duì)應(yīng)下city的界面
queryCities();
}else if(currentLevel == LEVEL_CITY){
//記住選中的City
selectedCity = cityList.get(position);
//切換到相應(yīng)的county界面
queryCounties();
}else if(currentLevel == LEVEL_COUNTY){
String weatherId = countyList.get(position).getWeatherId();
if(getActivity()instanceof MainActivity){
Intent intent = new Intent(getActivity(),WeatherActivity.class);
intent.putExtra("weather_id",weatherId);
startActivity(intent);
getActivity().finish();
}else if(getActivity() instanceof WeatherActivity){
WeatherActivity activity = (WeatherActivity)getActivity();
activity.drawerLayout.closeDrawers();
activity.swipeRefresh.setRefreshing(true);
activity.requestWeather(weatherId);
}
}
}
});
后臺(tái)自動(dòng)更新天氣
要想自動(dòng)更新天氣廉沮,需要?jiǎng)?chuàng)建一個(gè)長(zhǎng)期在后臺(tái)運(yùn)行額定時(shí)任務(wù),因此新建一個(gè)服務(wù)AutoUpdateService徐矩。
onStartCommand()方法
package com.example.stardream.coolweather.service;
import android.app.AlarmManager;
import android.app.PendingIntent;
import android.app.Service;
import android.content.Intent;
import android.content.SharedPreferences;
import android.os.IBinder;
import android.os.SystemClock;
import android.preference.PreferenceManager;
import com.example.stardream.coolweather.gson.Weather;
import com.example.stardream.coolweather.util.HttpUtil;
import com.example.stardream.coolweather.util.Utility;
import java.io.IOException;
import okhttp3.Call;
import okhttp3.Callback;
import okhttp3.Response;
public class AutoUpdateService extends Service {
public AutoUpdateService() {
}
@Override
public IBinder onBind(Intent intent) {
return null;
}
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
updateWeather();
updateBingPic();
AlarmManager manager = (AlarmManager) getSystemService(ALARM_SERVICE);
int anHour = 8*60*60*1000;//8個(gè)小時(shí)的毫秒數(shù)
long triggerAtTime = SystemClock.elapsedRealtime()+anHour;
Intent i = new Intent(this,AutoUpdateService.class);
PendingIntent pi = PendingIntent.getService(this,0,i,0);
manager.cancel(pi);
manager.set(AlarmManager.ELAPSED_REALTIME_WAKEUP,triggerAtTime,pi);
return super.onStartCommand(intent, flags, startId);
}
在onStartCommand()方法中先調(diào)用了updateWeather()方法更新天氣滞时,調(diào)用updateBingPic()方法更新背景圖片,將更新時(shí)間設(shè)置為8小時(shí)滤灯,定時(shí)鬧鐘見AlarmManager用法
updateWeather()方法
/*更新天氣信息
* */
private void updateWeather(){
SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(this);
String weatherString = prefs.getString("weather",null);
if(weatherString !=null){
//有緩存時(shí)直接解析天氣數(shù)據(jù)
Weather weather = Utility.handleWeatherResponse(weatherString);
String weatherId = weather.basic.weatherId;
String weatherUrl = "http://guolin.tech/api/weather?cityid="+
weatherId+"&key=";
HttpUtil.sendOkHttpRequest(weatherUrl, new Callback() {
@Override
public void onFailure(Call call, IOException e) {
e.printStackTrace();
}
@Override
public void onResponse(Call call, Response response) throws IOException {
String responseText = response.body().string();
Weather weather = Utility.handleWeatherResponse(responseText);
if(weather !=null && "ok".equals(weather.status)){
SharedPreferences.Editor editor = PreferenceManager.getDefaultSharedPreferences(AutoUpdateService.this).edit();
editor.putString("weather",responseText);
editor.apply();
}
}
});
}
}
updateBingPic()方法
/*更新必應(yīng)每日一圖
* */
private void updateBingPic(){
String requestBingPic ="http://guolin.tech/api/bing_pic";
HttpUtil.sendOkHttpRequest(requestBingPic, new Callback() {
@Override
public void onFailure(Call call, IOException e) {
e.printStackTrace();
}
@Override
public void onResponse(Call call, Response response) throws IOException {
String bingPic = response.body().string();
SharedPreferences.Editor editor = PreferenceManager.getDefaultSharedPreferences(AutoUpdateService.this).edit();
editor.putString("bing_pic",bingPic);
editor.apply();
}
});
}
修改WeatherActivity
在showWeather()方法的最后加入啟動(dòng)AutoUpdateService這個(gè)服務(wù)的代碼纺座,一旦選中某個(gè)城市并成功更新天氣之后遭赂,AutoUpdateService就會(huì)一直在后臺(tái)運(yùn)行,并保證每8小時(shí)更新一次天氣驻襟。
private void showWeatherInfo(Weather weather){
……
}
if(weather.aqi != null){
aqiText.setText(weather.aqi.city.aqi);
pm25Text.setText(weather.aqi.city.pm25);
Intent intent = new Intent(this, AutoUpdateService.class);
startService(intent);
}
……
}
修改圖標(biāo)和名稱
在AndroidManifest.xml修改圖標(biāo)
android:icon="@mipmap/logo"
在strings.xml修改app名稱
<string name="app_name" translatable="false">我的天氣</string>
終于學(xué)(照貓畫虎)完了這個(gè)天氣預(yù)報(bào)仿滔,可是還沒有正常運(yùn)行桨踪,據(jù)說(shuō)是和風(fēng)天氣接口過期了伪节,汗顏亚斋。可能還要重新找新的API調(diào)用拂募。
emmmmm……我來(lái)為其正名庭猩,接口沒問題窟她,是自己的原因陈症,天氣數(shù)據(jù)是可以成功調(diào)出來(lái)的,現(xiàn)在界面處理方面還有點(diǎn)問題震糖,debug中……
我發(fā)現(xiàn)這本書的代碼還是有問題的录肯,如下:
- 1.ListView與ArrayAdapter的使用不當(dāng),每當(dāng)適配器中的內(nèi)容發(fā)生變化時(shí)吊说,要再一次載入listView论咏。
adapter = new ArrayAdapter<>(getContext(),android.R.layout.simple_list_item_1,dataList);
//載入listView
listView.setAdapter(adapter);
- 2.每一次在數(shù)據(jù)庫(kù)中查詢是否存在省市縣以及天氣的數(shù)據(jù),若存在則直接取出顯示出來(lái)颁井。這個(gè)完全沒有考慮存在于數(shù)據(jù)庫(kù)中的數(shù)據(jù)是否是選中的省市縣厅贪,不然會(huì)導(dǎo)致選中的省市縣和顯示出來(lái)的不相符,因此要添加限制條件雅宾。整個(gè)這一塊的代碼都很混亂养涮。。眉抬。在經(jīng)歷心情第一喪之后終于調(diào)出來(lái)了贯吓,細(xì)節(jié)修改的地方忘記了,核心在這里蜀变。
queryCities()
//在數(shù)據(jù)庫(kù)中查詢對(duì)應(yīng)的City數(shù)據(jù)
//原來(lái)代碼的問題時(shí)把所有City的數(shù)據(jù)取出來(lái)了悄谐,然后就發(fā)生混亂
//應(yīng)該取出的是選中省份的city
cityList = DataSupport.where("provinceId = ?",String.valueOf(selectedProvince.getId())).find(City.class);
queryCounties()
//在數(shù)據(jù)庫(kù)中查詢對(duì)應(yīng)的county數(shù)據(jù)
//原來(lái)代碼的問題時(shí)把所有County的數(shù)據(jù)取出來(lái)了,然后就發(fā)生混亂
//應(yīng)該取出的是選中city的county
countyList = DataSupport.where("cityId = ?",String.valueOf(selectedCity.getId())).find(County.class);
界面展示
最關(guān)鍵的界面圖片總是上傳失敗库北,不知道是什么原因爬舰。们陆。。不傳了洼专,真是想吐槽這個(gè)上傳圖片功能棒掠。
詳細(xì)代碼請(qǐng)見CoolWeather源代碼