android開發學習之路——天氣預報之顯示天氣信息(三)


    由於和風天氣返回的JSON數據結構非常復雜,我們借助GSON來對天氣信息進行解析。

 

(一)定義GSON實體類

    GSON的用法比較簡單。先將數據對應的實體類創建好。由於和風天氣返回的數據非常多,作者篩選了一些比較重要的數據來進行解析。

    先回顧一下返回數據的大致格式:

 1  2     "HeWeather":[
 3         {
 4             "status":"ok",
 5             "basic":{},
 6             "api":{},
 7             "now":{},
 8             "suggestion":{},
 9             "daily_forecast":[]
10          }
11     ]
12 }

    其中,basic、api、now、suggetion和daily_forecast的內部又都會有具體的內容,那么我們就可以將這5個部分定義成5個實體類。

    下面我們就可以將這5個部分定義成5個實體類。

    basic中具體內容如下所示:

1 "basic":{
2     "city":"蘇州”,
3     “id”:“CN101190401",
4     "update”{
5         “loc”:“2016-08-08 21:586 7

    其中,city表示城市名,id表示城市對應的天氣id,update中的loc表示天氣的更新時間。我們按照此結構就可以在gson包下建立一個Basic類,代碼如下:

1 public class Basic{
2     @SerializedName("city")
3 public String cityName;
4     @SerializedName("id")
5 public String weatherId;
6 public Update update;
7 public class Update{
8        @SerializedName("loc")
9 public String updateTime;
10    }
11 }

   由於JSON中的一些字段可能不太適合直接作為Java字段來命名,因此這里使用了@SerializedName注解的方式來讓JSON字段和Java字段之間建立映射關系。

   這樣我們就將Basic類定義好了,其余的幾個實體類也是類似的,我們使用同樣的方式來定義。api中的具體內容如下:

1 api":{
2     "city":{
3     "api":"44",
4     "pm25":"13"
5     }
6 }

    在gson包下新建一個AQI類,代碼如下:

1 public class AQI{
2     public AQICity city;
3     public class AQICity{
4         public String api;
5         public String pm25;
6     }
7 }

    now中的具體內容如下所示:

1 "now":{
2     "tmp":"29",
3     "cond":{
4         "txt":"陣雨“
5 6

    在gson包下新建一個Now類,代碼如下:

 1 public class Now{
 2     @SerializedName("tmp")
 3     public String temperature;
 4     @SerializedName("cond")
 5     public More more;
 6     public class More{
 7        @SerializedName("txt")
 8        public String info;
 9     }
10 }

    suggestion中的具體內容如下所示:

 1 "suggestion":{
 2     "comf":{
 3         "txt":"白天天氣較熱,雖然有雨,但仍然無法削弱較高氣溫給人們帶來的      暑意,這種天氣會讓您感到不很舒適。"
 4     },
 5      "cw":{
 6         "txt":"不宜洗車,未來24小時內有雨,如果在此期間洗車,雨水和路上的泥水..."
 7     },
 8     "sport":{
 9         "txt":"有降水,且風力較強,建議...."
10     }
11 }

    在gson包下新建一個Suggestion類,代碼如下:

 1 public class Suggestion {
 2 
 3     @SerializedName("comf")
 4     public Comfort comfort;
 5 
 6     @SerializedName("cw")
 7     public CarWash carWash;
 8 
 9     public Sport sport;
10 
11     public class Comfort {
12 
13         @SerializedName("txt")
14         public String info;
15 
16     }
17 
18     public class CarWash {
19 
20         @SerializedName("txt")
21         public String info;
22 
23     }
24 
25     public class Sport {
26 
27         @SerializedName("txt")
28         public String info;
29 
30     }
31 
32 }

    到目前為止都還比較簡單,不過接下來的一項數據就有點特殊了,daily_forecast中的具體內容如下所示:

 1 "daily_forecast":{
 2  3         date":"2016-08-08",
 4         "cond":{
 5             "txt_d":"陣雨"
 6             },
 7         "tmp":{
 8             "max":"34",
 9             "min":"27"
10         }
11 12 13         date":"2016-08-09",
14         "cond":{
15             "txt_d":"多雲"
16             },
17         "tmp":{
18             "max":"35",
19             "min":"29"
20         }
21     },
22     ....
23 }

     可以看到,daily_forecast中包含的是一個數組,數組中的每一項都代表着未來一天的天氣信息。因此,我們需要定義單日天氣的實體類,然后在聲明實體類引用的時候使用集合類型來進行聲明。

    在gson包下新建一個Forecast類,代碼如下所示:

 1 public class Forecast {
 2 
 3     public String date;
 4 
 5     @SerializedName("tmp")
 6     public Temperature temperature;
 7 
 8     @SerializedName("cond")
 9     public More more;
10 
11     public class Temperature {
12 
13         public String max;
14 
15         public String min;
16 
17     }
18 
19     public class More {
20 
21         @SerializedName("txt_d")
22         public String info;
23 
24     }
25 
26 }

    這樣我們就把basic、aqi、now、suggestion和daily_forecast對應的實體類全部都創建好了,接下來再創建一個總的實例類來引用剛剛創建的各種實體類。再gson包下新建一個weather類,代碼如下:

public class Weather {

    public String status;

    public Basic basic;

    public AQI aqi;

    public Now now;

    public Suggestion suggestion;

    @SerializedName("daily_forecast")
    public List<Forecast> forecastList;

}

    在Weather類中,我們對Basic、AQI、Now、Suggestion和Forecast類進行引用。其中由於daily_forecast中包含的是一個數組,因此用List集合來引用Forecas類。

    另外,返回的天氣數據中還會包含一項status數據,成功返回ok,失敗則會返回具體原因。

    現在所有GSON實體類都定義好餓,接下來開始編寫天氣界面。

 

(二)編寫天氣界面

    先創建一個用於顯示天氣信息的活動。右擊com.coolweather.android包-New-Activity-Empty Activity,創建一個WeatherActivity,並將布局名指定成activity_weather.xml。

    由於所有的天氣信息都將在同一個界面上顯示,因此activity_weather.xml會是一個很長的布局文件。那么為了讓里面的代碼不至於那么混亂,這里使用引入布局技巧。即將界面的不同部分寫在不同的布局文件里面,再通過引入布局的方式集成到activity_weather.xml中,這樣整個布局文件就會顯得非常工整。

    右擊res/layout-New-Layout resource file,新建一個title.xml作為頭布局,代碼如下所示:

 1 <RelativeLayout
 2     xmlns:android="http://schemas.android.com/apk/res/android
 3     android:layout_height="?attr/actionBarSize"
 4     android:layout_width="match_parent"">
 5      <TextView 
 6         android:id="@+id/title_city"
 7         android:layout_height="wrap_content"
 8         android:layout_width="wrap_content"
 9         android:textSize="20sp" 
10         android:textColor="#fff"
11         android:layout_centerInParent="true"/>
12      <TextView 
13         android:id="@+id/title_update_time"
14         android:layout_height="wrap_content"
15         android:layout_width="wrap_content"
16         android:layout_centerVertical="true"
17         android:textSize="16sp" 
18         android:textColor="#fff"
19         android:layout_alignParentRight="true"
20         android:layout_marginRight="10dp"/>
21  </RelativeLayout>

    這段代碼在頭布局中放置了兩個TextView,一個居中顯示城市名,一個居右顯示更新時間。

    新建一個now.xml作為當前天氣信息的布局,代碼如下:

 1 <LinearLayout 
 2     xmlns:android="http://schemas.android.com/apk/res/android"
 3     android:orientation="vertical"
 4     android:layout_height="wrap_content"
 5     android:layout_width="match_parent" 
 6     android:layout_margin="15dp"  >
 7     <TextView 
 8          android:id="@+id/degree_text"
 9          android:layout_height="wrap_content"
10          android:layout_width="wrap_content"
11          android:textSize="60sp" 
12          android:textColor="#fff" 
13          android:layout_gravity="end" />
14     <TextView 
15          android:id="@+id/weather_info_text"
16          android:layout_height="wrap_content"
17          android:layout_width="wrap_content"
18          android:textSize="20sp" 
19          android:textColor="#fff" 
20          android:layout_gravity="end" /> 
21 </LinearLayout>  

    當前天氣信息的布局中放置兩個TextView,一個用於顯示當前氣溫,一個用於顯示天氣概況。

    新建forecast.xml作為未來幾天天氣信息的布局,代碼如下:

 1 <LinearLayout 
 2     xmlns:android="http://schemas.android.com/apk/res/android"
 3     android:layout_height="wrap_content"
 4     android:layout_width="match_parent"
 5     android:orientation="vertical" 
 6     android:background="#8000"
 7     android:layout_margin="15dp" >
 8     <TextView 
 9         android:layout_height="wrap_content"
10         android:layout_width="wrap_content" 
11         android:layout_marginTop="15dp"
12         android:layout_marginLeft="15dp"
13         android:textSize="20sp" 
14         android:textColor="#fff"
15         android:text="預報" /> 
16     <LinearLayout 
17         android:id="@+id/forecast_layout"
18         android:orientation="vertical"
19         android:layout_height="wrap_content"
20         android:layout_width="match_parent" > 
21     </LinearLayout> 
22 </LinearLayout>

    這里最外層使用LinearLayout定義了一個半透明的背景,然后使用TextView定義了一個標題,接着又使用一個Linearlayout定義了一個用於顯示未來幾天天氣信息的布局。不過這個布局中並沒有放入任何內容,因為這是要根據服務器返回的數據在代碼中動態添加的。

    為此,我們還需要再定義一個未來天氣信息的子項布局,創建forecast_item.xml文件,代碼如下所示:

 1 <LinearLayout 
 2     xmlns:android="http://schemas.android.com/apk/res/android"
 3     android:layout_margin="15dp"
 4     android:layout_height="wrap_content"
 5     android:layout_width="match_parent" >
 6     <TextView
 7         android:id="@+id/date_text"
 8         android:layout_height="wrap_content"
 9         android:layout_width="0dp" 
10         android:textColor="#fff" android:layout_weight="2"
11         android:layout_gravity="center_vertical" />
12     <TextView 
13         android:id="@+id/info_text"
14         android:layout_height="wrap_content"
15         android:layout_width="0dp" 
16         android:textColor="#fff"
17         android:layout_weight="1"
18         android:layout_gravity="center_vertical"
19         android:gravity="center"/>
20     <TextView 
21         android:id="@+id/max_text"
22         android:layout_height="wrap_content"
23         android:layout_width="0dp" android:textColor="#fff"
24         android:layout_weight="1"
25         android:layout_gravity="center" 
26         android:gravity="right"/>
27     <TextView 
28         android:id="@+id/min_text"
29         android:layout_height="wrap_content"
30         android:layout_width="0dp" 
31         android:textColor="#fff"
32         android:layout_weight="1"
33         android:layout_gravity="center" 
34         android:gravity="right"/>
35 </LinearLayout>

    子項布局中放置了4個TextView,一個用於顯示天氣預報,一個用於顯示天氣概況,另外兩個分別用於顯示當前的最高溫度和最低溫度。

    新建api.xml作為空氣質量信息的布局,代碼如下所示:

 1 <LinearLayout
 2     xmlns:android="http://schemas.android.com/apk/res/android"
 3     android:layout_height="wrap_content"
 4     android:layout_width="match_parent"
 5     android:orientation="vertical"
 6     android:background="#8000"
 7     android:layout_margin="15dp"> 
 8     <TextView 
 9         android:layout_height="wrap_content"
10         android:layout_width="wrap_content"
11         android:layout_marginTop="15dp"
12         android:layout_marginLeft="15dp"
13         android:textSize="20sp" 
14         android:textColor="#fff" 
15         android:text="空氣質量" />
16     <LinearLayout 
17         android:layout_margin="15dp"
18         android:layout_height="wrap_content"
19         android:layout_width="match_parent">
20         <RelativeLayout 
21             android:layout_height="match_parent"
22             android:layout_width="0dp"
23             android:layout_weight="1"> 
24             <LinearLayout 
25                 android:layout_height="wrap_content"
26                 android:layout_width="match_parent"
27                 android:orientation="vertical"
28                 android:layout_centerInParent="true"> 
29                 <TextView 
30                     android:id="@+id/aqi_text"
31                     android:layout_height="wrap_content"
32                     android:layout_width="wrap_content"
33                     android:textSize="40sp" 
34                     android:textColor="#fff"
35                     android:layout_gravity="center" />
36                 <TextView 
37                     android:layout_height="wrap_content"
38                     android:layout_width="wrap_content"
39                     android:layout_gravity="center"
40                     android:textColor="#fff" 
41                     android:text="AQI指數" />
42             </LinearLayout>
43         </RelativeLayout> 
44         <RelativeLayout 
45             android:layout_height="match_parent"
46             android:layout_width="0dp"
47             android:layout_weight="1">
48             <LinearLayout 
49                 android:layout_height="wrap_content"
50                 android:layout_width="match_parent"
51                 android:orientation="vertical" 
52                 android:layout_centerInParent="true">
53                 <TextView 
54                      android:id="@+id/pm25_text
55                      android:layout_height="wrap_content"
56                      android:layout_width="wrap_content"
57                      android:layout_gravity="center"
58                      android:textSize="40sp" 
59                      android:textColor="#fff""/>
60                 <TextView 
61                     android:layout_height="wrap_content"
62                     android:layout_width="wrap_content"
63                     android:layout_gravity="center"
64                     android:textColor="#fff"
65                     android:text="PM2.5指數"/>
66             </LinearLayout>
67          </RelativeLayout>
68      </LinearLayout>
69 </LinearLayout>

    這個布局看上去有點長,但很好理解。首先跟前面一樣的,使用LinearLayout定義一個半透明的背景,然后使用TextView定義了一個標題。接下來,這里使用LinearLayout和RelativeLayout嵌套的方式實現了一個左右平分並且居中對齊的布局,分別用於顯示AQI指數和PM2.5指數。

    新建suggestion.xml作為生活建議信息的布局,代碼如下:

 1 <LinearLayout
 2     xmlns:android="http://schemas.android.com/apk/res/android"
 3     android:layout_margin="15dp"
 4     android:layout_height="wrap_content"
 5     android:layout_width="match_parent"
 6     android:orientation="vertical"
 7     android:background="#8000" >
 8      <TextView 
 9         android:layout_height="wrap_content"
10         android:layout_width="wrap_content"
11         android:layout_marginTop="15dp"
12         android:layout_marginLeft="15dp"
13         android:textSize="20sp" 
14         android:textColor="#fff" android:text="生活建議" />
15         <TextView 
16             android:id="@+id/comfort_text"
17             android:layout_margin="15dp"
18             android:layout_height="wrap_content"
19             android:layout_width="wrap_content"
20             android:textColor="#fff" />
21         <TextView 
22             android:id="@+id/car_wash_text"
23             android:layout_margin="15dp"
24             android:layout_height="wrap_content"
25             android:layout_width="wrap_content"
26             android:textColor="#fff"/> 
27         <TextView
28             android:id="@+id/sport_text"w 
29             android:layout_margin="15dp"
30             android:layout_height="wrap_content"
31             android:layout_width="wrap_content"
32             android:textColor="#fff" />
33 </LinearLayout> 

    這里同樣也是定義了一個半透明的背景和一個標題,然后下面使用3個TextView分別用於顯示舒適度、洗車指數、和運動建議的相關數據。

    這樣我們就把天氣界面上每個部分的布局文件都編寫好了,接下來的工作就是將它們引入到activity_weather.xml當中,如下所示:

 1 <FrameLayout 
 2     xmlns:android="http://schemas.android.com/apk/res/android"
 3     android:layout_height="match_parent"
 4     android:layout_width="match_parent"
 5     android:background="@color/colorPrimary">
 6     <ScrollView 
 7         android:id="@+id/weather_layout"
 8         android:layout_height="match_parent"
 9         android:layout_width="match_parent"
10         android:overScrollMode="never" 
11         android:scrollbars="none">
12        <LinearLayout 
13             android:layout_height="wrap_content"
14             android:layout_width="match_parent"
15             android:fitsSystemWindows="true"
16             android:orientation="vertical">
17             <include layout="@layout/title"/>
18             <include layout="@layout/now"/>
19             <include layout="@layout/forecast"/>
20             <include layout="@layout/aqi"/>
21             <include layout="@layout/suggestion"/>
22         </LinearLayout>
23      </ScrollView> 
24 </FrameLayout>

    可以看到,首先最外層布局使用了一個FrameLayout,並將它的背景色設置成colorPrimary。然后在FrameLayout中嵌套了一個ScrollView,這是因為天氣界面中的內容比較多,使用ScrollWiew可以允許我們通過滾動的方式查看屏幕以外的內容。

    由於ScrollView的內部只允許存在一個直接子布局,因此這里又嵌套了一個垂直方向的LinearLayout,然后在LinearLayout中將剛才定義的布局逐個引入。

    這樣,我們天氣界面編寫完成了,接下來編寫業務邏輯,將天氣顯示到界面上。

 

(三)將天氣顯示到界面上

    首先在Utility類中添加一個用於解析天氣JSON數據的方法,如下所示:

public class Utility{
    ....
    /**
     * 將返回的JSON數據解析成Weather實體類
     */
    public static Weather handleWeatherResponse(String response){
        try{
            JSONObject jsonObject = new JSONObject(response);
            JSONArray jsonArray = jsonObject.getJSONArray("HeWeather");
            String weatherContent = jsonArray.getJSONObject(0).toString();
            return new Gson().fromJson(weatherContent,Weather.class);
        }catch(Exception e){
              e.printStackTrace();
        }
        return null;
    }
}
        

    可以看到,handleWeatherResponse()方法中先是通過JSONObject和JSONArray將天氣數據中的主題內容解析出來,即如下內容:

{
    "status":"ok:,
    "basic":{},
    "aqi":{},
    "now":{},
    "suggestion":{},
    "daily_forecast":[]
}

    由於我們之前已經按照上面的數據格式定義過相應的GSON實體類,因此只需要通過調用fromJson()方法就可以直接將JSON數據轉換成Weather對象了。

    接下來的工作是我們如何在活動中去請求天氣數據,以及將數據顯示到界面上。修改WeatherActivity中的代碼,如下所示:

  1 public class WeatherActivity extends AppCompatActivity {
  2 
  3     private ScrollView weatherLayout;
  4 
  5     private TextView titleCity;
  6 
  7     private TextView titleUpdateTime;
  8 
  9     private TextView degreeText;
 10 
 11     private TextView weatherInfoText;
 12 
 13     private LinearLayout forecastLayout;
 14 
 15     private TextView aqiText;
 16 
 17     private TextView pm25Text;
 18 
 19     private TextView comfortText;
 20 
 21     private TextView carWashText;
 22 
 23     private TextView sportText;
 24 
 25     @Override
 26     protected void onCreate(Bundle savedInstanceState) {
 27         super.onCreate(savedInstanceState);
 28         setContentView(R.layout.activity_weather);
 29         // 初始化各控件
 30         weatherLayout = (ScrollView) findViewById(R.id.weather_layout);
 31         titleCity = (TextView) findViewById(R.id.title_city);
 32         titleUpdateTime = (TextView) findViewById(R.id.title_update_time);
 33         degreeText = (TextView) findViewById(R.id.degree_text);
 34         weatherInfoText = (TextView) findViewById(R.id.weather_info_text);
 35         forecastLayout = (LinearLayout) findViewById(R.id.forecast_layout);
 36         aqiText = (TextView) findViewById(R.id.aqi_text);
 37         pm25Text = (TextView) findViewById(R.id.pm25_text);
 38         comfortText = (TextView) findViewById(R.id.comfort_text);
 39         carWashText = (TextView) findViewById(R.id.car_wash_text);
 40         sportText = (TextView) findViewById(R.id.sport_text);
 41         SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(this);
 42         String weatherString = prefs.getString("weather", null);
 43         final String weatherId;
 44         if (weatherString != null) {
 45             // 有緩存時直接解析天氣數據
 46             Weather weather = Utility.handleWeatherResponse(weatherString);
 47             showWeatherInfo(weather);
 48         } else {
 49             // 無緩存時去服務器查詢天氣
 50             String weatherId = getIntent().getStringExtra("weather_id")
 51             weatherLayout.setVisibility(View.INVISIBLE);
 52             requestWeather(weatherId);
 53         }
 54     }
 55 
 56     /**
 57      * 根據天氣id請求城市天氣信息。
 58      */
 59     public void requestWeather(final String weatherId) {
 60         String weatherUrl = "http://guolin.tech/api/weather?cityid=" + weatherId + "&key=bc0418b57b2d4918819d3974ac1285d9";
 61         HttpUtil.sendOkHttpRequest(weatherUrl, new Callback() {
 62             @Override
 63             public void onResponse(Call call, Response response) throws IOException {
 64                 final String responseText = response.body().string();
 65                 final Weather weather = Utility.handleWeatherResponse(responseText);
 66                 runOnUiThread(new Runnable() {
 67                     @Override
 68                     public void run() {
 69                         if (weather != null && "ok".equals(weather.status)) {
 70                             SharedPreferences.Editor editor = PreferenceManager.getDefaultSharedPreferences(WeatherActivity.this).edit();
 71                             editor.putString("weather", responseText);
 72                             editor.apply();
 73                             showWeatherInfo(weather);
 74                         } else {
 75                             Toast.makeText(WeatherActivity.this, "獲取天氣信息失敗", Toast.LENGTH_SHORT).show();
 76                         }
 77                     }
 78                 });
 79             }
 80 
 81             @Override
 82             public void onFailure(Call call, IOException e) {
 83                 e.printStackTrace();
 84                 runOnUiThread(new Runnable() {
 85                     @Override
 86                     public void run() {
 87                         Toast.makeText(WeatherActivity.this, "獲取天氣信息失敗", Toast.LENGTH_SHORT).show();
 88                         swipeRefresh.setRefreshing(false);
 89                     }
 90                 });
 91             }
 92         });
 93     }
 94     /**
 95      * 處理並展示Weather實體類中的數據。
 96      */
 97     private void showWeatherInfo(Weather weather) {
 98         String cityName = weather.basic.cityName;
 99         String updateTime = weather.basic.update.updateTime.split(" ")[1];
100         String degree = weather.now.temperature + "℃";
101         String weatherInfo = weather.now.more.info;
102         titleCity.setText(cityName);
103         titleUpdateTime.setText(updateTime);
104         degreeText.setText(degree);
105         weatherInfoText.setText(weatherInfo);
106         forecastLayout.removeAllViews();
107         for (Forecast forecast : weather.forecastList) {
108             View view = LayoutInflater.from(this).inflate(R.layout.forecast_item, forecastLayout, false);
109             TextView dateText = (TextView) view.findViewById(R.id.date_text);
110             TextView infoText = (TextView) view.findViewById(R.id.info_text);
111             TextView maxText = (TextView) view.findViewById(R.id.max_text);
112             TextView minText = (TextView) view.findViewById(R.id.min_text);
113             dateText.setText(forecast.date);
114             infoText.setText(forecast.more.info);
115             maxText.setText(forecast.temperature.max);
116             minText.setText(forecast.temperature.min);
117             forecastLayout.addView(view);
118         }
119         if (weather.aqi != null) {
120             aqiText.setText(weather.aqi.city.aqi);
121             pm25Text.setText(weather.aqi.city.pm25);
122         }
123         String comfort = "舒適度:" + weather.suggestion.comfort.info;
124         String carWash = "洗車指數:" + weather.suggestion.carWash.info;
125         String sport = "運行建議:" + weather.suggestion.sport.info;
126         comfortText.setText(comfort);
127         carWashText.setText(carWash);
128         sportText.setText(sport);
129         weatherLayout.setVisibility(View.VISIBLE);
130     }
131 
132 }

    這個活動中的代碼比較長,我們一步步梳理下。在onCreate()方法中先是去獲取一些控件的實例,然后會嘗試從本地緩存中讀取天氣數據。第一次是沒有緩存的,因此會從Intent中取出天氣id,並調用requestWeather()方法來從服務器請求天氣數據。注意,請求數據的時候先將ScrollView進行隱藏,不然空數據的界面看上去會很奇怪。

    requestWeather()方法中先是使用了參數中傳入的天氣id,並調用requestWeather()方法來從服務器請求天氣數據。注意,請求數據的時候先將ScollView進行隱藏,不然空數據的界面看上去會很奇怪。

    requestWeather()方法中先是使用了參數中傳入的天氣id和我們之前申請好的APIKey拼裝出一個接口地址,接着調用HttpUtil.sendOkHttpRequest()方法來向該地址發出請求,服務器會將相應城市的天氣信息以JSON格式返回。然后我們在onResponse()回調中先調用Utility.handleWeatherResponse()方法將返回的JSON數據轉換成Weather對象,再將當前線程切換到主線程。然后進行判斷,如果服務器返回的status狀態是ok,就說明請求成功了,此時將返回的數據緩存到SharedPreferences當中,並調用showWeatherInfo()方法來進行內容顯示。

    showWeatherInfo()方法中的邏輯就比較簡單了,其實就是從weather對象中獲取數據,然后顯示到相應的控件上。注意在未來幾天天氣預報的部分我們使用for循環來處理每天的天氣信息,在循環中動態加載forecast_item.xml布局並設置相應的數據,然后添加到父布局當中。設置完了所有數據之后,記得要將ScrollView重新變成可見的。

    這樣我們就將首次進入WeatherActivity時的邏輯全部梳理完了,那么當下一次再進入WeatherActivity時,由於緩存已經存在了,因此會直接解析並顯示天氣數據,而不會再次發起網絡請求了。

    處理完了WeatherActivity中的邏輯,接下來我們要做的,就是如何從省市縣列表界面跳轉到天氣界面了,修改ChooseAreaFragment中的代碼,如下所示:

 1 public class ChooseAreaFragment extends Fragment {
 2    ...
 3     @Override
 4     public void onActivityCreated(Bundle savedInstanceState) {
 5         super.onActivityCreated(savedInstanceState);
 6         listView.setOnItemClickListener(new AdapterView.OnItemClickListener() {
 7             @Override
 8             public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
 9                 if (currentLevel == LEVEL_PROVINCE) {
10                     selectedProvince = provinceList.get(position);
11                     queryCities();
12                 } else if (currentLevel == LEVEL_CITY) {
13                     selectedCity = cityList.get(position);
14                     queryCounties();
15                 } else if (currentLevel == LEVEL_COUNTY) {
16                     String weatherId = countyList.get(position).getWeatherId();
17                     if (getActivity() instanceof MainActivity) {
18                         Intent intent = new Intent(getActivity(), WeatherActivity.class);
19                         intent.putExtra("weather_id", weatherId);
20                         startActivity(intent);
21                         getActivity().finish();
22                     }
23             }
24        });
25        ...
26     }
27     ...
28 }

    這里在onItemClick()方法中加入了一個if判斷,如果當前級別時LEVEL_COUNTY,就啟動WeatherActivity,並把當前選中縣的天氣id傳遞過去。

    另外,我們還需要在MainActivity中加入一個緩存數據的判斷才行。修改MainActivity中的代碼,如下所示:

 1 public class MainActivity extends AppCompatActivity{
 2     @Override
 3     protected void onCreate(Bundle savedInstanceState){
 4         super.onCreate(savedInstanceState);
 5         setContentView(R.layout.activity_main);
 6         SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(this);
 7         if(prefs.getString("weather",null) != null){
 8             Intent intent = new Intent(this,WeatherActivity.class);
 9             startActivity(intent);
10             finish();
11         }
12     }
13 }

    可以看到,這里在onCreate()方法的一開始先從SharedPreferences文件中讀取緩存數據,如果不為null就說明之前已經請求過天氣數據了,那就直接跳轉到WeatherActivity即可。

 

(四)獲取必應每日一圖

    現在我們已經把天氣界面編寫得不錯了,不過和市場上的天氣如見的界面相比還是又一定差距。出色的天氣軟件不會使用一個固定的背景色。因此我們添加更換背景的功能。這里我們使用一個巧妙的方法。

    必應時一個由微軟開發的搜索引擎網站,它每天都會在首頁展示一張精美的背景圖片。如果我們使用它們來作為天氣界面的背景圖,不僅使界面更美觀,而且解決一成不變的問題。

    為此,作者准備了一個獲取必應每日一圖的接口:http://guolin.tech/api/bing_pic。

    訪問這個接口,服務器會返回今日的必應背景圖連接:

    http://cn.bing.com/az/hprichbg/rb/ChicagoHarborLH_ZH-CN9974330969_1920x1080.jpg.

    然后我們再使用Glide去加載這張圖片就可以了。

    首先修改activity_weather.xml中的代碼,如下所示:

 1 <FrameLayout
 2     xmlns:android="http://schemas.android.com/apk/res/android"
 3     android:layout_width="match_parent"
 4     android:layout_height="match_parent"
 5     android:background="@color/colorPrimary">
 6     <ImageView
 7         android:id="@+id/bing_pic_img"
 8         android:layout_width="match_parent"
 9         android:layout_height="match_parent"
10         android:scaleType="centerCrop"/>
11     <ScrollView
12         android:id="@+id/weather_layout"
13         android:layout_width="match_parent"
14         android:layout_height="match_parent"
15         android:scrollbars="none"
16         android:overScrollMode="never">
17 
18     ....
19 
20     </ScrollView>
21 </FrameLayout>

    這里我們在FrameLayout中添加了一個ImageView,並且將它的寬和高都設置成match_parent。由於FrameLayout默認情況下會將控件都放置在左上角,因此ScrollView會完全覆蓋住ImageView,從而ImageView也就成為背景圖片了。

    接着修改WeatherActivity中的代碼,如下所示:

 

public class WeatherActivity extends AppCompatActivity {

    private ImageView bingPicImg;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_weather);
        // 初始化各控件
        bingPicImg = (ImageView) 
        ...
        String bingPic = prefs.getString("bing_pic", null);
        if (bingPic != null) {
            Glide.with(this).load(bingPic).into(bingPicImg);
        } else {
            loadBingPic();
        }
    }

    /**
     * 根據天氣id請求城市天氣信息。
     */
    public void requestWeather(final String weatherId) {
        ...
        loadBingPic();
    }

    /**
     * 加載必應每日一圖
     */
    private void loadBingPic() {
        String requestBingPic = "http://guolin.tech/api/bing_pic";
        HttpUtil.sendOkHttpRequest(requestBingPic, new Callback() {
            @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);
                    }
                });
            }

            @Override
            public void onFailure(Call call, IOException e) {
                e.printStackTrace();
            }
        });
    }

   ...

}

    可以看到,首先在onCreate()方法中獲取了新增控件ImageView的實例,然后嘗試從SharedPreferences中讀取緩存的背景圖片。如果有緩存的話就直接使用Glide來加載這張圖片,如果沒有的話就調用loadBingPic()方法去請求今日的必應背景圖。

    loadBingPic()方法中的邏輯就非常簡單了,先是調用了HttpUtil.sendOkHttpRequest()方法獲取到必應背景圖的連接,然后將這個鏈接緩存到SharedPreferences當中,再將當前線程切換到主線程,最后使用Glide來加載這張圖片就可以了。另外需要注意,在requestWeather()方法的最后也需要調用一下loadBingPic()方法,這樣在每次請求天氣信息的時候同時也會刷新背景圖片。這樣每天都會是不同的圖片。

    不過這樣背景圖和狀態欄沒有融合到一起,這里我們使用一種簡單的實現方式。修改WeatherActivity中的代碼,如下所示:

 1 public class WeatherActivity extends AppCompatActivity {
 2 
 3     @Override
 4     protected void onCreate(Bundle savedInstanceState) {
 5         super.onCreate(savedInstanceState);
 6         if (Build.VERSION.SDK_INT >= 21) {
 7             View decorView = getWindow().getDecorView();
 8             decorView.setSystemUiVisibility(View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN
 9                     | View.SYSTEM_UI_FLAG_LAYOUT_STABLE);
10             getWindow().setStatusBarColor(Color.TRANSPARENT);
11         }
12         setContentView(R.layout.activity_weather);
13         ...
14     }
15     ...
16 }

    由於這個功能是Android5.0及以上的系統才支持的,因此我們先在代碼中做了一個系統版本號的判斷,只有當版本號大於或等於21,也就是5.0及以上系統時才會執行后面的代碼。

    接着我們調用getWindow().getDecorView()方法拿到當前活動的DecorView,在調用它的setSystemUiVisibility()方法來改變系統UI的顯示,這里傳入View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN和View.SYSTEM_UI_FLAG_LAYOUT_STABLE就表示活動的布局會顯示在狀態欄上面,最后調用一下setStatusBarColor()方法將狀態欄設置成透明色即可。但僅僅這些代碼,天氣界面的頭布局幾乎和系統狀態欄緊貼到一起,這是由於系統狀態欄已經成為我們布局的一部分,因此沒有單獨為它留出空間。當然這個問題也是非常好解決的。借助android:fitsSystemWindows屬性就可以了。修改activity_weather.xml中的代碼,如下所示:

 1 <FrameLayout 
 2     xmlns:android="http://schemas.android.com/apk/res/android"
 3     android:layout_height="match_parent"
 4     android:layout_width="match_parent"
 5     android:background="@color/colorPrimary">
 6     <ScrollView 
 7         android:id="@+id/weather_layout"
 8         android:layout_height="match_parent"
 9         android:layout_width="match_parent"
10         android:overScrollMode="never" 
11         android:scrollbars="none">
12        <LinearLayout 
13             android:layout_height="wrap_content"
14             android:layout_width="match_parent"
15             android:fitsSystemWindows="true"
16             android:orientation="vertical"
17             android:fitsSystemWindows="true">
18             ...
19         </LinearLayout>
20      </ScrollView> 
21 </FrameLayout>

     這里在ScrollView的LinearLayout中增加了android:fitsSystemWindows屬性,設置成true就表示會為系統狀態欄留出空間。

      下一章節開發手動更新天氣和切換城市的功能。

 

具體實現步驟連接:

android開發學習之路——天氣預報之技術分析與數據庫(一)

android開發學習之路——天氣預報之遍歷省市縣數據(二)

android開發學習之路——天氣預報之顯示天氣信息(三)

android開發學習之路——天氣預報之手動更新天氣和切換城市(四)

android開發學習之路——天氣預報之后台自動更新天氣(五)

    


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM