zoukankan      html  css  js  c++  java
  • 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开发学习之路——天气预报之后台自动更新天气(五)

        

  • 相关阅读:
    static inline和inline的区别——stm32实测
    实现自动构建编译javaweb项目并发布到N台服务器
    手把手教你用Mysql-Cluster-7.5搭建数据库集群
    HAProxy实现mysql负载均衡
    使用LVS+keepalived实现mysql负载均衡的实践和总结
    阿里巴巴Java开发手册———总结
    阿里巴巴Java开发手册———个人追加的见解和补充(五)
    阿里巴巴Java开发手册———个人追加的见解和补充(四)
    Chapter 3 Phenomenon——24
    阿里巴巴Java开发手册———个人追加的见解和补充(三)
  • 原文地址:https://www.cnblogs.com/weilongfu/p/7435895.html
Copyright © 2011-2022 走看看