紧接上文,下面完成显示天气信息功能
1. 定义GSON实体类
返回得数据格式如下所示
{"HeWeather": [{"basic":{"cid":"CN101270101","location":"成都","parent_city":"成都","admin_area":"四川","cnty":"中国","lat":"30.65946198","lon":"104.06573486","tz":"+8.00","city":"成都","id":"CN101270101","update":{"loc":"2018-11-01 23:49","utc":"2018-11-01 15:49"}},"update":{"loc":"2018-11-01 23:49","utc":"2018-11-01 15:49"},"status":"ok","now":{"cloud":"0","cond_code":"100","cond_txt":"晴","fl":"12","hum":"95","pcpn":"0.0","pres":"1021","tmp":"12","vis":"1","wind_deg":"94","wind_dir":"东风","wind_sc":"1","wind_spd":"5","cond":{"code":"100","txt":"晴"}},"daily_forecast":[{"date":"2018-11-01","cond":{"txt_d":"晴"},"tmp":{"max":"23","min":"15"}},{"date":"2018-11-02","cond":{"txt_d":"晴"},"tmp":{"max":"22","min":"14"}},{"date":"2018-11-03","cond":{"txt_d":"晴"},"tmp":{"max":"20","min":"14"}}],"aqi":{"city":{"aqi":"99","pm25":"74","qlty":"良"}},"suggestion":{"comf":{"type":"comf","brf":"舒适","txt":"今天夜间不太热也不太冷,风力不大,相信您在这样的天气条件下,应会感到比较清爽和舒适。"},"sport":{"type":"sport","brf":"适宜","txt":"天气较好,赶快投身大自然参与户外运动,尽情感受运动的快乐吧。"},"cw":{"type":"cw","brf":"较适宜","txt":"较适宜洗车,未来一天无雨,风力较小,擦洗一新的汽车至少能保持一天。"}}}]}
其中basic,aqi,now,suggestion,daily_forecast的内容又有具体内容,因此我们将这5个部分定义成5个实体类。
Basic类
public class Basic {
@SerializedName("city")
public String cityName; //城市名称
@SerializedName("id")
public String weatherId; //天气ID
public Update update;
public class Update{
@SerializedName("loc")
public String updateTime; //更新时间
}
}
AQI类
public class AQI {
public AQICity city;
public class AQICity {
public String aqi;
public String pm25;
}
}
Now类
public class Now {
@SerializedName("tmp")
public String temperature;
@SerializedName("cond")
public More more;
public class More{
@SerializedName("txt")
public String info;
}
}
Suggestion类
public class Suggestion {
@SerializedName("comf")
public Comfort comfort;
@SerializedName("cw")
public CarWash carWash;
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;
}
}
Forecast类
public class Forecast {
public String date;
@SerializedName("tmp")
public Temperature temperature;
@SerializedName("cond")
public More more;
public class Temperature {
public String max;
public String min;
}
public class More{
@SerializedName("txt_d")
public String info;
}
}
接下来还需要创建一个总的实体类来因以哦那个刚刚创建的各个实体类。在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;
}
2. 编写天气界面
首先创建一个显示天气的活动WeatherActivity。如果将所有天气信息显示在activity_weather.xml页面上,会很混乱,我们将不同部分写在不同的页面里面,在通过引入布局方式集成到activity_weather.xml中。
新建title.xml页面,其中包含两个TextView,一个居中显示城市名,另一个显示居右更新时间
<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>
新建now.xml页面,其中包含两个TextView,一个显示当前气温,另一个显示天气概况
<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="生活建议"
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>
新建forecast.xml页面,作为未来几天天气信息的布局,使用TextView显示标题,接着使用LinearLayout定义了一个用于显示未来几天天气信息对的布局,根据服务器返回的数据在代码中动态添加
<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="预报"
android:textColor="#fff"
android:textSize="20sp"/>
<LinearLayout
android:id="@+id/forecast_layout"
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="wrap_content"></LinearLayout>
</LinearLayout>
为此,我们还需要定义一个未来天气信息的子项布局,创建forecast_item.xml。子项布局中放置了4个TextView,分别用于显示天气预报日期,天气概况,当天的最高温度和最低温度。
<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"
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="right"
android:layout_weight="1"
android:gravity="center"
android:textColor="#fff" />
</LinearLayout>
新建aqi.xml作为空气质量的布局。使用TextView定义了一个标题,然后使用和LinearLayout和RelativeLayout嵌套的方式实现了一个左右平分并且居中对齐的布局,分别用于显示AQI指数和PM2.5指数。
LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_margin="15dp"
android:background="#8000"
android:orientation="vertical">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginLeft="15dp"
android:layout_marginTop="15dp"
android:text="空气质量"
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="match_parent"
android:layout_weight="1">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_centerInParent="true"
android:orientation="vertical">
<TextView
android:id="@+id/aqi_text"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android: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="AQI 指数"
android:textColor="#fff" />
</LinearLayout>
</RelativeLayout>
<RelativeLayout
android:layout_width="0dp"
android:layout_height="match_parent"
android:layout_weight="1">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_centerInParent="true"
android:orientation="vertical">
<TextView
android:id="@+id/pm25_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:text="PM2.5指数"
android:textColor="#fff" />
</LinearLayout>
</RelativeLayout>
</LinearLayout>
</LinearLayout>
新建suggestion.xml作为生活建议信息的布局。使用3个TextView分别用于显示舒适度、洗车指数和运动建议的相关数据。
<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="生活建议"
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>
最后把他们嵌入到activity_weather.xml中
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="@color/colorPrimary">
<ImageView
android:id="@+id/bing_pic_img"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:scaleType="centerCrop"/>
<ScrollView
android:id="@+id/weather_layout"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:overScrollMode="never"
android:scrollbars="none">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical">
<include layout="@layout/title"></include>
<include layout="@layout/now"></include>
<include layout="@layout/forecast"></include>
<include layout="@layout/aqi"></include>
<include layout="@layout/suggestion"></include>
</LinearLayout>
</ScrollView>
</FrameLayout>
3. 将天气显示到界面上
首先在Utility类中添加一个用于解析天气JSON数据的方法。
/**
* 将返回得JSON数据解析成Weather实体类
* @param response
* @return
*/
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 (JSONException e) {
e.printStackTrace();
}
return null;
}
接下来在活动中请求天气数据,并将其显示到界面上。在onCreate中线获得控件的实例,然后尝试在缓存中读取天气,第一次肯定没有缓存,因此从Intent中取出天气id,并调用requestWeather()方法从服务器请求数据。showWeatherInfo()就是从Weather对象中获取数据然后显示在相应控件上。
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;
private ImageView bingPicImg;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_weather);
// 初始化各控件
weatherLayout = (ScrollView) findViewById(R.id.weather_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);
bingPicImg = (ImageView) findViewById(R.id.bing_pic_img);
SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(this);
String weatherString = prefs.getString("weather", null);
if (weatherString != null) {
//有缓存直接解析天气信息
Weather weather = Utility.handleWeatherResponse(weatherString);
showWeatherInfo(weather);
} else {
// 无缓存去服务器查询天气
String weatherId = getIntent().getStringExtra("weather_id");
requestWeather(weatherId);
}
String bingPic = prefs.getString("big_pic", null);
if(bingPic!=null) {
Glide.with(this).load(bingPic).into(bingPicImg);
} else {
loadBingPic();
}
}
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);
}
});
}
});
}
public void requestWeather(final String weatherId) {
String weatherUrl = "http://guolin.tech/api/weather?cityid=" +
weatherId + "&key=9618c9b7080b4638a16fca8687bf9a60";
HttpUtil.sendOkHttpRequest(weatherUrl, new Callback() {
@Override
public void onFailure(Call call, IOException e) {
runOnUiThread(new Runnable() {
@Override
public void run() {
Toast.makeText(WeatherActivity.this, "获取天气信息失败1",
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, "获取天气信息失败2",
Toast.LENGTH_SHORT).show();
}
}
});
}
});
loadBingPic();
}
/**
* 处理并展示Weather实体类中得数据
*
* @param weather
*/
private void showWeatherInfo(Weather weather) {
try {
String cityName = weather.basic.cityName;
String updateTime = weather.basic.update.updateTime.split(" ")[1];
String degree = weather.now.temperature + "°C";
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.forecast_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 = "洗车指数:" + weather.suggestion.carWash.info;
String sport = "运动建议:" + weather.suggestion.sport.info;
comfortText.setText(comfort);
carWashText.setText(carWash);
sportText.setText(sport);
weatherLayout.setVisibility(View.VISIBLE);
} catch (Exception e) {
Toast.makeText(WeatherActivity.this, e.toString(), Toast.LENGTH_LONG).show();
e.printStackTrace();
}
}
}
处理完了WeatherActivity中的逻辑,接下来就是从省市县列表界面跳转到天气界面了,修改ChooseAreaFragment中的代码,如果当前级别是LEVEL_COUNTY,就启动WeatherActivity,如下:
@Override
public void onActivityCreated(@Nullable Bundle savedInstanceState) {
super.onActivityCreated(savedInstanceState);
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);
queryCities();
} else if (currentLevel == LEVEL_CITY) {
selectedCity = cityList.get(position);
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();
}
}
});
...
}
另外还需要在MianActivity中加入一个缓存数据判断,修改MainActivity中的代码,先从SharedPreferences文件中读取缓存数据,如果不为null就说明已经请求过天气数据了,无需再次选择城市,直接进入WeatherActivity,如下:
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();
}
}
3. 加载页面背景图
获取必应每日一图的接口,然后再使用Glide去加载这张图片就行了。首先修改activity_weather.xml中代码
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="@color/colorPrimary">
<!--我是新增的-->
<ImageView
android:id="@+id/bing_pic_img"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:scaleType="centerCrop"/>
...
</FrameLayout>
修改WeatherActivity中的代码
public class WeatherActivity extends AppCompatActivity {
...
private ImageView bingPicImg;
}
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
...
bingPicImg = (ImageView) findViewById(R.id.bing_pic_img);
...
String bingPic = prefs.getString("big_pic", null);
if (bingPic != null) {
Glide.with(this).load(bingPic).into(bingPicImg);
} else {
loadBingPic();
}
}
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);
}
});
}
});
}
修改之后发现背景图没得喝状态栏融合在一起,修改WeatherActivity中代码。
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
if (Build.VERSION.SDK_INT >= 21) {
View decorView = getWindow().getDecorView();
decorView.setSystemUiVisibility(View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN | View.SYSTEM_UI_FLAG_LAYOUT_STABLE);
getWindow().setStatusBarColor(Color.TRANSPARENT);
...
}
因为这个功能是5.0及以上才有的,所以先判断系统版本号。最后修改activity_weather.xml中代码。
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="@color/colorPrimary">
...
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
android:fitsSystemWindows="true">
</LinearLayout>
</ScrollView>
</FrameLayout>