《第一行代码Andorid》阅读笔记-第十三章(最终章)

这一部分是天气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. 数据库的搭建与对应

  1. 我们在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 分别与数据库中的三张表关联。
  1. 配置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;
}
  1. 设定布局文件
  2. 获取控件按钮(如 titleText(用于显示标题文本的 TextView)、backButton(用于返回的按钮 Button)、listView(用于显示地区列表的 ListView))
  3. 创建适配器实例并设置给listView。

3.2 onActivityCreated 方法

public void onActivityCreated(@Nullable Bundle savedInstanceState) {super.onActivityCreated(savedInstanceState);listView.setOnItemClickListener(new AdapterView.OnItemClickListener() {@Overridepublic 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() {@Overridepublic void onClick(View view) {if (currentLevel == LEVEL_COUNTY) {queryCities();} else if (currentLevel == LEVEL_CITY) {queryProvince();}}});//调用了queryProvinces()方法,也就是从这里开始加载省级数据的。queryProvince();
}

这是另一个生命周期方法,用于在 Fragment 与 Activity 关联之后进行一些初始化工作。

  1. listView.setOnItemClickListener: 这里设置了 ListView 的点击事件监听器,当用户点击列表项时,会触发 onItemClick 方法。在这个方法中,根据当前的列表级别(currentLevel)执行不同的操作。具体操作如下:
  • 如果当前级别是省级列表 (LEVEL_PROVINCE),则获取用户点击的省份数据,然后查询对应的城市数据。
  • 如果当前级别是城市列表 (LEVEL_CITY),则获取用户点击的城市数据,然后查询对应的县区数据。
  • 如果当前级别是县区列表 (LEVEL_COUNTY),则获取用户点击的县区数据的天气 ID (weatherId),然后执行不同的操作:
    • 如果当前 Activity 是 MainActivity 的实例,就创建一个意图 (Intent) 跳转到 WeatherActivity,并将天气 ID 作为额外数据传递给 WeatherActivity,最后关闭当前 Activity。
    • 如果当前 Activity 是 WeatherActivity 的实例,就获取 WeatherActivity 的实例,然后执行一些 UI 操作,如关闭抽屉布局、设置刷新状态,并请求天气信息。
  1. backButton.setOnClickListener: 这里设置了返回按钮的点击事件监听器,当用户点击返回按钮时,会触发 onClick 方法。在这个方法中,根据当前的列表级别执行不同的操作:
  • 如果当前级别是县区列表 (LEVEL_COUNTY),则返回到城市列表。
  • 如果当前级别是城市列表 (LEVEL_CITY),则返回到省级列表。
  1. 最后,调用 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");}
}
  1. 设置标题和返回按钮:首先,设置标题为"中国",表示当前正在浏览中国的省份信息,同时将返回按钮设置为不可见(View.GONE),因为在省份列表级别,不需要返回上一级。
  2. 查询本地数据库:接下来,使用 LitePal 数据库框架的 LitePal.findAll(Province.class) 方法尝试从本地数据库中查询省份数据。如果数据库中有省份数据(provinceList.size() > 0),就进行以下操作:
  • 清空数据列表 dataList,以便后续加载数据。
  • 遍历 provinceList,将每个省份的名称添加到 dataList 中。
  • 通过调用 adapter.notifyDataSetChanged() 刷新适配器,以更新界面显示。
  • 将 ListView 的选中项置为第一项,即 listView.setSelection(0),确保用户看到的是省份列表的第一个省份。
  • 设置当前列表级别为省份级别(currentLevel = LEVEL_PROVINCE),以便后续的操作。
  1. 从服务器获取数据:如果本地数据库中没有省份数据,就通过向指定的服务器地址发送请求,请求中国省份数据。具体操作如下:
  • 构建服务器地址 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() {@Overridepublic 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() {@Overridepublic void run() {closeProgressDialog();if ("province".equals(type)) {queryProvince();} else if ("city".equals(type)) {queryCities();} else if ("county".equals(type)) {queryCounties();}}});}}@Overridepublic void onFailure(@NonNull Call call, @NonNull IOException e) {//通过runOnUiThread()方法回到主线程处理逻辑getActivity().runOnUiThread(new Runnable() {@Overridepublic void run() {closeProgressDialog();Toast.makeText(getContext(), "加载失败", Toast.LENGTH_SHORT).show();}});}});
}
  1. 显示进度对话框:首先,调用 showProgressDialog() 方法显示一个进度对话框,用于提示用户正在加载数据。
  2. 发送网络请求:接着,使用 HttpUtil.sendOkHttpRequest(address, callback) 方法发送一个网络请求到指定的服务器地址(address),其中 callback 参数用于处理请求的响应。这里使用了 OkHttp 框架发送请求,该请求是异步的,不会阻塞主线程。
  3. 处理响应数据:在网络请求的回调方法中(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,用于关联县区数据与所属的城市。
  1. 切换到主线程:在数据解析和处理完毕后,根据处理结果(result)进行判断,如果处理成功,就通过 getActivity().runOnUiThread() 方法切换回主线程。
  • 如果请求的是省份数据,调用 queryProvince() 方法来重新加载省份数据。
  • 如果请求的是城市数据,调用 queryCities() 方法来加载城市数据。
  • 如果请求的是县区数据,调用 queryCounties() 方法来加载县区数据。
  • 同时,关闭进度对话框,提醒用户加载完成。
  1. 处理请求失败:如果网络请求失败(在 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();}
}
  1. showProgressDialog() 方法:
  • 首先,它检查一个名为 progressDialog 的进度条对话框是否已经被创建(是否为 null)。
  • 如果 progressDialog 为 null,表示尚未创建进度条对话框,于是会新建一个。
  • 设置对话框的提示信息为 “正在加载…”,表明正在进行数据加载操作。
  • 通过 setCanceledOnTouchOutside(false) 方法,禁止用户点击对话框外部区域来取消对话框。
  • 最后,调用 progressDialog.show() 方法将对话框显示在界面上,让用户看到加载过程的进展。
  1. 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() {@Overridepublic 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() {@Overridepublic 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);}}
  1. super.onCreate(savedInstanceState); 和 setContentView(R.layout.activity_weather);:
  • 这两行代码是通常的 Activity 生命周期中的启动和设置布局文件的操作。
  1. 初始化抽屉布局(DrawerLayout)和导航按钮(Button):
  • drawerLayout 用于创建一个抽屉式的布局,通常用于侧滑菜单等场景。
  • navButton 是一个按钮,点击它可以打开抽屉布局。
  1. 适配 Android 5.0 以上版本的状态栏:
  • 如果运行 Android 5.0 及以上版本,这段代码会设置状态栏的透明效果,使布局内容可以延伸到状态栏区域。此时状态栏会变成我们布局的一部分
  1. 初始化各种控件:
  • 这部分代码初始化了很多界面上的 TextView、LinearLayout、ImageView 等控件,这些控件用于显示天气信息和其他相关内容。
  1. 初始化下拉刷新控件(SwipeRefreshLayout):
  • swipeRefreshLayout 是一个下拉刷新控件,用于实现用户下拉刷新天气数据的功能。
  • 设置下拉刷新时的进度条颜色。
  1. 从 SharedPreferences 中获取天气数据缓存:
  • 通过 PreferenceManager 从默认的 SharedPreferences 获取缓存的天气数据,存储在 weatherString 变量中。
  1. 根据是否有缓存数据来显示天气信息或请求服务器数据:
  • 如果有缓存数据(weatherString != null),则使用 Utility.handleWeatherResponse(weatherString) 方法解析天气数据,并调用 showWeatherInfo(weather) 方法显示天气信息。
  • 如果没有缓存数据,说明需要向服务器请求数据,获取 weatherId(通过 getIntent().getStringExtra(“weather_id”) 获取)后,隐藏天气信息的布局(weatherLayout.setVisibility(View.INVISIBLE)),并调用 requestWeather(weatherId) 方法来请求天气数据。
  1. 设置下拉刷新监听器:
  • 当用户下拉刷新时,会触发 onRefresh 方法,该方法会重新请求天气数据。
  1. 获取必应每日一图(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() {@Overridepublic void onFailure(@NonNull Call call, @NonNull IOException e) {Log.d("loadBingPic", "Failure ");}@Overridepublic 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() {@Overridepublic void run() {
//                        Glide.with(WeatherActivity.this).load(bingPic).into(bingPicImg);Glide.with(WeatherActivity.this).load(requestBingPic).into(bingPicImg);}});}});}
  1. 定义 loadBingPic 方法:
  • 这个方法用于加载必应每日一图的背景图片。
  1. 构建请求必应每日一图的 URL:
  • 使用字符串 requestBingPic 存储了请求必应每日一图的 URL,通常这个 URL 返回的是一张背景图片。
  1. 发送网络请求:
  • 使用 HttpUtil.sendOkHttpRequest 方法,通过 OkHttp 发送一个异步的 HTTP GET 请求,请求必应每日一图的图片。
  1. 处理请求成功(onResponse)的回调:
  • 如果请求成功,服务器会返回图片的数据。
  • 首先,在 onResponse 方法中,将服务器返回的图片数据(response.body().string())存储在 bingPic 变量中。
  1. 缓存图片地址:
  • 使用 SharedPreferences 来存储获取到的 bingPic 数据,以便后续使用。
  1. 在主线程中更新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() {@Overridepublic void onFailure(@NonNull Call call, @NonNull IOException e) {Toast.makeText(WeatherActivity.this, "onFailure:获取天气信息失败", Toast.LENGTH_SHORT).show();}@Overridepublic 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() {@Overridepublic 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();}
  1. requestWeather 方法接受一个参数 weatherId,表示天气的城市代码。
  2. 构建天气信息请求的 URL:
  • 使用传入的 weatherId 构建了一个 URL,这个 URL 包括了城市代码和一个 API 密钥。该 URL 用于向服务器请求天气数据。
  1. 发送网络请求:
  • 使用 HttpUtil.sendOkHttpRequest 方法,通过 OkHttp 发送一个异步的 HTTP GET 请求,请求天气信息。
  1. 处理请求成功(onResponse)的回调:
  • 如果请求成功,服务器会返回天气信息的数据。
  • 在 onResponse 方法中,首先将服务器返回的数据存储在 responseText 变量中。
  • 然后,使用 Utility.handleWeatherResponse 方法解析 responseText,将 JSON 数据转化为 Weather 对象。
  1. 在主线程中更新 UI:
  • 使用 runOnUiThread 方法,确保 UI 操作在主线程中进行。
  • 如果成功解析了天气数据(weather != null 且 weather.status 为 “ok”),则进行以下操作:
    • 使用 SharedPreferences 存储获取到的天气信息数据,以便后续使用。
    • 更新 mWeatherId,以便在后续的刷新操作中重新请求该城市的天气信息。
    • 调用 showWeatherInfo 方法,将解析后的天气数据显示在界面上。
  • 如果解析天气数据失败,显示一个失败的提示信息。
  1. 停止刷新操作:
  • 使用 swipeRefreshLayout.setRefreshing(false) 来停止刷新操作,因为在获取完天气信息后不再需要刷新。
  1. 最后,调用 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);}
}
  1. 获取天气信息对象 Weather 中的各种数据,包括城市名称、更新时间、温度、天气信息、天气预报、空气质量、舒适度指数、洗车指数和运动建议等。
  2. 将获取到的数据显示在对应的 TextView 控件中:
  • cityName 显示在 titleCity 控件中,表示城市名称。
  • updateTime 显示在 titleUpdateTime 控件中,表示更新时间。
  • degree 显示在 degreeText 控件中,表示温度。
  • weatherInfo 显示在 weatherInfoText 控件中,表示天气信息。
  1. 清空 forecastLayout 布局中的子视图,以便加载新的未来天气预报数据。
  2. 遍历天气预报数据列表 weather.forecastList,为每一天的天气预报创建一个新的视图,并将数据显示在相应的 TextView 控件中。每个预报包括日期、天气信息、最高温度和最低温度。
  3. 如果天气信息中包含空气质量数据 (weather.aqi != null),则将空气质量指数(AQI)和 PM2.5 数据显示在 aqiText 和 pm25Text 控件中。
  4. 显示舒适度指数、洗车指数和运动建议等建议信息,将这些信息显示在相应的 TextView 控件中。
  5. 最后,将天气信息的整个布局 weatherLayout 设置为可见,以便用户看到完整的天气信息。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.hqwc.cn/news/126774.html

如若内容造成侵权/违法违规/事实不符,请联系编程知识网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

iPhone升级iOS17出现无法连接互联网的错误提示怎么办?

最新的iOS 17系统已经发布了快一个月了&#xff0c;很多人都已升级体验更多全新功能&#xff0c;但有部分用户却在升级过程中遇到一些问题&#xff1a;如无法验证更新&#xff0c;iOS17验证失败&#xff0c;因为您不再连接到互联网、 iPhone无法检查更新等错误问题。明明网络稳…

最新Uniapp软件社区-全新带勋章源码

测试环境&#xff1a;php7.1。ng1.2&#xff0c;MySQL 5.6 常见问题&#xff1a; 配置好登录后转圈圈&#xff0c;检查环境及伪静态以及后台创建好应用 上传图片不了&#xff0c;检查php拓展fileinfo 以及public文件权限 App个人主页随机背景图&#xff0c;在前端uitl文件夹里面…

工程派工单,建筑工程派工单

工程派工单是指建设项目管理人员或工程维修人员发出的文件&#xff0c;用于标明工人或维修人员在建设项目或设备中处理或维修问题的任务。派工单包括建设项目的实际维护任务、所需材料、工具等信息&#xff0c;以及具体的执行人员和完成时间。工程派工单是保证建设项目顺利开展…

Spring AOP(JavaEE进阶系列5)

目录 前言&#xff1a; 1.什么是Spring AOP 2.为什么要使用AOP呢&#xff1f; 3.AOP的组成 3.1切面 3.2切点 3.3通知 3.4连接点 4.Spring AOP的实现 4.1添加依赖 4.2定义切面 4.3定义切点 4.4实现通知 5.AOP的实现原理 结束语&#xff1a; 前言&#xff1a; 在…

2023/9/28 -- ARM

【内存读写指令】 int *p0X12345678 *p100;//向内存中写入数据 int a *p;//从内存读取 1.单寄存器内存读写指令 1.1 指令码以及功能 向内存中写&#xff1a; str:向内存中写一个字(4字节)的数据 strh:向内存写半个字&#xff08;2字节&#xff09;的数据 strb:向内存写一个字…

侯捷 C++ STL标准库和泛型编程【C++学习笔记】 超详细 万字笔记总结 笔记合集

关于STL这部分&#xff0c;原课程将其分为了四部分&#xff0c;我做笔记时&#xff0c;会将其整合&#xff0c;使其更具有整体性 文章目录 1 STL概述1.1 头文件名称1.2 STL基础介绍1.3 typename 2 OOP vs. GP3 容器3.1 容器结构分类3.2 序列式容器3.2.1 array测试深度探索 3.2.…

HTML5开发实例-3D全景(ThreeJs全景Demo) 详解(图)

前言 在现在市面上很多全景H5的环境下,要实现全景的方式有很多,可以用css3直接构建也可以用基于threeJs的库来实现,还有很多别的制作全景的软件使用 本教学适用于未开发过3D全景的工程狮 如果觉得内容太无聊可以直接跳到最后 下载代码 理论 整个3D全景所用的相关理论就…

【JavaEE初阶】 多线程(初阶)——壹

文章目录 &#x1f332;线程的概念&#x1f6a9;线程是什么&#x1f6a9;为啥要有线程&#x1f6a9;进程和线程的区别&#x1f6a9;Java 的线程 和 操作系统线程 的关系 &#x1f60e;第一个多线程程序&#x1f6a9;使用 jconsole 命令观察线程 &#x1f38d;创建线程&#x1f…

【Linux】信号屏蔽与信号捕捉的原理与实现(附图解与代码)

这一篇的篇幅可能有点长&#xff0c;如果已经了解了以下两个知识点的同学可以自行跳到第三部分——信号屏蔽的实现。 不太了解的同学希望你们能够静下心来看完&#xff0c;相信一定会有不小的收获。那么话不多说&#xff0c;我们这就开始啦&#xff01;&#xff01;&#xff0…

【ARM CoreLink 系列 5 -- CI-700 控制器介绍 】

文章目录 1.1 什么是 CI-700?1.1.1 关于 CI-7001.1.2 CI-700 特点1.2 全局配置参数1.2.1 寻址能力1.3 组件和配置1.3.1 CI-700 互联的结构1.3.2 Crosspoint(XP)1.3.3 外部接口1.4 组件(Components)1.1 什么是 CI-700? CI-700是一种AMBA 5 CHI互连,具有可定制的网状拓扑结构…

Web server failed to start. Port 8080 was already in use

一、问题 package com.djc.boot;import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annota…

计算机网络笔记3 数据链路层

计算机网络系列笔记目录&#x1f447; 计算机网络笔记6 应用层计算机网络笔记5 运输层计算机网络笔记4 网络层计算机网络笔记3 数据链路层计算机网络笔记2 物理层计算机网络笔记1 概述 文章前言 &#x1f497; 站在巨人的肩膀上&#xff0c;让知识的获得更加容易&#xff01…