这一部分是天气API的笔记,这本书最后会让你做一个天气的app程序
其他的无关紧要的部分我就不写了,这是因为我原本的笔记是在飞书上面的,同步到CSDN上的流程稍显复杂
天气API
1. 项目结构
类:
- MainActivity:主活动
- WeatherActivity:城市天气活动
- ChooseAreaFragment:选择城市活动
包:
- db包用于存放数据库模型相关的代码;
- gson包用于存放GSON模型相关的代码;
- service包用于存放服务相关的代码;
- util包用于存放工具相关的代码。
布局文件:
- activity_main.xml是主活动布局,里面是一个FrameLayout包含着一个fragment碎片,叫做choose_area_fragment
- activity_weather.xml是显示具体某一个城市天气的布局,里面分了五个模块,这五个模块分别通过标签导入相应的布局。
- aqi.xml是空气质量模块布局
- choose_area.xml是选择城市模块的布局
- forecast.xml是近七天预报的大布局
- forecast_item.xml是近七天预报的listView的具体一行的布局
- my_list_item_1.xml
- now.xml是显示当天天气的布局,两个TextView,一个用于显示当前气温,一个用于显示天气概况。
- suggestion.xml作为生活建议信息的布局
- title.xml作为头布局显示天气页面的头部
assets 包:
通常用于存储应用所需的原始资源文件,里面我们放了litepal.xml文件是用于配置 LitePal 数据库框架的配置文件。
2. 数据库的搭建与对应
- 我们在db包下建立Province、City、County这3个类,分别对应3张表:province、city、county。
例:Province类
package com.coolweather.android.db;
import org.litepal.crud.LitePalSupport;
//书中继承的是DataSupport(已经弃用)
public class Province extends LitePalSupport {
//id是每个实体类中都应该有的字段
private int id;
//provinceName记录省的名字
private String provinceName;
//provinceCode记录省的代号
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;
}
}
首先要继承LitePalSupport因为我们用的是LitePalSupport方法操作数据库。
然后给出各个字段以及getter和setter方法
2. 我们在assets目录下的litepal.xml文件中
<?xml version="1.0" encoding="utf-8"?>
<litepal>
<dbname value="cool_weather" />
<version value="1" />
<list>
<mapping class="com.coolweather.android.db.Province"/>
<mapping class="com.coolweather.android.db.City"/>
<mapping class="com.coolweather.android.db.County"/>
</list>
</litepal>
将数据库名指定成cool_weather,数据库版本指定成1,并将Province、City和County这3个实体类添加到映射列表当中。
- 元素是配置文件的根元素,它包含了整个配置的信息。
- 元素指定了数据库的名称,这里设置为 “cool_weather”,表示数据库的名称为 “cool_weather”。
- 元素指定了数据库的版本号,这里设置为 “1”,表示数据库的版本号为 1。
- 元素包含了一个或多个 元素,用于指定数据库表与模型类之间的映射关系。
- 元素用于指定一个数据库表与一个模型类之间的映射关系。在这里,有三个 元素,分别映射了三个模型类 com.coolweather.android.db.Province、com.coolweather.android.db.City 和 com.coolweather.android.db.County 分别与数据库中的三张表关联。
- 配置LitePalApplication,在AndroidManifest.xml中加入
android:name="org.litepal.LitePalApplication"
这样我们就将所有的配置都完成了,数据库和表会在首次执行任意数据库操作的时候自动创建。
3. 城市选择碎片
该功能的主要实现都在ChooseAreaFragment.java中,代码有多行。方法如下:
3.1 onCreateView 方法
这是 Fragment 的生命周期方法之一,用于创建并返回与该 Fragment 关联的视图。
public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
//在onCreateView()方法中先是获取到了一些控件的实例,然后去初始化了ArrayAdapter,并将它设置为ListView的适配器。
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(),R.layout.my_list_item_1,dataList);
listView.setAdapter(adapter);
return view;
}
- 设定布局文件
- 获取控件按钮(如 titleText(用于显示标题文本的 TextView)、backButton(用于返回的按钮 Button)、listView(用于显示地区列表的 ListView))
- 创建适配器实例并设置给listView。
3.2 onActivityCreated 方法
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();
if (getActivity() instanceof MainActivity) {
Intent intent = new Intent(getActivity(), WeatherActivity.class);
intent.putExtra("weather_id",weatherId);
Log.d("requestWeather", "intent.putExtra.weatherId: " + weatherId);
startActivity(intent);
getActivity().finish();
} else if (getActivity() instanceof WeatherActivity) {
//instanceof 是 Java 中的一个关键字,它用于检查一个对象是否是某个类的一个实例。
WeatherActivity activity = (WeatherActivity) getActivity();
activity.drawerLayout.closeDrawers();
activity.swipeRefreshLayout.setRefreshing(true);
activity.requestWeather(weatherId);
}
}
}
});
/**
* 在返回按钮的点击事件里,会对当前ListView的列表级别进行判断。
* 如果当前是县级列表,那么就返回到市级列表,
* 如果当前是市级列表,那么就返回到省级表列表。
* 当返回到省级列表时,返回按钮会自动隐藏,从而也就不需要再做进一步的处理了。
*/
backButton.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
if (currentLevel == LEVEL_COUNTY) {
queryCities();
} else if (currentLevel == LEVEL_CITY) {
queryProvince();
}
}
});
//调用了queryProvinces()方法,也就是从这里开始加载省级数据的。
queryProvince();
}
这是另一个生命周期方法,用于在 Fragment 与 Activity 关联之后进行一些初始化工作。
- listView.setOnItemClickListener: 这里设置了 ListView 的点击事件监听器,当用户点击列表项时,会触发 onItemClick 方法。在这个方法中,根据当前的列表级别(currentLevel)执行不同的操作。具体操作如下:
- 如果当前级别是省级列表 (LEVEL_PROVINCE),则获取用户点击的省份数据,然后查询对应的城市数据。
- 如果当前级别是城市列表 (LEVEL_CITY),则获取用户点击的城市数据,然后查询对应的县区数据。
- 如果当前级别是县区列表 (LEVEL_COUNTY),则获取用户点击的县区数据的天气 ID (weatherId),然后执行不同的操作:
- 如果当前 Activity 是 MainActivity 的实例,就创建一个意图 (Intent) 跳转到 WeatherActivity,并将天气 ID 作为额外数据传递给 WeatherActivity,最后关闭当前 Activity。
- 如果当前 Activity 是 WeatherActivity 的实例,就获取 WeatherActivity 的实例,然后执行一些 UI 操作,如关闭抽屉布局、设置刷新状态,并请求天气信息。
- backButton.setOnClickListener: 这里设置了返回按钮的点击事件监听器,当用户点击返回按钮时,会触发 onClick 方法。在这个方法中,根据当前的列表级别执行不同的操作:
- 如果当前级别是县区列表 (LEVEL_COUNTY),则返回到城市列表。
- 如果当前级别是城市列表 (LEVEL_CITY),则返回到省级列表。
- 最后,调用 queryProvince() 方法加载省级数据。这是在 onActivityCreated 方法中的末尾调用的,表示当 Fragment 第一次创建时就会加载省级数据。
3.3 queryProvince、queryCities、queryCounties 方法:
这些方法用于查询省、市和县的数据,并将数据显示在列表中。以queryCounties为例
private void queryCounties() {
titleText.setText(selectedCity.getCityName());
backButton.setVisibility(View.VISIBLE);
countyList = LitePal.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 provinceCode = selectedProvince.getProvinceCode();
int cityCode = selectedCity.getCityCode();
String address = "http://guolin.tech/api/china/" + provinceCode + "/" + cityCode;
queryFromService(address,"county");
}
}
- 设置标题和返回按钮:首先,设置标题为"中国",表示当前正在浏览中国的省份信息,同时将返回按钮设置为不可见(View.GONE),因为在省份列表级别,不需要返回上一级。
- 查询本地数据库:接下来,使用 LitePal 数据库框架的 LitePal.findAll(Province.class) 方法尝试从本地数据库中查询省份数据。如果数据库中有省份数据(provinceList.size() > 0),就进行以下操作:
- 清空数据列表 dataList,以便后续加载数据。
- 遍历 provinceList,将每个省份的名称添加到 dataList 中。
- 通过调用 adapter.notifyDataSetChanged() 刷新适配器,以更新界面显示。
- 将 ListView 的选中项置为第一项,即 listView.setSelection(0),确保用户看到的是省份列表的第一个省份。
- 设置当前列表级别为省份级别(currentLevel = LEVEL_PROVINCE),以便后续的操作。
- 从服务器获取数据:如果本地数据库中没有省份数据,就通过向指定的服务器地址发送请求,请求中国省份数据。具体操作如下:
- 构建服务器地址 address,这里使用了一个示例地址 “http://guolin.tech/api/china”,该地址指向一个提供了中国省市县数据的接口。
- 调用 queryFromService(address, “province”) 方法,向服务器请求数据,第二个参数 “province” 表示请求省份数据。该方法的解释就在下面。
3.4 queryFromService 方法:
这个方法用于向服务器发送网络请求,查询省、市、县的数据。它会在请求过程中显示一个进度条对话框。根据请求的类型(省、市、县),从服务器返回的数据经过解析后将存储到本地数据库中。
private void queryFromService(String address,final String type) {
showProgressDialog();
HttpUtil.sendOkHttpRequest(address, new Callback() {
@Override
public void onResponse(@NonNull Call call, @NonNull 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());
}
/**
* 在解析和处理完数据之后,再次调用了queryProvinces()方法来重新加载省级数据,
* 由于queryProvinces()方法牵扯到了UI操作,因此必须要在主线程中调用,
* 这里借助了runOnUiThread()方法来实现从子线程切换到主线程。
* 现在数据库中已经存在了数据,因此调用queryProvinces()就会直接将数据显示到界面上了。
*/
if (result) {
getActivity().runOnUiThread(new Runnable() {
@Override
public void run() {
closeProgressDialog();
if ("province".equals(type)) {
queryProvince();
} else if ("city".equals(type)) {
queryCities();
} else if ("county".equals(type)) {
queryCounties();
}
}
});
}
}
@Override
public void onFailure(@NonNull Call call, @NonNull IOException e) {
//通过runOnUiThread()方法回到主线程处理逻辑
getActivity().runOnUiThread(new Runnable() {
@Override
public void run() {
closeProgressDialog();
Toast.makeText(getContext(), "加载失败", Toast.LENGTH_SHORT).show();
}
});
}
});
}
- 显示进度对话框:首先,调用 showProgressDialog() 方法显示一个进度对话框,用于提示用户正在加载数据。
- 发送网络请求:接着,使用 HttpUtil.sendOkHttpRequest(address, callback) 方法发送一个网络请求到指定的服务器地址(address),其中 callback 参数用于处理请求的响应。这里使用了 OkHttp 框架发送请求,该请求是异步的,不会阻塞主线程。
- 处理响应数据:在网络请求的回调方法中(onResponse() 和 onFailure()),根据不同的数据类型(type)来处理服务器响应的数据。
- 如果 type 是 “province”,表示请求的是省份数据,调用 Utility.handleProvinceResponse(responseText) 方法来解析和处理省份数据。该方法会将解析后的数据存储到本地数据库中。
- 如果 type 是 “city”,表示请求的是城市数据,调用 Utility.handleCityResponse(responseText, selectedProvince.getId()) 方法来解析和处理城市数据。其中 selectedProvince.getId() 表示当前选中的省份的 ID,用于关联城市数据与所属的省份。
- 如果 type 是 “county”,表示请求的是县区数据,调用 Utility.handleCountyResponse(responseText, selectedCity.getId()) 方法来解析和处理县区数据。其中 selectedCity.getId() 表示当前选中的城市的 ID,用于关联县区数据与所属的城市。
- 切换到主线程:在数据解析和处理完毕后,根据处理结果(result)进行判断,如果处理成功,就通过 getActivity().runOnUiThread() 方法切换回主线程。
- 如果请求的是省份数据,调用 queryProvince() 方法来重新加载省份数据。
- 如果请求的是城市数据,调用 queryCities() 方法来加载城市数据。
- 如果请求的是县区数据,调用 queryCounties() 方法来加载县区数据。
- 同时,关闭进度对话框,提醒用户加载完成。
- 处理请求失败:如果网络请求失败(在 onFailure() 回调中),也需要切换回主线程,关闭进度对话框,并显示一个短暂的 Toast 提示用户加载失败。
3.5 showProgressDialog 和 closeProgressDialog 方法:
这些方法分别用于显示和关闭进度条对话框,以提供加载数据时的用户反馈。
private void showProgressDialog() {
if (progressDialog == null) {
progressDialog = new ProgressDialog(getActivity());
progressDialog.setMessage("正在加载...");
progressDialog.setCanceledOnTouchOutside(false);
}
progressDialog.show();
}
/**
* 关闭进度条
*/
private void closeProgressDialog() {
if (progressDialog != null) {
progressDialog.dismiss();
}
}
- showProgressDialog() 方法:
- 首先,它检查一个名为 progressDialog 的进度条对话框是否已经被创建(是否为 null)。
- 如果 progressDialog 为 null,表示尚未创建进度条对话框,于是会新建一个。
- 设置对话框的提示信息为 “正在加载…”,表明正在进行数据加载操作。
- 通过 setCanceledOnTouchOutside(false) 方法,禁止用户点击对话框外部区域来取消对话框。
- 最后,调用 progressDialog.show() 方法将对话框显示在界面上,让用户看到加载过程的进展。
- closeProgressDialog() 方法:
- 这个方法用于关闭进度条对话框。
- 首先检查 progressDialog 是否为 null,如果不为 null,表示进度条对话框已经被创建并显示在界面上。
- 调用 progressDialog.dismiss() 方法关闭对话框,这会使进度条对话框消失。
4. 显示天气信息活动
该部分代码在Weather.java中
4.1 定义GSON实体类
我们需要将数据对应的实体类创建好,我们共建立了六个实体类分别如下
- AQI:存放城市的AQI指数以及PM2.5指数
- Basic:存放城市名、weatherId、update、updateTime
- Forecast:存放date、temperature、more、最高气温、最低气温、info
- Now:气温和天气情况
- Suggestion:生活建议
- Weather:近期每一天的天气情况,放了一个List
4.2 onCreate方法
这个方法主要是初始化界面控件、根据缓存数据或请求服务器数据来显示天气信息,支持下拉刷新天气数据,并获取并显示必应每日一图。
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_weather);
drawerLayout = (DrawerLayout) findViewById(R.id.drawer_layout);
navButton = (Button) findViewById(R.id.nav_button);
navButton.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
drawerLayout.openDrawer(GravityCompat.START);
}
});
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);
}
//初始化各种控件
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.pic_img);
swipeRefreshLayout = (SwipeRefreshLayout) findViewById(R.id.swipe_refresh);
swipeRefreshLayout.setColorSchemeResources(com.google.android.material.R.color.design_default_color_primary);
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");
weatherLayout.setVisibility(View.INVISIBLE);
requestWeather(weatherId);
}
swipeRefreshLayout.setOnRefreshListener(new SwipeRefreshLayout.OnRefreshListener() {
@Override
public void onRefresh() {
// mWeatherId = mWeatherId.substring(mWeatherId.length() - 9);
requestWeather(mWeatherId);
}
});
String bingPic = prefs.getString("bing_pic",null);
if (bingPic != null) {
Glide.with(this).load(bingPic).into(bingPicImg);
// Log.d("loadBingPic", "bingPic != null!!!!!!!!!!!!! "+ bingPic);
} else {
loadBingPic();
// Log.d("loadBingPic", "loadBingPic启动 "+ bingPic);
}
}
- super.onCreate(savedInstanceState); 和 setContentView(R.layout.activity_weather);:
- 这两行代码是通常的 Activity 生命周期中的启动和设置布局文件的操作。
- 初始化抽屉布局(DrawerLayout)和导航按钮(Button):
- drawerLayout 用于创建一个抽屉式的布局,通常用于侧滑菜单等场景。
- navButton 是一个按钮,点击它可以打开抽屉布局。
- 适配 Android 5.0 以上版本的状态栏:
- 如果运行 Android 5.0 及以上版本,这段代码会设置状态栏的透明效果,使布局内容可以延伸到状态栏区域。此时状态栏会变成我们布局的一部分
- 初始化各种控件:
- 这部分代码初始化了很多界面上的 TextView、LinearLayout、ImageView 等控件,这些控件用于显示天气信息和其他相关内容。
- 初始化下拉刷新控件(SwipeRefreshLayout):
- swipeRefreshLayout 是一个下拉刷新控件,用于实现用户下拉刷新天气数据的功能。
- 设置下拉刷新时的进度条颜色。
- 从 SharedPreferences 中获取天气数据缓存:
- 通过 PreferenceManager 从默认的 SharedPreferences 获取缓存的天气数据,存储在 weatherString 变量中。
- 根据是否有缓存数据来显示天气信息或请求服务器数据:
- 如果有缓存数据(weatherString != null),则使用 Utility.handleWeatherResponse(weatherString) 方法解析天气数据,并调用 showWeatherInfo(weather) 方法显示天气信息。
- 如果没有缓存数据,说明需要向服务器请求数据,获取 weatherId(通过 getIntent().getStringExtra(“weather_id”) 获取)后,隐藏天气信息的布局(weatherLayout.setVisibility(View.INVISIBLE)),并调用 requestWeather(weatherId) 方法来请求天气数据。
- 设置下拉刷新监听器:
- 当用户下拉刷新时,会触发 onRefresh 方法,该方法会重新请求天气数据。
- 获取必应每日一图(Bing 每日壁纸)的地址并显示:
- 从 SharedPreferences 中获取 bingPic 数据,这是存储必应每日一图地址的缓存数据。
- 如果有缓存数据,就使用 Glide 库加载图片并显示在 bingPicImg ImageView 中。
- 如果没有缓存数据,则调用 loadBingPic() 方法来获取必应每日一图并显示。
4.3 loadBingPic方法
这段代码的主要目的是请求必应每日一图的图片数据,并将图片地址缓存起来,然后在主线程中通过 Glide 库将图片显示在界面上,以作为应用的背景图片。这样,用户每次打开应用时都能看到不同的壁纸。
private void loadBingPic() {
String requestBingPic = "https://imgapi.cn/bing.php";
HttpUtil.sendOkHttpRequest(requestBingPic, new Callback() {
@Override
public void onFailure(@NonNull Call call, @NonNull IOException e) {
Log.d("loadBingPic", "Failure ");
}
@Override
public void onResponse(@NonNull Call call, @NonNull 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();
// Log.d("loadBingPic", "Response " + bingPic);
runOnUiThread(new Runnable() {
@Override
public void run() {
// Glide.with(WeatherActivity.this).load(bingPic).into(bingPicImg);
Glide.with(WeatherActivity.this).load(requestBingPic).into(bingPicImg);
}
});
}
});
}
- 定义 loadBingPic 方法:
- 这个方法用于加载必应每日一图的背景图片。
- 构建请求必应每日一图的 URL:
- 使用字符串 requestBingPic 存储了请求必应每日一图的 URL,通常这个 URL 返回的是一张背景图片。
- 发送网络请求:
- 使用 HttpUtil.sendOkHttpRequest 方法,通过 OkHttp 发送一个异步的 HTTP GET 请求,请求必应每日一图的图片。
- 处理请求成功(onResponse)的回调:
- 如果请求成功,服务器会返回图片的数据。
- 首先,在 onResponse 方法中,将服务器返回的图片数据(response.body().string())存储在 bingPic 变量中。
- 缓存图片地址:
- 使用 SharedPreferences 来存储获取到的 bingPic 数据,以便后续使用。
- 在主线程中更新UI:
- 使用 runOnUiThread 方法,确保 UI 操作在主线程中进行。
- 在这里,通过 Glide 库加载 bingPic 到 bingPicImg 控件中,以显示必应每日一图的背景图片。
4.4 requestWeather方法
请求指定城市的天气信息,然后将获取到的天气数据解析并显示在界面上,同时也会更新背景图片。如果请求或解析失败,会给用户相应的提示信息。
public void requestWeather(String weatherId) {
// weatherId = weatherId.substring(weatherId.length() - 9);
String weatherUrl = "http://guolin.tech/api/weather?cityid=" +
weatherId + "&key=2b8d73c0f8734617aff756f3f4477ded";
Log.d("requestWeather", "weatherId: " + weatherId);
HttpUtil.sendOkHttpRequest(weatherUrl, new Callback() {
@Override
public void onFailure(@NonNull Call call, @NonNull IOException e) {
Toast.makeText(WeatherActivity.this, "onFailure:获取天气信息失败", Toast.LENGTH_SHORT).show();
}
@Override
public void onResponse(@NonNull Call call, @NonNull 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();
mWeatherId = weather.basic.weatherId;
showWeatherInfo(weather);
} else {
Toast.makeText(WeatherActivity.this, "onResponse:获取天气信息失败", Toast.LENGTH_SHORT).show();
}
swipeRefreshLayout.setRefreshing(false);
}
});
}
});
loadBingPic();
}
- requestWeather 方法接受一个参数 weatherId,表示天气的城市代码。
- 构建天气信息请求的 URL:
- 使用传入的 weatherId 构建了一个 URL,这个 URL 包括了城市代码和一个 API 密钥。该 URL 用于向服务器请求天气数据。
- 发送网络请求:
- 使用 HttpUtil.sendOkHttpRequest 方法,通过 OkHttp 发送一个异步的 HTTP GET 请求,请求天气信息。
- 处理请求成功(onResponse)的回调:
- 如果请求成功,服务器会返回天气信息的数据。
- 在 onResponse 方法中,首先将服务器返回的数据存储在 responseText 变量中。
- 然后,使用 Utility.handleWeatherResponse 方法解析 responseText,将 JSON 数据转化为 Weather 对象。
- 在主线程中更新 UI:
- 使用 runOnUiThread 方法,确保 UI 操作在主线程中进行。
- 如果成功解析了天气数据(weather != null 且 weather.status 为 “ok”),则进行以下操作:
- 使用 SharedPreferences 存储获取到的天气信息数据,以便后续使用。
- 更新 mWeatherId,以便在后续的刷新操作中重新请求该城市的天气信息。
- 调用 showWeatherInfo 方法,将解析后的天气数据显示在界面上。
- 如果解析天气数据失败,显示一个失败的提示信息。
- 停止刷新操作:
- 使用 swipeRefreshLayout.setRefreshing(false) 来停止刷新操作,因为在获取完天气信息后不再需要刷新。
- 最后,调用 loadBingPic 方法,用于加载必应每日一图的背景图片。
4.5 showWeatherInfo方法
将天气信息以用户友好的方式呈现在界面上,包括当前天气、未来天气预报、空气质量、生活建议等
private void showWeatherInfo(Weather weather) {
String cityName = weather.basic.cityName;
String updateTime = weather.basic.update.updateTime.split(" ")[1];
String degree = weather.now.temperature + "℃";
String weatherInfo = weather.now.more.info;
titleCity.setText(cityName);
degreeText.setText(degree);
weatherInfoText.setText(weatherInfo);
titleUpdateTime.setText(updateTime);
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);
}
}
- 获取天气信息对象 Weather 中的各种数据,包括城市名称、更新时间、温度、天气信息、天气预报、空气质量、舒适度指数、洗车指数和运动建议等。
- 将获取到的数据显示在对应的 TextView 控件中:
- cityName 显示在 titleCity 控件中,表示城市名称。
- updateTime 显示在 titleUpdateTime 控件中,表示更新时间。
- degree 显示在 degreeText 控件中,表示温度。
- weatherInfo 显示在 weatherInfoText 控件中,表示天气信息。
- 清空 forecastLayout 布局中的子视图,以便加载新的未来天气预报数据。
- 遍历天气预报数据列表 weather.forecastList,为每一天的天气预报创建一个新的视图,并将数据显示在相应的 TextView 控件中。每个预报包括日期、天气信息、最高温度和最低温度。
- 如果天气信息中包含空气质量数据 (weather.aqi != null),则将空气质量指数(AQI)和 PM2.5 数据显示在 aqiText 和 pm25Text 控件中。
- 显示舒适度指数、洗车指数和运动建议等建议信息,将这些信息显示在相应的 TextView 控件中。
- 最后,将天气信息的整个布局 weatherLayout 设置为可见,以便用户看到完整的天气信息。