使用的教材是國@郭霖寫的《第二行代碼》
應用名: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>
即可