文章目录

  • 需求分析
  • 数据来源
  • 创建数据库和表
    • 加载全国省市区数据
    • 请求和解析数据工具类
    • 碎片界面布局
  • 加载数据碎片逻辑
    • 测试

本系列记录一下跟随guolin大神学习的项目自己完成一个天气预报Android App.

需求分析

  1. 可以列出全国所有省市县
  2. 可以查看全国任意城市天气信息
  3. 可以自由切换城市,查看不同地区天气
  4. 有手动刷新和后台自动更新天气功能

数据来源

郭神提供的数据接口,包含全国省市县名称和编号信息:

省级单位:

http://guolin.tech/api/china

服务器会返回JSON格式数据

市级单位:在后面加上具体省份id即可

http://guolin.tech/api/china/16

县级单位以此类推:

http://guolin.tech/api/china/16/116

接着为了获取每个地区具体的天气情况需要注册和风天气的接口:

拿到自己App的API KEY

之后配合每个具体地区的weather_id即可查看天气信息,如:

http://guolin.tech/api/weathercityid=cn101190401&key=bc0418b57b2d4918819d3974ac1285d9

返回的数据如:

数据获取后接着做JSON解析工作即可。

创建数据库和表

第一阶段要做的就是创建好数据库和表,从而将服务器获取到的数据存储到本地。这里使用 LitePal来管理数据库。

首先创建目录结构 ,其中db包用于存放数据库模型相关的代码

gson包用于存放GSON模型相关的代码,

service包用于存放服务相关的代码

util包用于存放工具相关的代码。

添加相关依赖:

使用 LiteRal,可以用面向对象的思维来实现数据库相关操作,比如定义一个 Java bean,在Book类中我们定义了id、 author、 price、 pages、name这几个字段,并生成了相应的 getter和 setter方法。Book类就会对应数据库中的Book表,而类中的每一个字段分别对应了表中的每一个列,这就是对象关系映射最直观的体验。

在db下新建省市县三个bean来对应三张表,具体代码如下:

/***省信息表*/
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;}
}

LiteRal进行表管理操作时不需要模型类有任何的继承结构,但是进行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;}}

/*** 地区/县信息表*/
public class County extends DataSupport {private int id;private String countyName;//县名private String weatherId;//天气idprivate int cityId;//所属县IDpublic 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 String getWeatherId() {return weatherId;}public void setWeatherId(String weatherId) {this.weatherId = weatherId;}public int getCityId() {return cityId;}public void setCityId(int cityId) {this.cityId = cityId;}
}

接下来需要配置``litepal.xml`

<?xml version="1.0" encoding="utf-8"?>
<litepal><dbname value ="MyWeatherApp"/><version value="1"/><list><mapping class="com.wz.myweatherapp.db.County"/><mapping class="com.wz.myweatherapp.db.City"/><mapping class="com.wz.myweatherapp.db.Province"/></list>
</litepal>

其中,< dbname>标签用于指定数据库名,< version>标签用于指定数据库版本号,
标签用于指定所有的映射模型,我们稍后就会用到。

最后还需要再配置一下 LitePalApplication,修改 Androidmanifest xml中的代码,如下所示:

<manifest xmlns:android="http://schemas.android.com/apk/res/android"package="com.wz.myweatherapp"><uses-permission android:name="android.permission.INTERNET"/><applicationandroid:name="org.litepal.LitePalApplication"android:allowBackup="true"android:icon="@mipmap/ic_launcher"......

加载全国省市区数据

请求和解析数据工具类

由于数据是从网络服务端获取的,故可以创建工具类

public class HttpUtil {/*** 传入请求地址 注册一个回调来处理服务器响应* @param address* @param callback*/public static void sendOkHttpRequest(String address,Callback callback){//创建一个OkHttpClient实例OkHttpClient client = new OkHttpClient();//创建Request来发起HTTP请求Request request = new Request.Builder().url(address).build();//之后调用OkhttpClient的newCall()方法来创建一个CalL对象,并调用它的execute()方//法来发送请求并获取服务器返回的数据,写法如下client.newCall(request).enqueue(callback);}
}

另外,由于服务器返回的省市县数据都是JSON格式的,所以最好再提供一个工具类来解析和处理这种数据。在util包下新建一个 Utility类,代码如下所示

public class Utility {/*** 解析处理服务器返回的省级数据**/public static boolean handleProvinceResponse(String response){if (!TextUtils.isEmpty(response)) {try {//可以看到,解析JSON的代码非常简单,由于在服务器中定义的是一个JSON数组,//因此这里首先是将服务器返回的数据传入到了一个JSONArray对象中。然后循环遍历这个JSONArray,// 从中取出的每一个元素都是一个JSONArray对象,每个JSONArray对象中又会包含id、name和 version这些数据。//接下来只需要调用 getstring()方法将这些数据取出,并打印出来即可。//先使用JSONArray 和 JSONObject将数据解析JSONArray allProvince = new JSONArray(response);for (int i = 0; i < allProvince.length(); i++) {JSONObject provinceObject = allProvince.getJSONObject(i);//装入实体对象Province province = new Province();province.setProvinceName(provinceObject.getString("name"));province.setProvinceCode(provinceObject.getInt("id"));//由于province 继承了litepal特性 故使用save存储进数据库province.save();}return true;} catch (JSONException e) {e.printStackTrace();}}return false;}
/*** 解析处理服务器返回的市级数据*/
public static boolean handleCityResponse(String response,int provinceId){try {if (!TextUtils.isEmpty(response)) {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){try {if (!TextUtils.isEmpty(response)) {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;
}}

需要准备的工具类就这么多,现在可以开始写界面了。由于遍历全国省市县的功能我们在后面还会复用,因此就不写在活动里面了,而是写在碎片里面,这样需要复用的时候直接在布局里面引用碎片就可以了。

碎片界面布局

<?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">
<RelativeLayoutandroid:layout_width="match_parent"android:layout_height="?attr/actionBarSize"android:background="?attr/colorPrimary"><TextViewandroid:id="@+id/title_text"android:layout_width="wrap_content"android:layout_height="wrap_content"android:layout_centerInParent="true"android:textSize="20sp"android:textColor="#fff"/><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>

加载数据碎片逻辑

接下来也是最关键的一步,需要编写用于加载省市县数据的碎片了。新建 ChooseAreaFragment继承自 Fragment这个逻辑却不复杂,需要慢慢理一下。在 onCreateview()方法中先是获取到了一些控件的实例,然后去初始化了 Array Adapter,并将它设置为 List view的适配器。接着在 onActivityCreated()方法中给 Listview和Button设置了点击事件,到这里初始化工作就算是完成了。

@Overridepublic View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {View view = inflater.inflate(R.layout.choose_area, container, false);mTitleText = view.findViewById(R.id.title_text);mButton = view.findViewById(R.id.back_button);mListView = view.findViewById(R.id.list_view);//不过,数组中的数据是无法直接传递给 Listview的,我们还需要借助适配器来完成。 Android//中提供了很多适配器的实现类,其中我认为最好用的就是 Array Adapter。它可以通过泛型来指定//要适配的数据类型,然后在构造函数中把要适配的数据传入。 Array Adapter有多个构造函数的重//载,你应该根据实际情况选择最合适的一种。这里如果提供的数据都是字符串,可以将//ArrayAdapter的泛型指定为 String// 然后在ArrayAdapter的构造函数中依次传入当前上下文,List view子项布局的id,以及要适配的数据。// 注意,使用了simple_list_item_1作为 List view子项布局的id,这是一个 Android内置的布局文件,里面只有一个//Text View,可用于简单地显示一段文本。这样适配器对象就构建好了//最后,还需要调用 List View的 setAdapter()方法,将构建好的适配器对象传递进去,这样//List view和数据之间的关联就建立完成了。adapter = new ArrayAdapter<>(getContext(),android.R.layout.simple_list_item_1,dataList);mListView.setAdapter(adapter);return view;
}@Override
public void onActivityCreated(@Nullable Bundle savedInstanceState) {super.onActivityCreated(savedInstanceState);/*列表点击事件*/mListView.setOnItemClickListener(new AdapterView.OnItemClickListener() {//可以看到,我们使用 setonItemClicklistener()方法为 Listview注册了一个监听器,当//用户点击了 Listview中的任何一个子项时,就会回调 onItemclick()方法。在这个方法中可以//通过 position参数判断出用户点击的是哪一个子项,然后获取到相应的类信息,并通过Toast显示@Overridepublic void onItemClick(AdapterView<?> adapterView, View view, int pos, long idl) {if (currentLevel == LEVEL_PROVINCE){selectedProvince = provinceList.get(pos);queryCity();}else if (currentLevel == LEVEL_CITY){selectedCity = cityList.get(pos);queryCounty();}}});/*返回按钮 点击事件*/mButton.setOnClickListener(new View.OnClickListener() {@Overridepublic void onClick(View view) {if (currentLevel == LEVEL_COUNTY){/*如果是在县页面返回 则获取市信息*/queryCity();}else if (currentLevel == LEVEL_CITY){/*如果实在市级别返回 则获取省信息*/queryProvince();}}});/*创建时默认获取省信息*/queryProvince();
}
/*
查询省内所有市*/private void queryCity() {mTitleText.setText(selectedProvince.getProvinceName());mButton.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();mListView.setSelection(0);currentLevel = LEVEL_CITY;}else {int provinceCode = selectedProvince.getProvinceCode();String address ="http://guolin.tech/api/china/"+provinceCode;queryFromServer(address,"city");}}/*查询全国所有省 优先从数据可查询 若无再去服务器查询query Provinces()方法中首先会将头布局的标题设置成中国,将返回按钮隐藏起来,因为省级列表已经不能再返回了。然后调用 LiteRal的查询接口来从数据库中读取省级数据,如果读取到了就直接将数据显示到界面上,如果没有读取到组装出一个请求地址,然后调用 queryFromServer()方法来从服务器上查询数据。*/private void queryProvince() {mTitleText.setText("中国");//另外还有一点需要注意,在返回按钮的点击事件里,会对当前 List view的列表级别进行判断。//如果当前是县级列表,那么就返回到市级列表,如果当前是市级列表,那么就返回到省级表列表。//当返回到省级列表时,返回按钮会自动隐藏,从而也就不需要再做进一步的处理了。mButton.setVisibility(View.GONE);provinceList = DataSupport.findAll(Province.class);if (provinceList.size() > 0) {dataList.clear();for (Province province : provinceList) {dataList.add(province.getProvinceName());}adapter.notifyDataSetChanged();mListView.setSelection(0);currentLevel = LEVEL_PROVINCE;}else {String address = "http://guolin.tech/api/china";queryFromServer(address, "province");}}/*** query Fromserver()方法中会调用 HttpUtil的send0httPrequest()方法来向服务器发送* 请求,响应的数据会回调到 onResponse()方法中,然后我们在这里去调用 Utility的* handleprovincesresponse()方法来解析和处理服务器返回的数据,并存储到数据库中。接下* 来的一步很关键,在解析和处理完数据之后,再次调用了 queryProvinces()方法来重新加* 载省级数据,由于 queryProvinces()方法牵扯到了U操作,因此必须要在主线程中调用,这* 里借助了 runonuiThread()方法来实现从子线程切换到主线程。现在数据库中已经存在了数据* 因此调用 queryProvinces()就会直接将数据显示到界面上了* @param address* @param type*/private void queryFromServer(String address, final String type) {showProgressDialog();HttpUtil.sendOkHttpRequest(address, new Callback() {@Overridepublic void onFailure(Call call, IOException e) {getActivity().runOnUiThread(new Runnable() {@Overridepublic void run() {closeProgressDialog();Toast.makeText(getContext(), "加载失败", Toast.LENGTH_SHORT).show();}});}@Overridepublic void onResponse(Call call, 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() {@Overridepublic void run() {closeProgressDialog();if ("province".equals(type)){queryProvince();}else if ("city".equals(type)){queryCity();}else if ("county".equals(type)){queryCounty();}}});}}});}private void closeProgressDialog() {if (mProgressDialog != null) {mProgressDialog.dismiss();}}private void showProgressDialog() {if (mProgressDialog == null) {mProgressDialog = new ProgressDialog(getActivity());mProgressDialog.setMessage("正在加载");mProgressDialog.setCanceledOnTouchOutside(false);}mProgressDialog.show();}/*查询市内所有区/县*/private void queryCounty() {mTitleText.setText(selectedCity.getCityName());mButton.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();mListView.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");}}

这样我们就把加载全国省市县的功能完成了,可是碎片是不能直接显示在界面上的,因此我们还需要把它添加到活动里才行。修改 activity main.xml中的代码,如下所示:

<?xml version="1.0" encoding="utf-8"?>
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"xmlns:app="http://schemas.android.com/apk/res-auto"xmlns:tools="http://schemas.android.com/tools"android:layout_width="match_parent"android:layout_height="match_parent"><fragmentandroid:layout_width="match_parent"android:layout_height="match_parent"android:id="@+id/choose_area_fragment"android:name="com.wz.myweatherapp.ChooseAreaFragment"/></FrameLayout>

另外,我们刚才在碎片的布局里面已经自定义了一个标题栏,因此就不再需要原生的Action bar了,修改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>

声明所需网络权限

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

测试

现在可以运行一下程序进行测试了,效果如下。

动手实现天气预报App(一)——数据、工具类和碎片布局准备相关推荐

  1. php获得帮助类数据_PHP解析xml格式数据工具类示例

    本文实例讲述了PHP解析xml格式数据工具类.分享给大家供大家参考,具体如下: class ome_xml { /** * xml资源 * * @var resource * @see xml_par ...

  2. php解析xml数据格式,PHP解析xml格式数据工具类实例分享

    本文主要介绍了PHP解析xml格式数据工具类,涉及php针对xml格式数据节点添加.获取.解析等相关操作技巧,需要的朋友可以参考下,希望能帮助到大家. 本文实例讲述了PHP解析xml格式数据工具类.分 ...

  3. 动手实现天气预报App(二)——显示天气信息

    文章目录 显示天气信息 解析天气数据 basic aqi now suggestion daily_forecast weather 编写天气界面 将天气信息显示到界面上 点击地区跳转到具体天气信息界 ...

  4. 使用java多线程分批处理数据工具类

    最近由于业务需要,数据量比较大,需要使用多线程来分批处理,提高处理效率和能力,于是就写了一个通用的多线程处理工具,只需要实现自己的业务逻辑就可以正常使用,现在记录一下 主要是针对大数据量list,将l ...

  5. poi导入数据工具类,直接复制使用,有详细注释

    poi导入工具类,直接复制使用,有详细的注释 前言 一.引入依赖 二.封装的工具类以及注解类直接copy使用 首先是工具类无需做操作 然后是封装的两个注解类,也是直接复制使用 测试工具类功能 测试实体 ...

  6. 使用open3d加载点云数据工具类

    设计思路: 将点云文件加载成tensor类型,用于PointNet进行处理. 将tensor类型的点云文件保存到指定的位置 将点云数据可视化 类似于ply类型的点云文件,使用open3d读入之后,类型 ...

  7. JAVA使用POI对Word docx模板文件替换数据工具类

    word模板文件参考下面: Map<String, Object> params = new HashMap<String, Object>(); params.put(&qu ...

  8. Bootstrap(一)——简介、布局容器和工具类使用(flex布局)

    文章目录 一.Bootstrap简介 二.布局容器 2.1 定宽容器 container 2.2 变宽容器 container-fluid 三.工具类 3.1 颜色与排版 (1)常用颜色 (2)常用排 ...

  9. Android APP更新下载工具类——简单封装DownloadManager

    几乎所有APP都包含了检查更新功能,更新下载功能的实现方式常用的有两种:1.使用App网络框架的文件下载请求:2.使用自带的DownloadManager类:本文介绍第二种,简单封装一下Downloa ...

最新文章

  1. PHP实现上一篇、下一篇
  2. 26.使用ajaxSetup()方法设置全局Ajax默认选项
  3. 邮件服务的安装及简单应用
  4. mysql replication 协议_深入解析MySQL replication协议
  5. python编程小案例_用Python3编程写第一个小案例!-Go语言中文社区
  6. 算法基础之搜索和经典排序
  7. mysql 5.7 io 性能 aio_深入理解MySQL的InnoDB引擎
  8. Windows 7 资源管理器搜索Channel 9 视频
  9. android 半圆滚动菜单,自定义控件:实现半圆滚动菜单效果
  10. 类名作为方法和形参的返回值
  11. 戏说前端 JavaScript 之『防抖节流』基础知识
  12. 【转】史上最简单的软件破解——5行脚本代码完美破解99%的过期软件
  13. 在 iOS 设备上安装和使用 OpenSSH
  14. 数据分析——鸢尾花数据集
  15. uniapp横竖屏切换
  16. 用java做一个校园网站,基于jsp的校园网站-JavaEE实现校园网站 - java项目源码
  17. 饥荒 Don‘t Starve Together Mac游戏介绍
  18. Lenb函数计算中文字节出错处理办法
  19. 慕容垂:百万战骨风云里——激荡的鲜卑史略之一(转载)
  20. 解决mac 休眠后唤醒死机问题

热门文章

  1. python爬虫爬取豆瓣top排行图片
  2. 大一新生的pta错题归纳
  3. 异质性分析:系数平滑可变模型
  4. UE4/UE5 多线程开发 附件插件下载地址
  5. ionic-打包成iOS系统Camera插件获取视频路径之后访问无权限
  6. 智慧电网是什么?如何理解智慧?
  7. Qt Charts 动态实时折线图绘制
  8. c语言分隔符的作用,句子分割代码(C语言程序的语句分隔符是)
  9. 案例:马斯洛需求层次理论——西游记的5人团队
  10. python用read_html抓取网页表格型数据