android开发学习之路——天气预报之遍历省市县数据(二)

    我们已经知道省市县的数据都是从服务器端获取到的,因此与服务器的交互是必不可少的,我们再util包下增加一个HttpUtil类,代码如下所示:

1 public class HttpUtil{
2     public static void sendOkHttpRequest(String address,okttp3.Callback callback)
3     {
4         OkHttpClient client = new OkHttpClient();
5         Request request= new Request.Builder().url(address).build();
6         Client.newcall(request).enqueue(callback);
7     }
8 }

    由于Okhttp的出色封装,这里和服务器进行交互的代码非常简单,仅仅3行就完成了。现在我们发起一条HTTP请求只需要调用sendOkHttpRequest()方法,传入请求地址,并注册一个回调来处理服务器响应就可以了。

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

 1 public class Utility {
 2 
 3     /**
 4      * 解析和处理服务器返回的省级数据
 5      */
 6     public static boolean handleProvinceResponse(String response) {
 7         if (!TextUtils.isEmpty(response)) {
 8             try {
 9                 JSONArray allProvinces = new JSONArray(response);
10                 for (int i = 0; i < allProvinces.length(); i++) {
11                     JSONObject provinceObject = allProvinces.getJSONObject(i);
12                     Province province = new Province();
13                     province.setProvinceName(provinceObject.getString("name"));
14                     province.setProvinceCode(provinceObject.getInt("id"));
15                     province.save();
16                 }
17                 return true;
18             } catch (JSONException e) {
19                 e.printStackTrace();
20             }
21         }
22         return false;
23     }
24 
25     /**
26      * 解析和处理服务器返回的市级数据
27      */
28     public static boolean handleCityResponse(String response, int provinceId) {
29         if (!TextUtils.isEmpty(response)) {
30             try {
31                 JSONArray allCities = new JSONArray(response);
32                 for (int i = 0; i < allCities.length(); i++) {
33                     JSONObject cityObject = allCities.getJSONObject(i);
34                     City city = new City();
35                     city.setCityName(cityObject.getString("name"));
36                     city.setCityCode(cityObject.getInt("id"));
37                     city.setProvinceId(provinceId);
38                     city.save();
39                 }
40                 return true;
41             } catch (JSONException e) {
42                 e.printStackTrace();
43             }
44         }
45         return false;
46     }
47 
48     /**
49      * 解析和处理服务器返回的县级数据
50      */
51     public static boolean handleCountyResponse(String response, int cityId) {
52         if (!TextUtils.isEmpty(response)) {
53             try {
54                 JSONArray allCounties = new JSONArray(response);
55                 for (int i = 0; i < allCounties.length(); i++) {
56                     JSONObject countyObject = allCounties.getJSONObject(i);
57                     County county = new County();
58                     county.setCountyName(countyObject.getString("name"));
59                     county.setWeatherId(countyObject.getString("weather_id"));
60                     county.setCityId(cityId);
61                     county.save();
62                 }
63                 return true;
64             } catch (JSONException e) {
65                 e.printStackTrace();
66             }
67         }
68         return false;
69     }
70 
71

    可以看到,我们提供了handleProvincesResponse()、handleCitiesResponse()、handleCountiesResponse()这3个方法,分别用于解析和处理服务器返回的省级、市级和县级数据。处理的方法是类似的,先使用JSONArray和JSONObject将数据解析出来,然后组装成实体类对象,再调用save()方法将数据存储到数据库当中。

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

    在res/layout目录中新建choose_area.xml布局,代码如下所示:

 1 <LinearLayout
 2   xmlns:android="http://schemas.android.com/apk/res/android"
 3     android:orientation="vertical"
 4     android:layout_height="match_parent"    
 5     android:layout_width="match_parent"
 6     android:background="#fff "  >
 7     <RelativeLayout  
 8         android:layout_height="?attr/actionBarSize"
 9         android:layout_width="match_parent
10         android:background="?attr/colorPrimary""> 
11         <TextView 
12            android:id="@+id/title_text"
13            android:layout_height="wrap_content"
14            android:layout_width="wrap_content"
15            android:textSize="20sp" 
16            android:textColor="#fff"
17            android:layout_centerInParent="true" /> 
18         <Button 
19            android:id="@+id/back_button" 
20            android:layout_height="25dp" 
21            android:layout_width="25dp"
22            android:layout_centerVertical="true"
23            android:layout_alignParentLeft="true"
24            android:layout_marginLeft="10dp"
25            android:background="@drawable/ic_back"/> 
26     </RelativeLayout> 
27     <ListView 
28         android:id="@+id/list_view"
29         android:layout_height="match_parent"
30         android:layout_width="match_parent"/> 
31 </LinearLayout>

    布局文件中的内容并不复杂,我们先定义了一个头布局作为标题栏,将布局高度设置为actionBar的高度,背景色设置为colorPrimary。然后在头布局中放置一个TextView用于显示标题内容,放置了一个Button用于执行返回操作,注意我已经提前准备好了一张ic_back.png图片作为按钮的背景图。这里之所以要自己定义标题栏,是因为碎片中最好不要直接使用ActionBar或Toolbar,不然在复用的时候可能出现一些你不想看到的效果。

    接下来在头布局的下面定义了一个ListView,省市县的数据就将显示在这里。之所以这次使用ListView,是因为它会自动给每个子项之间添加一条分割线,而如果使用RecyclerView想实现同样的功能则会比较麻烦,这里我们总是选择最优的实现方案。

    接下来我们需要编写用于遍历省市县数据的碎片了。新建ChooseAreaFragment继承自Fragment,代码如下:

  1 public class ChooseAreaFragment extends Fragment {
  2 
  3     private static final String TAG = "ChooseAreaFragment";
  4 
  5     public static final int LEVEL_PROVINCE = 0;
  6 
  7     public static final int LEVEL_CITY = 1;
  8 
  9     public static final int LEVEL_COUNTY = 2;
 10 
 11     private ProgressDialog progressDialog;
 12 
 13     private TextView titleText;
 14 
 15     private Button backButton;
 16 
 17     private ListView listView;
 18 
 19     private ArrayAdapter<String> adapter;
 20 
 21     private List<String> dataList = new ArrayList<>();
 22 
 23     /**
 24      * 省列表
 25      */
 26     private List<Province> provinceList;
 27 
 28     /**
 29      * 市列表
 30      */
 31     private List<City> cityList;
 32 
 33     /**
 34      * 县列表
 35      */
 36     private List<County> countyList;
 37 
 38     /**
 39      * 选中的省份
 40      */
 41     private Province selectedProvince;
 42 
 43     /**
 44      * 选中的城市
 45      */
 46     private City selectedCity;
 47 
 48     /**
 49      * 当前选中的级别
 50      */
 51     private int currentLevel;
 52 
 53 
 54     @Override
 55     public View onCreateView(LayoutInflater inflater, ViewGroup container,
 56                              Bundle savedInstanceState) {
 57         View view = inflater.inflate(R.layout.choose_area, container, false);
 58         titleText = (TextView) view.findViewById(R.id.title_text);
 59         backButton = (Button) view.findViewById(R.id.back_button);
 60         listView = (ListView) view.findViewById(R.id.list_view);
 61         adapter = new ArrayAdapter<>(getContext(), android.R.layout.simple_list_item_1, dataList);
 62         listView.setAdapter(adapter);
 63         return view;
 64     }
 65 
 66     @Override
 67     public void onActivityCreated(Bundle savedInstanceState) {
 68         super.onActivityCreated(savedInstanceState);
 69         listView.setOnItemClickListener(new AdapterView.OnItemClickListener() {
 70             @Override
 71             public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
 72                 if (currentLevel == LEVEL_PROVINCE) {
 73                     selectedProvince = provinceList.get(position);
 74                     queryCities();
 75                 } else if (currentLevel == LEVEL_CITY) {
 76                     selectedCity = cityList.get(position);
 77                     queryCounties();
 78                 } else if (currentLevel == LEVEL_COUNTY) {
 79                     String weatherId = countyList.get(position).getWeatherId();
 80                     if (getActivity() instanceof MainActivity) {
 81                         Intent intent = new Intent(getActivity(), WeatherActivity.class);
 82                         intent.putExtra("weather_id", weatherId);
 83                         startActivity(intent);
 84                         getActivity().finish();
 85                     } else if (getActivity() instanceof WeatherActivity) {
 86                         WeatherActivity activity = (WeatherActivity) getActivity();
 87                         activity.drawerLayout.closeDrawers();
 88                         activity.swipeRefresh.setRefreshing(true);
 89                         activity.requestWeather(weatherId);
 90                     }
 91                 }
 92             }
 93         });
 94         backButton.setOnClickListener(new View.OnClickListener() {
 95             @Override
 96             public void onClick(View v) {
 97                 if (currentLevel == LEVEL_COUNTY) {
 98                     queryCities();
 99                 } else if (currentLevel == LEVEL_CITY) {
100                     queryProvinces();
101                 }
102             }
103         });
104         queryProvinces();
105     }
106 
107     /**
108      * 查询全国所有的省,优先从数据库查询,如果没有查询到再去服务器上查询。
109      */
110     private void queryProvinces() {
111         titleText.setText("中国");
112         backButton.setVisibility(View.GONE);
113         provinceList = DataSupport.findAll(Province.class);
114         if (provinceList.size() > 0) {
115             dataList.clear();
116             for (Province province : provinceList) {
117                 dataList.add(province.getProvinceName());
118             }
119             adapter.notifyDataSetChanged();
120             listView.setSelection(0);
121             currentLevel = LEVEL_PROVINCE;
122         } else {
123             String address = "http://guolin.tech/api/china";
124             queryFromServer(address, "province");
125         }
126     }
127 
128     /**
129      * 查询选中省内所有的市,优先从数据库查询,如果没有查询到再去服务器上查询。
130      */
131     private void queryCities() {
132         titleText.setText(selectedProvince.getProvinceName());
133         backButton.setVisibility(View.VISIBLE);
134         cityList = DataSupport.where("provinceid = ?", String.valueOf(selectedProvince.getId())).find(City.class);
135         if (cityList.size() > 0) {
136             dataList.clear();
137             for (City city : cityList) {
138                 dataList.add(city.getCityName());
139             }
140             adapter.notifyDataSetChanged();
141             listView.setSelection(0);
142             currentLevel = LEVEL_CITY;
143         } else {
144             int provinceCode = selectedProvince.getProvinceCode();
145             String address = "http://guolin.tech/api/china/" + provinceCode;
146             queryFromServer(address, "city");
147         }
148     }
149 
150     /**
151      * 查询选中市内所有的县,优先从数据库查询,如果没有查询到再去服务器上查询。
152      */
153     private void queryCounties() {
154         titleText.setText(selectedCity.getCityName());
155         backButton.setVisibility(View.VISIBLE);
156         countyList = DataSupport.where("cityid = ?", String.valueOf(selectedCity.getId())).find(County.class);
157         if (countyList.size() > 0) {
158             dataList.clear();
159             for (County county : countyList) {
160                 dataList.add(county.getCountyName());
161             }
162             adapter.notifyDataSetChanged();
163             listView.setSelection(0);
164             currentLevel = LEVEL_COUNTY;
165         } else {
166             int provinceCode = selectedProvince.getProvinceCode();
167             int cityCode = selectedCity.getCityCode();
168             String address = "http://guolin.tech/api/china/" + provinceCode + "/" + cityCode;
169             queryFromServer(address, "county");
170         }
171     }
172 
173     /**
174      * 根据传入的地址和类型从服务器上查询省市县数据。
175      */
176     private void queryFromServer(String address, final String type) {
177         showProgressDialog();
178         HttpUtil.sendOkHttpRequest(address, new Callback() {
179             @Override
180             public void onResponse(Call call, Response response) throws IOException {
181                 String responseText = response.body().string();
182                     boolean result = false;
183                 if ("province".equals(type)) {
184                     result = Utility.handleProvinceResponse(responseText);
185                 } else if ("city".equals(type)) {
186                     result = Utility.handleCityResponse(responseText, selectedProvince.getId());
187                 } else if ("county".equals(type)) {
188                     result = Utility.handleCountyResponse(responseText, selectedCity.getId());
189                 }
190                 if (result) {
191                     getActivity().runOnUiThread(new Runnable() {
192                         @Override
193                         public void run() {
194                             closeProgressDialog();
195                             if ("province".equals(type)) {
196                                 queryProvinces();
197                             } else if ("city".equals(type)) {
198                                 queryCities();
199                             } else if ("county".equals(type)) {
200                                 queryCounties();
201                             }
202                         }
203                     });
204                 }
205             }
206 
207             @Override
208             public void onFailure(Call call, IOException e) {
209                 // 通过runOnUiThread()方法回到主线程处理逻辑
210                 getActivity().runOnUiThread(new Runnable() {
211                     @Override
212                     public void run() {
213                         closeProgressDialog();
214                         Toast.makeText(getContext(), "加载失败", Toast.LENGTH_SHORT).show();
215                     }
216                 });
217             }
218         });
219     }
220 
221     /**
222      * 显示进度对话框
223      */
224     private void showProgressDialog() {
225         if (progressDialog == null) {
226             progressDialog = new ProgressDialog(getActivity());
227             progressDialog.setMessage("正在加载...");
228             progressDialog.setCanceledOnTouchOutside(false);
229         }
230         progressDialog.show();
231     }
232 
233     /**
234      * 关闭进度对话框
235      */
236     private void closeProgressDialog() {
237         if (progressDialog != null) {
238             progressDialog.dismiss();
239         }
240     }
241 
242 }

   这个类里的代码非常多,但逻辑并不复杂。在onCreateView()方法中先是获取到了一些控件的实例,然后去初始化ArrayAdapter,并将它设置为ListView的适配器。接着在onActivityCreated()方法中给ListView和Button设置了点击事件,到这初始工作算是完成。

    在onAcivityCreated()方法的最后,调用了queryProvinces()方法,也就是从这里开始加载省级数据的。queryProvinces()方法中首先会将头布局的标题设置成中国,将返回按钮隐藏起来,因为省级列表已经不能再返回了。然后调用LitePal的查询接口来从数据库中读取省级数据,如果读取到了就直接将数据显示到界面上,如果没有,就组装出一个请求地址,然后调用queryFromServer()方法来从服务器上查询数据。

    queryFromServer()方法会调用HttpUtil的sendOkHttpRequest()方法来向服务器发送请求,响应的数据会回调到onResponse()方法中,然后我们在这里去调用Utility的handleProvincesResponse()方法来解析和处理服务器返回的数据,并存储到数据库中。在解析和处理完数据之后,我们再次调用了queryProvinces()方法来重新加载省级数据,由于queryProvinces()方法牵扯到了UI操作,因此必须要在主线程中调用,,这里借助了runOnUiThread()方法来实现从子线程切换到主线程。现在数据库中已经存在了数据,因此调用queryProvinces()就会直接将数据显示到界面上了。

    当你点击了某个省的时候会进入到ListView的onItemClick()方法中,这个时候会根据当前的级别来判断是去调用queryCities()方法还是queryCounties()方法,queryCities()方法是去查询市级数据,而queryCounties()方法是去查询县级数据。

    另外,在返回按钮的点击事件里,会对当前ListView的列表级别进行判断。如果当前是县级列表,那么就返回到市级列表,如果当前是市级列表,那么就返回到省级列表。当返回到省级列表时,返回按钮会自动隐藏。

    这样我们就把遍历省市县的功能完成了,但碎片不能直接显示在界面上的,因为我们需要将它添加到活动当中。修改activity_main.xml中的代码,如下所示:

 1 <FrameLayout
 2     xmlns:android="http://schemas.android.com/apk/res/android"
 3     android:layout_height="match_parent"
 4     android:layout_width="match_parent" > 
 5     <fragment 
 6         android:id="@+id/choose_area_fragment"
 7         android:name="com.coolweather.android.ChooseAreaFragment"
 8         android:layout_height="match_parent"
 9         android:layout_width="match_parent"  />
10  </FrameLayout>

    定义了一个FrameLayout,然后将ChooseAreaFragment添加进来,并让它充满整个布局。

    另外,我们刚才在碎片的布局里面已经自定义了一个标题栏,因此就不再需要原生的AcitonBar了,修改res/values/styles.xml中的代码,如下所示:

1 <resources> 
2     <!-- Base application theme. -->
3     <style parent="Theme.AppCompat.Light.NoActionBar">
4          .... 
5      </style>
6 </resources>

     接下来声明程序所需要的权限。修改AndroidManifest.xml中的代码,如下所示:

1 <manifest 
2     package="com.coolweather.android"
3     xmlns:android="http://schemas.android.com/apk/res/android">
4     <uses-permission android:name="android.permission.INTERNET"/>
5     .... 
6 </manifest>

    由于我们是通过网络接口来获取全国省市县数据的,因此必须要添加访问网络的权限才行。

    下一章节设计并编写显示天气信息的布局和功能。

 

具体实现步骤连接:

android开发学习之路——天气预报之技术分析与数据库(一)

android开发学习之路——天气预报之遍历省市县数据(二)

android开发学习之路——天气预报之显示天气信息(三)

android开发学习之路——天气预报之手动更新天气和切换城市(四)

android开发学习之路——天气预报之后台自动更新天气(五)

posted @ 2017-08-30 00:12  Ling先生  阅读(4705)  评论(1编辑  收藏  举报