使用的教材是国@郭霖写的《第二行代码》
应用名:Cool Weather
一、功能需求及技术可行性分析
1、具备的功能
- 可以罗列出全国所有的省市县
- 可以查看全国任意城市的天气信息
- 可以自由地切换城市,去查看其他城市地天气
- 提供手动更新以及后台自动更新天气的功能
2、技术可行性分析
- 如何获取全国省市县的信息:
天气数据:和风天气的数据接口
省市县数据:http://guolin.tech/api/china
- 编写步骤
- 基本配置
- 创建数据库和表
- 遍历全国省市县数据
- 显示天气数据
- 编写天气界面
- 将天气显示到界面上
- 获取必应天气壁纸
- 手动更新天气
- 手动切换城市
- 后台自动更新天气
- 扩展功能【有空再写】
- 修改图标和名称
二、正式编写
新建一个空项目
Cool Weather
一、基本配置
配置Git
,启用版本控制
将app/build.gradle
依赖库更新到最新
打开AndroidManifest.xml
:
- 设置
<application>
属性tools:ignore="GoogleAppIndexingWarning"
【应用内打开app的功能,此功能不需要】 - 将
<application>
属性android:allowBackup
设置为false
【不允许应用内数据被取出】
二、创建数据库和表
为了让项目有更好的结构,在com.coolweather.android
下新建几个包
1、添加依赖库
implementation 'org.litepal.android:java:3.0.0' // 数据库操作
implementation "com.squareup.okhttp3:okhttp:4.3.1" // 网络请求
implementation 'com.google.code.gson:gson:2.8.6' // 解析JSON
implementation 'com.github.bumptech.glide:glide:4.11.0'
annotationProcessor 'com.github.bumptech.glide:compiler:4.11.0' // 加载图片的库
implementation 'androidx.swiperefreshlayout:swiperefreshlayout:1.0.0' // 下拉刷新
2、设计数据库表的结构
在db
下建立三张表 province
,city
,county
public class Province extends LitePalSupport {
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;
}
}
public class City extends LitePalSupport {
private int id;
private String cityName;
private int provinceId;
private int cityCode;
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 getProvinceId() {
return provinceId;
}
public void setProvinceId(int provinceId) {
this.provinceId = provinceId;
}
public int getCityCode() {
return cityCode;
}
public void setCityCode(int cityCode) {
this.cityCode = cityCode;
}
}
public class County extends LitePalSupport {
private int id;
private String countyName;
private int cityId;
private String weatherId;
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 getCityId() {
return cityId;
}
public void setCityId(int cityId) {
this.cityId = cityId;
}
public String getWeatherId() {
return weatherId;
}
public void setWeatherId(String weatherId) {
this.weatherId = weatherId;
}
}
3、数据库版本控制
app/src/main
目录下新建一个文件夹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>
引入LitePal
,修改AndroidManifest
:
<application
android:name="org.litepal.LitePalApplication"
三、遍历全国省市县数据
1、服务器交互
在util
包下增加一个HttpUtil
类,用于服务器交互:
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);
}
}
2、解析JSON
增加一个Utility
类,便于解析json
数据:
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.setCityId(cityId);
county.setCountyName(countyObject.getString("name"));
county.setWeatherId(countyObject.getString("weather_id"));
county.save();
}
return true;
}catch (JSONException e){
e.printStackTrace();
}
}
return false;
}
}
3、选择地区的布局
choose_area.xml
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical" android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="#fff">
<RelativeLayout
android:layout_width="match_parent"
android:layout_height="?attr/actionBarSize"
android:background="?attr/colorPrimary">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:id="@+id/title_text"
android:layout_centerInParent="true"
android:textColor="#fff"
android:textSize="20sp"/>
<Button
android:layout_width="25dp"
android:layout_height="25dp"
android:id="@+id/back_button"
android:layout_marginLeft="10dp"
android:layout_alignParentLeft="true"
android:layout_centerVertical="true"
android:background="@drawable/ic_back"
android:layout_marginStart="10dp"
android:layout_alignParentStart="true" />
</RelativeLayout>
<ListView
android:layout_width="match_parent"
android:layout_height="match_parent"
android:id="@+id/list_view"/>
</LinearLayout>
4、遍历省市县的碎片
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 progressDialog;
private TextView titleText;
private Button backButton;
private ListView listView;
private ArrayAdapter<String> adapter;
private List<String> dataList = new ArrayList<>();
private List<Province> provinceList;
private List<City> cityList;
private List<County> countyList;
private Province selectedProvince;
private City selectedCity;
private int currentLevel;
@Nullable
@Override
public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
View view = inflater.inflate(R.layout.choose_area, container, false);
titleText = view.findViewById(R.id.title_text);
backButton = view.findViewById(R.id.back_button);
listView = view.findViewById(R.id.list_view);
assert getContext()!= null;
adapter = new ArrayAdapter<>(getContext(), android.R.layout.simple_list_item_1, dataList);
listView.setAdapter(adapter);
return view;
}
@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();
}
}
});
backButton.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
if(currentLevel == LEVEL_COUNTY){
queryCities();
}else if(currentLevel == LEVEL_CITY){
queryProvinces();
}
}
});
queryProvinces();
}
/*c查询全国的所有的省,优先从数据库查询,没有则到服务器上查询*/
private void queryProvinces(){
titleText.setText("中国");
backButton.setVisibility(View.GONE);
provinceList = LitePal.findAll(Province.class);
if(provinceList.size() > 0){
dataList.clear();
for (Province province : provinceList){
dataList.add(province.getProvinceName());
}
adapter.notifyDataSetChanged();
listView.setSelection(0);
currentLevel = LEVEL_PROVINCE;
}else {
String address = "http://guolin.tech/api/china";
queryFromServer(address, "province");
}
}
/*查询省内所有的市,优先从数据库查询*/
private void queryCities(){
titleText.setText(selectedProvince.getProvinceName());
backButton.setVisibility(View.VISIBLE);
cityList = LitePal.where("provinceId = ?", String.valueOf(selectedProvince.getId())).find(City.class);
if (cityList.size() > 0){
dataList.clear();
for (City city : cityList){
dataList.add(city.getCityName());
}
adapter.notifyDataSetChanged();
listView.setSelection(0);
currentLevel = LEVEL_CITY;
}else {
int provinceCode = selectedProvince.getProvinceCode();
String address = "http://guolin.tech/api/china/" + provinceCode;
queryFromServer(address, "city");
}
}
/*查询选中市内的所有县,优先从数据库查询*/
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;
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) {
// 回到主线程处理逻辑
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, selectedProvince.getId());
}else if("county".equals(type)){
result = Utility.handleCountyResponse(responseText, selectedCity.getId());
}
if (result){
getActivity().runOnUiThread(new Runnable() {
@Override
public void run() {
closeProgressDialog();
if ("province".equals(type)){
queryProvinces();
}else if("city".equals(type)){
queryCities();
}else if("county".equals(type)){
queryCounties();
}
}
});
}
}
});
}
/*显示进度对话框*/
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();
}
}
}
5、布局中引入碎片
主布局
<?xml version="1.0" encoding="utf-8"?>
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent">
<fragment
android:layout_width="match_parent"
android:layout_height="match_parent"
android:name="com.coolweather.android.ChooseAreaFragment"
android:id="@+id/choose_area_fragment"/>
</FrameLayout>
去除原生标题栏,修改res/values/styles.xml
<resources>
<!-- Base application theme. -->
<style name="AppTheme" parent="Theme.AppCompat.Light.NoActionBar">
<!-- Customize your theme here. -->
<item name="colorPrimary">@color/colorPrimary</item>
<item name="colorPrimaryDark">@color/colorPrimaryDark</item>
<item name="colorAccent">@color/colorAccent</item>
</style>
</resources>
6、声明权限
<uses-permission android:name="android.permission.INTERNET"/>
如果是安卓Q版本,还要给<application>
设置属性
android:usesCleartextTraffic="true"
7、展示
四、显示天气数据
1、基本格式
天气数据返回的内容非常多,但解析数据只要一行代码,但前提是将数据对应的实体类建立好,查询开发文档,得到三个请求分别为:
请求参数为now
:
{
"HeWeather6": [
{
"basic": {},
"update": {},
"status": "ok",
"now": {},
},
]
}
请求参数为forecast
:
{
"HeWeather6": [
{
"basic": {},
"update": {},
"status": "ok",
"daily_forecast": []
},
]
}
请求参数为life_style
:
{
"HeWeather6": [
{
"basic": {},
"update": {},
"status": "ok",
"lifestyle": []
},
]
}
2、建立关系
basic
示例:
"basic": {
"cid": "CN101010100",
"location": "北京",
"parent_city": "北京",
"admin_area": "北京",
"cnty": "中国",
"lat": "39.90498734",
"lon": "116.4052887",
"tz": "+8.00"
}
对应Basic
类:
public class Basic {
@SerializedName("location")
public String cityName;
@SerializedName("cid")
public String weatherId;
}
update
示例:
"update": {
"loc": "2019-06-05 22:39",
"utc": "2019-06-05 14:39"
}
对应Update
类:
public class Update {
@SerializedName("loc")
public String updateTime;
}
以此类推:
public class Now {
@SerializedName("tmp")
public String temperature;
@SerializedName("cond_txt")
public String info;
@SerializedName("wind_dir")
public String directionOfWind;
@SerializedName("wind_sc")
public String powerOfWind;
@SerializedName("hum")
public String humidity;
@SerializedName("vis")
public String visibility;
}
public class Forecast {
public String date;
@SerializedName("sr")
public String sunrise;
@SerializedName("ss")
public String sunset;
public String tmp_max;
public String tmp_min;
@SerializedName("con_txt_d")
public String weatherDay;
@SerializedName("con_txt_n")
public String weatherNight;
@SerializedName("pop")
public String precipitationProbability;
}
public class LifeStyle {
public String type;
public String brf;
public String txt;
}
最后用一个Weather
类把所有类串起来:
public class Weather {
public Basic basic;
public Update update;
public String status;
public Now now;
@SerializedName("daily_forecast")
public List<Forecast> forecastList;
@SerializedName("lifestyle")
public List<LifeStyle> lifeStyleList;
}
五、编写天气界面
所有的天气信息将在同一个界面显示,新建一个WeatherActivity
,布局指定为activity_weather.xml
1、头布局
新建头布局title.xml
:
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="?attr/actionBarSize">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:id="@+id/title_city"
android:layout_centerInParent="true"
android:textColor="#fff"
android:textSize="20sp"/>
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:id="@+id/title_update_time"
android:layout_marginRight="10dp"
android:layout_alignParentRight="true"
android:layout_centerVertical="true"
android:textColor="#fff"
android:textSize="16sp"
android:layout_alignParentEnd="true"
android:layout_marginEnd="10dp"
tools:ignore="RelativeOverlap" />
</RelativeLayout>
2、当前天气布局
再来个now.xml
作为当前天气信息的布局:
<?xml version="1.0" encoding="utf-8"?>
<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">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="end"
android:textColor="#fff"
android:textSize="60sp"
android:id="@+id/degree_text"/>
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:id="@+id/weather_info_text"
android:layout_gravity="end"
android:textSize="20sp"
android:textColor="#fff"/>
</LinearLayout>
3、未来天气布局
未来天气的布局forecast.xml
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
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"
android:layout_marginStart="15dp"
tools:ignore="HardcodedText" />
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
android:id="@+id/forecast_layout">
</LinearLayout>
</LinearLayout>
4、未来天气子项布局
未来天气的子项布局forecast_item
:
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_margin="15dp">
<TextView
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_gravity="center_vertical"
android:id="@+id/date_text"
android:layout_weight="2"
android:textColor="#fff"/>
<TextView
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:id="@+id/info_text"
android:layout_gravity="center_vertical"
android:gravity="center"
android:textColor="#fff"/>
<TextView
android:layout_width="0dp"
android:layout_height="wrap_content"
android:id="@+id/max_text"
android:layout_weight="1"
android:layout_gravity="center_vertical"
android:gravity="right"
android:textColor="#fff"
tools:ignore="RtlHardcoded" />
<TextView
android:layout_width="0dp"
android:layout_height="wrap_content"
android:id="@+id/min_text"
android:layout_weight="1"
android:layout_gravity="center_vertical"
android:gravity="right"
android:textColor="#fff"
tools:ignore="RtlHardcoded" />
</LinearLayout>
5、空气质量布局
空气质量的布局aqi.xml
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
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"
android:layout_marginStart="15dp"
tools:ignore="HardcodedText" />
<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:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_centerInParent="true">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:id="@+id/hum_text"
android:textColor="#fff"
android:textSize="40sp"/>
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:text="空气湿度"
android:textColor="#fff"
tools:ignore="HardcodedText" />
</LinearLayout>
</RelativeLayout>
<RelativeLayout
android:layout_width="0dp"
android:layout_height="match_parent"
android:layout_weight="1">
<LinearLayout
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_centerInParent="true">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:textColor="#fff"
android:textSize="40sp"
android:id="@+id/wind_text"/>
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:text="风向"
android:textColor="#fff"
tools:ignore="HardcodedText" />
</LinearLayout>
</RelativeLayout>
</LinearLayout>
</LinearLayout>
6、生活建议布局
然后是生活建议suggestions.xml
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
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"
tools:ignore="HardcodedText"
android:layout_marginStart="15dp" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_margin="15dp"
android:textColor="#fff"
android:id="@+id/comfort_text"/>
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_margin="15dp"
android:textColor="#fff"
android:id="@+id/car_wash_text"/>
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_margin="15dp"
android:textColor="#fff"
android:id="@+id/sport_text"/>
</LinearLayout>
7、合并布局
最后把它们引入到activity_weather.xml
<?xml version="1.0" encoding="utf-8"?>
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="@color/colorPrimary">
<ScrollView
android:layout_width="match_parent"
android:layout_height="match_parent"
android:scrollbars = "none"
android:overScrollMode="never"
android:id="@+id/weather_layout">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical">
<include layout="@layout/title"/>
<include layout="@layout/now" />
<include layout="@layout/forecast" />
<include layout="@layout/aqi"/>
<include layout="@layout/suggestions" />
</LinearLayout>
</ScrollView>
</FrameLayout>
六、将天气显示到界面上
1、解析服务器JSON数据
在Utility
类中添加一个用于解析天气 JSON 数据的方法:
public class Utility {
。。。。。。
// 将返回的数据解析成实体类
public static Weather handleWeatherResponse(String response, Weather weather, String cate){
try{
JSONObject jsonObject = new JSONObject(response);
JSONArray jsonArray = jsonObject.getJSONArray("HeWeather6");
String weatherContent = jsonArray.getJSONObject(0).toString();
if ("now".equals(cate)){
Now nowWeather = new Gson().fromJson(weatherContent, Now.class);
weather.basic = nowWeather.basic;
weather.update = nowWeather.update;
weather.now = nowWeather.now;
weather.status = nowWeather.status;
return weather;
}else if("forecast".equals(cate)){
Forecast forecastWeather = new Gson().fromJson(weatherContent, Forecast.class);
weather.forecastList = forecastWeather.forecastList;
weather.status = forecastWeather.status;
}else if("lifestyle".equals(cate)){
LifeStyle lifeStyleWeather = new Gson().fromJson(weatherContent, LifeStyle.class);
weather.lifestyleList = lifeStyleWeather.lifestyleList;
weather.status = lifeStyleWeather.status;
}
return weather;
}catch (Exception e){
e.printStackTrace();
}
return null;
}
}
2、在活动中处理数据
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 humText;
private TextView windText;
private TextView comfortText;
private TextView drsgText;
private TextView sportText;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_weather);
// 初始化控件
weatherLayout = findViewById(R.id.weather_layout);
titleCity = findViewById(R.id.title_city);
titleUpdateTime = findViewById(R.id.title_update_time);
weatherInfoText = findViewById(R.id.weather_info_text);
forecastLayout = findViewById(R.id.forecast_layout);
humText = findViewById(R.id.hum_text);
windText = findViewById(R.id.wind_text);
comfortText = findViewById(R.id.comfort_text);
drsgText = findViewById(R.id.drsg_text);
sportText = findViewById(R.id.sport_text);
degreeText = findViewById(R.id.degree_text);
Weather weather = new Weather();
SharedPreferences prefs = getApplicationContext().getSharedPreferences("weather_data", MODE_PRIVATE);
String nowString = prefs.getString("now", null);
String forecastString = prefs.getString("forecast", null);
String lifestyleString = prefs.getString("lifestyle", null);
if(TextUtils.isEmpty(nowString) || TextUtils.isEmpty(forecastString) || TextUtils.isEmpty(lifestyleString)){
// 无缓存时去服务器查询天气
String weatherId = getIntent().getStringExtra("weather_id");
weatherLayout.setVisibility(View.INVISIBLE);
requestWeather(weatherId, weather, "now");
}else{
// 有缓存时直接读取数据
weather = Utility.handleWeatherResponse(nowString, weather, "now");
weather = Utility.handleWeatherResponse(forecastString, weather, "forecast");
weather = Utility.handleWeatherResponse(lifestyleString, weather, "lifestyle");
assert weather != null;
showWeatherInfo(weather);
}
}
// 构造Url
private String reUrl(String cate, String weatherId){
String key = "cd9034ce70ad477b8454b6638261f0a5";
return "https://free-api.heweather.net/s6/weather/" + cate + "?location=" + weatherId + "&key=" + key;
}
// 根据天气 id 查询城市天气
public void requestWeather(final String weatherId, final Weather weather,final String cate){
String requestUrl = reUrl(cate, weatherId);
Log.d("UUU", requestUrl);
HttpUtil.sendOkHttpRequest(requestUrl, new Callback() {
@Override
public void onFailure(@NotNull final Call call, @NotNull IOException e) {
e.printStackTrace();
runOnUiThread(new Runnable() {
@Override
public void run() {
switch (cate) {
case "now":
Toast.makeText(WeatherActivity.this, "获取今天天气失败", Toast.LENGTH_SHORT).show();
break;
case "lifestyle":
Toast.makeText(WeatherActivity.this, "获取生活指数失败", Toast.LENGTH_SHORT).show();
break;
case "forecast":
Toast.makeText(WeatherActivity.this, "获取预报天气失败", Toast.LENGTH_SHORT).show();
break;
}
}
});
}
@Override
public void onResponse(@NotNull Call call, @NotNull Response response) throws IOException {
final String responseText = response.body().string();
runOnUiThread(new Runnable() {
@Override
public void run() {
if ((weather != null && "ok".equals(weather.status)) || ("now".equals(cate))){
SharedPreferences.Editor editor = getApplicationContext().getSharedPreferences("weather_data", MODE_PRIVATE).edit();
Weather weather_new = Utility.handleWeatherResponse(responseText, weather, cate);
editor.putString(cate, responseText);
editor.apply();
switch (cate) {
case "now":
requestWeather(weatherId, weather_new, "forecast");
break;
case "forecast":
requestWeather(weatherId, weather_new, "lifestyle");
break;
case "lifestyle":
assert weather_new != null;
showWeatherInfo(weather_new);
break;
}
}else {
Toast.makeText(WeatherActivity.this, "请求到的数据出错", Toast.LENGTH_SHORT).show();
}
}
});
}
});
}
// 处理并展示实体类 Weather 的数据
private void showWeatherInfo(Weather weather){
String cityName = weather.basic.cityName;
String updateTime = weather.update.updateTime;
String degree = weather.now.temperature + "℃";
String weatherInfo = weather.now.info;
titleCity.setText(cityName);
titleUpdateTime.setText(updateTime);
degreeText.setText(degree);
weatherInfoText.setText(weatherInfo);
forecastLayout.removeAllViews();
for (Forecast.forecast forecast: weather.forecastList){
View view= LayoutInflater.from(this).inflate(R.layout.forecast_item, forecastLayout, false);
TextView dateText = view.findViewById(R.id.date_text);
TextView infoText = view.findViewById(R.id.info_text);
TextView maxText = view.findViewById(R.id.max_text);
TextView minText = view.findViewById(R.id.min_text);
dateText.setText(forecast.date);
if (forecast.day.equals(forecast.night)){
infoText.setText(forecast.day);
}else {
infoText.setText(String.format("%s转%s", forecast.day, forecast.night));
}
maxText.setText(forecast.tmp_max);
minText.setText(forecast.tmp_min);
forecastLayout.addView(view);
}
if(weather.now != null){
humText.setText(weather.now.humidity);
windText.setText(weather.now.directionOfWind);
}
for(LifeStyle.lifestyle lifestyle: weather.lifestyleList){
if ("comf".equals(lifestyle.type)){
comfortText.setText(String.format("舒适度:%s", lifestyle.txt));
}
if ("drsg".equals(lifestyle.type)){
drsgText.setText(String.format("穿衣指数:%s", lifestyle.txt));
}
if ("sport".equals(lifestyle.type)){
sportText.setText(String.format("运动建议:%s", lifestyle.txt));
}
}
weatherLayout.setVisibility(View.VISIBLE);
}
}
3、从省市县列表跳转到天气页面
修改ChooseAreaFragment.java
@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();
}
}
});
backButton.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
if(currentLevel == LEVEL_COUNTY){
queryCities();
}else if(currentLevel == LEVEL_CITY){
queryProvinces();
}
}
});
queryProvinces();
}
4、选择启动页面
MainActivity
判断缓存数据来决定::
public class MainActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
SharedPreferences prefs = getApplicationContext().getSharedPreferences("weather_data", MODE_PRIVATE);
if (prefs.getString("lifestyle", null) != null){
Intent intent = new Intent(this, WeatherActivity.class);
startActivity(intent);
finish();
}
}
}
5、展示
七、获取必应天气壁纸
用http://guolin.tech/api/bing_pic
这个接口可以得到每日壁纸的url
1、天气布局修改
<?xml version="1.0" encoding="utf-8"?>
<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:layout_width="match_parent"
android:layout_height="match_parent"
android:id="@+id/bing_pic_img"
android:scaleType="centerCrop"/>
<ScrollView
......
2、加载每日壁纸
WeatherActivity.java
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 humText;
private TextView windText;
private TextView comfortText;
private TextView drsgText;
private TextView sportText;
private ImageView bingPicImg;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_weather);
// 初始化控件
weatherLayout = findViewById(R.id.weather_layout);
titleCity = findViewById(R.id.title_city);
titleUpdateTime = findViewById(R.id.title_update_time);
weatherInfoText = findViewById(R.id.weather_info_text);
forecastLayout = findViewById(R.id.forecast_layout);
humText = findViewById(R.id.hum_text);
windText = findViewById(R.id.wind_text);
comfortText = findViewById(R.id.comfort_text);
drsgText = findViewById(R.id.drsg_text);
sportText = findViewById(R.id.sport_text);
degreeText = findViewById(R.id.degree_text);
bingPicImg = findViewById(R.id.bing_pic_img);
Weather weather = new Weather();
SharedPreferences prefs = getApplicationContext().getSharedPreferences("weather_data", MODE_PRIVATE);
String bingPic = prefs.getString("bing_pic", null);
if(bingPic != null){
Glide.with(this).load(bingPic).into(bingPicImg);
}else {
loadBingPic();
}
String nowString = prefs.getString("now", null);
String forecastString = prefs.getString("forecast", null);
String lifestyleString = prefs.getString("lifestyle", null);
if(TextUtils.isEmpty(nowString) || TextUtils.isEmpty(forecastString) || TextUtils.isEmpty(lifestyleString)){
// 无缓存时去服务器查询天气
String weatherId = getIntent().getStringExtra("weather_id");
weatherLayout.setVisibility(View.INVISIBLE);
requestWeather(weatherId, weather, "now");
}else{
// 有缓存时直接读取数据
weather = Utility.handleWeatherResponse(nowString, weather, "now");
weather = Utility.handleWeatherResponse(forecastString, weather, "forecast");
weather = Utility.handleWeatherResponse(lifestyleString, weather, "lifestyle");
assert weather != null;
showWeatherInfo(weather);
}
}
// 加载每日一图
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) {
Toast.makeText(WeatherActivity.this, "壁纸加载出错", Toast.LENGTH_SHORT).show();
e.printStackTrace();
}
@Override
public void onResponse(@NotNull Call call, @NotNull Response response) throws IOException {
final String bingPic = response.body().string();
SharedPreferences.Editor editor = getApplicationContext().getSharedPreferences("weather_data", MODE_PRIVATE).edit();
editor.putString("bing_pic", bingPic);
editor.apply();
runOnUiThread(new Runnable() {
@Override
public void run() {
Glide.with(WeatherActivity.this).load(bingPic).into(bingPicImg);
}
});
}
});
}
// 构造Url
private String reUrl(String cate, String weatherId){
String key = "cd9034ce70ad477b8454b6638261f0a5";
return "https://free-api.heweather.net/s6/weather/" + cate + "?location=" + weatherId + "&key=" + key;
}
// 根据天气 id 查询城市天气
public void requestWeather(final String weatherId, final Weather weather,final String cate){
String requestUrl = reUrl(cate, weatherId);
HttpUtil.sendOkHttpRequest(requestUrl, new Callback() {
@Override
public void onFailure(@NotNull final Call call, @NotNull IOException e) {
e.printStackTrace();
runOnUiThread(new Runnable() {
@Override
public void run() {
switch (cate) {
case "now":
Toast.makeText(WeatherActivity.this, "获取今天天气失败", Toast.LENGTH_SHORT).show();
break;
case "lifestyle":
Toast.makeText(WeatherActivity.this, "获取生活指数失败", Toast.LENGTH_SHORT).show();
break;
case "forecast":
Toast.makeText(WeatherActivity.this, "获取预报天气失败", Toast.LENGTH_SHORT).show();
break;
}
}
});
}
@Override
public void onResponse(@NotNull Call call, @NotNull Response response) throws IOException {
final String responseText = response.body().string();
runOnUiThread(new Runnable() {
@Override
public void run() {
if ((weather != null && "ok".equals(weather.status)) || ("now".equals(cate))){
SharedPreferences.Editor editor = getApplicationContext().getSharedPreferences("weather_data", MODE_PRIVATE).edit();
Weather weather_new = Utility.handleWeatherResponse(responseText, weather, cate);
editor.putString(cate, responseText);
editor.apply();
switch (cate) {
case "now":
requestWeather(weatherId, weather_new, "forecast");
break;
case "forecast":
requestWeather(weatherId, weather_new, "lifestyle");
break;
case "lifestyle":
assert weather_new != null;
showWeatherInfo(weather_new);
break;
}
}else {
Toast.makeText(WeatherActivity.this, "请求到的数据出错", Toast.LENGTH_SHORT).show();
}
}
});
}
});
}
// 处理并展示实体类 Weather 的数据
private void showWeatherInfo(Weather weather){
loadBingPic();
String cityName = weather.basic.cityName;
String updateTime = weather.update.updateTime;
String degree = weather.now.temperature + "℃";
String weatherInfo = weather.now.info;
titleCity.setText(cityName);
titleUpdateTime.setText(updateTime);
degreeText.setText(degree);
weatherInfoText.setText(weatherInfo);
forecastLayout.removeAllViews();
for (Forecast.forecast forecast: weather.forecastList){
View view= LayoutInflater.from(this).inflate(R.layout.forecast_item, forecastLayout, false);
TextView dateText = view.findViewById(R.id.date_text);
TextView infoText = view.findViewById(R.id.info_text);
TextView maxText = view.findViewById(R.id.max_text);
TextView minText = view.findViewById(R.id.min_text);
dateText.setText(forecast.date);
if (forecast.day.equals(forecast.night)){
infoText.setText(forecast.day);
}else {
infoText.setText(String.format("%s转%s", forecast.day, forecast.night));
}
maxText.setText(forecast.tmp_max);
minText.setText(forecast.tmp_min);
forecastLayout.addView(view);
}
if(weather.now != null){
humText.setText(weather.now.humidity);
windText.setText(weather.now.directionOfWind);
}
for(LifeStyle.lifestyle lifestyle: weather.lifestyleList){
if ("comf".equals(lifestyle.type)){
comfortText.setText(String.format("舒适度:%s", lifestyle.txt));
}
if ("drsg".equals(lifestyle.type)){
drsgText.setText(String.format("穿衣指数:%s", lifestyle.txt));
}
if ("sport".equals(lifestyle.type)){
sportText.setText(String.format("运动建议:%s", lifestyle.txt));
}
}
weatherLayout.setVisibility(View.VISIBLE);
}
}
3、状态栏融合
public class WeatherActivity extends AppCompatActivity {
。。。。。。
@Override
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);
}
setContentView(R.layout.activity_weather);
4、状态栏不融合,只透明
<?xml version="1.0" encoding="utf-8"?>
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
......
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
android:fitsSystemWindows="true">
......
5、展示
八、手动更新天气
1、使用下拉控件
<androidx.swiperefreshlayout.widget.SwipeRefreshLayout
android:id="@+id/swipe_refresh"
android:layout_height="match_parent"
android:layout_width="match_parent">
<ScrollView>
......
</ScrollView>
</androidx.swiperefreshlayout.widget.SwipeRefreshLayout>
2、加入逻辑
public class WeatherActivity extends AppCompatActivity {
......
@Override
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);
}
setContentView(R.layout.activity_weather);
swipeRefreshLayout = findViewById(R.id.swipe_refresh);
swipeRefreshLayout.setColorSchemeResources(R.color.colorPrimary);
// 初始化控件
weatherLayout = findViewById(R.id.weather_layout);
titleCity = findViewById(R.id.title_city);
titleUpdateTime = findViewById(R.id.title_update_time);
weatherInfoText = findViewById(R.id.weather_info_text);
forecastLayout = findViewById(R.id.forecast_layout);
humText = findViewById(R.id.hum_text);
windText = findViewById(R.id.wind_text);
comfortText = findViewById(R.id.comfort_text);
drsgText = findViewById(R.id.drsg_text);
sportText = findViewById(R.id.sport_text);
degreeText = findViewById(R.id.degree_text);
bingPicImg = findViewById(R.id.bing_pic_img);
Weather weather = new Weather();
SharedPreferences prefs = getApplicationContext().getSharedPreferences("weather_data", MODE_PRIVATE);
String bingPic = prefs.getString("bing_pic", null);
if(bingPic != null){
Glide.with(this).load(bingPic).into(bingPicImg);
}else {
loadBingPic();
}
String nowString = prefs.getString("now", null);
String forecastString = prefs.getString("forecast", null);
String lifestyleString = prefs.getString("lifestyle", null);
if(TextUtils.isEmpty(nowString) || TextUtils.isEmpty(forecastString) || TextUtils.isEmpty(lifestyleString)){
// 无缓存时去服务器查询天气
mWeatherId = getIntent().getStringExtra("weather_id");
weatherLayout.setVisibility(View.INVISIBLE);
requestWeather(mWeatherId, weather, "now");
}else{
// 有缓存时直接读取数据
weather = Utility.handleWeatherResponse(nowString, weather, "now");
weather = Utility.handleWeatherResponse(forecastString, weather, "forecast");
weather = Utility.handleWeatherResponse(lifestyleString, weather, "lifestyle");
mWeatherId = weather.basic.weatherId;
assert weather != null;
showWeatherInfo(weather);
}
swipeRefreshLayout.setOnRefreshListener(new SwipeRefreshLayout.OnRefreshListener() {
@Override
public void onRefresh() {
requestWeather(mWeatherId, new Weather(), "now");
}
});
}
// 加载每日一图
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) {
Toast.makeText(WeatherActivity.this, "壁纸加载出错", Toast.LENGTH_SHORT).show();
e.printStackTrace();
}
@Override
public void onResponse(@NotNull Call call, @NotNull Response response) throws IOException {
final String bingPic = response.body().string();
SharedPreferences.Editor editor = getApplicationContext().getSharedPreferences("weather_data", MODE_PRIVATE).edit();
editor.putString("bing_pic", bingPic);
editor.apply();
runOnUiThread(new Runnable() {
@Override
public void run() {
Glide.with(WeatherActivity.this).load(bingPic).into(bingPicImg);
}
});
}
});
}
// 构造Url
private String reUrl(String cate, String weatherId){
String key = "cd9034ce70ad477b8454b6638261f0a5";
return "https://free-api.heweather.net/s6/weather/" + cate + "?location=" + weatherId + "&key=" + key;
}
// 根据天气 id 查询城市天气
public void requestWeather(final String weatherId, final Weather weather,final String cate){
String requestUrl = reUrl(cate, weatherId);
Log.d("???", requestUrl);
HttpUtil.sendOkHttpRequest(requestUrl, new Callback() {
@Override
public void onFailure(@NotNull final Call call, @NotNull IOException e) {
e.printStackTrace();
runOnUiThread(new Runnable() {
@Override
public void run() {
switch (cate) {
case "now":
Toast.makeText(WeatherActivity.this, "获取今天天气失败", Toast.LENGTH_SHORT).show();
break;
case "lifestyle":
Toast.makeText(WeatherActivity.this, "获取生活指数失败", Toast.LENGTH_SHORT).show();
break;
case "forecast":
Toast.makeText(WeatherActivity.this, "获取预报天气失败", Toast.LENGTH_SHORT).show();
break;
}
swipeRefreshLayout.setRefreshing(false);
}
});
}
@Override
public void onResponse(@NotNull Call call, @NotNull Response response) throws IOException {
final String responseText = response.body().string();
runOnUiThread(new Runnable() {
@Override
public void run() {
if ((weather != null && "ok".equals(weather.status)) || ("now".equals(cate))){
SharedPreferences.Editor editor = getApplicationContext().getSharedPreferences("weather_data", MODE_PRIVATE).edit();
Weather weather_new = Utility.handleWeatherResponse(responseText, weather, cate);
editor.putString(cate, responseText);
editor.apply();
switch (cate) {
case "now":
requestWeather(weatherId, weather_new, "forecast");
break;
case "forecast":
requestWeather(weatherId, weather_new, "lifestyle");
break;
case "lifestyle":
assert weather_new != null;
showWeatherInfo(weather_new);
mWeatherId = weather.basic.weatherId;
swipeRefreshLayout.setRefreshing(false);
break;
}
}else {
swipeRefreshLayout.setRefreshing(false);
Toast.makeText(WeatherActivity.this, "请求到的数据出错", Toast.LENGTH_SHORT).show();
}
}
});
}
});
}
// 处理并展示实体类 Weather 的数据
private void showWeatherInfo(Weather weather){
loadBingPic();
String cityName = weather.basic.cityName;
String updateTime = weather.update.updateTime;
String degree = weather.now.temperature + "℃";
String weatherInfo = weather.now.info;
titleCity.setText(cityName);
titleUpdateTime.setText(updateTime);
degreeText.setText(degree);
weatherInfoText.setText(weatherInfo);
forecastLayout.removeAllViews();
for (Forecast.forecast forecast: weather.forecastList){
View view= LayoutInflater.from(this).inflate(R.layout.forecast_item, forecastLayout, false);
TextView dateText = view.findViewById(R.id.date_text);
TextView infoText = view.findViewById(R.id.info_text);
TextView maxText = view.findViewById(R.id.max_text);
TextView minText = view.findViewById(R.id.min_text);
dateText.setText(forecast.date);
if (forecast.day.equals(forecast.night)){
infoText.setText(forecast.day);
}else {
infoText.setText(String.format("%s转%s", forecast.day, forecast.night));
}
maxText.setText(forecast.tmp_max);
minText.setText(forecast.tmp_min);
forecastLayout.addView(view);
}
if(weather.now != null){
humText.setText(weather.now.humidity);
windText.setText(weather.now.directionOfWind);
}
for(LifeStyle.lifestyle lifestyle: weather.lifestyleList){
if ("comf".equals(lifestyle.type)){
comfortText.setText(String.format("舒适度:%s", lifestyle.txt));
}
if ("drsg".equals(lifestyle.type)){
drsgText.setText(String.format("穿衣指数:%s", lifestyle.txt));
}
if ("sport".equals(lifestyle.type)){
sportText.setText(String.format("运动建议:%s", lifestyle.txt));
}
}
weatherLayout.setVisibility(View.VISIBLE);
}
}
3、展示
九、手动切换城市
1、导航按钮
在屏幕左边先加个按钮:
title.xml
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="?attr/actionBarSize">
<Button
android:layout_width="30dp"
android:layout_height="30dp"
android:layout_marginLeft="10dp"
android:layout_alignParentLeft="true"
android:layout_centerVertical="true"
android:id="@+id/nav_button"
android:background="@drawable/home"
android:layout_alignParentStart="true"
android:layout_marginStart="10dp" />
.........
2、滑动菜单
activity_weather.xml
<?xml version="1.0" encoding="utf-8"?>
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
......
<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_height="match_parent"
android:layout_width="match_parent">
<ScrollView
......
<LinearLayout
......
</LinearLayout>
</ScrollView>
</androidx.swiperefreshlayout.widget.SwipeRefreshLayout>
<fragment
android:layout_width="match_parent"
android:layout_height="match_parent"
android:id="@+id/choose_area_fragment"
android:name="com.coolweather.android.ChooseAreaFragment"
android:layout_gravity="start"/>
</androidx.drawerlayout.widget.DrawerLayout>
</FrameLayout>
3、导航按钮,滑动菜单 联结
public class WeatherActivity extends AppCompatActivity {
......
public DrawerLayout drawerLayout;
private Button navButton;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
......
// 初始化控件
......
drawerLayout = findViewById(R.id.drawer_layout);
navButton = findViewById(R.id.nav_button);
navButton.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
drawerLayout.openDrawer(GravityCompat.START);
}
});
4、逻辑处理
修改碎片ChooseAreaFragment
:
......
@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();
if(getActivity() instanceof MainActivity){
Intent intent = new Intent(getActivity(), WeatherActivity.class);
intent.putExtra("weather_id", weatherId);
startActivity(intent);
getActivity().finish();
}else if (getActivity() instanceof WeatherActivity){
WeatherActivity activity = (WeatherActivity) getActivity();
activity.drawerLayout.closeDrawers();
activity.swipeRefreshLayout.setRefreshing(true);
activity.requestWeather(weatherId, new Weather(), "now");
}
}
}
});
......
5、展示
十、后台自动更新天气
1、新建服务
新建一个服务AutoUpdateService
:
ublic class AutoUpdateService extends Service {
@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 = 2 * 60 * 60 * 1000; // 每两小时更新一次
long triggerAtTime = SystemClock.elapsedRealtime()+anHour;
Intent i = new Intent(this, AutoUpdateService.class);
PendingIntent pi = PendingIntent.getService(this, 0, i ,0);
manager.cancel(pi);
manager.set(AlarmManager.ELAPSED_REALTIME_WAKEUP, triggerAtTime, pi);
return super.onStartCommand(intent, flags, startId);
}
private void updateWeather() {
final SharedPreferences prefs = getSharedPreferences("weather_data", MODE_PRIVATE);
String nowString = prefs.getString("now", null);
if(nowString != null){
String weatherId = Utility.handleWeatherResponse(nowString, new Weather(), "now").basic.weatherId;
savedData(reUrl("now", weatherId), "now");
savedData(reUrl("forecast", weatherId), "forecast");
savedData(reUrl("lifestyle", weatherId), "lifestyle");
}
}
// 存放数据
private void savedData(String weatherUrl, final String cate){
HttpUtil.sendOkHttpRequest(weatherUrl, new Callback() {
@Override
public void onFailure(@NotNull final Call call, @NotNull IOException e) {
e.printStackTrace();
}
@Override
public void onResponse(@NotNull Call call, @NotNull Response response) throws IOException {
String responseText = response.body().string();
SharedPreferences.Editor editor = getSharedPreferences("weather_data", MODE_PRIVATE).edit();
editor.putString(cate, responseText);
editor.apply();
}
});
}
// 构造Url
private String reUrl(String cate, String weatherId){
String key = "cd9034ce70ad477b8454b6638261f0a5";
return "https://free-api.heweather.net/s6/weather/" + cate + "?location=" + weatherId + "&key=" + key;
}
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 = getSharedPreferences("weather_data", MODE_PRIVATE).edit();
editor.putString("bing_pic", bingPic);
editor.apply();
}
});
}
}
2、启动服务
// 处理并展示实体类 Weather 的数据
private void showWeatherInfo(Weather weather){
......
weatherLayout.setVisibility(View.VISIBLE);
Intent intent = new Intent(this, AutoUpdateService.class);
startService(intent);
}
十一、修改图标和名称
1、修改图标
修改<application>
的属性:
android:icon="@mipmap/logo"
然后将不同分辨率的logo.png
放在所有的mipmap
目录下,图标就修改好了
2、修改应用名
修改res/values/string.xml
<resources>
<string name="app_name">酷欧天气</string>
</resources>
即可