简介
参考《第一行代码》,开发出一款全国省市县的天气预报app.
创建数据库和表
使用LitePal对数据库进行操作,创建三个实体类分别是Province、City和County。
1. 添加依赖项
compile 'org.litepal.android:core:1.3.2'
2. 创建实体类
package com.example.stardream.coolweather.db;
import org.litepal.crud.DataSupport;
/**
* Created by StarDream on 2018/8/22.
*/
//LitePal中的每一个实体类都应该继承DataSupport
public class Province extends DataSupport {
private int id; //实体类具有的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实体类和County实体类同理。每个实体类代表一张表,实体类中的属性代表表中的每一列。
3. 配置litepal.xml文件
4. 修改AndroidManifest.xml文件
将项目的application配置为org.litepal.LitePalApplication
android:name="org.litepal.LitePalApplication"
关于LitePal的具体内容详见LitePal详解
遍历全国省市县数据
1. 客户端与服务器的交互
package com.example.stardream.coolweather.util;
import okhttp3.OkHttpClient;
import okhttp3.Request;
/**
* Created by StarDream on 2018/8/22.
*/
//采用OkHttp与服务器进行通信
public class HttpUtil {
//与服务器进行交互发起一条http请求只需要调用sendOkHttpRequest()即可
//传入要请求的地址,注册一个回调来处理服务器的响应
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);
}
}
发起http请求只需调用sendOkHttprequest()这个方法,传入一个地址,并且注册一个回调来处理服务器的响应。
2. 处理服务器返回的Json格式的数据
新建一个Utility类处理和解析Json数据。
package com.example.stardream.coolweather.util;
import android.text.TextUtils;
import com.example.stardream.coolweather.db.City;
import com.example.stardream.coolweather.db.County;
import com.example.stardream.coolweather.db.Province;
import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;
/**
* Created by StarDream on 2018/8/22.
*/
public class Utility {
//处理和解析省份的数据
public static boolean hanldeProvinceResponse(String response){
if(!TextUtils.isEmpty(response)){
try{
//json的对象的数组,用来接收传回的多个省份的数据
JSONArray allProvinces = new JSONArray(response);
for(int i=0;i
省级、市级和县级对服务器发来的数据的处理解析方式都是类似的,使用JSONArray和JSONObject进行解析,然后组装成实体类对象,再调用save()方法存储到数据库中。
3. 遍历省市县
遍历省市县功能.xml
因为遍历省市县的功能会用到多次,因此将其写为碎片的形式而不是写在活动里面,这样复用的时候可以直接在布局文件中调用碎片。
android:id="@+id/list_view/>
一般情况下标题栏可以采用ActionBar,但是碎片中最好不用ActionBar或Toolbar,否则会有问题。
遍历省市县碎片
1. 新建一个ChooseAreaFragment用于展示查询界面和实现基本查询功能,定义一些量值。
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 adapter;//适配器,与ListView配合使用
private List dataList = new ArrayList<>();//动态数组
//省列表
private List provinceList;
//市列表
private List cityList;
//县列表
private List countyList;
//选中的省份
private Province selectedProvince;
//选中的城市
private City selectedCity;
//当前选中的级别
private int currentLevel;
}
2. onCreateView()方法中获取到一些控件的实例,初始化了ArrayAdapter,将其设置为ListView的适配器。
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
/*
* 【LayoutInflater】其实是在res/layout/下找到xml布局文件,并且将其实例化,
* 对于一个没有被载入或者想要动态载入的界面,都需要使用LayoutInflater.inflate()来载入;
* */
View view = inflater.inflate(R.layout.choose_area,container,false);
titleText = (TextView) view.findViewById(R.id.title_text);
backButton = (Button) view.findViewById(R.id.back_button);
listView = (ListView) view.findViewById(R.id.list_view);
adapter = new ArrayAdapter<>(getContext(),android.R.layout.simple_list_item_1,dataList);
//载入listView
listView.setAdapter(adapter);
return view;
}
3. 在onActivityCreated()方法中设置ListView和Button的点击事件,在这里完成了基本的初始化操作,调用queryProvinces()方法,加载省级数据。
@Override
public void onActivityCreated(@Nullable Bundle savedInstanceState) {
super.onActivityCreated(savedInstanceState);
//对列表设置监听事件
listView.setOnItemClickListener(new AdapterView.OnItemClickListener(){
@Override
public void onItemClick(AdapterView> parent, View view, int position, long id) {
if(currentLevel == LEVEL_PROVINCE){
//记住选中的省份
selectedProvince = provinceList.get(position);
//显示出省份对应下city的界面
queryCities();
}else if(currentLevel == LEVEL_CITY){
//记住选中的City
selectedCity = cityList.get(position);
//切换到相应的county界面
queryCounties();
}
}
});
//为返回按钮注册监听事件
backButton.setOnClickListener(new View.OnClickListener(){
public void onClick(View v){
//若在county切换到City
if(currentLevel == LEVEL_COUNTY){
queryCities();
}else if(currentLevel == LEVEL_CITY){
//若在City切换到province
queryProvinces();
}
}
});
//初始状态下显示province
queryProvinces();
}
4. 在queryProvinces()方法中,设置头布局的标题,返回按钮。调用LitePal查询结构读取省级数据,若读取到则将其显示在界面上,若没有则调用queryServer()方法从服务器查询数据。queryCities()方法和queryCounty()方法同理。
- 查询省级数据
/*查询全国所有的省,先从数据库查,没有的话去服务器查询
* */
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");
}
}
- 查询市级数据
/*查询对应省的City数据,优先从数据库查,若没有,则去服务器查询
* */
private void queryCities(){
//设置标题栏
titleText.setText(selectedProvince.getProvinceName());
//设置返回按钮可见
backButton.setVisibility(View.VISIBLE);
//在数据库中查询对应的City数据
cityList = DataSupport.findAll(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{
String address = "http://guolin.tech/api/china/"+selectedProvince.getProvinceCode();
queryFromServer(address,"city");
}
}
- 查询县级数据
/*查询选中的市内的所有县,优先从数据库查,若没有则去服务器查询
* */
private void queryCounties(){
titleText.setText(selectedCity.getCityName());
backButton.setVisibility(View.VISIBLE);
countyList = DataSupport.findAll(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{
String address = "http://guolin.tech/api/china/"+
selectedProvince.getProvinceCode()+"/"+selectedCity.getCityCode();
queryFromServer(address,"county");
}
}
5. 在queryFromServer()方法中,调用HttpUtil中的sendOkHttpRequest()方法向服务器发送请求,响应的数据回调到onResponse()方法中,然后调用Utility中的handleProvincesResponse()方法解析和处理服务器返回的数据,然后将其存储到数据库中。最后再次调用queryProvinces()方法重新加载省级数据,因为queryProvinces()涉及UI操作,则须在主线程中调用,因此借助runOnUiThread()方法从子线程切换到主线程。
- (问题:queryFromServer()在哪里说明是是在子线程中执行的???)
/*根据传入的地址和类型从服务器上获取数据
* */
private void queryFromServer(String address,final String type){
//未查出之前显示出进度条框
showProgressDialog();
HttpUtil.sendOkHttpRequest(address, new Callback() {
@Override
public void onFailure(Call call, IOException e) {
//通过runOnUiThread回到主线程处理逻辑
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 responseText = response.body().string();
boolean result = false;
if(type.equals("province")){
result = Utility.hanldeProvinceResponse(responseText);
}else if(type.equals("city")){
result = Utility.handleCityResponse(responseText,selectedProvince.getId());
}else if(type.equals("county")){
result = Utility.handleCountyResponse(responseText,selectedCity.getId());
}
if(result){
getActivity().runOnUiThread(new Runnable() {
@Override
public void run() {
closeProgressDialog();
if(type.equals("province")){
queryProvinces();
}else if(type.equals("city")){
queryCities();
}else if(type.equals("county")){
queryCounties();
}
}
});
}
}
});
}
6. 涉及到的进度条框
- 进度条框的显示
//显示进度条框
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();
}
}
将碎片添加到活动
1. 修改activity_main.xml中的代码
定义一个FrameLayout,将ChooseAreaFragment加入,并让其充满整个布局。
2. 删除原生的ActionBar
在style.xml中,