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開發學習之路——天氣預報之后台自動更新天氣(五)


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM