《第二行代码》—— 酷欧天气的开发

使用的教材是国@郭霖写的《第二行代码》

应用名:Cool Weather

一、功能需求及技术可行性分析

1、具备的功能

  • 可以罗列出全国所有的省市县
  • 可以查看全国任意城市的天气信息
  • 可以自由地切换城市,去查看其他城市地天气
  • 提供手动更新以及后台自动更新天气的功能

2、技术可行性分析

  1. 如何获取全国省市县的信息:

天气数据:和风天气的数据接口
省市县数据:http://guolin.tech/api/china

  1. 编写步骤
  • 基本配置
  • 创建数据库和表
  • 遍历全国省市县数据
  • 显示天气数据
  • 编写天气界面
  • 将天气显示到界面上
  • 获取必应天气壁纸
  • 手动更新天气
  • 手动切换城市
  • 后台自动更新天气
  • 扩展功能【有空再写】
  • 修改图标和名称

二、正式编写

新建一个空项目 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下建立三张表 provincecitycounty

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>

即可
在这里插入图片描述

  • 8
    点赞
  • 28
    收藏
    觉得还不错? 一键收藏
  • 8
    评论
评论 8
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值