利用百度API Store接口進行火車票查詢

火車票查詢java

 

  項目源碼下載連接:android

  Github:https://github.com/VincentWYJ/TrainTicketQuery  git

  博客文件:http://files.cnblogs.com/files/tgyf/TrainTicketQuery.rargithub

 

1. 獲取api keyjson

  API Store連接地址:http://apistore.baidu.com/api

  1.1 經過上述連接進入百度API Store主頁以後,左下角有一個「旅遊票務」項,選擇其中的「去哪兒網火車票」(目前該項中只有這麼一個選擇,沒有汽車、飛機等票務信息);網絡

  1.2 通常咱們查詢的是站與站之間的餘票狀況,好比「杭州」—「北京」,本文中的案例就是以「站站搜索接口」來進行實現(其餘接口使用方法相似);app

  1.3 點擊「站站搜索接口」項,頁面右邊是對應的使用方法與參數信息,包括調試、SDK下載連接,請求參數、請求示例、返回結果說明;ide

  1.4 該接口套餐爲免費,並且申請權限與訪問峯值都是沒有限制的(相信你們都喜歡這點);佈局

  1.5 使用該api惟一的條件是——得到api key,點擊頁面上的「獲取apikey」,會跳轉到相應頁面,其實也不難,只要用百度帳號登陸後完善我的信息並提交就能夠了(若是沒有百度帳號立刻花一點點時間申請一個);

 

  到此,就可以獲取到api key與相應的sdk了。不過若是採用Android來進行開發,能夠利用Java語言欄的示例代碼直接用網絡資源請求類HttpURLConnection來完成火車票信息的查詢,而不須要像Android sdk壓縮包中給出的例子那樣添加額外的jar包(「ApiStoreSDK1.0.4.jar」),你們根據需求自行選擇,只要能獲得想要的結果就OK。

  說實話,相比於其餘api接口申請步驟與套餐策略,去「哪兒網火車票」這個算是簡單使用了(絕非打廣告,誰用誰知道,只但願不要哪一天忽然不支持)。

  若是有朋友想作測試,但懶得本身動手獲取的,能夠直接用實例代碼中的api key(在文件MainActivity.java開頭部分,項目源碼連接在文章開頭已給出)。還有一點須要知道的是:獲取過一次api key,那麼以後再申請調用百度API Store中的其餘接口是經過的,至於套餐是否是免費就是另一回事了。

 

2. 火車票查詢實現

  整個查詢流程大概是這樣的:

  a. 輸入始發地、目的地,選好出發日期,點擊「查詢」按鍵開始嘗試火車票的查詢;

  b. 先判斷網絡是否鏈接,若沒有,則進入網絡設置選擇頁面(用戶本身選擇是設置「移動數據」或「無線網絡」);

  c. 若用戶沒有設置好網絡就返回到查詢主頁面,那麼查詢過程終止,除非再次點擊「查詢」按鍵;

  d. 若已經聯網或網絡設置好返回查詢頁面,那麼還會判斷一次始發地與目的地的輸入字串是否爲空,如有一個爲空則給出Toast提示;

  e. 若都不爲空,則開始查詢,若是站--站名稱無誤,就會將查詢結果顯示在列表中(結果是以出發時間爲升序排列);

  項目源碼經過文章開頭連接能夠下載到,因此接下來只針對部分重要或者須要注意的代碼進行分析,歡迎你們指正與討論。先上兩張效果圖:

               

  2.1 AndroidManifest.xml文件中添加網絡訪問與狀態獲取的權限申請:

1 <!-- 查詢火車票須要聯網 -->
2 <uses-permission android:name="android.permission.INTERNET" />
3 <!-- 查詢前進行網絡判斷, 須要獲取網絡狀態 -->
4 <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />

  注意:網絡相關權限無論android sdk版本是L仍是M(最新的已經爲N--7.0),都是應用安裝好後自動打開的。不像文件寫權限「WRITE_EXTERNAL_STORAGE」等被Google定義爲危險的權限類型,須要在應用使用過程當中彈出自定義的頁面讓用戶選擇是否容許打開某權限的申請。

  2.2 抽象出來一個簡單實用的類Utils:

 1 private static final String TAG = "MainActivity";
 2 
 3 public static Context mContext;
 4 
 5 public static boolean mIsBackFromSetNetwork;
 6 
 7 /*
 8  * 判斷網絡鏈接狀況
 9  */
10 public static boolean isNetWorkConnected(){
11     ConnectivityManager connectivityManager = (ConnectivityManager) mContext.getSystemService(Context.CONNECTIVITY_SERVICE);
12     NetworkInfo networkInfo = connectivityManager.getActiveNetworkInfo();
13 
14     return networkInfo != null && networkInfo.isConnected();
15 }
16 
17 /*
18  * Toast
19  */
20 public static void showToast(String message) {
21     Toast.makeText(mContext, message, Toast.LENGTH_SHORT).show();
22 }
23 
24 /*
25  * Log
26  */
27 public static void showLog(Object message) {
28     Log.i(TAG, ""+message);
29 }

  方法isNetworkConnected()判斷設備是否聯網,showToast(String message)和showLog(String message)則分別對Toast和Log信息的顯示進行了封裝(使用時傳入要顯示的信息便可,不用每次都繁瑣地去指定那些固定的參數值)。

  說明:Utils類中大部分變量與方法都是public static類型,須要特別注意mContext成員的賦值方式——最好不要將Activity的上下文環境(Context)賦給全局的Context類對象,如:

1 Utils.mContext = MainActivity.this

  由於當應用程序徹底退出以前全局變量是一直存在的,而若是那樣賦值以後,當Activity想destroy時,因爲還有其餘對象在引用其環境,可能出現內存泄漏。可行的方式之一:

1 Utils.mContext = getApplicationContext()

  2.3 輸入始發地與目的地並選擇好時間後,點擊界面上的「查詢」按鈕,調用方法startTicketInquireThread():

 1 private void startTicketInquireThread() {
 2     if(Utils.isNetWorkConnected()) {
 3         new Thread(new Runnable() {
 4 
 5             @Override
 6             public void run() {
 7                 inquireTrainTickets();
 8             }
 9         }).start();
10     } else {
11         Intent intent = new Intent(this, AccessNetworkActivity.class);
12         startActivity(intent);
13     }
14 }

  2.3.1 首先判斷設備是否聯網(包括移動與無線,具體見Utils類),若檢測到網絡不通,則跳轉到網絡設置的選擇頁面AccessNetworkActivity,界面以下:

  有兩個選擇:移動網絡和無線網絡,點擊後分別進入相應的系統網絡設置頁面:


         

  而這兩個頁面也是Activity,對應的Intent創建代碼分別以下:

1 Intent intent = null;
2 //移動數據
3 intent =  new Intent(Settings.ACTION_DATA_ROAMING_SETTINGS);
4 //無線網絡
5 intent =  new Intent(Settings.ACTION_WIFI_SETTINGS);

  以打開移動數據爲例,如圖:

  鏈接網絡後點擊back鍵返回到查詢主界面(或沒有打開直接返回),主程序MainActivity類會在onResume()方法中會緊接着作網絡鏈接判斷及相應的處理:

 1 public void onResume() {
 2     super.onResume();
 3 
 4     Utils.showLog("MainActivity Resume");
 5 
 6     if(Utils.mIsBackFromSetNetwork) {
 7         if (Utils.isNetWorkConnected()) {
 8             startTicketInquireThread();
 9         } else {
10             mResultMapList.clear();
11             mResultListAdapter.notifyDataSetChanged();
12         }
13 
14         Utils.mIsBackFromSetNetwork = false;
15     }
16 }

  這裏該解釋下Utils類中聲明的變量mIsBackFromSetNetwork了,用於標記應用返回MainActivity頁面時是由於網絡設置仍是其餘緣由(如從home界面從新回到本應用等)。若是該標記爲true,那麼進一步判斷網絡設置是否成功,若是成功則從新調用startTicketInquireThread()嘗試火車票查詢;若是不成功則將結果列表數據清空(無論以前列表中有沒有結果),真正作到界面上的列表清空代碼是第11行,由SimpleAdapter類對象mResultListAdapter調用方法notifyDataSetChanged(),該對象爲ListView對象mResultMapList的數據適配器。

   2.3.2 若一開始或者從網絡設置返回到查詢頁面時網絡鏈接正常,那麼開始真正的查詢過程,調用方法inquireTrainTickets(),該方法開始時會先判斷始發地與目的地是否都有輸入:

1 String startPlace = mStartPlaceET.getText().toString();
2 String endPlace = mEndPlaceET.getText().toString();
3 if(TextUtils.isEmpty(startPlace) || TextUtils.isEmpty(endPlace)) {
4     mHandler.sendEmptyMessage(ERROR_WHAT);
5 }

  TextUtils是很好用的一個類,其中有很多關於字符串的方法。代碼判斷若是始發地與目的地只要有一個爲空,就經過Handler類的消息機制給出提示信息。其實這裏徹底能夠直接調用Utils.showToast(String message)方法,可是下面會給出mHandler對象的定義,把查詢成功與失敗的處理放在了一塊兒,便於統一管理。

 1 private Handler mHandler = new Handler() {
 2 
 3     @Override
 4     public void handleMessage(Message msg) {
 5         super.handleMessage(msg);
 6 
 7         int what = msg.what;
 8         if(what == SUCCESS_WHAT) {
 9             mResultListAdapter.notifyDataSetChanged();
10         }else if(what == ERROR_WHAT) {
11             Utils.showToast(getString(R.string.please_input_place));
12         }
13     }
14 };

  即地點輸入不全時,利用Toast提示用戶先輸入再查詢,至於字串的定義在res/values/strings.xml文件中,這裏不進行描述。效果圖:

  2.3.3 若從網絡設置返回到查詢頁面,網絡鏈接仍是失敗,則清空列表數據,這點在上面onResume()方法的描述中已經說起了。

  2.4 在講解火車票數據的獲取及處理以前,還要補充一點,觀察方法startTicketInquireThread()判斷網絡鏈接成功後,調用方法inquireTrainTickets()是在新開的一個線程中。這樣作能夠說是廣泛的處理方式或是良好的習慣,由於像獲取網絡數據這種操做通常都比較費時,放在主UI線程中確定是不妥的(哪怕某次操做很是快,利用另外一個線程處理老是保險的作法)。其實說到這,你們已經知道爲何要用Handler類機制了,它很重要的做用就是幫助子線程來在主UI線程中更新組件信息,本案例中是更新利用ListView顯示的火車票信息。

  2.4.1 搜索站--站火車票的關鍵代碼定義在inquireTrainTickets()方法中:

 1 private final String API_KEY = "361cf2a2459552575b0e86e0f62302bc";
 2 private final String HTTP_URL = "http://apis.baidu.com/qunar/qunar_train_service/s2ssearch";
 3 private final String HTTP_ARG = "version=1.0&from=START&to=END&date=YEAR-MOUTH-DAY";
 4 
 5 String httpUrl;
 6 String httpArg = HTTP_ARG.replace("START", startPlace)
 7         .replace("END", endPlace)
 8         .replace("YEAR", ""+mYear)
 9         .replace("MOUTH", mMouth>9?""+mMouth:"0"+mMouth)
10         .replace("DAY", mDayOfMouth>9?""+mDayOfMouth:"0"+mDayOfMouth);
11 
12 Utils.showLog(httpArg);
13 
14 BufferedReader reader = null;
15 String result = null;
16 StringBuffer sbf = new StringBuffer();
17 httpUrl = HTTP_URL + "?" + httpArg;
18 try {
19     URL url = new URL(httpUrl);
20     HttpURLConnection connection = (HttpURLConnection) url
21             .openConnection();
22     connection.setRequestMethod("GET");
23     connection.setRequestProperty("apikey",  API_KEY);
24     connection.connect();
25     InputStream is = connection.getInputStream();
26     reader = new BufferedReader(new InputStreamReader(is, "UTF-8"));
27     String strRead = null;
28     while ((strRead = reader.readLine()) != null) {
29         sbf.append(strRead);
30         sbf.append("\r\n");
31     }
32     reader.close();
33     result = sbf.toString();
34 } catch (Exception e) {
35     e.printStackTrace();
36 }

  注意開頭的1-3行代碼須要放在方法體以外,這裏放在一塊兒只是方便說明,分別定義了當前api的key、站--站搜索接口連接、以及版本+地址+時間的格式字串。

  能夠看到在對時間的月和日設置時,對值小於10的數據作了加「0」處理。這是由於api的規定,接口主頁上明確給出了格式爲「出發日期:格式 YYYY-MM-DD」,也就是說年爲4位,月和日均爲2位(等到年爲5位的時候估計不坐火車了)。固然字串HTTP_ARG的定義能夠將待替換的部分用佔位符聲明,這樣就不用屢次調用replace(String src, String dst)方法了。

1 String HTTP_ARG = "version=1.0&from=%1$s&to=%2$s&date=%3$s-%4$s-%5$s";
2 String string2 = String.format(HTTP_ARG, startPlace, endPlace, ""+mYear, mMouth>9?""+mMouth:"0"+mMouth, mDayOfMouth>9?""+mDayOfMouth:"0"+mDayOfMouth);

  代碼是否是簡潔多了,佔位符序號從1開始,傳入參數時也必須按照聲明的順序。

  2.4.2 獲取的結果存在字串result中,這裏有兩種狀況:第一種是地址錯誤,那麼獲得的結果爲空;第二種就是成功獲取到火車票信息,須要在列表中進行顯示。無論哪一種狀況,都會在方法inquireTrainTickets()末尾調用了方法setTrainTicketList(String result),這裏有必要貼一下代碼:

 1 private void setTrainTicketList(String result) {
 2     mResultMapList.clear();
 3     try {
 4         JSONArray jsonArray1 = new JSONObject(result).getJSONObject("data").getJSONArray("trainList");
 5         mJsonArray = jsonArray1;
 6         for(int i=0; i<jsonArray1.length(); ++i) {
 7             JSONObject jsonObject1 = jsonArray1.getJSONObject(i);
 8             Map<String, Object> map = new HashMap<String, Object>();
 9             map.put(START_TIME, jsonObject1.get(START_TIME));
10             map.put(END_TIME, jsonObject1.get(END_TIME));
11             map.put(FROM, jsonObject1.get(FROM));
12             map.put(TO, jsonObject1.get(TO));
13             map.put(TRAIN_NO, jsonObject1.get(TRAIN_NO));
14             map.put(DURATION, jsonObject1.get(DURATION));
15             JSONArray jsonArray2 = jsonObject1.getJSONArray(SEAT_INFOS);
16             JSONObject jsonObject2 = jsonArray2.getJSONObject(0);
17             map.put(SEAT, jsonObject2.get(SEAT));
18             map.put(SEAT_PRICE, jsonObject2.get(SEAT_PRICE)+getString(R.string.train_price_unit));
19             map.put(REMAIN_NUM, jsonObject2.get(REMAIN_NUM)+getString(R.string.train_ticket_unit));
20             mResultMapList.add(map);
21         }
22     } catch (JSONException e) {
23         e.printStackTrace();
24     }
25     Collections.sort(mResultMapList, mComparatorResultMap);
26     mHandler.sendEmptyMessage(SUCCESS_WHAT);
27 }

  進入方法後,代碼第2行將列表數據清空,第26行則發送消息進而更新列表。因爲網絡請求結果通常爲JSON格式字串,因此每每須要將String對象轉化爲JSONObject對象再進行具體數據的提取。和以前網絡請求過程相似,本案例中JSON字串處理的代碼較基礎,不在展開詳述了,若是對這塊不熟悉的朋友能夠自學習下相關知識,入門仍是不難的。

  補充:關於JSON字串的處理,上面代碼雖然不算複雜也勉強可以達到目的,但從實用、簡潔與面向對象的角度來講是落伍了。以後有時間打算用GSon+GsonFormat進行實現,GsonFormat做爲一款插件,能夠快速創建JSON字串對應的JavaBean類,而後利用GSON對象操做數據,很是好用,能夠說它們將眼花繚亂的字串變成了咱們熟悉的類。

  2.4.3 前面提到列表中顯示的火車票信息是按照出發時間升序排列的,即將時間早的排在前面,這樣比較符合用戶的查詢習慣。而進列表數據進行排序的代碼爲:

1 Collections.sort(mResultMapList, mComparatorResultMap);

  這行代碼在上面setTrainTicketList(String result)方法的第25行,先看一下Collections中關於該方法的源碼:

1 public static void sort(List list, Comparator comparator) {
2     throw new RuntimeException("Stub!");
3 }

  第一個參數爲List對象,第二個參數爲Comparator對象。接着給出接口Comparator的源碼:

1 public interface Comparator {
2     public abstract int compare(Object obj, Object obj1);
3     public abstract boolean equals(Object obj);
4 }

  咱們的列表對象mResultMapList的類型爲List<Map<String, Object>>,知足第一個參數的List類型要求,可是其內部數據類型爲Map<String, Object>,既不是String也不是Object,這時候就須要本身實現compare(Obejct obj, Object obj1)方法了。

1 private Collator mCollator = Collator.getInstance(Locale.CHINA);
2 private Comparator<Map<String, Object>> mComparatorResultMap = new Comparator<Map<String, Object>>() {
3 
4     @Override
5     public int compare(Map<String, Object> lhs, Map<String, Object> rhs) {
6         return mCollator.compare(lhs.get(START_TIME), rhs.get(START_TIME));
7     }
8 };

  首先聲明一個Collator類對象,若是在中文環境中使用,在獲取實例時必須傳入Locale.CHINA類型,不然對中文字串的排序不許確或無效。其實重載該方法的關鍵就是在返回值部分,調用了Collator類的compare(Object object1, Object object2),源碼以下:

1 public int compare(Object object1, Object object2) {
2     return compare((String) object1, (String) object2);
3 }

  進而調用了其自身的抽象方法compare(String string1, String string2):

1 public abstract int compare(String string1, String string2);

  因此,最終進行比較的是String類型值,只要將Map<String, Object>對象中字段START_TIME對應的字串值當作參數傳入便可。順便將START_TIME等變量定義給出:

 1 private final String START_TIME = "startTime";
 2 private final String END_TIME = "endTime";
 3 private final String FROM = "from";
 4 private final String TO = "to";
 5 private final String TRAIN_NO = "trainNo";
 6 private final String DURATION = "duration";
 7 private final String SEAT_INFOS = "seatInfos";
 8 private final String SEAT = "seat";
 9 private final String SEAT_PRICE = "seatPrice";
10 private final String REMAIN_NUM = "remainNum";

  很簡單的定義,這裏想說明的是:若是採用本案例中的常規方式進行JSON字串的處理(不用其餘庫或插件),那麼就好將變量的值設置爲與JSON字串中key-value部分的key值一致,這樣在之後的處理過程當中比較直觀,不易出錯。

  2.4.4 運行過程序的朋友就會知道,點擊列表中的某一車次信息後,會彈出一個對話框,顯示具體的座位與餘票信息,這個和前一步顯示全部查詢結果相似,再也不進行講解了。感興趣的能夠閱讀相關代碼中方法showTicketsInfoDialog(String trainNo),經過列車號做爲索引來獲取具體信息,效果圖以下:

  須要注意的是:從獲取網絡請求結果到顯示在列表經歷了網絡請求->結果返回->String到JSONObject類型轉換->數據提取成Map對象添加到List列表->列表信息排序->顯示,通過排序以後列表中的火車票信息與網絡請求返回的順序很大多是不一致的,因此在點擊某一車次獲取具體信息時不可以以列表元素下標做爲索引,而是要以列車號爲索引(傳給showTicketsInfoDialog(String trainNo)方法)。還有就是對話框佈局是自定義的,有三部分組成:標題(包含當前車次號)、餘票(以座位類型區分)、確認按鈕。

  2.4.5 始發地與目的地的互換,就是將兩個EditText組件的值進行調換,給出代碼:

1 String startPlace = mStartPlaceET.getText().toString();
2 String endPlace = mEndPlaceET.getText().toString();
3 mStartPlaceET.setText(endPlace);
4 mEndPlaceET.setText(startPlace);

  2.4.6 關於時間的選擇,要好好地說一說。目前我國的購票政策是:從今天算起,可以買到60天以內的票。那麼咱們在實現時間的選擇器的時候,就要規定好可選時間的區間(總不能老提示用戶您選的時間不對吧),獲取當前時間與計算時間上限的代碼:

1 mCalendar = Calendar.getInstance();
2 mMinTimeMills = mCalendar.getTimeInMillis();
3 mMaxTimeMills = mMinTimeMills+59l*24*3600*1000;

  時間上下限的單位爲毫秒,變量類型爲long。順便提一下從Calendar實例中獲取年月日的代碼:

1 mYear = mCalendar.get(Calendar.YEAR);
2 mMouth = mCalendar.get(Calendar.MONTH)+1;
3 mDayOfMouth = mCalendar.get(Calendar.DAY_OF_MONTH);

  月份須要進行+1處理,即若是這個月爲8月,實際獲取的值爲7。還有獲取星期幾的狀況也是如此,

1 mDayOfWeek = mCalendar.get(Calendar.DAY_OF_WEEK);

  獲取的值多是1,2,3,4,5,6,7之一,對應的星期實際上是日,一,二,三,四,五,六,即也存在差一的狀況。

   下面貼出日期選擇對話框的佈局與實現代碼:

 1 <?xml version="1.0" encoding="utf-8"?>
 2 <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
 3     android:layout_width="match_parent"
 4     android:layout_height="match_parent"
 5     android:orientation="vertical"
 6     android:gravity="center"
 7     android:background="#ffffffff" >
 8     
 9     <DatePicker
10         android:id="@+id/train_datepicker"
11         android:layout_width="wrap_content"
12         android:layout_height="wrap_content"
13         android:calendarViewShown="false"
14         android:layout_marginTop="0dp"
15         android:layout_marginBottom="10dp" />
16     
17     <View 
18         style="@style/TrainLine" />
19     
20     <LinearLayout
21         android:layout_width="match_parent"
22         android:layout_height="60dp"
23         android:padding="0dp"
24         android:orientation="horizontal"
25         android:gravity="center" >
26         
27         <TextView
28             android:id="@+id/date_cancel"
29             android:layout_width="0dp"
30             android:layout_height="wrap_content"
31             android:layout_weight="1"
32             android:text="@string/cancel"
33             android:textColor="#ff000000"
34             android:gravity="center" />
35         
36         <View 
37             android:layout_width="0.5dp"
38             android:layout_height="match_parent"
39             android:background="#ffff0000" />
40         
41         <TextView
42             android:id="@+id/date_confirm"
43             android:layout_width="0dp"
44             android:layout_height="wrap_content"
45             android:layout_weight="1"
46             android:text="@string/confirm"
47             android:textColor="#ff000000"
48             android:gravity="center" />
49         
50     </LinearLayout>
51 
52 </LinearLayout>
 1 private void showDateSelectDialog() {
 2     final Dialog dateDialog = new Dialog(this, R.style.DialogFixTitle);
 3     dateDialog.setContentView(R.layout.train_datepicker);
 4     dateDialog.setCanceledOnTouchOutside(true);
 5     TextView cancel = (TextView) dateDialog.findViewById(R.id.date_cancel);
 6     cancel.setOnClickListener(new OnClickListener() {
 7 
 8         @Override
 9         public void onClick(View v) {
10             dateDialog.cancel();
11         }
12     });
13     TextView confirm = (TextView) dateDialog.findViewById(R.id.date_confirm);
14     confirm.setOnClickListener(new OnClickListener() {
15 
16         @Override
17         public void onClick(View v) {
18             dateDialog.cancel();
19             mTargetDayTV.setText(tempTargetDay);
20             getDateParams();
21             startTicketInquireThread();
22         }
23     });
24     DatePicker datePicker = (DatePicker) dateDialog.findViewById(R.id.train_datepicker);
25     datePicker.setMinDate(mMinTimeMills);
26     datePicker.setMaxDate(mMaxTimeMills);
27     datePicker.init(mYear, mMouth-1, mDayOfMouth, new OnDateChangedListener() {
28 
29         @Override
30         public void onDateChanged(DatePicker view, int year, int monthOfYear, int dayOfMonth) {
31             mCalendar.set(year, monthOfYear, dayOfMonth);
32             tempTargetDay = getTempTargetDay(year, monthOfYear+1, dayOfMonth, mCalendar.get(Calendar.DAY_OF_WEEK));
33 
34             Utils.showLog(tempTargetDay);
35         }
36     });
37     dateDialog.show();
38 }

  佈局文件沒什麼好說的,除了兩個按鈕,較關鍵的就是DatePicker,是Android自帶的時間選擇組件組件。效果圖:

            

  首先從佈局中獲取組件實例,進行時間區間上下限的設置;接着調用init(mYear, mMouth-1, mDayOfMouth, new OnDateChangedListener())方法對DatePicker類對象dataPicker進行初始化,一樣地,傳入月份時須要作-1處理;而後在其中重載監聽器OnDateChangedListener接口的onDateChanged()方法,能夠獲取到DatePicker組件選擇後的結果。

  時間選擇好後,點擊「確認」按鈕會調用作兩件事情:一、更新時間選擇按鈕的信息,顯示爲最新選擇的日期;二、調用方法startTicketInquireThread()開始嘗試對應時間的火車票查詢,就不用再次點擊「查詢」按鍵了。

  不過這個時間選擇組件在不一樣機器上的顯示形式會不同,感受上面最左圖好醜有沒有。若是想作到像去哪兒app(上面最右圖)那樣統一的效果,還得想其餘方法(好比萬能的自定義)。

  2.4.7 最後貼一下網絡請求返回數據,當地址錯誤等緣由引發查詢結果爲空時:

  {

    "ret":true,

    "data":{

      "trainList":null

    }

  }

  正常的火車票信息,時間爲2016年9月13日 週二,杭州到北京:

{
  "ret":true,
  "data":{
    "trainList":[
      {

        "trainType":"直達特快","trainNo":"Z10","from":"杭州","to":"北京",

        "startTime":"17:17","endTime":"07:34","duration":"14時17分",

        "seatInfos":[

              {"seat":"無座","seatPrice":"192","remainNum":278},

              {"seat":"硬座","seatPrice":"192","remainNum":526},

              {"seat":"硬臥","seatPrice":"328","remainNum":365},

              {"seat":"軟臥","seatPrice":"515","remainNum":4}]

      },
      {

        "trainType":"空調特快","trainNo":"T32","from":"杭州","to":"北京",

        "startTime":"18:20","endTime":"10:26","duration":"16時6分",

        "seatInfos":[

              {"seat":"高級軟臥","seatPrice":"949","remainNum":4},

              {"seat":"無座","seatPrice":"192","remainNum":236},

              {"seat":"硬座","seatPrice":"192","remainNum":365},

              {"seat":"硬臥","seatPrice":"328","remainNum":164},

              {"seat":"軟臥","seatPrice":"515","remainNum":28},

              {"seat":"一人軟包","seatPrice":"1342","remainNum":2}]

      }
    ]
  }
}

 

3. 總結

  本文利用百度API Store中去哪兒網提供火車票查詢接口,實現了國內簡單的站--站火車票搜索功能。整個過程當中,網絡數據請求其實比較簡單,難的是對獲取到的數據進行處理,以及作出美觀、交互性強的界面。因此,後續有時間會對案例進行優化,目前想到的能夠作的事情包括:

  a. 顯示火車票信息的列表能夠用RecyclerView替代ListView,添加下拉刷新等功能;

  b. 時間選擇組件的統一化,讓不一樣設備顯示出的畫面一致;

  c. 始發地與目的地的設置,再也不利用EditText組件進行文本輸入,而是像通常上線的app那樣直接提供地區選擇列表;

  d. 將項目改用Android Studio實現,添加引用庫或者插件簡直so easy;

  對於火車票的預訂暫時沒什麼想法,畢竟涉及到支付了,不過關於這塊內容但願有開發經驗的前輩能夠指點一二,沒經驗的也歡迎一塊兒學習與討論,謝謝。

相關文章
相關標籤/搜索