安卓學習日誌 Day11 — JSON 解析

概述

我們將設計一個地震報告應用 (Quake Report),目的是讓用戶能夠更好地獲取身邊或全世界有關地震活動的諮詢。

有好的目標才能做出好的應用,那麼體現出好目標的最好方式就是構建一個簡單易用、目的明確的用戶界面。

在這裏插入圖片描述

USGS 網站

探索 USGS 站點,並瞭解如何請求應用所需要的數據。同時瞭解數據以何種結構返回給我們,並建立一個方案,在代碼中以編程方式將數據提取出來。

以用戶身份瀏覽

打開 USGS 站點,在 Spreadsheet Format 頁面中我們可以看到 CSV 格式的地震數據,這是一種看起來類似表格的數據格式。

用戶可以從該頁面下載到 地震數據的 CSV 文件,下載之後可以使用 Excel(工作表)來打開並查看,數據的的結構確實如表格一樣清晰,易於查看。

在這裏插入圖片描述

以開發者身份瀏覽

而在 GeoJSON Summary Format 頁面中 USGS 爲開發者提供了一個具有特殊格式的數據,叫做 JSON。開發者可以通過 USGS 規定的格式來請求需要的數據(請求方法可查看文檔 API Documentation - Earthquake Catalog)。

比如在瀏覽器中訪問 這個地址 將會得到下圖所示的響應:

在這裏插入圖片描述

導入項目

當某位 Android 專業開發者加入新項目時,通常,團隊可能已經有了一個可繼續處理的應用。 最好知道如何讀取現有代碼庫,因爲通常不需要從頭創建新應用。

我已經在 GitHub 創建好了一個 Quake Report 應用 的初始版本,使用 Git 命令克隆該項目:

git clone -b starting-point https://github.com/HEY-BLOOD/QuakeReport.git

然後按以下說明將 Quake Report 項目導入計算機上的 Android Studio 中:

  • 打開 Android Studio。
  • 選擇“文件”(File) >“導入項目”(Import Project),然後選擇 “Quake Report” 文件夾。 導入項目需要一些時間。
  • 成功導入該應用後,在 Android 設備(手機、平板電腦或模擬器)上運行該應用。其外觀類似於 以下屏幕截圖。

在這裏插入圖片描述

在 Android Studio 中,瀏覽代碼庫找到 Quake Report 應用,並熟悉項目文件。

可以看出在 Quake Report 應用的初始版本只有一個頁面,該頁面只顯示了地震位置信息(place)的類別,接下來更改項目使這個頁面的列表中每一項都顯示一次地震的 震級(magnitude)、位置(place)、時間(time)。

這需要使用到 ListView 和 自定義適配器的知識,我在 安卓學習日誌 Day03 — Custom Adapter 已經說明過了,更改後的 Quake Report 應用應該和下方的截圖相似。(更改前後的代碼對比可以在 此鏈接 查看)

在這裏插入圖片描述

JSON

Json 文本的鍵和值以冒號分隔,同時每個鍵/值對以逗號分隔。 JSON 支持可在大多數編程語言中找到的基本數據類型, 如,數字、字符串、布爾值、數組和對象。

要處理 JSON 格式的數據需要使用到 Java 中的 JSON 對象,具體方法請參考鏈接 Android JSON Parsing with Examples - Tutlane

JSON 處理

那麼接下來,試着對真實的地震數據(JSON 格式)進行處理,並顯示到 QuakeReport 應用當中。這裏我藉助了 QueryUtils.java 類 的代碼,使用該類 只需更改 文件頂部的 SAMPLE_JSON_RESPONSE String 常量中定義的 JSON 字符串(暫時採用硬編碼的方法獲取 JSON 字符串) 以及 QueryUtils.extractEarthquakes() 方法中剩下的 TODO 部分即可,以下是 需要執行的一些僞代碼:

 將 SAMPLE_JSON_RESPONSE String 轉換爲 JSONObject
 提取 "features" JSONArray
 依次循環數組中的每個特徵
 獲取 i 位置的 earthquake JSON 對象
 獲取 "properties" JSON 對象
 針對震級提取 "mag"
 針對位置提取 "place"
 針對時間提取 "time"
 通過震級、位置和時間創建 Earthquake java 對象
 將地震添加到地震列表

改 EarthquakeActivity 調用 QueryUtils.extractEarthquakes() 從 JSON 響應獲取 Earthquake 對象的列表。

在 EarthquakeActivity 中,不再創建僞地震列表,而是替換爲以下代碼:

ArrayList<Earthquake> earthquakes = QueryUtils.extractEarthquakes();

然後更改 QueryUtils.extractEarthquakes() 方法中剩下的 TODO 部分如下:

    /**
     * Return a list of {@link Earthquake} objects that has been built up from
     * parsing a JSON response.
     */
    public static ArrayList<Earthquake> extractEarthquakes() {
   
   

        // Create an empty ArrayList that we can start adding earthquakes to
        ArrayList<Earthquake> earthquakes = new ArrayList<>();

        // Try to parse the SAMPLE_JSON_RESPONSE. If there's a problem with the way the JSON
        // is formatted, a JSONException exception object will be thrown.
        // Catch the exception so the app doesn't crash, and print the error message to the logs.
        try {
   
   
            // TODO: Parse the response given by the SAMPLE_JSON_RESPONSE string and
            // build up a list of Earthquake objects with the corresponding data.
            JSONObject rootJsonObj = new JSONObject(SAMPLE_JSON_RESPONSE);
            JSONArray featuresArray = rootJsonObj.getJSONArray("features");
            for (int i = 0; i < featuresArray.length(); i++) {
   
   
                JSONObject feature = featuresArray.getJSONObject(i);
                JSONObject properties = feature.getJSONObject("properties");
                String earthquakeMag = properties.getDouble("mag") + "";
                String earthquakePlace = properties.getString("place");
                String earthquakeTime = properties.getLong("time") + "";

                Earthquake earthquake = new Earthquake(earthquakeMag, earthquakePlace, earthquakeTime);

                earthquakes.add(earthquake);
            }
        } catch (JSONException e) {
   
   
            // If an error is thrown when executing any of the above statements in the "try" block,
            // catch the exception here, so the app doesn't crash. Print a log message
            // with the message from the exception.
            Log.e("QueryUtils", "Problem parsing the earthquake JSON results", e);
        }

        // Return the list of earthquakes
        return earthquakes;
    }

更改完成後試着運行一下應用,這時 地震信息的列表 應該如下圖類似(更改前後的代碼對比參考 此鏈接):

在這裏插入圖片描述

Unix 時間

首先來更新 發生地震時顯示相關信息的方式。以應用目前的狀態,可以 輕鬆看到每次地震的震級和位置,但時間 則是一長串數字。第一次地震的時間是 1454124312220,這種格式稱爲 Unix 時間,此格式對我們沒有幫助。

Unix 時間,將時間描述爲 從英國 1970 年 1 月 1 日 0 時 0 分 0 秒起至現在的總毫秒數 (更技術性的叫法爲協調世界時間)。

我們想要顯示的時間信息 應包含具體的年月日,所以需要將此 Unix 時間轉換成更易懂的日期 和時間,但這將是一個非常複雜的命題。我們可能希望 按照用戶的本地時區顯示時間,但時區極爲複雜,另外,根據用戶在 世界所處的位置,日期的書寫方式也會有所不同。

日期格式設置

很慶幸,我們無需自己處理日期格式。Java 中有 一個很奇妙的名爲 SimpleDateFormat 的類,知曉所有關於時區的信息, 以及知曉如何按照世界的不同地區書寫日期,它會 處理這些難題。

接下來,使用 SimpleDateFormat 的類將 QuakeReport 應用中 的時間格式更改爲 人類可讀的 日期時間格式(如,Jan 3, 2020)。

由於 SimpleDateFormat.format() 方法需要一個Date 對象,而該對象需要長整型數據作爲輸入,因此我們應從 JSON 響應將地震時間提取爲長整型 (long) 數據類型(而不是 String)。長整型是 Java 中的 8 個原始數據類型之一。

將 QueryUtils.extractEarthquakes() 方法修改爲 從 JSON 響應中將地震時間提取爲 “long” 數據類型。

// Extract the value for the key called "time"
long time = properties.getLong("time");

這會引發錯誤,因爲 Earthquake 構造函數不 接受長整型數據類型作爲第三輸入參數。因此,我們需要 對 Earthquake 類進行一些更改。

更改全局變量的數據類型,對其進行重命名,以使名稱 更能描述其中所存儲的信息。

在 Earthquake.java 中:

    /** Time of the earthquake */
    private long timeInMilliseconds;

修改 Earthquake 構造函數,使其採用長整型數據類型表示時間 並更新 Javadoc 註釋。

在 Earthquake.java 中:

    /**
     * 構造一個新的 {@link Earthquake} 對象。
     *
     * @param mag   表示地震的震級(大小)
     * @param place 表示地震的城市位置
     * @param timeInMilliseconds  表示地震發生時以毫秒(根據 Epoch)計的時間
     */
    public Earthquake(String mag, String place, long timeInMilliseconds) {
   
   
        this.mag = mag;
        this.place = place;
        this.timeInMilliseconds = timeInMilliseconds;
    }

更新公共 getter 方法,以便返回長整型數據類型。

在 Earthquake.java 中:

    /**
     * 返回地震的時間。
     */
    public long getTimeInMilliseconds() {
   
   
        return timeInMilliseconds;
    }

當 EarthquakeAdapter 創建每次地震的列表項時, 適配器必須將以毫秒爲單位的時間轉換爲相應 格式的日期和時間。現在,有 2 個 TextView 來分別顯示日期 和時間,因此我們需要修改 earthquake_item.xml 佈局以添加另外一個 TextView 併爲其提供一個合適的視圖 ID,並將地震發生的 日期 和 時間 縱向排列。

更改 earthquake_item.xml 文件:

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:orientation="horizontal"
    android:padding="@dimen/dp_16">

    <TextView
        android:id="@+id/mag_text"
        android:layout_width="0dp"
        android:layout_height="wrap_content"
        android:layout_gravity="center_vertical"
        android:layout_weight="1"
        tools:text="8.0" />

    <TextView
        android:id="@+id/place_text"
        android:layout_width="0dp"
        android:layout_height="wrap_content"
        android:layout_gravity="center_vertical"
        android:layout_weight="3"
        tools:text="Wenchuan Sichuan" />

    <LinearLayout
        android:layout_width="0dp"
        android:layout_height="wrap_content"
        android:layout_weight="2"
        android:orientation="vertical">

        <TextView
            android:id="@+id/date_text"
            android:layout_width="match_parent"
            android:layout_height="0dp"
            android:layout_weight="1"
            android:gravity="right"
            tools:text="May 12, 2008" />

        <TextView
            android:id="@+id/time_text"
            android:layout_width="match_parent"
            android:layout_height="0dp"
            android:layout_weight="1"
            android:gravity="right"
            android:textAllCaps="true"
            tools:text="3:00 PM" />

    </LinearLayout>

</LinearLayout>

在 EarthquakeAdapter 中,修改 getView() 方法, 使其生成要在相應的 TextView 中顯示的 格式化字符串。使用 currentEarthquake.getTimeInMilliseconds() 從當前的 Earthquake 對象中獲取時間,並將其傳遞到 Date 構造函數中,以形成一個新的 Date 對象。

在 EarthquakeAdapter.java 中:

public View getView(int position, @Nullable View convertView, @NonNull ViewGroup parent) {
   
   
        // 檢查是否已經有可以重用的列表項視圖(稱爲 convertView),
        // 否則,如果 convertView 爲 null,則 inflate 一個新列表項佈局。
        View itemView = convertView;
        if (itemView == null) {
   
   
            itemView = LayoutInflater.from(getContext()).inflate(
                    R.layout.earthquake_item, parent, false);
        }

        // 在地震列表中的給定位置找到地震
        Earthquake currentEarthquake = getItem(position);

        // 找到視圖 ID 爲 magnitude 的 TextView
        TextView magnitudeView = (TextView) itemView.findViewById(R.id.mag_text);
        // 在該 TextView 中顯示目前地震的震級
        magnitudeView.setText(currentEarthquake.getMag());

        // 找到視圖 ID 爲 location 的 TextView
        TextView locationView = (TextView) itemView.findViewById(R.id.place_text);
        // 在該 TextView 中顯示目前地震的位置
        locationView.setText(currentEarthquake.getPlace());

        // 根據地震時間(以毫秒爲單位)創建一個新的 Date 對象
        Date dateObject = new Date(currentEarthquake.getTimeInMilliseconds());

        // 找到視圖 ID 爲 date 的 TextView
        TextView dateView = (TextView) itemView.findViewById(R.id.date_text);
        // 設置日期字符串的格式(即 "Mar 3, 1984")
        String formattedDate = formatDate(dateObject);
        // 在該 TextView 中顯示目前地震的日期
        dateView.setText(formattedDate);

        // 找到視圖 ID 爲 time 的 TextView
        TextView timeView = (TextView) itemView.findViewById(R.id.time_text);
        // 設置時間字符串的格式(即 "4:30PM")
        String formattedTime = formatTime(dateObject);
        // 在該 TextView 中顯示目前地震的時間
        timeView.setText(formattedTime);

        // 返回目前顯示適當數據的列表項視圖
        return itemView;
    }

以上代碼包含兩個輔助方法,formatDate() 和 formatTime(),創建這兩個方法來接收 Date 對象 並使用 SimpleDateFormat 返回一個格式正確的日期字符串。

    /**
     * 從 Date 對象返回格式化的日期字符串(即 "Mar 3, 1984")。
     */
    private String formatDate(Date dateObject) {
   
   
        SimpleDateFormat dateFormat = new SimpleDateFormat("LLL dd, yyyy");
        return dateFormat.format(dateObject);
    }

    /**
     * 從 Date 對象返回格式化的時間字符串(即 "4:30 PM")。
     */
    private String formatTime(Date dateObject) {
   
   
        SimpleDateFormat timeFormat = new SimpleDateFormat("h:mm a");
        return timeFormat.format(dateObject);
    }

更改代碼的前後對比參考 此鏈接

最後運行一下應用,查看效果

在這裏插入圖片描述

位置信息拆分

如果查看從 USGS 數據集收到的 JSON 響應, 會發現 “place” 鍵的字符串值有兩種形式: “74km NW of Rumoi, Japan”、“Pacific-Antarctic Ridge”

目標是將一個位置字符串分成兩個字符串, 使它們能夠在兩個不同的 TextView 中顯示由於 某些位置可能不會提供與位置之間的公里距離詳細信息, 可以使用 "Near the" 來替代。 "74km NW of Rumoi, Japan" --> "74km NW of""Rumoi, Japan""Pacific-Antarctic Ridge" --> "Near the""Pacific-Antarctic Ridge"

爲了方便引用這些單獨的字符串, 可以將一個字符串稱作主要位置(即 "Rumoi, Japan""Pacific-Antarctic Ridge"),將另一字符串稱作 位置偏移("74km NW of""Near the")。

下面 在 getView() 方法中對 位置信息進行拆分處理並顯示到各自的 TextView 當中。

要完成模擬UI的效果,應該在列表項佈局中加入另一個TextView, 兩個位置TextView的ID分別爲 “@+id/primary_place”“@+id/place_offset”

earthquake_item.xml 中:

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:orientation="horizontal"
    android:padding="@dimen/dp_16">

    <TextView
        android:id="@+id/mag_text"
        android:layout_width="0dp"
        android:layout_height="wrap_content"
        android:layout_gravity="center_vertical"
        android:layout_weight="1"
        tools:text="8.0" />

    <TextView
        android:id="@+id/place_offset_text"
        android:layout_width="0dp"
        android:layout_height="wrap_content"
        android:layout_gravity="center_vertical"
        android:layout_weight="2"
        tools:text="30km S of" />

    <TextView
        android:id="@+id/primary_place_text"
        android:layout_width="0dp"
        android:layout_height="wrap_content"
        android:layout_gravity="center_vertical"
        android:layout_weight="2"
        tools:text="San Francisco, CA" />

    <LinearLayout
        android:layout_width="0dp"
        android:layout_height="wrap_content"
        android:layout_weight="2"
        android:orientation="vertical">

        <TextView
            android:id="@+id/date_text"
            android:layout_width="match_parent"
            android:layout_height="0dp"
            android:layout_weight="1"
            android:gravity="right"
            tools:text="May 12, 2008" />

        <TextView
            android:id="@+id/time_text"
            android:layout_width="match_parent"
            android:layout_height="0dp"
            android:layout_weight="1"
            android:gravity="right"
            android:textAllCaps="true"
            tools:text="3:00 PM" />

    </LinearLayout>

</LinearLayout>

需要做出的另外一個改動是爲那些 沒有位置偏移(以公里爲單位)的位置添加 “Near the” 作爲字符串資源。原始 JSON 響應中沒有該字符串,添加該字符串的目的 是滿足應用程序 UI 設計。我們在 res/values/strings.xml 資源文件中添加該字符串,所以不必將英文字符串 硬編碼轉換爲 Java 代碼。

在 res/values/strings.xml 中添加:

 <!-- Default text to show with the earthquake location ("Near the Pacific-Antarctic Ridge")
  if no specific kilometer distance from the primary location is given [CHAR LIMIT=30] -->
 <string name="near_the">Near the</string>

接下來,需要修改 EarthquakeAdapter getView() 方法。在 此解決方案的底部,有指向完整 EarthquakeAdapter 類 的鏈接。如之前所提到的,即使你的實現方法與我們並不完全相同, 也沒有任何問題。下面是創建所需字符串的 一種方法。

首先,從 Earthquake 對象獲取原始位置字符串 並將其存儲在一個變量中。

EarthquakeAdapter.getView()中:

String originalPlace = currentEarthquake.getPlace();

同時創建新變量(主要位置和位置偏移) 來存儲拆分後的兩個字符串。

String primaryPlace;
String placeOffset;

使用 String 類中的 split(String string) 方法 將原始字符串在 " of " 文本處拆分 開來。結果將爲兩個字符串,分別包含 " of " 之前的字符和 " of " 之後的字符。由於需要頻繁引用 " of " 文本, 我們可以在 EarthquakeAdapter 類的頂部定義一個 static final String 常量(全局變量)。

在 EarthquakeAdapter 中:

private static final String LOCATION_SEPARATOR = " of ";

如果原始位置 字符串爲 "74km NW of Rumoi, Japan",並且使用 LOCATION_SEPARATOR 拆分字符串,那麼會得到一個 String 數組作爲返回值。在 String 數組中,第 0 個數組元素爲 "74km NW",第一個數組元素爲 "Rumoi, Japan"。然後,也會將 " of " 文本重新添加到數組的第 0 個元素中, 因此 placeOffset 會顯示 "74km NW of "

還有一個問題就是某些位置字符串沒有 位置偏移。因此在決定使用 LOCATION_SEPARATOR 拆分字符串之前, 應先檢查原始位置字符串 是否包含該分隔符。如果原始位置字符串中 沒有 LOCATION_SEPARATOR,那麼可以 假定應將 "Near the" 文本用作位置偏移, 並將原始位置字符串用作 主要位置。下面代碼的實現:

在 EarthquakeAdapter getView() 中:

        String originalPlace = currentEarthquake.getPlace();
        if (originalPlace.contains(LOCATION_SEPARATOR)) {
   
   
            String[] parts = originalPlace.split(LOCATION_SEPARATOR);
            placeOffset = parts[0] + LOCATION_SEPARATOR;
            primaryPlace = parts[1];
        } else {
   
   
            placeOffset = getContext().getString(R.string.near_the);
            primaryPlace = originalPlace;
        }

獲得兩個單獨的字符串之後,可以在 列表項佈局的兩個 TextView 中顯示這兩個字符串。

        // 找到 place 位置信息 的兩個 TextView
        TextView placeOffsetView = (TextView) itemView.findViewById(R.id.place_offset_text);
        TextView primaryPlaceView = (TextView) itemView.findViewById(R.id.primary_place_text);
        // 在該 TextView 中顯示目前地震的位置
        placeOffsetView.setText(placeOffset);
        primaryPlaceView.setText(primaryPlace);

更改完成後運行應用,QuakeReport 應該和下面的截圖類似(更改前後的代碼差異參考 此鏈接):

在這裏插入圖片描述

震級信息

震級顯示一位小數

如果已看到服務器返回的 JSON 響應中的震級值, 就會注意到震級有時帶有一位或者兩位小數, 如 “7.2” 或 “7.29”。但是根據 設計模擬,最好以帶有一位小數的字符串形式 顯示地震震級(即 “7.2”),以便震級值可以居中顯示在 顏色漂亮的圓圈中。

Java 中有一個類可以幫到我們!這個類叫做 DecimalFormat 類。順便還有 NumberFormat 類可用於處理所有 類型的數字的格式,但這是一個抽象類, 而 DecimalFormat 是一個具體類,可以與之交互。

如:

 DecimalFormat formatter = new DecimalFormat("0.00");
 String output = formatter.format(2.3234);

順便一提,DecimalFormat format() 方法 以雙精度值作爲輸入。

下面在 QuakeReport 應用中針對 震級信息進行操作。

首先需要將震級存儲爲 double 數據類型。 開始時需要在解析 JSON 響應時以 double 形式 提取震級值。

在 QueryUtils extractEarthquakes() 中:

                // 提取名爲 "mag" 的鍵的值
                double earthquakeMag = properties.getDouble("mag");

這會在應用的其他部分造成連鎖反應,需要 更新 Earthquake 類,例如爲了處理 double 值形式的震級的存儲問題。同時在 EarthquakeAdapter 中, 當嘗試顯示震級時,需要使用 DecimalFormat 類 對震級進行格式化,以只顯示一位小數。

Android Studio 會顯示一條錯誤,因爲不能將 double 值傳遞到 Earthquake 構造函數中,因此需要 更新 Earthquake 類。在 Earthquake 類中: magnitude 全局變量應該是 double, 構造函數應當接受 double 值作爲輸入 , 並且 magnitude getter 方法應該返回一個 double。

Earthquake.java 中:

public class Earthquake {
   
   
    /** 地震震級 */
    private double mag;
	
    …………
        
    public double getMag() {
   
   
        return mag;
    }
	
    …………
    
    /**
     * 構造一個新的 {@link Earthquake} 對象。
     *
     * @param mag   表示地震的震級(大小)
     * @param place 表示地震的城市位置
     * @param timeInMilliseconds  表示地震發生時以毫秒(根據 Epoch)計的時間
     */
    public Earthquake(double mag, String place, long timeInMilliseconds) {
   
   
        this.mag = mag;
        this.place = place;
        this.timeInMilliseconds = timeInMilliseconds;
    }
}

在 EarthquakeAdapter 中,將數值格式化爲 UI 所需的形式。在這裏,僅需要顯示 一位小數。爲此創建了一個名爲 formatMagnitude() 的輔助方法, 該方法接收 double 值作爲輸入,並返回格式化後的字符串。 該幫助方法使用模式字符串 "0.0" 初始化 DecimalFormat 對象實例。然後在適配器的 getView() 方法中, 我們可以從當前的 Earthquake 對象中讀取震級值, 將數值格式化爲字符串,並更新 TextView 以顯示這個值。

在 EarthquakeAdapter.java 中:

import java.text.DecimalFormat;

…………

    @NonNull
    @Override
    public View getView(int position, @Nullable View convertView, @NonNull ViewGroup parent) {
   
   
		…………

        // 使用視圖 ID magnitude 找到 TextView
        TextView magnitudeView = (TextView) itemView.findViewById(R.id.mag_text);
        // 格式化震級使其顯示一位小數
        String formattedMagnitude = formatMagnitude(currentEarthquake.getMag());
        // 在該 TextView 中顯示目前地震的震級
        magnitudeView.setText(formattedMagnitude);

        …………
    }

    /**
     * 從十進制震級值返回格式化後的僅顯示一位小數的震級字符串
     * (如“3.2”)。
     */
    private String formatMagnitude(double magnitude) {
   
   
        DecimalFormat magnitudeFormat = new DecimalFormat("0.0");
        return magnitudeFormat.format(magnitude);
    }


…………

QueryUtils.java 更改假數據,方便用於測試:

    /**
     * Sample JSON response for a USGS query
     */
    private static final String SAMPLE_JSON_RESPONSE = "{\"type\":\"FeatureCollection\",\"metadata\":{\"generated\":1462295443000,\"url\":\"http://earthquake.usgs.gov/fdsnws/event/1/query?format=geojson&starttime=2016-01-01&endtime=2016-01-31&minmag=6&limit=10\",\"title\":\"USGS Earthquakes\",\"status\":200,\"api\":\"1.5.2\",\"limit\":10,\"offset\":1,\"count\":10},\"features\":[{\"type\":\"Feature\",\"properties\":{\"mag\":7.2,\"place\":\"88km N of Yelizovo, Russia\",\"time\":1454124312220,\"updated\":1460674294040,\"tz\":720,\"url\":\"http://earthquake.usgs.gov/earthquakes/eventpage/us20004vvx\",\"detail\":\"http://earthquake.usgs.gov/fdsnws/event/1/query?eventid=us20004vvx&format=geojson\",\"felt\":2,\"cdi\":3.4,\"mmi\":5.82,\"alert\":\"green\",\"status\":\"reviewed\",\"tsunami\":1,\"sig\":798,\"net\":\"us\",\"code\":\"20004vvx\",\"ids\":\",at00o1qxho,pt16030050,us20004vvx,gcmt20160130032510,\",\"sources\":\",at,pt,us,gcmt,\",\"types\":\",cap,dyfi,finite-fault,general-link,general-text,geoserve,impact-link,impact-text,losspager,moment-tensor,nearby-cities,origin,phase-data,shakemap,tectonic-summary,\",\"nst\":null,\"dmin\":0.958,\"rms\":1.19,\"gap\":17,\"magType\":\"mww\",\"type\":\"earthquake\",\"title\":\"M 7.2 - 88km N of Yelizovo, Russia\"},\"geometry\":{\"type\":\"Point\",\"coordinates\":[158.5463,53.9776,177]},\"id\":\"us20004vvx\"},\n" +
            "{\"type\":\"Feature\",\"properties\":{\"mag\":6.23,\"place\":\"94km SSE of Taron, Papua New Guinea\",\"time\":1453777820750,\"updated\":1460156775040,\"tz\":600,\"url\":\"http://earthquake.usgs.gov/earthquakes/eventpage/us20004uks\",\"detail\":\"http://earthquake.usgs.gov/fdsnws/event/1/query?eventid=us20004uks&format=geojson\",\"felt\":null,\"cdi\":null,\"mmi\":4.1,\"alert\":\"green\",\"status\":\"reviewed\",\"tsunami\":1,\"sig\":572,\"net\":\"us\",\"code\":\"20004uks\",\"ids\":\",us20004uks,gcmt20160126031023,\",\"sources\":\",us,gcmt,\",\"types\":\",cap,geoserve,losspager,moment-tensor,nearby-cities,origin,phase-data,shakemap,tectonic-summary,\",\"nst\":null,\"dmin\":1.537,\"rms\":0.74,\"gap\":25,\"magType\":\"mww\",\"type\":\"earthquake\",\"title\":\"M 6.1 - 94km SSE of Taron, Papua New Guinea\"},\"geometry\":{\"type\":\"Point\",\"coordinates\":[153.2454,-5.2952,26]},\"id\":\"us20004uks\"},\n" +
            "{\"type\":\"Feature\",\"properties\":{\"mag\":6.3,\"place\":\"50km NNE of Al Hoceima, Morocco\",\"time\":1453695722730,\"updated\":1460156773040,\"tz\":0,\"url\":\"http://earthquake.usgs.gov/earthquakes/eventpage/us10004gy9\",\"detail\":\"http://earthquake.usgs.gov/fdsnws/event/1/query?eventid=us10004gy9&format=geojson\",\"felt\":117,\"cdi\":7.2,\"mmi\":5.28,\"alert\":\"green\",\"status\":\"reviewed\",\"tsunami\":0,\"sig\":695,\"net\":\"us\",\"code\":\"10004gy9\",\"ids\":\",us10004gy9,gcmt20160125042203,\",\"sources\":\",us,gcmt,\",\"types\":\",cap,dyfi,geoserve,impact-text,losspager,moment-tensor,nearby-cities,origin,phase-data,shakemap,tectonic-summary,\",\"nst\":null,\"dmin\":2.201,\"rms\":0.92,\"gap\":20,\"magType\":\"mww\",\"type\":\"earthquake\",\"title\":\"M 6.3 - 50km NNE of Al Hoceima, Morocco\"},\"geometry\":{\"type\":\"Point\",\"coordinates\":[-3.6818,35.6493,12]},\"id\":\"us10004gy9\"},\n" +
            "{\"type\":\"Feature\",\"properties\":{\"mag\":7.1,\"place\":\"86km E of Old Iliamna, Alaska\",\"time\":1453631430230,\"updated\":1460156770040,\"tz\":-540,\"url\":\"http://earthquake.usgs.gov/earthquakes/eventpage/us10004gqp\",\"detail\":\"http://earthquake.usgs.gov/fdsnws/event/1/query?eventid=us10004gqp&format=geojson\",\"felt\":1816,\"cdi\":7.2,\"mmi\":6.6,\"alert\":\"green\",\"status\":\"reviewed\",\"tsunami\":1,\"sig\":1496,\"net\":\"us\",\"code\":\"10004gqp\",\"ids\":\",at00o1gd6r,us10004gqp,ak12496371,gcmt20160124103030,\",\"sources\":\",at,us,ak,gcmt,\",\"types\":\",cap,dyfi,finite-fault,general-link,general-text,geoserve,impact-link,impact-text,losspager,moment-tensor,nearby-cities,origin,phase-data,shakemap,tectonic-summary,trump-origin,\",\"nst\":null,\"dmin\":0.72,\"rms\":2.11,\"gap\":19,\"magType\":\"mww\",\"type\":\"earthquake\",\"title\":\"M 7.1 - 86km E of Old Iliamna, Alaska\"},\"geometry\":{\"type\":\"Point\",\"coordinates\":[-153.4051,59.6363,129]},\"id\":\"us10004gqp\"},\n" +
            "{\"type\":\"Feature\",\"properties\":{\"mag\":6.6,\"place\":\"215km SW of Tomatlan, Mexico\",\"time\":1453399617650,\"updated\":1459963829040,\"tz\":-420,\"url\":\"http://earthquake.usgs.gov/earthquakes/eventpage/us10004g4l\",\"detail\":\"http://earthquake.usgs.gov/fdsnws/event/1/query?eventid=us10004g4l&format=geojson\",\"felt\":11,\"cdi\":2.7,\"mmi\":3.92,\"alert\":\"green\",\"status\":\"reviewed\",\"tsunami\":1,\"sig\":673,\"net\":\"us\",\"code\":\"10004g4l\",\"ids\":\",at00o1bebo,pt16021050,us10004g4l,gcmt20160121180659,\",\"sources\":\",at,pt,us,gcmt,\",\"types\":\",cap,dyfi,geoserve,impact-link,impact-text,losspager,moment-tensor,nearby-cities,origin,phase-data,shakemap,tectonic-summary,\",\"nst\":null,\"dmin\":2.413,\"rms\":0.98,\"gap\":74,\"magType\":\"mww\",\"type\":\"earthquake\",\"title\":\"M 6.6 - 215km SW of Tomatlan, Mexico\"},\"geometry\":{\"type\":\"Point\",\"coordinates\":[-106.9337,18.8239,10]},\"id\":\"us10004g4l\"},\n" +
            "{\"type\":\"Feature\",\"properties\":{\"mag\":6.7,\"place\":\"52km SE of Shizunai, Japan\",\"time\":1452741933640,\"updated\":1459304879040,\"tz\":540,\"url\":\"http://earthquake.usgs.gov/earthquakes/eventpage/us10004ebx\",\"detail\":\"http://earthquake.usgs.gov/fdsnws/event/1/query?eventid=us10004ebx&format=geojson\",\"felt\":51,\"cdi\":5.8,\"mmi\":6.45,\"alert\":\"green\",\"status\":\"reviewed\",\"tsunami\":1,\"sig\":720,\"net\":\"us\",\"code\":\"10004ebx\",\"ids\":\",us10004ebx,pt16014050,at00o0xauk,gcmt20160114032534,\",\"sources\":\",us,pt,at,gcmt,\",\"types\":\",associate,cap,dyfi,geoserve,impact-link,impact-text,losspager,moment-tensor,nearby-cities,origin,phase-data,shakemap,\",\"nst\":null,\"dmin\":0.281,\"rms\":0.98,\"gap\":22,\"magType\":\"mww\",\"type\":\"earthquake\",\"title\":\"M 6.7 - 52km SE of Shizunai, Japan\"},\"geometry\":{\"type\":\"Point\",\"coordinates\":[142.781,41.9723,46]},\"id\":\"us10004ebx\"},\n" +
            "{\"type\":\"Feature\",\"properties\":{\"mag\":6.1,\"place\":\"12km WNW of Charagua, Bolivia\",\"time\":1452741928270,\"updated\":1459304879040,\"tz\":-240,\"url\":\"http://earthquake.usgs.gov/earthquakes/eventpage/us10004ebw\",\"detail\":\"http://earthquake.usgs.gov/fdsnws/event/1/query?eventid=us10004ebw&format=geojson\",\"felt\":3,\"cdi\":2.2,\"mmi\":2.21,\"alert\":\"green\",\"status\":\"reviewed\",\"tsunami\":0,\"sig\":573,\"net\":\"us\",\"code\":\"10004ebw\",\"ids\":\",us10004ebw,gcmt20160114032528,\",\"sources\":\",us,gcmt,\",\"types\":\",cap,dyfi,geoserve,impact-text,losspager,moment-tensor,nearby-cities,origin,phase-data,shakemap,tectonic-summary,\",\"nst\":null,\"dmin\":5.492,\"rms\":1.04,\"gap\":16,\"magType\":\"mww\",\"type\":\"earthquake\",\"title\":\"M 6.1 - 12km WNW of Charagua, Bolivia\"},\"geometry\":{\"type\":\"Point\",\"coordinates\":[-63.3288,-19.7597,582.56]},\"id\":\"us10004ebw\"},\n" +
            "{\"type\":\"Feature\",\"properties\":{\"mag\":6.2,\"place\":\"74km NW of Rumoi, Japan\",\"time\":1452532083920,\"updated\":1459304875040,\"tz\":540,\"url\":\"http://earthquake.usgs.gov/earthquakes/eventpage/us10004djn\",\"detail\":\"http://earthquake.usgs.gov/fdsnws/event/1/query?eventid=us10004djn&format=geojson\",\"felt\":8,\"cdi\":3.4,\"mmi\":3.74,\"alert\":\"green\",\"status\":\"reviewed\",\"tsunami\":0,\"sig\":594,\"net\":\"us\",\"code\":\"10004djn\",\"ids\":\",us10004djn,gcmt20160111170803,\",\"sources\":\",us,gcmt,\",\"types\":\",cap,dyfi,geoserve,impact-text,losspager,moment-tensor,nearby-cities,origin,phase-data,shakemap,tectonic-summary,\",\"nst\":null,\"dmin\":1.139,\"rms\":0.96,\"gap\":33,\"magType\":\"mww\",\"type\":\"earthquake\",\"title\":\"M 6.2 - 74km NW of Rumoi, Japan\"},\"geometry\":{\"type\":\"Point\",\"coordinates\":[141.0867,44.4761,238.81]},\"id\":\"us10004djn\"},\n" +
            "{\"type\":\"Feature\",\"properties\":{\"mag\":6.5,\"place\":\"227km SE of Sarangani, Philippines\",\"time\":1452530285900,\"updated\":1459304874040,\"tz\":480,\"url\":\"http://earthquake.usgs.gov/earthquakes/eventpage/us10004dj5\",\"detail\":\"http://earthquake.usgs.gov/fdsnws/event/1/query?eventid=us10004dj5&format=geojson\",\"felt\":1,\"cdi\":2.7,\"mmi\":7.5,\"alert\":\"green\",\"status\":\"reviewed\",\"tsunami\":1,\"sig\":650,\"net\":\"us\",\"code\":\"10004dj5\",\"ids\":\",at00o0srjp,pt16011050,us10004dj5,gcmt20160111163807,\",\"sources\":\",at,pt,us,gcmt,\",\"types\":\",cap,dyfi,geoserve,impact-link,impact-text,losspager,moment-tensor,nearby-cities,origin,phase-data,shakemap,tectonic-summary,\",\"nst\":null,\"dmin\":3.144,\"rms\":0.72,\"gap\":22,\"magType\":\"mww\",\"type\":\"earthquake\",\"title\":\"M 6.5 - 227km SE of Sarangani, Philippines\"},\"geometry\":{\"type\":\"Point\",\"coordinates\":[126.8621,3.8965,13]},\"id\":\"us10004dj5\"},\n" +
            "{\"type\":\"Feature\",\"properties\":{\"mag\":6,\"place\":\"Pacific-Antarctic Ridge\",\"time\":1451986454620,\"updated\":1459202978040,\"tz\":-540,\"url\":\"http://earthquake.usgs.gov/earthquakes/eventpage/us10004bgk\",\"detail\":\"http://earthquake.usgs.gov/fdsnws/event/1/query?eventid=us10004bgk&format=geojson\",\"felt\":0,\"cdi\":1,\"mmi\":0,\"alert\":\"green\",\"status\":\"reviewed\",\"tsunami\":0,\"sig\":554,\"net\":\"us\",\"code\":\"10004bgk\",\"ids\":\",us10004bgk,gcmt20160105093415,\",\"sources\":\",us,gcmt,\",\"types\":\",cap,dyfi,geoserve,losspager,moment-tensor,nearby-cities,origin,phase-data,shakemap,\",\"nst\":null,\"dmin\":30.75,\"rms\":0.67,\"gap\":71,\"magType\":\"mww\",\"type\":\"earthquake\",\"title\":\"M 6.0 - Pacific-Antarctic Ridge\"},\"geometry\":{\"type\":\"Point\",\"coordinates\":[-136.2603,-54.2906,10]},\"id\":\"us10004bgk\"}],\"bbox\":[-153.4051,-54.2906,10,158.5463,59.6363,582.56]}";

然後運行應用,運行結果應該如下所示(與之前無明顯變化),更改前後的代碼差異參考 此鏈接

震級的圓圈背景

在最終的設計中,每個列表項的震級值後邊 均有一個帶顏色的圓圈。

這些顏色從藍色(低震級值)到紅色(高 震級值)。大於 10 級的地震一概使用 最深的紅色 (#C03823)。9 級和 10 級之間的地震 使用稍微淺一些的紅色 (#D93218),8 級和 9 級 之間的地震使用更淺一些的紅色 (#E13A20),以此類推。每個級別都有不同的顏色。2 級以下的地震 一概使用藍色 (#4A7BA7)。以下提供了 特定的十六進制值。

將這些顏色添加到 res/values/colors.xml 文件。在設置列表項佈局時,需要 稍後引用 EarthquakeAdapter 中的 這些顏色資源 ID。

<!-- Color for magnitude 0 and 2 -->
    <color name="magnitude1">#4A7BA7</color>
    <!-- Magnitude circle color for magnitude between 2 and 3 -->
    <color name="magnitude2">#04B4B3</color>
    <!-- Magnitude circle color for magnitude between 3 and 4 -->
    <color name="magnitude3">#10CAC9</color>
    <!-- Magnitude circle color for magnitude between 4 and 5 -->
    <color name="magnitude4">#F5A623</color>
    <!-- Magnitude circle color for magnitude between 5 and 6 -->
    <color name="magnitude5">#FF7D50</color>
    <!-- Magnitude circle color for magnitude between 6 and 7 -->
    <color name="magnitude6">#FC6644</color>
    <!-- Magnitude circle color for magnitude between 7 and 8 -->
    <color name="magnitude7">#E75F40</color>
    <!-- Magnitude circle color for magnitude between 8 and 9 -->
    <color name="magnitude8">#E13A20</color>
    <!-- Magnitude circle color for magnitude between 9 and 10 -->
    <color name="magnitude9">#D93218</color>
    <!-- Magnitude circle color for magnitude over 10 -->
    <color name="magnitude10plus">#C03823</color>

爲有色圓圈定義一個新的 drawable。在 Android Studio 的 項目目錄面板中,右鍵單擊 res/drawable 文件夾 以添加新的 drawable 資源 XML 文件。將文件命名爲 magnitude_circle

將 res/drawable/magnitude_circle.xml 文件的內容 替換爲下面的 XML。

magnitude_circle.xml 中:

<?xml version="1.0" encoding="utf-8"?>
 <!-- Background circle for the magnitude value -->
 <shape 
    xmlns:android="http://schemas.android.com/apk/res/android"
    android:shape="oval">
    <solid android:color="@color/magnitude1" />
    <size
        android:width="36dp"
        android:height="36dp" />
    <corners android:radius="18dp" />
 </shape>

通過在 XML 中定義圓圈形狀,創建了一個靈活的資源, 該資源可以在各種設備中使用,而不用從設計器中 包含多個圖像資源。我們也可以在 Java 代碼中對顏色進行操縱, 以進一步減少所需的資源數量。項目中圖像文件越少 意味着應用越小,這對於最終用戶來說 是好事!

現在,修改列表項佈局,使震級 TextView 的 背景屬性引用我們剛剛定義的新 drawable 資源 (android:background="@drawable/magnitude_circle")。也可以對 TextView 的外觀進行其他改動,例如字體大小和顏色, 使其與設計模擬相符。

earthquake_item.xml 中:

 <TextView
    android:id="@+id/magnitude"
    android:layout_width="36dp"
    android:layout_height="36dp"
    android:layout_gravity="center_vertical"
    android:background="@drawable/magnitude_circle"
    android:fontFamily="sans-serif-medium"
    android:gravity="center"
    android:textColor="@android:color/white"
    android:textSize="16sp"
    tools:text="8.9" />

可以在位置偏移 TextView 上添加 16dp 的 左側邊距,使位置 TextView 和震級 TextView 之間 具有一定的空白。

earthquake_item.xml 中:

    <TextView
        android:id="@+id/place_offset_text"
        android:layout_width="0dp"
        android:layout_height="wrap_content"
        android:layout_gravity="center_vertical"
        android:layout_marginLeft="16dp"
        android:layout_weight="2"
        tools:text="30km S of" />

最後,需要修改 EarthquakeAdapter,以將 每個列表項的背景圓圈設置爲正確的顏色。此代碼段 假設 magnitudeView 是列表項佈局中 R.id.mag_text TextView 的參考。你還需要導入 GradientDrawable 類。

EarthquakeAdapter.java 中:

 import android.graphics.drawable.GradientDrawable;

EarthquakeAdapter.getView() 中:

        // 爲震級圓圈設置正確的背景顏色。
        // 從 TextView 獲取背景,該背景是一個 GradientDrawable。
        GradientDrawable magnitudeCircle = (GradientDrawable) magnitudeView.getBackground();
        // 根據當前的地震震級獲取相應的背景顏色
        int magnitudeColor = getMagnitudeColor(currentEarthquake.getMag());
        // 設置震級圓圈的顏色
        magnitudeCircle.setColor(magnitudeColor);

這會在 Android Studio 中看到一個錯誤, 表示應用無法識別 getMagnitudeColor() 方法,我們可以通過定義名爲 getMagnitudeColor(double magnitude) 的專用輔助方法,基於 當前的地震震級值返回正確的顏色值來實現此設計更改。 (使用 switch 語句實現)

關於顏色值的注意事項:在 Java 代碼中,可以參考 使用顏色資源 ID(如 R.color.magnitude1、R.color.magnitude2) 在 colors.xml 文件中定義的顏色。儘管仍需將顏色 資源 ID 轉換爲顏色整數值。示例:

 int magnitude1Color = ContextCompat.getColor(getContext(), R.color.magnitude1);

EarthquakeAdapter 類中的輔助方法 getMagnitudeColor(double magnitude) 定義如下:

/**
     * 根據 震級的級別 返回不同的 震級圓圈顏色
     */
    private int getMagnitudeColor(double magnitude) {
   
   
        int magnitudeColorResourceId;
        // 返回顏色整數 值。對於正 小數,可以將其看作截去小數點後的 數字部分。
        int magnitudeFloor = (int) Math.floor(magnitude);
        switch (magnitudeFloor) {
   
   
            case 0:
            case 1:
                magnitudeColorResourceId = R.color.magnitude1;
                break;
            case 2:
                magnitudeColorResourceId = R.color.magnitude2;
                break;
            case 3:
                magnitudeColorResourceId = R.color.magnitude3;
                break;
            case 4:
                magnitudeColorResourceId = R.color.magnitude4;
                break;
            case 5:
                magnitudeColorResourceId = R.color.magnitude5;
                break;
            case 6:
                magnitudeColorResourceId = R.color.magnitude6;
                break;
            case 7:
                magnitudeColorResourceId = R.color.magnitude7;
                break;
            case 8:
                magnitudeColorResourceId = R.color.magnitude8;
                break;
            case 9:
                magnitudeColorResourceId = R.color.magnitude9;
                break;
            default:
                magnitudeColorResourceId = R.color.magnitude10plus;
                break;
        }
        // 將顏色資源 ID 轉換爲 實際整數顏色值,並將結果作爲 返回值。
        return ContextCompat.getColor(getContext(), magnitudeColorResourceId);
    }

更改完成後運行一下應用,結果和預期的一樣 ,不同的震級有着不同顏色的背景圓圈,Nice。(更改前後的代碼對比參考 此鏈接

改進界面

新顏色

1.將這些新顏色添加到 res/values/colors.xml 文件。稍後將在 我們爲你提供的 earthquake_item 佈局中引用這些顏色

+    <!-- Text color for the details of the earthquake in the list item -->
+    <color name="textColorEarthquakeDetails">#B4BAC0</color>
+    <!-- Text color for the primary location of the earthquake in the list item -->
+    <color name="textColorEarthquakePlace">#2B3D4D</color>

列表項佈局

2.使用提供的佈局更新 earthquake_item.xml 文件。

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="?android:attr/listPreferredItemHeight"
    android:orientation="horizontal"
    android:paddingStart="16dp"
    android:paddingLeft="16dp"
    android:paddingEnd="16dp"
    android:paddingRight="16dp">

    <TextView
        android:id="@+id/mag_text"
        android:layout_width="36dp"
        android:layout_height="36dp"
        android:layout_gravity="center_vertical"
        android:background="@drawable/magnitude_circle"
        android:fontFamily="sans-serif-medium"
        android:gravity="center"
        android:textColor="@android:color/white"
        android:textSize="16sp"
        tools:text="8.9" />

    <LinearLayout
        android:layout_width="0dp"
        android:layout_height="wrap_content"
        android:layout_gravity="center_vertical"
        android:layout_marginStart="16dp"
        android:layout_marginLeft="16dp"
        android:layout_weight="1"
        android:orientation="vertical">

        <TextView
            android:id="@+id/place_offset_text"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:ellipsize="end"
            android:fontFamily="sans-serif-medium"
            android:maxLines="1"
            android:textAllCaps="true"
            android:textColor="@color/textColorEarthquakeDetails"
            android:textSize="12sp"
            tools:text="30km S of" />

        <TextView
            android:id="@+id/primary_place_text"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:ellipsize="end"
            android:maxLines="2"
            android:textColor="@color/textColorEarthquakePlace"
            android:textSize="16sp"
            tools:text="Long placeholder location that should wrap to more than 2 lines of text" />

    </LinearLayout>

    <LinearLayout
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_gravity="center_vertical"
        android:layout_marginStart="16dp"
        android:layout_marginLeft="16dp"
        android:orientation="vertical">

        <TextView
            android:id="@+id/date_text"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_gravity="end"
            android:textColor="@color/textColorEarthquakeDetails"
            android:textSize="12sp"
            tools:text="Mar 6, 2010" />

        <TextView
            android:id="@+id/time_text"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_gravity="end"
            android:textColor="@color/textColorEarthquakeDetails"
            android:textSize="12sp"
            tools:text="3:00 PM" />

    </LinearLayout>
</LinearLayout>

隱藏列表項間的分隔線

3.要隱藏列表項間的分隔線, 可在 earthquake_activity.xml 文件中的 ListView XML 元素上設置 兩個屬性。我們希望將 android:divider 設置爲 “@null” 並將 android:dividerHeight 設置爲 “0dp”。

<ListView     xmlns:android="http://schemas.android.com/apk/res/android"
     android:id="@+id/list"
     android:orientation="vertical"
     android:layout_width="match_parent"
     android:layout_height="match_parent"
     android:divider="@null"
     android:dividerHeight="0dp"/>

結果

完成以上所有代碼更改後,應用應如下所示(代碼更改前後差異參考 此鏈接

添加地震Intent

現在距離完成列表 UI 佈局只剩下 一件事。設置 Quake Report,以便當用戶單擊特定列表 項時,會將其發送到 USGS 網頁以查看有關該地震的 其他詳細信息。

來自服務器的 JSON 包含該地震的網站 URL。我們需要 提取該字段並將其存儲在應用中。

在檢測到用戶已單擊某個列表項之後,需要打開 正確的網頁。因爲不必關心網頁是由設備上的哪一個 Web 瀏覽器應用 打開的,因此可以創建併發送包含網站 URL 的 隱式 Intent。

提取網站 URL

JSON 響應在 “url” 鍵下包含每個地震的 網站 URL。在解析期間,我們需要提取其他地震數據 旁邊的此值。然後,我們會將該 url 傳遞到 Earthquake 對象中。

QueryUtils.extractEarthquake() 中:

// 提取名爲 "url" 的鍵的值
String earthquakeUrl = properties.getString("url");

// 使用震級、地點、時間和來自 JSON 響應的 url,
// 創建一個新的 {@link Earthquake} 對象。
Earthquake earthquake = new Earthquake(earthquakeMag, earthquakePlace, earthquakeTime, earthquakeUrl);

存儲網站 URL

上述更改也需要更新 Earthquake 類。 需要:

  • 修改構造函數以接受 URL 的字符串輸入參數
  • 添加私有全局變量來存儲 URL
  • 提供公共 getter 方法,以便其他類可以訪問該變量。

在 Earthquake.java 中:

public class Earthquake {
   
   
	…………

    /**
     * 地震的網站 URL
     */
    private String url;

	……………

    /**
     * 返回用於查找關於地震的更多信息的網站 URL。
     */
    public String getUrl() {
   
   
        return url;
    }

    /**
     * 構造一個新的 {@link Earthquake} 對象。
     *
     * @param mag                表示地震的震級(大小)
     * @param place              表示地震的城市位置
     * @param timeInMilliseconds 表示地震發生時以毫秒(根據 Epoch)計的時間
     * @param url                表示用於查找關於地震的更多詳細信息的網站 URL
     */
    public Earthquake(double mag, String place, long timeInMilliseconds, String url) {
   
   
        this.mag = mag;
        this.place = place;
        this.timeInMilliseconds = timeInMilliseconds;
        this.url = url;
    }
}

處理列表項單擊

將地震 URL 正確存儲於 Earthquake 對象中後, 單擊列表項時可訪問該 URL。但是, 如何打開網站?如果用戶安裝了多個 web 瀏覽器該怎麼辦?哪個應用將打開地震網站? 最好使用用戶已經選擇的默認值, 或爲其提供選項。可通過創建隱式 Intent 來實現 Intent。例如,如果 URI 表示一個 位置,Android 將打開一個地圖應用。在此情況下,該 資源是一個 HTTP URL,因此 Android 通常會打開一個瀏覽器。

我們想要使用的 Intent 構造函數需要一個 Uri 對象, 因此我們需要將字符串形式的 URL 轉換爲 URI。 我們知道地震 URL 是一種形式更具體的 URI,因此 可以使用 Uri.parse 方法。

接下來需要在 ListView 上聲明一個 OnItemClickListener。 OnItemClickListener 是一個接口,其中包含一個單一的方法 onItemClick()。通過聲明一個實現此界面的匿名類, 併爲 onItemClick() 方法中應發生的內容 提供自定義邏輯。(必須在 EarthquakeAdapter 本地變量上 添加 final 修飾符,以便可以訪問 OnItemClickListener 中的適配器變量。)

EarthquakeActivity.onCreate() 中:

// Create a new {@link ArrayAdapter} of earthquakes
        final EarthquakeAdapter adapter = new EarthquakeAdapter(this, earthquakes);

        // Set the adapter on the {@link ListView}
        // so the list can be populated in the user interface
        earthquakeListView.setAdapter(adapter);

        earthquakeListView.setOnItemClickListener(new AdapterView.OnItemClickListener() {
   
   
            @Override
            public void onItemClick(AdapterView<?> adapterView, View view, int position, long l) {
   
   
                // 查找單擊的當前地震
                Earthquake currentEarthquake = adapter.getItem(position);

                // 將字符串 URL 轉換爲 URI 對象(以傳遞至 Intent 中 constructor)
                Uri earthquakeUri = Uri.parse(currentEarthquake.getUrl());

                // 創建一個新的 Intent 以查看地震 URI
                Intent websiteIntent = new Intent(Intent.ACTION_VIEW, earthquakeUri);

                // 發送 Intent 以啓動新活動
                startActivity(websiteIntent);
            }
        });

更改結束後運行應用,(代碼更改前後對比參考 此鏈接

QuakeReport — 應用測試

總結

通過對 QuakeReport 應用的學習,學習對 JSON 數據的解析方法,以及對解析後的數據按需求進行預處理,並利用 隱式 Intent 使用戶能夠跳轉到 每條地震詳情的 WEB 頁面從而瞭解更多。

但這還遠遠不夠,比如 JSON 數據是以硬編碼的形式定義,這意味着用戶無法從 應用中 查看最新的地震信息(應該沒人會整天瀏覽已經發生很久了的信息吧),這會在之後學習中解決。

沒想到這麼點東西竟花了兩天時間才完成(今天一整天都在路途中)

參考

USGS 地震實時反饋和通知

電子表格格式的 USGS 實時地震數據

org.json.JSONObject - Android Developers

Parsing JSON in Android

Android JSON Parsing with Examples - Tutlane

QueryUtils.java 類

Convert String to Uri

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章