先看下app效果圖:
App介紹:首次啓動應用時列表顯示全國34個省份及直轄市包括港澳臺,如果選擇省份進入所在省份下的市列表,如果再選擇市項進入該市下所有的區或縣(包括該市)列表,如果再選擇該列表下的項就顯示該區域的天氣預報界面。圖5從左滑出側邊欄可以選擇其他城市。如果是非首次啓動,則顯示上次選擇的城市天氣預報界面(比如退出時顯示廣州的天氣預報界面即圖4,再次進入時仍顯示該界面)。
具體app功能實現:
1.獲取全國城市列表(圖1到圖3)
想羅列出中國所有的省份,只需要訪問地址:http://guolin.tech/api/china,服務器會返回一段JSON格式的數據,包含中國所有省份名稱以及省份id。如果想知道某個省份內有哪些城市,比如江蘇的id是16,訪問地址:http://guolin.tech/api/china/16。只需要把省份id添加到url地址的最後面即可。比如蘇州的id是116,那麼想知道蘇州下有哪些縣和區的時候,訪問地址:http://guolin.tech/api/china/16/116。如此類推。
本app使用DataSupport這款開源的數據庫框架進行城市查詢,需要在app下的build.gradle導入:
implementation 'org.litepal.android:core:1.4.1'
在清單文件中的application節點中加入:
android:name="org.litepal.LitePalApplication"
此外還需要在assets目錄下新建litepal.xml,構建名爲weather_db的數據庫,分別構建Province、City、County三張表。
<?xml version="1.0" encoding="utf-8"?>
<litepal>
<dbname value="weather_db"></dbname>
<version value="1"></version>
<list>
<mapping class="db.Province"/>
<mapping class="db.City"/>
<mapping class="db.County"/>
</list>
</litepal>
對於省份、城市、區或縣進行數據庫查詢,則這些實體類需要繼承自DataSupport:
package db;
import org.litepal.crud.DataSupport;
public class City extends DataSupport {
private int id;
private String cityName;
private int cityCode;
private int provinceId;
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public String getCityName() {
return cityName;
}
public void setCityName(String cityName) {
this.cityName = cityName;
}
public int getCityCode() {
return cityCode;
}
public void setCityCode(int cityCode) {
this.cityCode = cityCode;
}
public int getProvinceId() {
return provinceId;
}
public void setProvinceId(int provinceId) {
this.provinceId = provinceId;
}
}
package db;
import org.litepal.crud.DataSupport;
public class County extends DataSupport {
private int id;
private String countyName;
private String weatherId;
public String getWeatherId() {
return weatherId;
}
public void setWeatherId(String weatherId) {
this.weatherId = weatherId;
}
private int cityId;
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public String getCountyName() {
return countyName;
}
public void setCountyName(String countyName) {
this.countyName = countyName;
}
public int getCityId() {
return cityId;
}
public void setCityId(int cityId) {
this.cityId = cityId;
}
}
package db;
import org.litepal.crud.DataSupport;
public class Province extends DataSupport {
private int id;
private String provinceName;
private int provinceCode;
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public String getProvinceName() {
return provinceName;
}
public void setProvinceName(String provinceName) {
this.provinceName = provinceName;
}
public int getProvinceCode() {
return provinceCode;
}
public void setProvinceCode(int provinceCode) {
this.provinceCode = provinceCode;
}
}
訪問服務器,所以需要在清單文件加入訪問網絡的權限:
<uses-permission android:name="android.permission.INTERNET" />
主程序佈局僅由一個fragment組成,該fragment的佈局由一個自定義的標題欄和一個listview組成:
activity_main.xml
<?xml version="1.0" encoding="utf-8"?>
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".MainActivity">
<fragment
android:id="@+id/choose_are_fragment"
android:name="mini.org.miniweather.fragment.ChooseAreaFragment"
android:layout_width="match_parent"
android:layout_height="match_parent"/>
</FrameLayout>
fragment的佈局choose_area.xml
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
android:background="#fff"
android:fitsSystemWindows="true">
<RelativeLayout
android:layout_width="match_parent"
android:layout_height="?attr/actionBarSize"
android:background="?attr/colorPrimary">
<TextView
android:id="@+id/title"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textSize="20sp"
android:textColor="#fff"
android:layout_centerInParent="true"/>
<ImageView
android:id="@+id/back"
android:layout_width="25dp"
android:layout_height="25dp"
android:layout_marginLeft="10dp"
android:layout_centerVertical="true"
android:layout_alignParentLeft="true"
android:background="@drawable/ic_back"/>
</RelativeLayout>
<ListView
android:id="@+id/listview"
android:layout_width="match_parent"
android:layout_height="match_parent">
</ListView>
</LinearLayout>
城市列表顯示界面ChooseAreaFragment,本文使用okhttp進行網絡請求,所以需在build.gradle添加:
implementation 'com.squareup.okhttp3:okhttp:3.4.1'
該fragment的代碼及分析如下:
package mini.org.miniweather.fragment;
import android.app.ProgressDialog;
import android.content.Intent;
import android.os.Bundle;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import android.support.v4.app.Fragment;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.AdapterView;
import android.widget.ArrayAdapter;
import android.widget.ImageView;
import android.widget.ListView;
import android.widget.TextView;
import android.widget.Toast;
import org.litepal.crud.DataSupport;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import db.City;
import db.County;
import db.Province;
import mini.org.miniweather.MainActivity;
import mini.org.miniweather.R;
import mini.org.miniweather.WeatherActivity;
import okhttp3.Call;
import okhttp3.Callback;
import okhttp3.Response;
import util.HttpUtil;
import util.Utility;
public class ChooseAreaFragment extends Fragment {
private static final int LEVEL_PROVINCE = 0;
private static final int LEVEL_CITY = 1;
private static final int LEVEL_COUNTY = 2;
private List<String> dataList = new ArrayList<>();
private ArrayAdapter<String> adapter;
private TextView titleText;
private ImageView backButton;
private ListView listView;
private ProgressDialog progressDialog;
/**
* 省列表
*/
private List<Province> provinceList;
/**
* 城市列表
*/
private List<City> cityList;
/**
* 城鎮列表
*/
private List<County> countyList;
/**
* 當前等級
*/
private int currentLevel;
/**
* 選中的省份
*/
private Province selectedProvince;
/**
* 選中的城市
*/
private City selectedCity;
@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 = (TextView)view.findViewById(R.id.title);
backButton = (ImageView)view.findViewById(R.id.back);
listView = (ListView)view.findViewById(R.id.listview);
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);
queryProvinces();
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.getWeatherInfo(weatherId);
}
}
}
});
backButton.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
if (currentLevel == LEVEL_COUNTY){
queryCities();
}else if (currentLevel == LEVEL_CITY){
queryProvinces();
}
}
});
}
/**
* 查詢所有的省,優先從數據庫查詢,如果沒有查到再去服務器上查詢
*/
private void queryProvinces(){
titleText.setText("中國");
backButton.setVisibility(View.GONE);
provinceList = DataSupport.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 = DataSupport.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 = DataSupport.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");
}
}
/**
* 根據傳入的地址和類型從服務器上查詢省市縣數據
* @param address
* @param type
*/
private void queryFromServer(String address, final String type) {
showProgressDialog();
HttpUtil.sendOkhttpRequest(address, new Callback() {
@Override
public void onFailure(Call call, IOException e) {
getActivity().runOnUiThread(new Runnable() {
@Override
public void run() {
closeProgressDialog();
Toast.makeText(getContext(),"加載失敗",Toast.LENGTH_SHORT).show();
}
});
}
@Override
public void onResponse(Call call, Response response) throws IOException {
String responceText = response.body().string();
boolean result = false;
if ("province".equals(type)){
result = Utility.handleProvinceResponce(responceText);
}else if ("city".equals(type)){
result = Utility.handleCityResponce(responceText,selectedProvince.getId());
}else if ("county".equals(type)){
result = Utility.handleCountyResponce(responceText,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();
}
}
}
該fragment涉及的工具類如下:
package util;
import okhttp3.OkHttpClient;
import okhttp3.Request;
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);
}
}
package util;
import android.text.TextUtils;
import android.util.Log;
import com.google.gson.Gson;
import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;
import db.City;
import db.County;
import db.Province;
import interfaces.heweather.com.interfacesmodule.bean.weather.Weather;
public class Utility {
/**
* 解析和返回服務器返回的省級數據
*/
public static boolean handleProvinceResponce(String responce){
if (!TextUtils.isEmpty(responce)){
try {
JSONArray allProvinces = new JSONArray(responce);
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 handleCityResponce(String responce,int provinceId){
if (!TextUtils.isEmpty(responce)){
try {
JSONArray allCities = new JSONArray(responce);
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 handleCountyResponce(String responce,int cityId){
if (!TextUtils.isEmpty(responce)){
try {
JSONArray allCounties = new JSONArray(responce);
for (int i=0;i<allCounties.length();i++){
JSONObject countyObject = allCounties.getJSONObject(i);
County county = new County();
county.setCountyName(countyObject.getString("name"));
county.setWeatherId(countyObject.getString("weather_id"));
county.setCityId(cityId);
county.save();
}
return true;
} catch (JSONException e) {
e.printStackTrace();
}
}
return false;
}
}
2.獲取城市天氣預報
本文使用和風天氣平臺提供的天氣數據來源,關於如何使用該平臺獲得城市天氣預報情況,可以參考Android 獲取實時天氣數據,在這就不多加贅述。
每次啓動應用時,先去檢查SharedPreference中保存鍵爲"weather"的值(即和風天氣需要獲取天氣情況的城市代碼)是否爲空,如爲空,顯示城市列表fragment即ChooseAreFragment,如不爲空,則將該值通過Intent傳遞給城市天氣預報界面WeatherActivity,並啓動該Activity。
package mini.org.miniweather;
import android.content.Intent;
import android.content.SharedPreferences;
import android.preference.PreferenceManager;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
public class MainActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
SharedPreferences sharedPreferences =
PreferenceManager.getDefaultSharedPreferences(this);
if (sharedPreferences.getString("weather",null)!= null){
Intent intent = new Intent(this,WeatherActivity.class);
startActivity(intent);
finish();
}
}
}
該WeatherActivity界面背景使用必應服務器提供的圖片,使用Glide三方框架加載緩存的背景圖片,代碼及分析如下:
package mini.org.miniweather;
import android.content.SharedPreferences;
import android.graphics.Color;
import android.os.Build;
import android.os.Bundle;
import android.preference.PreferenceManager;
import android.support.v4.view.GravityCompat;
import android.support.v4.widget.DrawerLayout;
import android.support.v7.app.AppCompatActivity;
import android.util.Log;
import android.view.LayoutInflater;
import android.view.View;
import android.widget.Button;
import android.widget.ImageView;
import android.widget.LinearLayout;
import android.widget.TextView;
import android.widget.Toast;
import com.bumptech.glide.Glide;
import com.google.gson.Gson;
import org.json.JSONException;
import org.json.JSONObject;
import java.io.IOException;
import java.util.List;
import interfaces.heweather.com.interfacesmodule.bean.Lang;
import interfaces.heweather.com.interfacesmodule.bean.Unit;
import interfaces.heweather.com.interfacesmodule.bean.air.now.AirNow;
import interfaces.heweather.com.interfacesmodule.bean.weather.Weather;
import interfaces.heweather.com.interfacesmodule.bean.weather.forecast.ForecastBase;
import interfaces.heweather.com.interfacesmodule.bean.weather.lifestyle.LifestyleBase;
import interfaces.heweather.com.interfacesmodule.view.HeConfig;
import interfaces.heweather.com.interfacesmodule.view.HeWeather;
import okhttp3.Call;
import okhttp3.Callback;
import okhttp3.Response;
import util.HttpUtil;
public class WeatherActivity extends AppCompatActivity {
private LinearLayout forecastLayout;
private TextView aqiText;
private TextView degreeText;
private TextView weatherInfoText;
private TextView pm25Text;
private String weatherId;
private TextView cityText;
private ImageView bingPicImg;
private TextView timeText;
private TextView weatherText;
private TextView comfortText;
private TextView carWashText;
private TextView sportText;
private Button navButton;
public DrawerLayout drawerLayout;
@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);
initView();
initWeather();
}
private void initWeather() {
HeConfig.init("HE1912311406181054","045f6a9bffa44f06a72c4dc903082906");
HeConfig.switchToFreeServerNode();
}
private void initView() {
forecastLayout = (LinearLayout)findViewById(R.id.forecast_layout);
aqiText = (TextView)findViewById(R.id.aqi_text);
degreeText = (TextView)findViewById(R.id.degree_text);
weatherInfoText = (TextView)findViewById(R.id.weather_info_text);
pm25Text = (TextView)findViewById(R.id.pm25_text);
cityText = (TextView)findViewById(R.id.title_city);
bingPicImg = (ImageView)findViewById(R.id.bing_pic);
timeText = (TextView)findViewById(R.id.title_time);
comfortText = (TextView)findViewById(R.id.comfort_text);
carWashText = (TextView)findViewById(R.id.car_wash_text);
sportText = (TextView)findViewById(R.id.sport_text);
navButton = (Button)findViewById(R.id.nav_button);
drawerLayout = (DrawerLayout)findViewById(R.id.drawer_layout);
SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(this);
String bingPic = prefs.getString("bing_pic", null);
if (bingPic != null) {
Glide.with(this).load(bingPic).into(bingPicImg);
} else {
loadBingPic();
}
weatherId = prefs.getString("weather",null);
if (weatherId == null){
weatherId = getIntent().getStringExtra("weather_id");
}
getWeatherInfo(weatherId);
navButton.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
drawerLayout.openDrawer(GravityCompat.START);
}
});
}
/**
* 加載必應每日一圖
*/
private void loadBingPic() {
String requestBingPic = "http://guolin.tech/api/bing_pic";
HttpUtil.sendOkhttpRequest(requestBingPic, new Callback() {
@Override
public void onResponse(Call call, Response response) throws IOException {
final String bingPic = response.body().string();
SharedPreferences.Editor editor = PreferenceManager.getDefaultSharedPreferences(WeatherActivity.this).edit();
editor.putString("bing_pic", bingPic);
editor.apply();
runOnUiThread(new Runnable() {
@Override
public void run() {
Glide.with(WeatherActivity.this).load(bingPic).into(bingPicImg);
}
});
}
@Override
public void onFailure(Call call, IOException e) {
e.printStackTrace();
}
});
}
/**
* 獲取城市天氣預報
* @param weatherid 城市代碼
*/
public void getWeatherInfo(final String weatherid) {
weatherId = weatherid;
HeWeather.getWeather(WeatherActivity.this, weatherId,
Lang.CHINESE_SIMPLIFIED, Unit.METRIC, new HeWeather.OnResultWeatherDataListBeansListener() {
@Override
public void onError(Throwable throwable) {
}
@Override
public void onSuccess(Weather weather) {
Log.d("hdj", "status: " + weather.getStatus());
if (weather != null && "ok".equals(weather.getStatus())){
SharedPreferences.Editor editor = PreferenceManager.getDefaultSharedPreferences(WeatherActivity.this).edit();
editor.putString("weather", weatherid);
editor.apply();
showWeatherInfo(weather);
}else{
Toast.makeText(WeatherActivity.this,"無法獲得該區域的天氣情況"
,Toast.LENGTH_LONG).show();
}
}
});
HeWeather.getAirNow(WeatherActivity.this, weatherId,
Lang.CHINESE_SIMPLIFIED, Unit.METRIC, new HeWeather.OnResultAirNowBeansListener() {
@Override
public void onError(Throwable throwable) {
}
@Override
public void onSuccess(AirNow airNow) {
showAirInfo(airNow);
}
});
}
/**
* 處理並展示Weather實體類中的數據。
*/
private void showWeatherInfo(Weather weather){
String cityName = weather.getBasic().getLocation();
String titleTime = weather.getUpdate().getLoc().split(" ")[1];
int hour = Integer.parseInt(titleTime.split(":")[0]);
boolean isDayTime = hour>=6&&hour<=18 ? true:false;
String weatherInfo = weather.getNow().getCond_txt();
String degreeInfo = weather.getNow().getTmp()+ "℃";
cityText.setText(cityName);
timeText.setText(titleTime);
weatherInfoText.setText(weatherInfo);
degreeText.setText(degreeInfo);
forecastLayout.removeAllViews();
for (ForecastBase forecast: weather.getDaily_forecast()) {
View view = LayoutInflater.from(this).inflate(R.layout.forecast_item, forecastLayout,
false);
TextView dateText = (TextView) view.findViewById(R.id.date_text);
TextView infoText = (TextView) view.findViewById(R.id.info_text);
TextView maxText = (TextView) view.findViewById(R.id.max_info);
TextView minText = (TextView) view.findViewById(R.id.min_info);
dateText.setText(forecast.getDate());
if (isDayTime) {
infoText.setText(forecast.getCond_txt_d());
}else{
infoText.setText(forecast.getCond_txt_n());
}
maxText.setText(forecast.getTmp_max()+"℃");
minText.setText(forecast.getTmp_min()+"℃");
forecastLayout.addView(view);
}
List<LifestyleBase> lifestyleBases = weather.getLifestyle();
comfortText.setText("舒適度:" +lifestyleBases.get(0).getTxt());
carWashText.setText("洗車指數:" + lifestyleBases.get(6).getTxt());
sportText.setText("出行建議:" + lifestyleBases.get(3).getTxt());
}
/**
* 顯示空氣質量
* @param airNow
*/
private void showAirInfo(AirNow airNow){
String airStatus = airNow.getStatus();
if (airStatus.equals("ok")){
String jsonData = new Gson().toJson(airNow.getAir_now_city());
JSONObject objectAir = null;
try {
objectAir = new JSONObject(jsonData);
String aqi = objectAir.getString("aqi");
String pm25 = objectAir.getString("pm25");
aqiText.setText(aqi);
pm25Text.setText(pm25);
} catch (JSONException e) {
e.printStackTrace();
}
}else {
aqiText.setText("無");
pm25Text.setText("無");
}
}
}
app的build.gradle中需要導入:
implementation 'com.github.bumptech.glide:glide:3.7.0'
以上就是整個app的具體介紹,具體思路大概是先通過DataSupport獲取遠程服務器上的城市列表,然後通過和風天氣平臺獲取選中的城市天氣預報及空氣質量指數等。
最後附上WeatherActivity的相關佈局文件代碼:
activity_weather:
<?xml version="1.0" encoding="utf-8"?>
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="@color/colorPrimary"
tools:context=".WeatherActivity">
<ImageView
android:id="@+id/bing_pic"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:scaleType="centerCrop"/>
<android.support.v4.widget.DrawerLayout
android:id="@+id/drawer_layout"
android:layout_width="match_parent"
android:layout_height="match_parent">
<ScrollView
android:id="@+id/weather_layout"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:scrollbars="none"
android:overScrollMode="never">
<LinearLayout
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:fitsSystemWindows="true">
<include layout="@layout/title"/>
<include layout="@layout/now"/>
<include layout="@layout/forecast"/>
<include layout="@layout/aqi" />
<include layout="@layout/suggestion"/>
</LinearLayout>
</ScrollView>
<fragment
android:id="@+id/choose_are_fragment"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_gravity="start"
android:name="mini.org.miniweather.fragment.ChooseAreaFragment"
/>
</android.support.v4.widget.DrawerLayout>
</FrameLayout>
title.xml:
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent">
<Button
android:id="@+id/nav_button"
android:layout_width="30dp"
android:layout_height="30dp"
android:layout_marginLeft="10dp"
android:layout_alignParentLeft="true"
android:layout_centerVertical="true"
android:background="@drawable/ic_home"/>
<TextView
android:id="@+id/title_city"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_centerInParent="true"
android:textSize="20sp"
android:textColor="#fff"/>
<TextView
android:id="@+id/title_time"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textSize="16sp"
android:textColor="#fff"
android:layout_centerVertical="true"
android:layout_marginRight="10dp"
android:layout_alignParentRight="true"/>
</RelativeLayout>
now.xml:
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
android:layout_margin="15dp">
<TextView
android:id="@+id/degree_text"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="end"
android:textColor="#fff"
android:textSize="60sp"/>
<TextView
android:id="@+id/weather_info_text"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="end"
android:textSize="20sp"
android:textColor="#fff"/>
</LinearLayout>
forecast.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"
android:background="#8000">
<TextView
android:layout_marginLeft="15dp"
android:layout_marginTop="15dp"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="預報"
android:textColor="#fff"
android:textSize="20sp"/>
<LinearLayout
android:id="@+id/forecast_layout"
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="wrap_content">
</LinearLayout>
</LinearLayout>
forecast_item.xml:
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_margin="15dp">
<TextView
android:id="@+id/date_text"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_gravity="center_vertical"
android:layout_weight="2"
android:textColor="#fff"/>
<TextView
android:layout_width="0dp"
android:layout_height="wrap_content"
android:id="@+id/info_text"
android:layout_weight="1"
android:layout_gravity="center_vertical"
android:gravity="center"
android:textColor="#fff"/>
<TextView
android:id="@+id/max_info"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:layout_gravity="center_vertical"
android:textColor="#fff"
android:gravity="right"/>
<TextView
android:id="@+id/min_info"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:layout_gravity="center_vertical"
android:textColor="#fff"
android:gravity="right"/>
</LinearLayout>
aqi.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"
android:background="#8000">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textSize="20sp"
android:textColor="#fff"
android:layout_marginLeft="15dp"
android:layout_marginTop="15dp"
android:text="空氣質量"/>
<LinearLayout
android:layout_margin="15dp"
android:layout_width="match_parent"
android:layout_height="wrap_content">
<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:id="@+id/aqi_text"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:textColor="#fff"
android:textSize="40sp"/>
<TextView
android:text="AQI指數"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:textColor="#fff"/>
</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:id="@+id/pm25_text"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:textColor="#fff"
android:textSize="40sp"/>
<TextView
android:text="PM2.5指數"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:textColor="#fff"/>
</LinearLayout>
</RelativeLayout>
</LinearLayout>
</LinearLayout>
suggestion.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"
android:background="#8000">
<TextView
android:text="生活建議"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="15dp"
android:layout_marginLeft="15dp"
android:textSize="20sp"
android:textColor="#fff"/>
<TextView
android:id="@+id/comfort_text"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textColor="#fff"
android:layout_margin="15dp"/>
<TextView
android:id="@+id/car_wash_text"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textColor="#fff"
android:layout_margin="15dp"/>
<TextView
android:id="@+id/sport_text"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textColor="#fff"
android:layout_margin="15dp"/>
</LinearLayout>
注:本文參考自歐酷天氣開源項目。