酷歐天氣的開發(fā)

簡(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ù)的處理解析方式都是類似的款侵,使用JSONArrayJSONObject進(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í)體類

返回天氣信息的格式:


返回?cái)?shù)據(jù)的格式

要為basic酥泞、aqi、now啃憎、suggestion和daily_forecase定義實(shí)體類芝囤。

basic
basic具體內(nèi)容

在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
aqi具體內(nèi)容
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
now具體內(nèi)容
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
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_forecast具體內(nèi)容

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);

界面展示

Icon

主界面

省級(jí)—甘肅

市級(jí)—臨夏

最關(guān)鍵的界面圖片總是上傳失敗库北,不知道是什么原因爬舰。们陆。。不傳了洼专,真是想吐槽這個(gè)上傳圖片功能棒掠。
詳細(xì)代碼請(qǐng)見CoolWeather源代碼

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市屁商,隨后出現(xiàn)的幾起案子烟很,更是在濱河造成了極大的恐慌,老刑警劉巖蜡镶,帶你破解...
    沈念sama閱讀 222,729評(píng)論 6 517
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件雾袱,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡官还,警方通過查閱死者的電腦和手機(jī)芹橡,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 95,226評(píng)論 3 399
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)望伦,“玉大人林说,你說(shuō)我怎么就攤上這事⊥蜕。” “怎么了腿箩?”我有些...
    開封第一講書人閱讀 169,461評(píng)論 0 362
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)劣摇。 經(jīng)常有香客問我珠移,道長(zhǎng),這世上最難降的妖魔是什么末融? 我笑而不...
    開封第一講書人閱讀 60,135評(píng)論 1 300
  • 正文 為了忘掉前任钧惧,我火速辦了婚禮,結(jié)果婚禮上勾习,老公的妹妹穿的比我還像新娘浓瞪。我一直安慰自己,他們只是感情好巧婶,可當(dāng)我...
    茶點(diǎn)故事閱讀 69,130評(píng)論 6 398
  • 文/花漫 我一把揭開白布乾颁。 她就那樣靜靜地躺著,像睡著了一般粹舵。 火紅的嫁衣襯著肌膚如雪钮孵。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 52,736評(píng)論 1 312
  • 那天眼滤,我揣著相機(jī)與錄音巴席,去河邊找鬼。 笑死诅需,一個(gè)胖子當(dāng)著我的面吹牛漾唉,可吹牛的內(nèi)容都是我干的荧库。 我是一名探鬼主播,決...
    沈念sama閱讀 41,179評(píng)論 3 422
  • 文/蒼蘭香墨 我猛地睜開眼赵刑,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼分衫!你這毒婦竟也來(lái)了?” 一聲冷哼從身側(cè)響起般此,我...
    開封第一講書人閱讀 40,124評(píng)論 0 277
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤蚪战,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后铐懊,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體邀桑,經(jīng)...
    沈念sama閱讀 46,657評(píng)論 1 320
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 38,723評(píng)論 3 342
  • 正文 我和宋清朗相戀三年科乎,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了壁畸。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 40,872評(píng)論 1 353
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡茅茂,死狀恐怖捏萍,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情空闲,我是刑警寧澤令杈,帶...
    沈念sama閱讀 36,533評(píng)論 5 351
  • 正文 年R本政府宣布,位于F島的核電站进副,受9級(jí)特大地震影響这揣,放射性物質(zhì)發(fā)生泄漏悔常。R本人自食惡果不足惜影斑,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 42,213評(píng)論 3 336
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望机打。 院中可真熱鬧矫户,春花似錦、人聲如沸残邀。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,700評(píng)論 0 25
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)芥挣。三九已至驱闷,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間空免,已是汗流浹背空另。 一陣腳步聲響...
    開封第一講書人閱讀 33,819評(píng)論 1 274
  • 我被黑心中介騙來(lái)泰國(guó)打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留蹋砚,地道東北人扼菠。 一個(gè)月前我還...
    沈念sama閱讀 49,304評(píng)論 3 379
  • 正文 我出身青樓摄杂,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親循榆。 傳聞我的和親對(duì)象是個(gè)殘疾皇子析恢,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,876評(píng)論 2 361

推薦閱讀更多精彩內(nèi)容

  • Android 自定義View的各種姿勢(shì)1 Activity的顯示之ViewRootImpl詳解 Activity...
    passiontim閱讀 172,331評(píng)論 25 707
  • 我在等風(fēng)也等你 可是 只等到了風(fēng)卻沒有你 我向街口看看 來(lái)來(lái)往往的人 唯獨(dú)沒有你 你看 陽(yáng)光正好 微風(fēng)不燥 怎么就...
    烤魚咯閱讀 246評(píng)論 0 0
  • 2017年12月20日農(nóng)歷11月3日,星期六秧饮,天氣睛 1.扎根三年,堅(jiān)持早起YY頻道學(xué)習(xí)第046天映挂。 2.扎根三年...
    _紫霞閱讀 246評(píng)論 0 0
  • 1 二十多歲的時(shí)候袖肥,我最喜歡的兩首歌,一首是鄭秀文的《感情線上》振劳,一首是王菲的《給自己的情書》椎组,經(jīng)年播放,從來(lái)不厭...
    蔡尖尖閱讀 464評(píng)論 0 3