参考文献:第一行代码(第二版),郭霖
源码地址:https://github.com/2066972218/coolweather/commits/master

一、功能需求
1、罗列全国的省、市、县
2、查看全国任意城市的天气情况
3、自由切换城市,查看异地城市的天气情况
4、手动更新与后台自动更新天气的功能

二、可行性分析:
1、需要用到的技术有:UI、网络、数据存储、服务
2、天气信息来源:和风天气,有3000+次/天免费数据获取
3、架设服务器用于提供全国的省市县的数据信息

三、全国地区信息获取

1、访问 http://guolin.tech/api/china 得到以下全国地区数据,其包括了中国所有的省份以及省份的id:
这是一个JSON格式的数组,在网址后加相应城市的id可以得到该省份城市下的所有市级城市,如:
http://guolin.tech/api/china/30
在网址后继续键入id,可以得到该市级下的县级城市:
如:http://guolin.tech/api/china/30/304

可以看到每一个县级城市都有一个weather_id,我们可以通过这个id去访问和风天气的接口,从而过去该地区的的天气情况。

2、使用和风天气的接口
注册账号:http://guolin.tech/api/weather/register
选择免费用户,注册成功后,去邮箱激活,之后登陆该账号,https://console.heweather.com/ 查看API接口说明文档。

3、Git时间
https://github.com/ 为GitHub官网,注册登陆,点击start a project~输入Coolweather~选择Android项目类型的gitignore文件和Apach License2.0作为天气的开源协议

创建成功后出现以下界面,其中README.md为版本库主页说明,点击Clone or download按钮,将该复制到剪贴板:
版本库主页为:https://github.com/2066972218/coolweather

在Android Studio中新建一个项目CoolWeather,然后将远程代码库克隆值本地,在Termiminal中输入
git clone https://github.com/2066972218/coolweather.git,
如图所示,说明克隆成功。

在Android Studio中把项目文件下的CoolWeather/coolweather的所有文件,包括隐藏的.git文件剪切(mv .git .. /)到CoolWeather文件目录下。

接下来通过在Termiminal中输入一下代码,将CoolWeather项目中的文件上传到Git版本控制中。
git add .
git commit -m "First commit"
git push origin master
在执行最后一条语句时发生一个错误,借助博客链接:
https://blog.csdn.net/jingfengvae/article/details/72859130 解决。
提交成功后,看版本库已经更新成功,也就是我们的代码已经提交值远程代码库了,如图:

也就是我们该项目的版本控制Git创建好了,下面我们来继续项目编码。

4、数据库的创建与配置
创建4个包db、gson、service、util,db包用于存放数据库模型的代码、gson用于存放GSON模型的相关代码,util包用于存储工具相关代码。
为简化数据库的操作我们采用LitePal来管理项目的数据库,先添加好一下我们后续需要用到的依赖闭包:

    //用于对数据库进行操作implementation 'org.litepal.android:core:1.4.1'//用于进行网络请求implementation 'com.squareup.okhttp3:okhttp:3.4.1'//用于JSON解析implementation 'com.google.code.gson:gson:2.7'//用于加载和展示图片implementation 'com.github.bumptech.glide:glide:3.7.0'

在db包下我们创建三张表(实体类):province、city、county分别存放省市县的数据信息。
province实体类如下:

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;}
}

同理,新建的City类,代码如下:

public class City extends DataSupport {private int id;   //每一个实体类都有一个idprivate String cityName;  //记录市名称private int cityCode;   //几率市的代码private int provinceCode;  //记录该市的上级省级代码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 getProvinceCode() {return provinceCode;}public void setProvinceCode(int provinceCode) {this.provinceCode = provinceCode;}
}

同理,记录代表区县的County类,如下:

public class County extends DataSupport {private int id;//每一个实体类都需有一个idprivate String countyName; //记录区县名称private int countyCode;//记录区县代码private int 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 getCountyCode() {return countyCode;}public void setCountyCode(int countyCode) {this.countyCode = countyCode;}public int getWeatherId() {return weatherId;}public void setWeatherId(int weatherId) {this.weatherId = weatherId;}public int getCityId() {return cityId;}public void setCityId(int cityId) {this.cityId = cityId;}
}

以上我们也就完成这三个实体类的内容,接下配置数据库

在main目录下创建一个assets的的路径,在该路径下创建一个litepal.xml文件,在该文件输入:

<?xml version="1.0" encoding="utf-8"?>
<litepal><dbname value = "cool_weather"/>    //指定数据库名<version value = "1"/>     //数据库版本号<!--将三个实体类添加到映射列表中--><list><mapping class = "com.example.coolweather.db.Province"/><mapping class = "com.example.coolweather.db.City"/><mapping class = "com.example.coolweather.db.County"/></list>
</litepal>

最后修改AndroidManifest.xml中添加以下代码:

 <applicationandroid:usesCleartextTraffic="true"android:name="org.litepal.LitePalApplication"android:allowBackup="true"android:icon="@mipmap/ic_launcher"android:label="@string/app_name"android:roundIcon="@mipmap/ic_launcher_round"android:supportsRtl="true"android:theme="@style/AppTheme"><activity android:name=".MainActivity"><intent-filter><action android:name="android.intent.action.MAIN" /><category android:name="android.intent.category.LAUNCHER" /></intent-filter></activity></application>

现在数据库配置完毕了,数据库和表会在首次执行自行创建,现在我们将这一阶段的代码提交到版本控制中,即在Terrminal中输入如下:
git add .
git commit -m "加入创建数据库和表的各项配置"
git push origin master

5、加载全国省市县的所有数据
从上面我们知道全国的省市县信息我们都是从服务器中获取的,所有这里我们需要与服务其进行交互。在util中创建一个HttpUtil类。

public class HttpUtil {public static void sendOKHttpRequest(String adress, okhttp3.Callback callback){OkHttpClient client = new OkHttpClient();Request request = new Request.Builder().url(adress).build();client.newCall(request).enqueue(callback);}
}

根据三的全国地区信息知道我们获取的信息为JSON格式的,需要解析后才能存入数据库,在util文件中新建一个Utility工具类来解析和处理这些信息。如下:

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.setCityCode(cityObject.getInt("id"));city.setCityName(cityObject.getString("name"));city.setProvinceCode(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.setCountyName(countyObject.getString("name"));//县级天气信息county.setWeatherId(countyObject.getString("weather_id"));//所属的市级代号county.setCityId(cityId);county.save();}return true;} catch (JSONException e) {e.printStackTrace();}}return false;}
}

解析出省市县的数据后,现在我们来编写界面UI,为后面我们布局复用,因为我们不实用原生的ActionBar
er,故我们在style.xml将其设置为

<style name="AppTheme" parent="Theme.AppCompat.Light.NoActionBar">

我们将界面写在碎片里,新建布局文件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"><RelativeLayoutandroid:layout_width="match_parent"android:layout_height="?attr/actionBarSize"android:background="@color/colorPrimary"><TextViewandroid:id="@+id/title_text"android:layout_width="wrap_content"android:layout_height="wrap_content"android:layout_centerInParent="true"android:textColor="#fff"android:textSize="20sp"/><Buttonandroid:id="@+id/back_button"android:layout_width="25dp"android:layout_height="25dp"android:layout_marginLeft="10dp"android:layout_alignParentLeft="true"android:layout_centerVertical="true"android:background="@drawable/ic_back" />        </RelativeLayout><ListViewandroid:id="@+id/list_view"android:layout_width="match_parent"android:layout_height="match_parent" />
</LinearLayout>

创建一个AreaFragment继承自Fragment,在其中编写遍历全国的省市县数据,通过从数据库获取和网络获取两种方式来加载全国省市县数据,逻辑代码如下:

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 int currentLevel;  //当前被选中的级别private Province selectedProvince;//被选中的省份private City selectedCity;//被选中的城市private List<Province> provinceList;//省列表private List<City> cityList;//市列表private List<County> countyList ;//县列表/*获取控件实例id*/@Overridepublic View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container,@Nullable Bundle savedInstanceState) {Log.d("ChooseAreaFragment","onCreateView");View view = inflater.inflate(R.layout.choose_area,container,false);titleText = (TextView)view.findViewById(R.id.title_text);  //获取标题栏文本idbackButton = (Button) view.findViewById(R.id.back_button);  //获取标题栏idlistView = (ListView)view.findViewById(R.id.list_view);    //获取Item列表id//获取ArrayAdapter对象adapter =new ArrayAdapter<>(getContext(), android.R.layout.simple_list_item_1, dataList);listView.setAdapter(adapter);//设置并初始化适配器return view;//将视图返回}/*点击事件集合*/@Overridepublic void onActivityCreated(@Nullable Bundle savedInstanceState) {Log.d("ChooseAreaFragment","onActivityCreated");super.onActivityCreated(savedInstanceState);//列表任意一栏被点击,则...listView.setOnItemClickListener(new AdapterView.OnItemClickListener() {@Overridepublic void onItemClick(AdapterView<?> parent, View view, int position, long id) {Log.d("ChooseAreaFragment","列表被点了的...");if (currentLevel == LEVEL_PROVINCE){   //当前选中的级别为省份时selectedProvince = provinceList.get(position);  //当前点击为选中状态queryCities();//查询市的方法}else if (currentLevel == LEVEL_CITY){selectedCity = cityList.get(position);queryCounties();}}});backButton.setOnClickListener(new View.OnClickListener() {@Overridepublic void onClick(View v) {if (currentLevel == LEVEL_COUNTY){queryCities();}else if (currentLevel == LEVEL_CITY){queryProvinces();}}});queryProvinces();}private void queryCities() {titleText.setText(selectedProvince.getProvinceName());  //设置市的标题内容backButton.setVisibility(View.VISIBLE);  //设置返回按钮可见//查询被选中的省份城市的市区cityList = DataSupport.where("provinceid=?",String.valueOf(selectedProvince.getId())).find(City.class);Log.d("ChooseAreaFragment","市级");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;//获取被选取地区的网络地址Log.d("ChooseAreaFragment","准备在网络中获取地址信息");queryFromServer(address,"city");   // 在网络中查询}}/*根据传入的地址和类型从服务器查询省市县数据*/private void queryFromServer(String adress, final String type) {showProgressDialog();//   发送一条网络请求HttpUtil.sendOKHttpRequest(adress, new Callback() {//请求加载失败@Overridepublic void onFailure(Call call, IOException e) {//通过runOnUiThread方法回到主线程逻辑getActivity().runOnUiThread(new Runnable() {@Overridepublic void run() {closeProgressDialog();Toast.makeText(getContext(),"加载失败",Toast.LENGTH_SHORT).show();}});}@Overridepublic void onResponse(Call call, Response response) throws IOException {Log.d("ChooseAreaFragment","加载地区信息...");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() {@Overridepublic void run() {Log.d("ChooseAreaFragment","开启线程更新UI");closeProgressDialog();if ("province".equals(type)){queryProvinces();}else if ("city".equals(type)){queryCities();}else if ("county".equals(type)){queryCounties();}}});}if ("city".equals(type)){result = Utility.handleProvinceResponse(responseText);}if ("county".equals(type)){result = Utility.handleProvinceResponse(responseText);}}});}/*显示进度对话框*/private void showProgressDialog() {if (progressDialog==null){progressDialog = new ProgressDialog(getActivity());progressDialog.setMessage("正在加载...");progressDialog.setCanceledOnTouchOutside(false);}progressDialog.show();}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 queryProvinces() {titleText.setText("中国");Log.d("ChooseAreaFragment","查询省中...");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 {Log.d("ChooseAreaFragment","服务器查询省中...");String address = "http://guolin.tech/api/china";queryFromServer(address,"province");}}private void closeProgressDialog() {if (progressDialog!=null){progressDialog.dismiss();}}
}

接下来将碎片县市县碎片县市在主布局中,修改activity.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"><fragmentandroid:id="@+id/choose_area_fragment"android:layout_width="match_parent"android:layout_height="match_parent"android:name = "com.example.coolweather.ChooseAreaFragment" />
</FrameLayout>

添加网络访问权限:

<uses-permission android:name="android.permission.INTERNET"/>

别忘了添加:

android:usesCleartextTraffic="true"

运行过程中出现一个错误为:

no such column: provinceId (code 1): , while compiling: SELECT * FROM city WHERE provinceId

说是在city中没有找到provinceId者一列的数据库,经分析为数据库在没有创建city列表前有创建国数据库,所有这里我们将litepal.xml中的版本号升级为2,即:

 <version value = "2"/>     //数据库版本号

查看下运行图:

第一部分也就完成了,将代码提交到远程仓库:

CoolWeather apple$ git add .CoolWeather apple$ git commit -m "遍历中国省市县三级列表显示功能"CoolWeather apple$ git push origin master

6、显示天气信息
由于获取的天气信息较为复杂,我们采用GSON来获取天气信息。
在gson包中建立一个Basic类,来显示城市的基础信息:

public class Basic {@SerializedName("city")   //JSON字段和Java字段建立映射public String cityName;   //城市名@SerializedName("id")public String weather_id;  //天气情况public Update update;  public class Update {@SerializedName("loc")public String updateTime;   //天气更新时间}
}

同理,新建一个AQI类:

public class AQI {public AQICity city;public class AQICity{public String aqi;public String pm25;}
}

新建一个Now类显示温度和天气情况:

public class Now {@SerializedName("tmp")public String tempeture;@SerializedName("cond")public More more;private class More {@SerializedName("txt")public String info;}
}

新建一个Suggestion类来推荐日常活动:

public class Suggestion {@SerializedName("comf")public Comfort comfort;   @SerializedName("cw")public  CarWash carWash;    public Sport sport;public class Comfort {@SerializedName("txt")public String info;}public class CarWash{@SerializedName("txt")public String info;}private class Sport {@SerializedName("txt")public String info;}
}

继续在gson包下新建一个Forecas的类开表示未来的天气情况,其包含的会是一个数组:

public class Forecast {public String data;@SerializedName("tmp")public Temperature temperature;@SerializedName("cond")public More more;public class Temperature {public  String max;public String min;}private class More {@SerializedName("txt_d")public String info;}
}

新建一个总的实例类,在gson下的Weather类来引用刚创建的各个实体类:

public class Weather {public String status;  //返回是否成功返回值public Basic basic;public Forecast forecast;public AQI aqi;public Now now;@SerializedName("daily_forecast")public List<Forecast> forecastList;  //解析出数组

GSon所有的实体类都定义好后,下面来编写天气界面UI
创建一个活动为WeatherActivity,布局指定为activity_weather.xml,这个界面我们用分模块来构建布局UI,后面在使用include来集成:
首先建立一个title.xml作为头布局,在该头布局中放置两个TextView用于分别显示:城市名和显示更新事件,如下:

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayoutxmlns:android="http://schemas.android.com/apk/res/android"android:layout_width="match_parent"android:layout_height="?attr/actionBarSize"><TextViewandroid:id="@+id/title_city"android:layout_width="wrap_content"android:layout_height="wrap_content"android:layout_centerInParent="true"android:textColor="#fff"android:textSize="20sp"/><TextViewandroid:id="@+id/title_update_time"android:layout_width="wrap_content"android:layout_height="wrap_content"android:layout_marginRight="10dp"android:layout_alignParentRight="true"android:layout_centerVertical="true"android:textColor="#fff"android:textSize="16sp"/>
</RelativeLayout>

在新建一个now.xml来显示天气信息,包括当前气温和天气概况,如下:

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"android:orientation="vertical"android:layout_width="match_parent"android:layout_height="wrap_content"><!--显示当前气温--><TextViewandroid:layout_width="wrap_content"android:layout_height="wrap_content" android:id="@+id/degree_text"android:layout_gravity="end"android:textSize="60sp"android:textColor="#fff"/><!--显示天气概况--><TextViewandroid: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>

在新建forecast.xml来显示未来几天的天气:

<?xml version="1.0" encoding="utf-8"?>
<!--外层定义了半透明的背景-->
<LinearLayoutxmlns: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"android:background="#8000"><!--定义了一个标题--><TextViewandroid: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"/><!--未来几天天气信息的布局,需要根据服务器返回的数据在代码中动态添加--><LinearLayoutandroid:layout_width="match_parent"android:layout_height="wrap_content"android:orientation="vertical"android:id="@+id/forecast_layout"></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="wrap_content"android:layout_margin="15dp"><!--天气时间--><TextViewandroid:id="@+id/data_text"android:layout_width="0dp"android:layout_height="wrap_content"android:layout_gravity="center_vertical"android:layout_weight="2"android:textColor="#fff"/><!--天气概况--><TextViewandroid:id="@+id/info_text"android:layout_width="0dp"android:layout_height="wrap_content" android:layout_gravity="center_vertical"android:layout_weight="1"android:gravity="center"android:textColor="#fff"/><!--最高温度--><TextViewandroid:id="@+id/max_text"android:layout_width="0dp"android:layout_height="wrap_content" android:layout_gravity="center"android:layout_weight="1"android:gravity="right"android:textColor="#fff"/><!--最低气温--><TextViewandroid:id="@+id/min_text"android:layout_width="0dp"android:layout_height="wrap_content"android:layout_gravity="center"android:layout_weight="1"android:gravity="right"android:textColor="#fff"/>
</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">    <TextViewandroid: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"/>    <LinearLayoutandroid:layout_width="match_parent"android:layout_height="wrap_content"android:layout_margin="15dp"><RelativeLayoutandroid:layout_width="0dp"android:layout_height="match_parent"android:layout_weight="1">            <LinearLayoutandroid:layout_width="match_parent"android:layout_height="wrap_content"android:orientation="vertical"android:layout_centerInParent="true"><TextViewandroid:id="@+id/aqi_text"android:layout_width="wrap_content"android:layout_height="wrap_content"android:layout_gravity="center"android:textColor="#fff"android:textSize="40sp"/><TextViewandroid:layout_width="wrap_content"android:layout_height="wrap_content" android:layout_gravity="center"android:text="AQI指数"android:textColor="#fff"/></LinearLayout></RelativeLayout><RelativeLayoutandroid:layout_width="0dp"android:layout_height="match_parent"android:layout_weight="1"><LinearLayoutandroid:layout_width="match_parent"android:layout_height="wrap_content"android:orientation="vertical"android:layout_centerInParent="true"><TextViewandroid:id="@+id/pm25_text"android:layout_width="wrap_content"android:layout_height="wrap_content"android:layout_gravity="center"android:textColor="#fff"android:textSize="40sp"/><TextViewandroid:layout_width="wrap_content"android:layout_height="wrap_content"android:layout_gravity="center"android:text="PM2.5指数"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">    <TextViewandroid: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" /><!--舒适度--><TextViewandroid:id="@+id/comfort_text"android:layout_width="wrap_content"android:layout_height="wrap_content"android:layout_margin="15dp"android:textColor="#fff"/><!--洗车指数--><TextViewandroid:id="@+id/car_wash_text"android:layout_width="wrap_content"android:layout_height="wrap_content"android:layout_margin="15dp"android:textColor="#fff"/><!--运动建议--><TextViewandroid:id="@+id/sport_text"android:layout_width="wrap_content"android:layout_height="wrap_content"android:layout_margin="15dp"android:textColor="#fff"/>
</LinearLayout>

最后将以上编写的布局引入到activity_weather.xml中,如下:

<?xml version="1.0" encoding="utf-8"?>
<FrameLayoutxmlns:android="http://schemas.android.com/apk/res/android"android:layout_width="match_parent"android:layout_height="match_parent"android:background="@color/colorPrimary">    <ScrollViewandroid:id="@+id/weather_layout"android:layout_width="match_parent"android:layout_height="match_parent"android:scrollbars="none"android:overScrollMode="never">        <LinearLayoutandroid: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/api"/><include layout="@layout/suggestion"/></LinearLayout></ScrollView>
</FrameLayout>

以上天气UI我们就编写完毕了,看一下效果:

接下来,开始逻辑部分的实现,首先得在Utility类中添加解析天气JSON数据的方法:

 /*将返回的JSON数据解析成Weather实体类*/public static Weather handleWeatherResponse(String response){try {//通过JSONObject和JSONArray将天气数据中的主体内容解析出来JSONObject jsonObject = new JSONObject(response);   JSONArray jsonArray = jsonObject.getJSONArray("HeWeather");String weatherContent = jsonArray.getJSONObject(0).toString();//将JSON数据转换成Weather对象return new Gson().fromJson(weatherContent,Weather.class);} catch (JSONException e) {e.printStackTrace();}return null;}

接着,在WeatherActivity中去请求天气数据,以及将数据展示到界面上,记得key值需要自己去和风天气官网去获取,每个人每个应用的的key不一样:

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 aqiText;                                                                                      private TextView pm25Text;                                                                                     private TextView comfortText;                                                                                  private TextView carWashText;                                                                                  private TextView sportText;                                                                                    @Override                                                                                                      protected void onCreate(Bundle savedInstanceState) {                                                           super.onCreate(savedInstanceState);                                                                        setContentView(R.layout.activity_weather);                                                                 initView();                                                                                                //定义缓存对象                                                                                                   SharedPreferences prefs= PreferenceManager.getDefaultSharedPreferences(this);                              String weatherString = prefs.getString("weather",null);                                                    if (weatherString!=null){                                                                                  //有缓存时直接解析天气数据                                                                                         Weather weather = Utility.handleWeatherResponse(weatherString);                                        showWeatherInfo(weather);                                                                              }                                                                                                          else {                                                                                                     //无缓存时去服务器查询天气信息                                                                                       String weatherId = getIntent().getStringExtra("weather_id");                                           weatherLayout.setVisibility(View.INVISIBLE);                                                           requestWeather(weatherId);                                                                             }                                                                                                          }                                                                                                              /*根据天气ID请求天气信息*/                                                                                              private void requestWeather(final String weatherId) {                                                          String weatherUrl = "http://guolin.tech/api/weather?cityid = "+                                            weatherId+"&key = 8614c6b9de8145b3a0ef78a277cc2db4";                                               HttpUtil.sendOKHttpRequest(weatherUrl, new Callback() {                                                    @Override                                                                                              public void onFailure(Call call, IOException e) {                                                      e.printStackTrace();                                                                               runOnUiThread(new Runnable() {                                                                     @Override                                                                                      public void run() {                                                                            Toast.makeText(WeatherActivity.this,"从网上获取天气信息失败",                                         Toast.LENGTH_SHORT).show();                                                        }                                                                                              });                                                                                                }                                                                                                      @Override                                                                                              public void onResponse(Call call, Response response) throws IOException {                              final String responseText = response.body().string();                                              final Weather weather = Utility.handleWeatherResponse(responseText);                               runOnUiThread(new Runnable() {                                                                     @Override                                                                                      public void run() {                                                                            if (weather!=null&&"ok".equals(weather.status)) {                                          SharedPreferences.Editor editor = PreferenceManager.                                   getDefaultSharedPreferences(WeatherActivity.this).edit();                      editor.putString("weather",responseText);                                              editor.apply();                                                                        showWeatherInfo(weather);                                                              }                                                                                          else {                                                                                     Toast.makeText(WeatherActivity.this,"获取天气信息失败",                                        Toast.LENGTH_SHORT).show();                                                    }                                                                                          }                                                                                              });                                                                                                }                                                                                                      });                                                                                                        }                                                                                                              //缓存数据下处理并展示Weather实体类中的数据                                                                                     private void showWeatherInfo(Weather weather) {                                                                String cityName = weather.basic.cityName;                                                                  String updateTime = weather.basic.update.updateTime.split(" ")[1]; //split:分解                              String degree = weather.now.tempeture+"°C";                                                                String weatherInfo = weather.now.more.info;                                                                titleCity.setText(cityName);                                                                               titleUpdateTime.setText(updateTime);                                                                       degreeText.setText(degree);                                                                                weatherInfoText.setText(weatherInfo);                                                                      forecastLayout.removeAllViews();                                                                           for (Forecast forecast : weather.forecastList){                                                            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_text);                                         TextView minText = (TextView)view.findViewById(R.id.min_text);                                         dateText.setText(forecast.date);                                                                       infoText.setText(forecast.more.info);                                                                  maxText.setText(forecast.temperature.max);                                                             minText.setText(forecast.temperature.min);                                                             forecastLayout.addView(view);                                                                          }                                                                                                          if (weather.aqi != null){                                                                                  aqiText.setText(weather.aqi.city.aqi);                                                            pm25Text.setText(weather.aqi.city.pm25);                                                          }                                                                                                          String comfort = "舒适度:" +weather.suggestion.comfort.info;                                                  String carWash = " 洗车指数:" +weather.suggestion.carWash.info;                                                String sport = "运动建议:" +weather.suggestion.sport.info;                                                     comfortText.setText(comfort);                                                                              carWashText.setText(carWash);                                                                              sportText.setText(sport);                                                                                  weatherLayout.setVisibility(View.VISIBLE);                                                                 }                                                                                                              //初识化控件                                                                                                        private void initView() {                                                                                      weatherLayout = (ScrollView) findViewById(R.id.weather_layout);                                            titleCity = (TextView)findViewById(R.id.title_city);                                                       titleUpdateTime = (TextView)findViewById(R.id.title_update_time);                                          degreeText = (TextView)findViewById(R.id.degree_text);                                                     weatherInfoText = (TextView)findViewById(R.id.weather_info_text);                                          forecastLayout = (LinearLayout)findViewById(R.id.forecast_layout);                                         aqiText = (TextView)findViewById(R.id.api_text);                                                           pm25Text = (TextView)findViewById(R.id.pm25_text);                                                         comfortText = (TextView) findViewById(R.id.comfort_text);                                                  carWashText = (TextView) findViewById(R.id.car_wash_text);                                                 sportText = (TextView)findViewById(R.id.sport_text);                                                       }
}

实现省市县列表界面跳转到天气界面的逻辑:
,修改ChooseAreaFragment,如下:

   //列表任意一栏被点击,则...listView.setOnItemClickListener(new AdapterView.OnItemClickListener() {@Overridepublic void onItemClick(AdapterView<?> parent, View view, int position, long id) {Log.d("ChooseAreaFragment","列表被点了的...");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();}}});

在MainActivity中加入一个缓存数据的判断如下:
public class MainActivity extends AppCompatActivity {

    @Overrideprotected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);setContentView(R.layout.activity_main);SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(this);if (prefs.getString("weather",null)!=null){Intent intent = new Intent(this,WeatherActivity.class);startActivity(intent);finish();}}
}

运行程序后发现和风天气的API接口以及更新升级了,也就是以上代码运行会存在Bug,如图:

所以去看一下和风天气的开发文档:
https://www.heweather.com/documents/sdk/android
在来开发。

基于Android Studio的简易天气APP项目开发相关推荐

  1. 基于android studio 安卓的汽车APP项目开发和设计

    一 项目介绍 汽车APP项目 包含了 APP客户端和后台管理系统,后台管理系统主要维护汽车的数据,比如汽车发布,汽车图集多图上传,汽车销售商,汽车类型,客户管理,系统管理等,APP客户端包含 用户注册 ...

  2. 【安卓基础】基于Android Studio的家庭理财通项目

    目录 声明: 此项目为移植<Android开发从入门到精通>项目实战篇,此项目仅用于学习.书本项目是基于Eclipse 4.2.2 + Android 5.0 开发运行环境 操作系统:wi ...

  3. 基于Android studio实习生招聘系统APP java

    本项目拟开发一个实习招聘APP来促进用户与企业间交流,帮助用户提高应聘能力,又能方便企业更加明确的提出 召聘条件,筛选出合适的员工.提高了大学生实习就业成功率. AndroidManifest.xml ...

  4. android studio新闻界面,课内资源 - 基于Android Studio实现的新闻APP

    一.作业题目 二.作业描述 互联网科技的发展让新闻的传播从报纸.广播.电视等方式发展到了网页以及最近几年兴起的手机app客户端,人们获取新闻的方式变得异常容易.手机上的新闻app让大家随时随地掏出手机 ...

  5. 基于Android Studio的记账类app开发全过程

    研究背景和意义 随着社会的发展,人们的消费水平有了明显提高,消费理念也发生了一定变化.合理的消费理念对于构建社会主义和谐社会具有十分重要的意义.大学生作为消费群体的重要组成部分,他们的消费行为对大学生 ...

  6. 基于Android Studio的记账类app开发

    功能分析 记账 APP 需要有如下三个系统: 统计系统.记账系统.用户系统. 统计系统需要实现当月消费统计,包括收入.支出.结余等内容, 并可以让用户通过可视化图的方式清晰了解使用情况. 记账系统需要 ...

  7. [内附完整源码和文档] 基于Android Studio实现的新闻APP

    一.作业题目 实现一个安卓新闻类App,最低支持安卓系统版本为5.1(minSdkVersion 为API 16 https://developer.android.com/guide/topics/ ...

  8. 基于Android studio的图书管理系统APP设计与开发案例(新版)

    一.功能介绍 1.用户模式 权限授予: 用户注册登录功能(头像.账号.密码): 图书推荐功能: 图书查询功能: 图书数量查询: 图书借出归还功能: 修改资料.关于软件.联系我们.使用说明.更新版本.退 ...

  9. Android Studio 实现的画板App

    基于Android Studio 实现的画板App ** 1.实现目标** 随着科技的不断发展和社会的不断进步,手机越来越流行,成为了人们日常生活和学习的必要工具,人们越来越离不开手机,还在几年前手机 ...

最新文章

  1. win2008 域服务器搭建教程
  2. MySQL规格列表(硬件优化上限)
  3. mysql备份时候事务日志_SQLSERVER备份事务日志的作用
  4. 字节输出流的续写和换行
  5. ArcGIS将CAD等高线转换为TIN/DEM数据
  6. 从安全和不安全两个角度,教你如何发布对象(含各种单例代码)
  7. wordpress python 采集_Python3利用Selenium3模拟wordpress博客登陆
  8. 僵尸 AI 来了,人类该怎么办?
  9. 前端开发-jQuery基本语法
  10. FFA 2021 专场解读 - 开源解决方案 / 流批一体
  11. 研究生文献笔记(obsidian模板分享!!):zotero+bookxnote pro+obsidian
  12. Python之scipy安装
  13. 商品分页查询 ego-prc 实现-easyui
  14. Azure kinect (五)人体跟踪器报错解决,第一次成功运行(持续更新中)
  15. 医院预算目标分解公式
  16. 可编程式坐标--单位圆坐标
  17. 微信开发安卓虚拟键盘挡住输入框的问题
  18. effective c++ 学习笔记之 Shifting from c to c++
  19. python 图像无缝拼接_Python+OpenCV实现图像的全景拼接的代码
  20. 大数据Hadoop之——Zookeeper鉴权认证(Kerberos认证+账号密码认证)

热门文章

  1. android获取apk名称_apk瘦身;如何缩小体积呢?这篇文章来教你
  2. Spring源码阅读之bean对象的创建过程
  3. 联想微型计算机开机黑屏什么原因,联想笔记本电脑开机黑屏是什么原因
  4. 概率dp总结 正在更新
  5. 阅读笔记-工程师思维
  6. Raspberry Pi 3使用IoT Dashboard和NOOBS 安装 windows IoT以及Raspbian 系统
  7. linux 添加静态路由
  8. JVM工具之jstat
  9. 《啪嗒砰2》常见问题汇总
  10. Python标准库中的re模块