Android简易天气预报App
先看下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三张表。
对于省份、城市、区或县进行数据库查询,则这些实体类需要继承自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;
}
}
访问服务器,所以需要在清单文件加入访问网络的权限:
主程序布局仅由一个fragment组成,该fragment的布局由一个自定义的标题栏和一个listview组成:
activity_main.xml
fragment的布局choose_area.xml
城市列表显示界面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 dataList = new ArrayList();
private ArrayAdapter adapter;
private TextView titleText;
private ImageView backButton;
private ListView listView;
private ProgressDialog progressDialog;
private List provinceList;
private List cityList;
private List 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");
}
}
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();
}
});
}
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);
}
});
}
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 lifestyleBases = weather.getLifestyle();
comfortText.setText("舒适度:" +lifestyleBases.get(0).getTxt());
carWashText.setText("洗车指数:" + lifestyleBases.get(6).getTxt());
sportText.setText("出行建议:" + lifestyleBases.get(3).getTxt());
}
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:
title.xml:
now.xml:
forecast.xml:
forecast_item.xml:
aqi.xml:
suggestion.xml:
注:本文参考自欧酷天气开源项目。
作者:Xhaka_Jim
免责声明:
① 本站未注明“稿件来源”的信息均来自网络整理。其文字、图片和音视频稿件的所属权归原作者所有。本站收集整理出于非商业性的教育和科研之目的,并不意味着本站赞同其观点或证实其内容的真实性。仅作为临时的测试数据,供内部测试之用。本站并未授权任何人以任何方式主动获取本站任何信息。
② 本站未注明“稿件来源”的临时测试数据将在测试完成后最终做删除处理。有问题或投稿请发送至: 邮箱/279061341@qq.com QQ/279061341