天气实战
- 第一步
第一步,注册和风天气控制台 | 和风天气,申请key
08f828b5a3ec4905a29156bf5f29136f
查看郭林的后台天气pai接口
- 第二步,建立省市县实体类
/**
* Description
* <p>
* id是每个实体该有的字段
* provinceName记录省的名字
* provinceCode记录省的代号
* @author qricis on 2020/9/3 14:47
* @version 1.0.0
*/
public class Province extends DataSupport {
private int id;
private String provinceName;
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;
}
}
/**
* Description
* <p>
* id是每个实体类都应该有的字段
* cityName记录市的名字
* cityCode记录市的代码
* provinceId记录当前市所属省的id值
* @author qricis on 2020/9/3 14:47
* @version 1.0.0
*/
public class City extends DataSupport {
private int id;
private String cityName;
private int citycode;
private int provinceId;
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public String getCityName() {
return cityName;
}
public void setCityName(String cityName) {
this.cityName = cityName;
}
public int getCitycode() {
return citycode;
}
public void setCitycode(int citycode) {
this.citycode = citycode;
}
public int getProvinceId() {
return provinceId;
}
public void setProvinceId(int provinceId) {
this.provinceId = provinceId;
}
}
/**
* Description
* <p>
* id是每个实体类都应该有的字段
* countyName记录县的名字
* weatherId记录县的天气代码
* cityId记录当前县所属市的id
* @author qricis on 2020/9/3 14:47
* @version 1.0.0
*/
public class County extends DataSupport {
private int id;
private String countyName;
private int countyCode;
private int cityId;
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public String getCountyName() {
return countyName;
}
public void setCountyName(String countyName) {
this.countyName = countyName;
}
public int getCountyCode() {
return countyCode;
}
public void setCountyCode(int countyCode) {
this.countyCode = countyCode;
}
public int getCityId() {
return cityId;
}
public void setCityId(int cityId) {
this.cityId = cityId;
}
}
- 第三步,配置litepal
<litepal>
<!-- 设置数据库名为cool_weather,数据库版本1,并将三个实体类映射到列表当中 -->
<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>
- 第四步,编写与服务端交互的类HttpUtil
/**
* Description
* <p>
* 与服务器进行交互
* @author qricis on 2020/9/3 15:46
* @version 1.0.0
*/
public class HttpUtil {
public static void sendOkHttpRequest(String address, okhttp3.Callback callback) {
OkHttpClient client = new OkHttpClient();
Request request = new Request.Builder().url(address).build();
client.newCall(request).enqueue(callback);
}
}
- 第五步,编写一个工具类用来处理从服务器端获取到的数据Utility
/**
* Description
* <p>
* 解析服务器返回的数据
* @author qricis on 2020/9/3 15:53
* @version 1.0.0
*/
public class Utility {
/**
* 解析和处理服务器返回的省级数据
* */
public static boolean handleProvinceResponse(String response) {
if (!TextUtils.isEmpty(response)) {
try {
JSONArray allProvinces = new JSONArray(response);
for (int i = 0; i < allProvinces.length(); i++) {
JSONObject provinceObject = allProvinces.getJSONObject(i);
Province province = new Province();
province.setProvinceName(provinceObject.getString("name"));
province.setProvinceCode(provinceObject.getInt("id"));
province.save();
}
return true;
} catch (JSONException e) {
e.printStackTrace();
}
}
return false;
}
/**
* 解析和处理服务器返回的市级数据
* */
public static boolean handleCityResponse(String response, int provinceId) {
if (!TextUtils.isEmpty(response)) {
try {
JSONArray allCities = new JSONArray(response);
for (int i = 0; i < allCities.length(); i++) {
JSONObject cityObject = allCities.getJSONObject(i);
City city = new City();
city.setCityName(cityObject.getString("name"));
city.setCitycode(cityObject.getInt("id"));
city.setProvinceId(provinceId);
city.save();
}
return true;
} catch (JSONException e) {
e.printStackTrace();
}
}
return false;
}
/**
* 解析和处理服务器返回的县级数据
* */
public static boolean handleCountyResponse(String response, int cityId) {
if (!TextUtils.isEmpty(response)) {
try {
JSONArray allCounties = new JSONArray(response);
for (int i = 0; i < allCounties.length(); i++) {
JSONObject countyObject = allCounties.getJSONObject(i);
County county = new County();
county.setCountyName(countyObject.getString("name"));
county.setCountyCode(countyObject.getInt("id"));
county.setCityId(cityId);
county.save();
}
return true;
} catch (JSONException e) {
e.printStackTrace();
}
}
return false;
}
}
- 第六步,编写选择布局choose_area
<!-- 定义一个展示所有省市县的碎片 -->
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
android:background="#fff">
<RelativeLayout
android:layout_width="match_parent"
android:layout_height="?attr/actionBarSize"
android:background="?attr/colorPrimary">
<TextView
android:id="@+id/area_tv_title"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_centerInParent="true"
android:textColor="#fff"
android:textSize="20sp"/>
<Button
android:id="@+id/area_btn_back"
android:layout_width="25dp"
android:layout_height="25dp"
android:layout_marginLeft="10dp"
android:layout_marginStart="10dp"
android:layout_alignParentLeft="true"
android:layout_alignParentStart="true"
android:layout_centerVertical="true"
android:background="@drawable/area_btn_back"
/>
</RelativeLayout>
<ListView
android:id="@+id/area_list_view"
android:layout_width="match_parent"
android:layout_height="match_parent" />
</LinearLayout>
- 第七步,编写用于遍历省市县数据的碎片ChooseAreaFragment
/**
* Description
* <p>
* 用于遍历省市县数据的碎片ChooseAreaFragment
* @author qricis on 2020/9/3 17:25
* @version 1.0.0
*/
public class ChooseAreaFragment extends Fragment {
public static final int LEVEL_PROVINCE = 0;
public static final int LEVEL_CITY = 1;
public static final int LEVEL_COUNTY = 2;
private ProgressDialog mProgressDialog;
private TextView mTitleText;
private Button mBackButton;
private ListView mListView;
private ArrayAdapter<String> mStringArrayAdapter;
private List<String> mDataList = new ArrayList<>();
/**
* 省列表
* */
private List<Province> mProvinceList;
/**
* 市列表
* */
private List<City> mCityList;
/**
* 县列表
* */
private List<County> mCountyList;
/**
* 选中的省份
* */
private Province mSelectedProvince;
/**
* 选中的城市
* */
private City mSelectedCity;
/**
* 当前选中的级别
* */
private int mCurrentLevel;
/**
* 获取控件实例,初始化mStringArrayAdapter,并将之设置为ListView的适配器
* */
@Nullable
@Override
public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
View view = inflater.inflate(R.layout.choose_area,container,false);
mTitleText = view.findViewById(R.id.area_tv_title);
mBackButton = view.findViewById(R.id.area_btn_back);
mListView = view.findViewById(R.id.area_list_view);
mStringArrayAdapter = new ArrayAdapter<>(getContext(),android.R.layout.simple_list_item_1,mDataList);
mListView.setAdapter(mStringArrayAdapter);
return view;
}
/**
* 给ListView和Button设置点击事件
* */
@Override
public void onActivityCreated(@Nullable Bundle savedInstanceState) {
super.onActivityCreated(savedInstanceState);
mListView.setOnItemClickListener(new AdapterView.OnItemClickListener() {
@Override
public void onItemClick(AdapterView<?> adapterView, View view, int position, long id) {
if (mCurrentLevel == LEVEL_PROVINCE) {
mSelectedProvince = mProvinceList.get(position);
queryCities();
} else if (mCurrentLevel == LEVEL_CITY) {
mSelectedCity = mCityList.get(position);
queryCounties();
}
}
});
mBackButton.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
if (mCurrentLevel == LEVEL_COUNTY) {
queryCities();
} else if (mCurrentLevel == LEVEL_CITY) {
queryProvinces();
}
}
});
queryProvinces();
}
/**
* 查询全国所有的省,优先从数据库查询,如果没有查询到再去服务器查询
* */
private void queryProvinces() {
mTitleText.setText("中国");
mBackButton.setVisibility(View.GONE);
mProvinceList = DataSupport.findAll(Province.class);
if (mProvinceList.size() > 0) {
mDataList.clear();
for (Province province : mProvinceList) {
mDataList.add(province.getProvinceName());
}
mStringArrayAdapter.notifyDataSetChanged();
mListView.setSelection(0);
mCurrentLevel = LEVEL_PROVINCE;
} else {
String address = "http://guolin.tech/api/china";
queryFromServer(address,"province");
}
}
/**
* 查询全国所有的市,优先从数据库查询,如果没有查询到再去服务器查询
* */
private void queryCities() {
mTitleText.setText(mSelectedProvince.getProvinceName());
mBackButton.setVisibility(View.VISIBLE);
mCityList = DataSupport.where("provinceid = ?", String.valueOf(mSelectedProvince.getId())).find(City.class);
if (mCityList.size() > 0) {
mDataList.clear();
for (City city : mCityList) {
mDataList.add(city.getCityName());
}
mStringArrayAdapter.notifyDataSetChanged();
mListView.setSelection(0);
mCurrentLevel = LEVEL_CITY;
} else {
int provinceCode = mSelectedProvince.getProvinceCode();
String address = "http://guolin.tech/api/china/" + provinceCode;
queryFromServer(address,"city");
}
}
/**
* 查询全国所有的县,优先从数据库查询,如果没有查询到再去服务器查询
* */
private void queryCounties() {
mTitleText.setText(mSelectedCity.getCityName());
mBackButton.setVisibility(View.VISIBLE);
mCountyList = DataSupport.where("cityid = ?", String.valueOf(mSelectedCity.getId())).find(County.class);
if (mCountyList.size() > 0) {
mDataList.clear();
for (County county : mCountyList) {
mDataList.add(county.getCountyName());
}
mStringArrayAdapter.notifyDataSetChanged();
mListView.setSelection(0);
mCurrentLevel = LEVEL_COUNTY;
} else {
int provinceCode = mSelectedProvince.getProvinceCode();
int cityCode = mSelectedCity.getCitycode();
String address = "http://guolin.tech/api/china/" + provinceCode + "/" + cityCode;
queryFromServer(address,"county");
}
}
/**
* 根据传入的地址和类型从服务器上查询省市县数据
* */
private void queryFromServer(String address, final String type) {
showProgressDialog();
HttpUtil.sendOkHttpRequest(address, new Callback() {
@Override
public void onFailure(@NotNull Call call, @NotNull IOException e) {
// 通过runOnUiThread回到主线程处理逻辑
getActivity().runOnUiThread(new Runnable() {
@Override
public void run() {
closeProgressDialog();
Toast.makeText(getContext(),"加载失败",Toast.LENGTH_SHORT).show();
}
});
}
@Override
public void onResponse(@NotNull Call call, @NotNull 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,mSelectedProvince.getId());
} else if ("county".equals(type)) {
result = Utility.handleCountyResponse(responseText,mSelectedCity.getId());
}
if (result) {
getActivity().runOnUiThread(new Runnable() {
@Override
public void run() {
closeProgressDialog();
switch (type) {
case "province":
queryProvinces();
break;
case "city":
queryCities();
break;
case "county":
queryCounties();
break;
}
}
});
}
}
});
}
/**
* 显示对话框
* */
private void showProgressDialog() {
if (mProgressDialog == null) {
mProgressDialog = new ProgressDialog(getActivity());
// 这里的话参数依次为,上下文,标题,内容,是否显示进度(flase表示显示进度),是否可以用取消按钮关闭
// ProgressDialog.show(getActivity(), "资源加载中", "资源加载中,请稍后...",false,false);
// setProgressStyle(ProgressDialog.STYLE_HORIZONTAL)这里是设置进度条的风格,HORIZONTAL是水平进度条,SPINNER是圆形进度条
mProgressDialog.setTitle("资源加载中");
mProgressDialog.setMessage("正在加载,请稍后...");
mProgressDialog.setCanceledOnTouchOutside(false);
}
mProgressDialog.show();
}
/**
* 关闭进度对话框
* */
private void closeProgressDialog() {
if (mProgressDialog != null) {
mProgressDialog.dismiss();
}
}
}
- 第八步,修改activity_main,使得碎片依附于活动显示
<?xml version="1.0" encoding="utf-8"?>
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".activity.MainActivity">
<fragment
android:id="@+id/area_fragment_choose"
android:name="com.coolweather.android.activity.ChooseAreaFragment"
android:layout_width="match_parent"
android:layout_height="match_parent"/>
</FrameLayout>
- 第九步,设置http的访问权限,新建xml/network_security_config
<?xml version="1.0" encoding="utf-8"?>
<network-security-config>
<base-config cleartextTrafficPermitted="true" />
</network-security-config>
- 第十步,修改Manifest
<!-- 配置LitePalApplication -->
<application
android:name="org.litepal.LitePalApplication"
android:networkSecurityConfig="@xml/network_security_config"
android:allowBackup="true"
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
android:roundIcon="@mipmap/ic_launcher_round"
android:supportsRtl="true"
android:theme="@style/AppTheme"
tools:targetApi="n">
<activity android:name=".activity.MainActivity">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
</application>
- 第十一步,查看天气json数据
http://guolin.tech/api/weather?cityid=CN101010100&key=08f828b5a3ec4905a29156bf5f29136fhttp://guolin.tech/api/weather?cityid=CN101010100&key=08f828b5a3ec4905a29156bf5f29136f
- 返回值
{"HeWeather":[{
"basic":{"cid":"CN101010100","location":"北京","parent_city":"北京","admin_area":"北京","cnty":"中国","lat":"25.04060936","lon":"102.71224976","tz":"+8.00","city":"北京","id":"CN101010100","update":{"loc":"2020-09-04 10:52","utc":"2020-09-04 02:52"}},
"update":{"loc":"2020-09-04 10:52","utc":"2020-09-04 02:52"},
"status":"ok",
"now":{"cloud":"0","cond_code":"100","cond_txt":"晴","fl":"19","hum":"34","pcpn":"0.0","pres":"1018","tmp":"21","vis":"16","wind_deg":"149","wind_dir":"东南风","wind_sc":"2","wind_spd":"10","cond":{"code":"100","txt":"晴"}},
"daily_forecast":[
{"date":"2020-09-05","cond":{"txt_d":"晴"},"tmp":{"max":"24","min":"7"}},
{"date":"2020-09-06","cond":{"txt_d":"晴"},"tmp":{"max":"26","min":"8"}},
{"date":"2020-09-07","cond":{"txt_d":"多云"},"tmp":{"max":"25","min":"10"}},
{"date":"2020-09-08","cond":{"txt_d":"阴"},"tmp":{"max":"27","min":"7"}},
{"date":"2020-09-09","cond":{"txt_d":"晴"},"tmp":{"max":"28","min":"9"}},
{"date":"2020-09-10","cond":{"txt_d":"多云"},"tmp":{"max":"26","min":"11"}}],
"aqi":{"city":{"aqi":"47","pm25":"28","qlty":"优"}},
"suggestion":{
"comf":{"type":"comf","brf":"舒适","txt":"白天不太热也不太冷,风力不大,相信您在这样的天气条件下,应会感到比较清爽和舒适。"},
"sport":{"type":"sport","brf":"较适宜","txt":"天气较好,户外运动请注意防晒。推荐您进行室内运动。"},
"cw":{"type":"cw","brf":"较适宜","txt":"较适宜洗车,未来一天无雨,风力较小,擦洗一新的汽车至少能保持一天。"}},
"msg":"所有天气数据均为模拟数据,仅用作学习目的使用,请勿当作真实的天气预报软件来使用。"
}]}
- 简化版
basic、now、daily_forecast、aqi、suggestion的内部又有其他的内容,因此我们可以将这5个部分定义成5个实体类
{
"HeWeather":[
{
"basic":{},
"status":"ok",
"now":{},
"daily_forecast":[],
"aqi":{},
"suggestion":{},
}
]
}
basic中,city表示城市名,id表示城市对应的天气update表示天气更新时间
"basic":{
"city":"北京",
"id":"CN101010100",
"update":{
"loc":"2020-09-04 10:52",
"utc":"2020-09-04 02:52"
}
}
- 实体类
public class Basic {
@SerializedName("city")
public String cityName;
@SerializedName("id")
public String weatherId;
@SerializedName("update")
public Update update;
public class Update {
@SerializedName("loc")
public String updateTime;
}
}
- aqi中
"aqi":{
"city":{
"aqi":"47",
"pm25":"28",
"qlty":"优"
}
}
- 实体类
public class AQI {
@SerializedName("city")
public AQICity mAQICity;
public class AQICity {
public String aqi;
public String pm25;
}
}
now
"now":{
"tmp":"21",
"cond":{
"code":"100",
"txt":"晴"
}
}
实体类
public class Now {
@SerializedName("tmp")
public String temperature;
@SerializedName("cond")
public More more;
public class More {
@SerializedName("txt")
public String info;
}
}
suggestipon
"suggestion":{ "comf":{ "type":"comf", "brf":"舒适", "txt":"白天不太热也不太冷,风力不大,相信您在这样的天气条件下,应会感到比较清爽和舒适。" }, "sport":{ "type":"sport", "brf":"较适宜", "txt":"天气较好,户外运动请注意防晒。推荐您进行室内运动。" }, "cw":{ "type":"cw", "brf":"较适宜", "txt":"较适宜洗车,未来一天无雨,风力较小,擦洗一新的汽车至少能保持一天。" } }
实体类
public class Suggestion {
@SerializedName("comf")
public Comfort mComfort;
@SerializedName("cw")
public CarWash mCarWash;
@SerializedName("sport")
public Sport mSport;
public static class Comfort {
@SerializedName("txt")
public String info;
}
public static class CarWash {
@SerializedName("txt")
public String info;
}
public static class Sport {
@SerializedName("txt")
public String info;
}
}
daily_forecast
"daily_forecast":[
{
"date":"2020-09-05",
"cond":{
"txt_d":"晴"
},
"tmp":{
"max":"24",
"min":"7"
}
},
{
"date":"2020-09-06",
"cond":{
"txt_d":"晴"
},
"tmp":{
"max":"26",
"min":"8"
}
},
...
]
实体类
public class Forecast {
public String date;
@SerializedName("tmp")
public Temperature mTemperature;
@SerializedName("cond")
public More mMore;
public static class Temperature {
public String max;
public String min;
}
public static class More {
@SerializedName("txt")
public String info;
}
}
weather
public class Weather {
public String status;
@SerializedName("basic")
public Basic mBasic;
@SerializedName("aqi")
public AQI mAQI;
@SerializedName("now")
public Now mNow;
@SerializedName("suggestion")
public Suggestion mSuggestion;
@SerializedName("daily_forecast")
public List<Forecast> mForecastList;
}
第十二步,编写天气界面,由于模块较多,分开写,最后include进来
<?xml version="1.0" encoding="utf-8"?>
<!--将所有的子布局加载进来显示-->
<FrameLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".activity.WeatherActivity"
android:background="@color/colorPrimary">
<ScrollView
android:id="@+id/weather_layout"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:scrollbars="none"
android:overScrollMode="never">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical">
<include layout="@layout/weather_title"/>
<include layout="@layout/weather_now"/>
<include layout="@layout/weather_forecast"/>
<include layout="@layout/weather_aqi"/>
<include layout="@layout/weather_suggestion"/>
</LinearLayout>
</ScrollView>
</FrameLayout>
weather_title
<?xml version="1.0" encoding="utf-8"?>
<!--头布局,用于居中显示城市名,居右显示更新时间-->
<RelativeLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="?attr/actionBarSize">
<TextView
android:id="@+id/weather_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/weather_title_update_time"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginRight="10dp"
android:layout_marginEnd="10dp"
android:layout_alignParentRight="true"
android:layout_alignParentEnd="true"
android:layout_centerVertical="true"
android:textColor="#fff"
android:textSize="16sp" />
</RelativeLayout>
weather_now
<!--当前天气信息的布局,用于显示当前气温和天气概况-->
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
android:layout_margin="15dp">
<TextView
android:id="@+id/weather_now_degree_text"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="end"
android:textColor="#fff"
android:textSize="60sp"/>
<TextView
android:id="@+id/weather_now_info_text"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="end"
android:textColor="#fff"
android:textSize="20sp"/>
</LinearLayout>
weather_forecast
<!--未来几天天气的布局,用于显示未来几天天气,包含一个标题和用于显示未来几天天气信息的布局,无任何内容,因为要动态添加-->
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
android:layout_margin="15dp"
android:background="#8000">
<TextView
android:id="@+id/weather_forecast_title"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginLeft="15dp"
android:layout_marginStart="15dp"
android:layout_marginTop="15dp"
android:text="@string/weather_forecast_text_view"
android:textColor="#fff"
android:textSize="20sp" />
<LinearLayout
android:id="@+id/weather_forecast_layout"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"/>
</LinearLayout>
item_forecast
<!--用于显示天气预报日期、天气概况、最高温、最低温-->
<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/item_forecast_data_text"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="2"
android:layout_gravity="center_vertical"
android:textColor="#fff"/>
<TextView
android:id="@+id/item_forecast_info_text"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:layout_gravity="center_vertical"
android:gravity="center"
android:textColor="#fff" />
<TextView
android:id="@+id/item_forecast_max_text"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:layout_gravity="center_vertical"
android:gravity="right"
android:textColor="#fff" />
<TextView
android:id="@+id/item_forecast_min_text"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:layout_gravity="center_vertical"
android:gravity="right"
android:textColor="#fff" />
</LinearLayout>
weather_aqi
<!--空气质量信息的布局,这里用于显示AQI指数和PM25指数-->
<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:id="@+id/weather_aqi_title"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginLeft="15dp"
android:layout_marginStart="15dp"
android:layout_marginTop="15dp"
android:text="@string/weather_aqi_title"
android:textColor="#fff"
android:textSize="20sp" />
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal"
android:layout_margin="15dp"
android:baselineAligned="false">
<LinearLayout
android:layout_width="0dp"
android:layout_weight="1"
android:layout_height="wrap_content"
android:orientation="vertical">
<TextView
android:id="@+id/weather_aqi_aqi_text"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:textColor="#fff"
android:textSize="40sp"/>
<TextView
android:id="@+id/weather_aqi_aqi_info"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:textColor="#fff"
android:text="@string/weather_aqi_aqi_info" />
</LinearLayout>
<LinearLayout
android:layout_width="0dp"
android:layout_weight="1"
android:layout_height="wrap_content"
android:orientation="vertical">
<TextView
android:id="@+id/weather_aqi_pm25_text"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:textColor="#fff"
android:textSize="40sp" />
<TextView
android:id="@+id/weather_aqi_pm25_info"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:textColor="#fff"
android:text="@string/weather_aqi_pm25_info" />
</LinearLayout>
</LinearLayout>
</LinearLayout>
weather_suggestion
<!--生活建议的布局,用于显示标题、舒适度、洗车指数和运动指数-->
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
android:layout_margin="15dp"
android:background="#8000">
<TextView
android:id="@+id/weather_suggestion_title"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginLeft="15dp"
android:layout_marginStart="15dp"
android:layout_marginTop="15dp"
android:text="@string/weather_suggestion_title"
android:textColor="#fff"
android:textSize="20sp" />
<TextView
android:id="@+id/weather_suggestion_comfort_text"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_margin="15dp"
android:textColor="#fff"/>
<TextView
android:id="@+id/weather_suggestion_car_wash_text"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_margin="15dp"
android:textColor="#fff" />
<TextView
android:id="@+id/weather_suggestion_sport_text"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_margin="15dp"
android:textColor="#fff" />
</LinearLayout>
- 第十三步,在Utility里添加一个用于解析天气Json数据的方法
/**
* 将返回的Json数据解析成Weather实体类
* */
public static Weather handleWeatherResponse(String response) {
try {
JSONObject jsonObject = new JSONObject(response);
JSONArray jsonArray = jsonObject.getJSONArray("HeWeather");
String weatherContent = jsonArray.getJSONArray(0).toString();
return new Gson().fromJson(weatherContent,Weather.class);
} catch (Exception e) {
e.printStackTrace();
}
return null;
}
- 第十四步,修改WeatherActivity的代码,在其中去请求天气数据,以及将数据展示在界面上
public class WeatherActivity extends AppCompatActivity {
private ScrollView weatherLayout;
private TextView weatherTitleCity;
private TextView weatherTitleUpdateTime;
private TextView weatherNowDegreeText;
private TextView weatherNowInfoText;
private LinearLayout weatherForecastLayout;
private TextView weatherAQIText;
private TextView weatherPM25Text;
private TextView weatherSuggestionComfortText;
private TextView weatherSuggestionCarWashText;
private TextView weatherSuggestionSportText;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_weather);
//初始化各控件
weatherLayout = findViewById(R.id.weather_layout);
weatherTitleCity = findViewById(R.id.weather_title_city);
weatherTitleUpdateTime = findViewById(R.id.weather_title_update_time);
weatherNowDegreeText = findViewById(R.id.weather_now_degree_text);
weatherNowInfoText = findViewById(R.id.weather_now_info_text);
weatherForecastLayout = findViewById(R.id.weather_forecast_layout);
weatherAQIText = findViewById(R.id.weather_aqi_aqi_text);
weatherPM25Text = findViewById(R.id.weather_aqi_pm25_text);
weatherSuggestionComfortText = findViewById(R.id.weather_suggestion_comfort_text);
weatherSuggestionCarWashText = findViewById(R.id.weather_suggestion_car_wash_text);
weatherSuggestionSportText = findViewById(R.id.weather_suggestion_sport_text);
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);
}
}
/**
* 根据天气id请求城市天气信息
* */
public void requestWeather(final String weatherId) {
String weatherUrl = "http://guolin.tech/api/weather?cityid=" + weatherId + "&key=08f828b5a3ec4905a29156bf5f29136f";
HttpUtil.sendOkHttpRequest(weatherUrl, new Callback() {
@Override
public void onFailure(@NotNull Call call, @NotNull IOException e) {
runOnUiThread(new Runnable() {
@Override
public void run() {
Toast.makeText(WeatherActivity.this,"获取天气信息失败",Toast.LENGTH_SHORT).show();
}
});
}
@Override
public void onResponse(@NotNull Call call, @NotNull 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,"获取天气信息失败",Toast.LENGTH_SHORT);
}
}
});
}
});
}
/**
* 处理并展示Weather实体类中的数据
* */
private void showWeatherInfo(Weather weather) {
String cityName = weather.mBasic.cityName;
String updateTime = weather.mBasic.mUpdate.updateTime.split( " ")[1];
String degree = weather.mNow.temperature + "℃";
String weatherInfo = weather.mNow.mMore.info;
weatherTitleCity.setText(cityName);
weatherTitleUpdateTime.setText(updateTime);
weatherNowDegreeText.setText(degree);
weatherNowInfoText.setText(weatherInfo);
weatherForecastLayout.removeAllViews();
for (Forecast forecast : weather.mForecastList) {
View view = LayoutInflater.from(this).inflate(R.layout.item_forecast,weatherForecastLayout,false);
TextView dateText = view.findViewById(R.id.item_forecast_data_text);
TextView infoText = view.findViewById(R.id.item_forecast_info_text);
TextView maxText = view.findViewById(R.id.item_forecast_max_text);
TextView minText = view.findViewById(R.id.item_forecast_min_text);
dateText.setText(forecast.date);
infoText.setText(forecast.mMore.info);
maxText.setText(forecast.mTemperature.max);
minText.setText(forecast.mTemperature.min);
weatherForecastLayout.addView(view);
}
if (weather.mAQI != null) {
weatherAQIText.setText(weather.mAQI.mAQICity.aqi);
weatherPM25Text.setText(weather.mAQI.mAQICity.pm25);
}
String comfort = "舒适度:" + weather.mSuggestion.mComfort.info;
String carWash = "洗车指数:" + weather.mSuggestion.mCarWash.info;
String sport = "运动指数:" + weather.mSuggestion.mSport.info;
weatherSuggestionComfortText.setText(comfort);
weatherSuggestionCarWashText.setText(carWash);
weatherSuggestionSportText.setText(sport);
weatherLayout.setVisibility(View.VISIBLE);
}
}
- 第十五步,修改ChooseAreaFragment的onActivityCreated()代码
mListView.setOnItemClickListener(new AdapterView.OnItemClickListener() {
@Override
public void onItemClick(AdapterView<?> adapterView, View view, int position, long id) {
if (mCurrentLevel == LEVEL_PROVINCE) {
mSelectedProvince = mProvinceList.get(position);
queryCities();
} else if (mCurrentLevel == LEVEL_CITY) {
mSelectedCity = mCityList.get(position);
queryCounties();
} else if (mCurrentLevel == LEVEL_COUNTY) {
String weatherId = mCountyList.get(position).getWeatherId();
Intent intent = new Intent(getActivity(),WeatherActivity.class);
intent.putExtra("weather_id",weatherId);
startActivity(intent);
getActivity().finish();
}
}
});
- 第十六步,修改MainActivity中的代码,判断是否已经请求过数据
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();
}
}
- 第十七步,获取必应的每日一图,修改activity_weather、weatherActivity
<ImageView
android:id="@+id/bing_pic_img"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:scaleType="centerCrop"
android:contentDescription="@string/bing_pic_img" />
/**
* 加载必应每日一图
* */
private void loadBingPic() {
String requestBingPic = "http://guolin.tech/api/bing_pic";
HttpUtil.sendOkHttpRequest(requestBingPic, new Callback() {
@Override
public void onFailure(@NotNull Call call, @NotNull IOException e) {
e.printStackTrace();
}
@Override
public void onResponse(@NotNull Call call, @NotNull 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);
}
});
}
});
}
- 第十八步,修改activity_weather,实现手动更新天气,切换城市
<!--使用DrawerLayout实现菜单布局,其中第一个子控件用于作为主屏幕显示的内容,第二个字控件用于作为滑动菜单中显示的内容
layout_gravity指定了菜单所处的方向,且必须指定-->
<androidx.drawerlayout.widget.DrawerLayout
android:id="@+id/drawer_layout"
android:layout_width="match_parent"
android:layout_height="match_parent">
<androidx.swiperefreshlayout.widget.SwipeRefreshLayout
android:id="@+id/swipe_refresh"
android:layout_width="match_parent"
android:layout_height="match_parent">
<ScrollView
android:id="@+id/weather_layout"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:scrollbars="none"
android:overScrollMode="never">
<!--为状态看留出空间fitsSystemWindows="true"-->
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
android:fitsSystemWindows="true">
<include layout="@layout/weather_title"/>
<include layout="@layout/weather_now"/>
<include layout="@layout/weather_forecast"/>
<include layout="@layout/weather_aqi"/>
<include layout="@layout/weather_suggestion"/>
</LinearLayout>
</ScrollView>
</androidx.swiperefreshlayout.widget.SwipeRefreshLayout>
<fragment
android:id="@+id/choose_area_fragment"
android:name="com.coolweather.android.activity.ChooseAreaFragment"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_gravity="start"/>
</androidx.drawerlayout.widget.DrawerLayout>
mSwipeRefreshLayout.setOnRefreshListener(new SwipeRefreshLayout.OnRefreshListener() {
@Override
public void onRefresh() {
requestWeather(weatherId);
}
});
String bingPic = prefs.getString("bing_pic",null);
if (bingPic != null) {
Glide.with(this).load(bingPic).into(bingPicImg);
} else {
loadBingPic();
}
- 第十九步,新建AutoUpdateService,实现自动更新天气与城市
public class AutoUpdateService extends Service {
public AutoUpdateService() {
}
@Override
public IBinder onBind(Intent intent) {
return null;
}
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
updateWeather();
updateBingPic();
AlarmManager manager = (AlarmManager) getSystemService(ALARM_SERVICE);
// 八小时更新一次
int anHour = 8 * 60 * 60 * 1000;
long triggerAtTime = SystemClock.elapsedRealtime() + anHour;
Intent intentOne = new Intent(this,AutoUpdateService.class);
PendingIntent pendingIntent = PendingIntent.getService(this,0,intentOne,0);
manager.cancel(pendingIntent);
manager.set(AlarmManager.ELAPSED_REALTIME_WAKEUP,triggerAtTime,pendingIntent);
return super.onStartCommand(intent, flags, startId);
}
/**
* 更新天气信息
* */
private void updateWeather() {
SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(this);
String weatherString = prefs.getString("weather",null);
if (weatherString != null) {
// 有缓存时直接解析天气数据
Weather weather = Utility.handleWeatherResponse(weatherString);
String weatherId = weather.mBasic.weatherId;
String weatherUrl = "http://guolin.tech/api/weather?cityid=" + weatherId + "&key=08f828b5a3ec4905a29156bf5f29136f";
HttpUtil.sendOkHttpRequest(weatherUrl, new Callback() {
@Override
public void onFailure(@NotNull Call call, @NotNull IOException e) {
e.printStackTrace();
}
@Override
public void onResponse(@NotNull Call call, @NotNull Response response) throws IOException {
String responseText = response.body().string();
Weather weather = Utility.handleWeatherResponse(responseText);
if (weather != null && "ok".equals(weather.status)) {
SharedPreferences.Editor editor = PreferenceManager.getDefaultSharedPreferences(AutoUpdateService.this).edit();
editor.putString("weather",responseText);
editor.apply();
}
}
});
}
}
/**
* 更新必应每日一图
* */
private void updateBingPic() {
String requestBingPic = "http://guolin.tech/api/bing_pic";
HttpUtil.sendOkHttpRequest(requestBingPic, new Callback() {
@Override
public void onFailure(@NotNull Call call, @NotNull IOException e) {
e.printStackTrace();
}
@Override
public void onResponse(@NotNull Call call, @NotNull Response response) throws IOException {
String bingPic = response.body().string();
SharedPreferences.Editor editor = PreferenceManager.getDefaultSharedPreferences(AutoUpdateService.this).edit();
editor.putString("bing_pic",bingPic);
editor.apply();
}
});
}
}
- 下载地址
https://github.com/qricis/DoSomeAndroidTest/tree/main/CoolWeather