1. 程式人生 > >天氣預報APP(1)

天氣預報APP(1)

round date toa 標題欄 raw edi client make 查看

一個天氣預報APP至少應該具備以下功能:

  *可以羅列出全國所有的省、市、縣;

  *可以查看全國任意城市的天氣信息;

  *可以自由的切換城市,去查看其他城市的天氣;

  *提供手動更新以及後臺自動更新天氣的功能;

這裏使用和風天氣作為天氣預報來源,全國省市縣的數據信息這裏使用的是《第一行代碼》的作者郭霖大佬架設的服務器。  

具體就是:想要羅列出中國所有的省份,只需要訪問這個地址:http://guolin.tech/api/china,服務器會返回一段JSON格式的數據,其中包括了中國所有省份的名稱及省份的id:

  [{"id":1,"name":"北京"},{"id":2,"name":"上海"},{"id":3,"name":"天津"},

  {"id":4,"name":"重慶"},{"id":5,"name":"香港"},{"id":6,"name":"澳門"},
  {"id":7,"name":"臺灣"},{"id":8,"name":"黑龍江"},{"id":9,"name":"吉林"},
  {"id":10,"name":"遼寧"},{"id":11,"name":"內蒙古"},{"id":12,"name":"河北"},
  {"id":13,"name":"河南"},{"id":14,"name":"山西"},{"id":15,"name":"山東"},
  {"id":16,"name":"江蘇"},{"id":17,"name":"浙江"},{"id":18,"name":"福建"},
  {"id":19,"name":"江西"},{"id":20,"name":"安徽"},{"id":21,"name":"湖北"},
  {"id":22,"name":"湖南"},{"id":23,"name":"廣東"},{"id":24,"name":"廣西"},
  {"id":25,"name":"海南"},{"id":26,"name":"貴州"},{"id":27,"name":"雲南"},
  {"id":28,"name":"四川"},{"id":29,"name":"西藏"},{"id":30,"name":"陜西"},
  {"id":31,"name":"寧夏"},{"id":32,"name":"甘肅"},{"id":33,"name":"青海"},
  {"id":34,"name":"新疆"}]

如果想要河北省有那些城市,就把id加上:http://guolin.tech/api/china/12

  [{"id":57,"name":"石家莊"},{"id":58,"name":"保定"},{"id":59,"name":"張家口"},
  {"id":60,"name":"唐山"},{"id":61,"name":"廊坊"},{"id":62,"name":"滄州"},
  {"id":63,"name":"衡水"},{"id":64,"name":"邢臺"},{"id":65,"name":"邯鄲"},
  {"id":66,"name":"秦皇島"}]

如果想要知道秦皇島有那些縣,就再加上id:http://guolin.tech/api/china/12/66

[{"id":557,"name":"秦皇島","weather_id":"CN101091101"},{"id":558,"name":"青龍","weather_id":"CN101091102"},

{"id":559,"name":"昌黎","weather_id":"CN101091103"},{"id":560,"name":"撫寧","weather_id":"CN101091104"},

{"id":561,"name":"盧龍","weather_id":"CN101091105"},{"id":562,"name":"北戴河","weather_id":"CN101091106"}]

以上是全國省市縣的數據,然後是天氣數據的api的使用:

https://free-api.heweather.com/v5/weather?city=yourcity&key=yourkey

這是一個免費的使用方法,一天可以訪問4000次,不過要先申請。yourcity部分就填之前獲取到的id即可,key的話申請就有了。

https://free-api.heweather.com/v5/weather?city=CN101091101&key=32d1c829ed7d483086f4f5b4d5947cef

這樣就能查詢到秦皇島的天氣情況,返回的數據就比較復雜了,官方的例子就是:

技術分享
{
    "HeWeather5": [
        {
            "alarms": [
                {
                    "level": "藍色",
                    "stat": "預警中",
                    "title": "山東省青島市氣象臺發布大風藍色預警",
                    "txt": "青島市氣象臺2016年08月29日15時24分繼續發布大風藍色預警信號:預計今天下午到明天,我市北風風力海上6到7級陣風9級,陸地4到5陣風7級,請註意防範。",
                    "type": "大風"
                }
            ],
            "aqi": {
                "city": {
                    "aqi": "60",
                    "co": "0",
                    "no2": "14",
                    "o3": "95",
                    "pm10": "67",
                    "pm25": "15",
                    "qlty": "良",  //共六個級別,分別:優,良,輕度汙染,中度汙染,重度汙染,嚴重汙染
                    "so2": "10"
                }
            },
            "basic": {
                "city": "青島",
                "cnty": "中國",
                "id": "CN101120201",
                "lat": "36.088000",
                "lon": "120.343000",
                "prov": "山東"  //城市所屬省份(僅限國內城市)
                "update": {
                    "loc": "2016-08-30 11:52",
                    "utc": "2016-08-30 03:52"
                }
            },
            "daily_forecast": [
                {
                    "astro": {
                        "mr": "03:09",
                        "ms": "17:06",
                        "sr": "05:28",
                        "ss": "18:29"
                    },
                    "cond": {
                        "code_d": "100",
                        "code_n": "100",
                        "txt_d": "晴",
                        "txt_n": "晴"
                    },
                    "date": "2016-08-30",
                    "hum": "45",
                    "pcpn": "0.0",
                    "pop": "8",
                    "pres": "1005",
                    "tmp": {
                        "max": "29",
                        "min": "22"
                    },
                    "vis": "10",
                    "wind": {
                        "deg": "339",
                        "dir": "北風",
                        "sc": "4-5",
                        "spd": "24"
                    }
                }
            ],
            "hourly_forecast": [
                {
                    "cond": {
                        "code": "100",
                        "txt": "晴"
                    },
                    "date": "2016-08-30 12:00",
                    "hum": "47",
                    "pop": "0",
                    "pres": "1006",
                    "tmp": "29",
                    "wind": {
                        "deg": "335",
                        "dir": "西北風",
                        "sc": "4-5",
                        "spd": "36"
                    }
                }
            ],
            "now": {
                "cond": {
                    "code": "100",
                    "txt": "晴"
                },
                "fl": "28",
                "hum": "41",
                "pcpn": "0",
                "pres": "1005",
                "tmp": "26",
                "vis": "10",
                "wind": {
                    "deg": "330",
                    "dir": "西北風",
                    "sc": "6-7",
                    "spd": "34"
                }
            },
            "status": "ok",
            "suggestion": {
                "comf": {
                    "brf": "較舒適",
                    "txt": "白天天氣晴好,您在這種天氣條件下,會感覺早晚涼爽、舒適,午後偏熱。"
                },
                "cw": {
                    "brf": "較不宜",
                    "txt": "較不宜洗車,未來一天無雨,風力較大,如果執意擦洗汽車,要做好蒙上汙垢的心理準備。"
                },
                "drsg": {
                    "brf": "熱",
                    "txt": "天氣熱,建議著短裙、短褲、短薄外套、T恤等夏季服裝。"
                },
                "flu": {
                    "brf": "較易發",
                    "txt": "雖然溫度適宜但風力較大,仍較易發生感冒,體質較弱的朋友請註意適當防護。"
                },
                "sport": {
                    "brf": "較適宜",
                    "txt": "天氣較好,但風力較大,推薦您進行室內運動,若在戶外運動請註意防風。"
                },
                "trav": {
                    "brf": "適宜",
                    "txt": "天氣較好,風稍大,但溫度適宜,是個好天氣哦。適宜旅遊,您可以盡情地享受大自然的無限風光。"
                },
                "uv": {
                    "brf": "強",
                    "txt": "紫外線輻射強,建議塗擦SPF20左右、PA++的防曬護膚品。避免在10點至14點暴露於日光下。"
                }
            }
        }
    ]
}
數據返回示例

首先是這次項目需要依賴的庫的聲明:

    compile org.litepal.android:core:1.6.0
    compile com.squareup.okhttp3:okhttp:3.9.0
    compile com.google.code.gson:gson:2.8.0
    compile com.github.bumptech.glide:glide:4.0.0

  Litepal用於數據庫操作。OkHttp用於進行網絡請求,GSON用於解析獲得的JSON數據,Glide用於加載展示圖片

1、建立數據庫和表

先建三張表,分別是省,市,縣:

Province:

技術分享
public class Province extends DataSupport {

    private int id;

    private String provinceName;//省的名字

    private int provinceCode;//省的代號

    public int getId() {
        return id;
    }

    public void setId(int id) {
        this.id = id;
    }

    public String getProvinceName() {
        return provinceName;
    }

    public void setProvinceName(String provinceName) {
        this.provinceName = provinceName;
    }

    public int getProvinceCode() {
        return provinceCode;
    }

    public void setProvinceCode(int provinceCode) {
        this.provinceCode = provinceCode;
    }
}
省的數據信息

City:

技術分享
public class City extends DataSupport {

    private int id;

    private String cityName;//城市的名字

    private int cityCode;//城市的代號

    private int provinceId;//城市所在省的id值

    public int getId() {
        return id;
    }

    public void setId(int id) {
        this.id = id;
    }

    public String getCityName() {
        return cityName;
    }

    public void setCityName(String cityName) {
        this.cityName = cityName;
    }

    public int getCityCode() {
        return cityCode;
    }

    public void setCityCode(int cityCode) {
        this.cityCode = cityCode;
    }

    public int getProvinceId() {
        return provinceId;
    }

    public void setProvinceId(int provinceId) {
        this.provinceId = provinceId;
    }
}
市的數據信息

County:

技術分享
public class County extends DataSupport {

    private int id;

    private String countyName;//縣的名字

    private String weatherId;//天氣的id

    private int cityId;//所屬市的id

    public int getId() {
        return id;
    }

    public void setId(int id) {
        this.id = id;
    }

    public String getCountyName() {
        return countyName;
    }

    public void setCountyName(String countyName) {
        this.countyName = countyName;
    }

    public String getWeatherId() {
        return weatherId;
    }

    public void setWeatherId(String weatherId) {
        this.weatherId = weatherId;
    }

    public int getCityId() {
        return cityId;
    }

    public void setCityId(int cityId) {
        this.cityId = cityId;
    }
}
縣的數據信息

然後是Litepal的建表操作,在app/src/main目錄下新建assets目錄,在其中新建Litepal.xml文件

<?xml version="1.0" encoding="utf-8"?>
<litepal>

    <dbname value = "cool_weather"></dbname>

    <version value = "1"></version>

    <list>
        <mapping class = "xbt.exp20.db.Province"></mapping>
        <mapping class = "xbt.exp20.db.City"></mapping>
        <mapping class = "xbt.exp20.db.County"></mapping>
    </list>

</litepal>

再然後是配置LitepalApplication:

<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="xbt.exp20">
...
    <application
        android:name="org.litepal.LitePalApplication"
       ...
    </application>

</manifest>

這樣三個類加上Litepal的配置,數據庫和表會在首次執行任意數據庫操作的時候自動創建。

2、遍歷全國省市縣數據

首先是新建一個HttpUtil類:

/**
 * 發起一條HTTP請求,傳入地址,並註冊一個回調來處理服務器響應
 */

public class HttpUtil {

    public static void sendOkHttpRequest(String address, okhttp3.Callback callback){
        OkHttpClient client = new OkHttpClient();
        Request request = new Request.Builder().url(address).build();
        client.newCall(request).enqueue(callback);
    }
}

然後是一個Utility類:

技術分享
public class Utility {
    /**
     *解析和處理服務器返回的省級數據
     */
    public static boolean handleProvinceResponse(String response){

        if(!TextUtils.isEmpty(response)){
            //如果字符序列不為空或長度為0
            try{
                JSONArray allProvinces = new JSONArray(response);
                for(int i = 0; i < allProvinces.length(); i++){
                    JSONObject provinceObject = allProvinces.getJSONObject(i);
                    Province province = new Province();
                    province.setProvinceName(provinceObject.getString("name"));
                    province.setProvinceCode(provinceObject.getInt("id"));
                    province.save();
                }
                return true;
            }catch (JSONException e){
                e.printStackTrace();
            }
        }
        return false;
    }

    /**
     *解析和處理服務器返回的市級數據
     */
    public static boolean handleCityResponse(String response, int provinceId){

        if(!TextUtils.isEmpty(response)){
            //如果字符序列不為空或長度為0
            try{
                JSONArray allCities = new JSONArray(response);
                for(int i = 0; i < allCities.length(); i++){
                    JSONObject CityObject = allCities.getJSONObject(i);
                    City city = new City();
                    city.setCityName(CityObject.getString("name"));
                    city.setCityCode(CityObject.getInt("id"));
                    city.setProvinceId(provinceId);
                    city.save();
                }
                return true;
            }catch (JSONException e){
                e.printStackTrace();
            }
        }
        return false;
    }

    /**
     *解析和處理服務器返回的縣級數據
     */
    public static boolean handleCountyResponse(String response, int cityId){

        if(!TextUtils.isEmpty(response)){
            //如果字符序列不為空或長度為0
            try{
                JSONArray allCounties = new JSONArray(response);
                for(int i = 0; i < allCounties.length(); i++){
                    JSONObject countyObject = allCounties.getJSONObject(i);
                    County county = new County();
                    county.setCountyName(countyObject.getString("name"));
                    county.setWeatherId(countyObject.getString("weather_id"));
                    county.setCityId(cityId);
                    county.save();
                }
                return true;
            }catch (JSONException e){
                e.printStackTrace();
            }
        }
        return false;
    }
}
解析處理數據

因為遍歷全國省市的功能經常復用,所以寫在碎片裏面:

  先是一個choose_area.xml作為碎片fragment的布局:

技術分享
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:orientation="vertical"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:background="#fff">

    <RelativeLayout
        android:layout_width="match_parent"
        android:layout_height="?attr/actionBarSize"
        android:background="?attr/colorPrimary">
        <TextView
            android:id="@+id/title_text"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_centerInParent="true"
            android:textColor="#fff"
            android:textSize="20sp"/>
        <Button
            android:id="@+id/back_button"
            android:layout_width="25dp"
            android:layout_height="25dp"
            android:layout_marginLeft="10dp"
            android:layout_alignParentLeft="true"
            android:layout_centerVertical="true"
            android:background="@drawable/ic_back"/>
    </RelativeLayout>

    <ListView
        android:id="@+id/list_view"
        android:layout_width="match_parent"
        android:layout_height="match_parent"></ListView>
</LinearLayout>
fragment的布局

一個標題欄和一個滾動控件ListView,標題欄是一個RelativeLayout,擁有一個文字標題,一個按鈕

然後是fragment的java代碼

技術分享
/**
 * Created by xbt on 2017/9/8.
 * 用於遍歷省市縣數據的碎片
 */

public class ChooseAreaFragment extends android.support.v4.app.Fragment {

    public static final int LEVEL_PROVINCE = 0;

    public static final int LEVEL_CITY = 1;

    public static final int LEVEL_COUNTY = 2;

    private ProgressBar progressDialog;

    private TextView titleText;

    private Button backButton;

    private ListView listView;

    private ArrayAdapter<String> adapter;

    private List<String> dataList  = new ArrayList<>();

    /**
     * 省列表
     */
    private List<Province> provinceList;

    /**
     * 市列表
     */
    private List<City> cityList;

    /**
     * 縣列表
     */
    private List<County> countyList;

    /**
     * 選中的省份
     */
    private Province selectedProvince;

    /**
     * 選中的城市
     */
    private City selectedCity;

    /**
     * 選中的縣
     */
    private County selectedCounty;

    /**
     * 當前被選中的級別
     */
    private int currentLevel;

    @Nullable
    @Override
    public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container, Bundle savedInstanceState) {
        View view = inflater.inflate(R.layout.choose_area,container,false);
        titleText = (TextView) view.findViewById(R.id.title_text);
        backButton = (Button) view.findViewById(R.id.back_button);
        listView = (ListView) view.findViewById(R.id.list_view);
            adapter = new ArrayAdapter<>(getContext(), android.R.layout.simple_list_item_1,dataList);
        listView.setAdapter(adapter);
        return view;
    }

    public void onActivityCreated( Bundle savedInstanceState) {
        super.onActivityCreated(savedInstanceState);
        listView.setOnItemClickListener(new AdapterView.OnItemClickListener() {
            @Override
            public void onItemClick(AdapterView<?> adapterView, View view, int position, long id) {
                if(currentLevel == LEVEL_PROVINCE){
                    selectedProvince = provinceList.get(position);
                    queryCities();
                }else if(currentLevel == LEVEL_CITY){
                    selectedCity = cityList.get(position);
                    queryCounties();
                }
            }
        });
        backButton.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                if(currentLevel == LEVEL_COUNTY){
                    queryCities();
                }else if(currentLevel == LEVEL_CITY){
                    queryProvinces();
                }
            }
        });
        queryProvinces();
    }

    /**
     * 查詢全國所有的省,優先從數據庫查詢,如果沒有查到再去服務器上查詢
     */
    private void queryProvinces(){
        titleText.setText("中國");
        backButton.setVisibility(View.GONE);
        provinceList = DataSupport.findAll(Province.class);
        if(provinceList.size() > 0){
            dataList.clear();
            for (Province province : provinceList){
                dataList.add(province.getProvinceName());
            }
            adapter.notifyDataSetChanged();
            listView.setSelection(0);
            currentLevel = LEVEL_PROVINCE;
        }else {
            String address = "http://guolin.tech/api/china";
            queryFromServer(address, "province");
        }
    }

    /**
     * 查詢全國所有的市,優先從數據庫查詢,如果沒有查到再去服務器上查詢
     */
    private void queryCities(){
        titleText.setText(selectedProvince.getProvinceName());
        backButton.setVisibility(View.VISIBLE);
        cityList = DataSupport.where("provinceid = ?", String.valueOf(selectedProvince.getId())).find(City.class);
        if(cityList.size() > 0){
            dataList.clear();
            for (City city : cityList){
                dataList.add(city.getCityName());
            }
            adapter.notifyDataSetChanged();
            listView.setSelection(0);
            currentLevel = LEVEL_CITY;
        }else {
            int provinceCode = selectedProvince.getProvinceCode();
            String address = "http://guolin.tech/api/china/" + provinceCode;
            queryFromServer(address, "city");
        }
    }

    /**
     * 查詢全國所有的縣,優先從數據庫查詢,如果沒有查到再去服務器上查詢
     */
    private void queryCounties(){
        titleText.setText(selectedCity.getCityName());
        backButton.setVisibility(View.VISIBLE);
        countyList = DataSupport.where("cityid = ?", String.valueOf(selectedCity.getId())).find(County.class);
        if(countyList.size() > 0){
            dataList.clear();
            for (County county : countyList){
                dataList.add(county.getCountyName());
            }
            adapter.notifyDataSetChanged();
            listView.setSelection(0);
            currentLevel = LEVEL_COUNTY;
        }else {
            int cityCode = selectedCity.getCityCode();
            int provinceCode = selectedProvince.getProvinceCode();
            String address = "http://guolin.tech/api/china/" + provinceCode + "/" + cityCode;
            queryFromServer(address, "county");
        }
    }

    /**
     * 根據傳入的地址和類型從服務器上查詢省市縣的數據
     */
    private void queryFromServer(String address, final String type){
        HttpUtil.sendOkHttpRequest(address, new Callback() {
            @Override
            public void onFailure(Call call, IOException e) {
                //通過runOnUiThread回到主線程處理邏輯
                getActivity().runOnUiThread(new Runnable() {
                    @Override
                    public void run() {
                        Toast.makeText(getContext(),"加載失敗",Toast.LENGTH_SHORT).show();
                    }
                });
            }

            @Override
            public void onResponse(Call call, Response response) throws IOException {
                String responseText = response.body().string();
                boolean result = false;
                if("province".equals(type)){
                    result = Utility.handleProvinceResponse(responseText);
                }else if("city".equals(type)){
                    result = Utility.handleCityResponse(responseText, selectedProvince.getId());
                }else if("county".equals(type)){
                    result = Utility.handleCountyResponse(responseText,selectedCity.getId());
                }
                if(result){
                    getActivity().runOnUiThread(new Runnable() {
                        @Override
                        public void run() {
                            if("province".equals(type)){
                                queryProvinces();
                            }else if("city".equals(type)){
                                queryCities();
                            }else if("county".equals(type)){
                                queryCounties();
                            }
                        }
                    });
                }
            }
        });
    }
}
遍歷全國省市縣的碎片java代碼

這段代碼的大概邏輯就是其中的這個方法:註釋寫的格外仔細了些

    public void onActivityCreated( Bundle savedInstanceState) {
        super.onActivityCreated(savedInstanceState);
        
        //列表的點擊事件響應
        listView.setOnItemClickListener(new AdapterView.OnItemClickListener() {
            @Override
            public void onItemClick(AdapterView<?> adapterView, View view, int position, long id) {
                //如果當前級別是省,那點擊選取的就是某個省,而想要查詢的是選中省有那些城市,市同理 ,想要查詢這個城市有那些縣
                if(currentLevel == LEVEL_PROVINCE){
                    selectedProvince = provinceList.get(position);
                    queryCities();//查詢全國所有的省,優先從數據庫查詢,如果沒有查到再去服務器上查詢
                }else if(currentLevel == LEVEL_CITY){
                    selectedCity = cityList.get(position);
                    queryCounties();//查詢全國所有的省,優先從數據庫查詢,如果沒有查到再去服務器上查詢
                }
            }
        });
        
        //返回按鈕響應
        backButton.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                //如果當前級別是市,點擊返回就是想重新選省,縣同理,想重新選市
                if(currentLevel == LEVEL_COUNTY){
                    queryCities();//查詢全國所有的省,優先從數據庫查詢,如果沒有查到再去服務器上查詢
                }else if(currentLevel == LEVEL_CITY){
                    queryProvinces();//查詢全國所有的省,優先從數據庫查詢,如果沒有查到再去服務器上查詢
                }
            }
        });
        queryProvinces();//活動剛啟動,沒有選取省市縣就直接展示全國34個省
    }

結果:

技術分享技術分享技術分享

天氣預報APP(1)