Android-入门学习笔记-JSON 解析

4

构建优秀 UI 的技巧

6

USGS 地震实时反馈和通知: http://earthquake.usgs.gov/earthquakes/feed/v1.0/index.php

电子表格格式的 USGS 实时地震数据: http://earthquake.usgs.gov/earthquakes/feed/v1.0/csv.php

 

8

请通过以下链接查看 USGS 查询结果: http://earthquake.usgs.gov/earthquakes/feed/v1.0/summary/4.5_week.geojson 从而了解数据的显示样式。表左列含有从 USGS 网站获取的一部分数据,你会在 JSON 中发现这些数据。你认为这些数据值表示什么呢?如果你 不知道,不妨猜一猜。在类似 jsonprettyprint.com 的网站中查看 JSON 格式数据,然后使用你的语言在表中填入你认为该参数所表示的内容。 可将答案与 GeoJSON 文档相对比。

 

9

为了帮助你入门,我们创建了初始版本的 Quake Report 应用,但请不要着急,未来你需要做的事情还有很多。

当某位 Android 专业开发者加入新项目时,通常,团队可能已经有了一个可继续处理的应用。 最好知道如何读取现有代码库,因为通常你不会从头创建新应用。

请按以下说明将 Quake Report 项目导入计算机上的 Android Studio 中:

  • 请转至此 GitHub 项目资源库链接

  • 单击“下载 zip”(Download zip) 按钮下载应用代码。

  • 在你的计算机上解压下载的文件,便可得到 "Quake Report" 文件夹。

  • 打开 Android Studio。

  • 选择“文件”(File) >“导入项目”(Import Project),然后选择 "Quake Report" 文件夹。 导入项目需要一些时间。如有任何问题,请查阅“故障排除”文档。

  • 成功导入该应用后,在你的 Android 设备(手机、平板电脑或模拟器)上运行该应用。其外观类似于 以下屏幕截图。

  • 在 Android Studio 中打开“项目”(Project) 选项卡,可看到文件 目录。
  • 在 Android Studio 中,浏览代码库找到 Quake Report 应用,并熟悉项目文件。然后回答 测验中的问题。

注释:如果你在理解代码上有困难,请不必担心。你可以考虑重新访问上节课的材料,其中列出了应用中的数据。

10

关于课程的剩余部分,你即将对我们在上次测验中为你提供的 Quake Report 进行代码更改。

无论你何时陷入僵局,我们都鼓励你尝试使用不同的方法来调试问题,如添加日志消息或使用 Android Studio 调试器。这样便可练就你的调试技能,当然,这也是专业开发者从无数次尝试与错误中获得的基本技能。

如果应用仍无法运行,那么最后一招是, 参考我们提供的代码。代码库位于 GitHub 上的以下网址中: github.com/udacity/ud843-QuakeReport

请确保从下拉菜单中导航到正确的 代码分支。

每节课(第一课、第二课和第三课)的结尾 都有一个代码状态分支。

如果单击“提交”(commits) 链接,便可看到构建应用过程中 特定时间点的所有个别提交(或当时捕获的屏幕截图) 的历史记录。

要下载代码,请找到工具栏右侧的灰色“下载 ZIP”(Download ZIP) 按钮。按下该按钮,便会将一个名为 ud843-QuakeReport.zip 的文件下载到你的计算机上。将该文件解压到 "ud843-QuakeReport" 文件夹中。

然后将该项目导入到 Android Studio 中。打开 Android Studio, 然后关闭所有打开的项目,直到显示启动画面为止。在 “快速启动”(Quick Start) 窗格中,选择“导入项目”(Import project)。显示 "ud843-QuakeReport" 文件夹后,选中该文件夹,然后点击“确定”(OK)。

11

请参考 GitHub 中上节课的 CustomAdapter 应用

提示:创建一个 Earthquake 类以保存 一次地震的所有相关信息。

提示:创建一个自定义 ArrayAdapter 以正确显示 Earthquake 对象的列表。

12

大括号

你会发现,JSON 示例中的所有内容都封闭在 一组大括号内,这表示可将整个内容视为一个 对象。

逗号

键和值以冒号分隔,同时每个键/值对以逗号分隔。 JSON 支持可在大多数编程语言中找到的基本数据类型, 如,数字、字符串、布尔值、数组和对象。 可在以下示例中看到其中几种数据类型:

Json解析

  • 第一个 "size" 键的值为 9.5,由此可以判断出 该值属于数值类型,因为值周围没有引号。

  • 第二个 "wide" 键的值为 true,不带引号。这 表示该值属于布尔值。如果数字周围有引号,则表示其为字符串。也就是我们在第三个键值对中找到的字符串。

  • 第三个 "country-of-origin"键的值为 "usa"。

  • 第四个键是 "style",其值表示为对象,因为两侧有大括号。

    在 style 对象内部,我们可以找到两个嵌套键/值对:一个为 catergories,一个为color。其实 catergories 的值是一个数组,“boot” 和 “winklepicker”被包含在中括号内组成一个数组。 按照这种方式,通过使用对象和数组,可以嵌套几组值, 随后便可在这些容器中放入不同的数据类型。

详细信息:

有关如何构建 JSON 的详细信息,请查看 http://www.json.org/

13

要查看语法规则,请参阅规范

要回答这些问题,请查看由发布到 Twitter 最受欢迎 API 请求得到的特定 JSON 响应, 此 API 会获取某位用户最近最喜欢的 20 篇 推文。你可以参阅 twitter API 文档页面获取更多详细信息。

 14

要回答测验问题,请查看 Instagram API 中的特定 JSON 响应了解有关特定图片文件 的信息。请参阅 instagram API 文档页面获取更多详细信息。

 15

在 Android 中解析 JSON 的相关教程 注:因为 tutorialspoint 修改了他们的教程,现在解析JSON的教程和答案视频中并不一样了。你可以直接查看答案视频查看教程。

其他资源:

JSONObject

JSONArray

16

第 1 步,使用此 URL 获取地震数据:http://earthquake.usgs.gov/fdsnws/event/1/query?format=geojson&starttime=2016-01-01&endtime=2016-01-31&minmag=6&limit=10

第 2 步,可以在线搜索 "JSON formatter",或使用以下网站:

第 3 步,若想知道每个属性键具体表示的内容,请查看详细的 API 指南:

http://earthquake.usgs.gov/earthquakes/feed/v1.0/geojson.php

http://earthquake.usgs.gov/earthquakes/feed/v1.0/geojson_detail.php

 17

使用此示例 JSON 响应回答测验问题。

使用以下 JSON 格式化程序:

JSON pretty print 或 JSON 格式化程序和验证程序

如需帮助,请参阅之前有关遍历路径的测验问题。

 18

在上次测验中,我们探讨了如何遍历 JSON 对象树来提取我们所关注的值。只想在你的 头脑中解析 JSON?那就有点太浪费人力了, 因此,还是让应用为我们执行这项工作吧!我们只需 逐步写下逻辑,应用便可充分利用 它来为我们服务。

从技术上讲,我们可以通过 Internet 获得实时响应, 但此刻,我们要通过在应用中执行硬编码来模拟 获取特定 JSON 响应这一情况。这样, 我们便可在我们的应用中构建出所有逻辑,并测试其能否 正常工作,从而使我们将来能够处理任何 JSON 响应。

我们要使用的响应如下: http://earthquake.usgs.gov/fdsnws/event/1/query?format=geojson&starttime=2016-01-01&endtime=2016-01-31&minmag=6&limit=10

你需执行的操作

在 Quake Report 应用中,写下用于转换此 JSON 响应的代码,然后将其转换为具有相应属性的 Earthquake 对象的 ArrayList 。

从我们为你提供的 QueryUtils.java 类中复制代码, 然后将代码粘贴到应用内的新文件中。请注意, 我们要使用的特定 JSON 响应已在 文件顶部的 SAMPLE_JSON_RESPONSE String 常量中定义。

完成执行 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 = new ArrayList<>();
 earthquakes.add(new Earthquake("7.2", "San Francisco", "Feb 2, 2016")); earthquakes.add(new Earthquake("6.1", "London", "July 20, 2015")); earthquakes.add(new Earthquake("3.9", "Tokyo", "Nov 10, 2014")); earthquakes.add(new Earthquake("5.4", "Mexico City", "May 3, 2014")); earthquakes.add(new Earthquake("2.8", "Moscow", "Jan 31, 2013")); earthquakes.add(new Earthquake("4.9", "Rio de Janeiro", "Aug 19, 2012")); earthquakes.add(new Earthquake("1.6", "Paris", "Oct 30, 2011")); 

而是替换为以下代码:

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

产生的应用应如下所示:

19

接下来的几组编码任务旨在改进 UI 使其 与我们设计师提供的设计模拟相匹配。这有点类似于 上节课中你构建 Miwok 语言应用时采用的视觉修饰步骤。

节点将以书面文本格式呈现,方便你 逐一查看所有步骤。同时会在其中 穿插阐释所产生的任何新主题,但本课程的重点是 网络而不是用户界面,因此,即使你不能 深入理解那些即将出现的主题,也不要担心。如果希望练习更多 Java 编程,强烈建议你投入精力 系统地执行这些即将出现的编码任务。

我们首先来更新 发生地震时显示相关信息的方式。以应用目前的状态,我可以 轻松看到每次地震的震级和位置,但时间 则是一长串数字。第一次地震的时间是 1454124312220,此格式对我们没有帮助。

 
 

我们来重新访问我们的设计模拟。可以看到,每次地震的日期和时间 都显示在单独的行中,这样就很容易读懂。

 
 

我们如何将以毫秒为单位的时间转换成 我们想要的日期/时间格式呢?首先,我们来了解一下以毫秒为单位的时间 表示什么。

Unix 时间

这种格式称为 Unix 时间,将时间描述为 从英国 1970 年 1 月 1 日 0 时 0 分 0 秒起至现在的总毫秒数 (更技术性的叫法为协调世界时间)。我们需要将此 Unix 时间转换成更易懂的日期 和时间,但这将是一个非常复杂的命题。你可能希望 按照用户的本地时区显示时间,但时区极为复杂, 这个Youtube视频详细阐述了这点。另外,根据你在 世界所处的位置,日期的书写方式也会有所不同。

有关 Unix 时间的详细信息,请查看此Youtube视频

日期格式设置

很庆幸,你无需自己处理日期格式。有 一个很奇妙的名为 SimpleDateFormat 的类,知晓所有关于时区的信息, 以及知晓如何按照世界的不同地区书写日期,它会 帮你处理这些难题。

提供一种所需的时间格式,如:"yyyy-MM-dd HH:mm:ss"

在时间格式语法中,字符具有特殊含义,这一点 在此表中进行了详尽阐述。

"y" 代表年,"yyyy" 代表 4 位数的年份,如 2016。

"M" 代表月,"MM" 代表 2 位数的月份,如 03。

"d" 代表日,"dd" 代表 2 位数的日期,如 10。

"H" 代表时。

"m" 代表分。

"s" 代表秒。

所有未在特殊字符表中列出的字符 都将在输出字符串中直接使用。例如,如果时间格式字符串 包含冒号、短划线或逗号,则输出字符串 也将在相应位置直接包含相同的标点符号。

下面是 1463159138711 毫秒的示例。我们使用 "yyyy-MM-dd HH:mm:ss a" 格式创建一个 SimpleDateFormat 对象,然后 传入一个以时间 1463159138711 毫秒初始化的 Date 对象,随后该日期的格式便会被设置为 "2016-05-13 12:07:46 PM"。

代码如下。首先通过调用 Date 构造函数将 以毫秒为单位的时间转换为 Date 对象。

long timeInMilliseconds = 1454124312220L;
Date dateObject = new Date(timeInMilliseconds);

然后便可初始化 SimpleDateFormat 实例,并将其配置为 根据指定格式提供更易懂的表示。

SimpleDateFormat dateFormatter = new SimpleDateFormat("MMM DD, yyyy");
String dateToDisplay = dateFormatter.format(dateObject);

执行完这 4 行代码后,dateToDisplay 的值 便会呈现为格式更为美观的日期:"Jan 29, 2016"。

如果 Android Studio 不识别这些类,你可能需要 在文件顶部手动导入它们。

import java.text.SimpleDateFormat;
import java.util.Date;

轮到你了!

由于 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");

这会引发连锁反应,因此我们需要同时更新 应用中的其他位置。

 

练习题

在 EarthquakeAdapter 类中,将每次地震的时间(以毫秒为单位)存储为长整型数据类型 并将日期和时间格式设定为用户易于读懂 的字符串:

  • 在 QueryUtils 类中,解析 JSON 响应时将时间提取为长整型数据类型

  • 在 Earthquake 类中, 将时间存为长整型变量

  • 根据以毫秒为单位的时间创建 Date 对象

  • 使用 SimpleDateFormat 将日期和时间的格式设定为易于读懂的 字符串

  • 更新列表项布局以在 2 个单独的 TextView 中显示日期和时间

提交
 

首先,将 QueryUtils extractEarthquakes() 方法修改为 从 JSON 响应中将时间提取为长整型数据类型。

在 QueryUtils extractEarthquakes() 方法中:

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

这会引发错误,因为 Earthquake 构造函数不 接受长整型数据类型作为第三输入参数。因此,我们需要 对 Earthquake 类进行一些更改。

更改全局变量的数据类型,对其进行重命名,以使名称 更能描述其中所存储的信息。

在 Earthquake.java 中:

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

修改 Earthquake 构造函数,使其采用长整型数据类型表示时间 并更新 Javadoc 注释。

在 Earthquake.java 中:

  /**
   * 构造一个新的 {@link Earthquake} 对象。
   *
   * @param magnitude 表示地震的震级(大小) 
   * @param location 表示地震的城市位置
   * @param timeInMilliseconds 表示地震发生时以毫秒(根据 Epoch)计的时间 */ public Earthquake(String magnitude, String location, long timeInMilliseconds) { mMagnitude = magnitude; mLocation = location; mTimeInMilliseconds = timeInMilliseconds; } 

更新公共 getter 方法,以便返回长整型数据类型。

在 Earthquake.java 中:

  /**
  * 返回地震的时间。
  */
  public long getTimeInMilliseconds() { return mTimeInMilliseconds; } 

当 EarthquakeAdapter 创建每次地震的列表项时, 适配器必须将以毫秒为单位的时间转换为相应 格式的日期和时间。现在,有 2 个 TextView 来分别显示日期 和时间,因此我们需要修改 earthquake_list_item.xml 布局以添加另外一个 TextView 并为其提供一个合适的视图 ID。

在 earthquake_list_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="match_parent"
      android:orientation="horizontal" android:padding="16dp"> <TextView android:id="@+id/magnitude" android:layout_width="0dp" android:layout_height="wrap_content" android:layout_weight="1" tools:text="8.9" /> <TextView android:id="@+id/location" android:layout_width="0dp" android:layout_height="wrap_content" android:layout_weight="1" tools:text="San Francisco, CA" /> <TextView android:id="@+id/date" android:layout_width="0dp" android:layout_height="wrap_content" android:layout_weight="1" tools:text="Mar 6, 2010" /> <TextView android:id="@+id/time" android:layout_width="0dp" android:layout_height="wrap_content" android:layout_weight="1" tools:text="3:00 PM" /> </LinearLayout> 

在 EarthquakeAdapter 中,修改 getView() 方法, 使其生成要在相应的 TextView 中显示的 格式化字符串。我们使用 currentEarthquake.getTimeInMilliseconds() 从当前的 Earthquake 对象中获取时间,并将其传递到 Date 构造函数中,以形成一个新的 Date 对象。

在 EarthquakeAdapter.java 中:

  public View getView(int position, View convertView, ViewGroup parent) {
          // 检查是否已经有可以重用的列表项视图(称为 convertView),
          // 否则,如果 convertView 为 null,则 inflate 一个新列表项布局。
          View listItemView = convertView;
          if (listItemView == null) {
              listItemView = LayoutInflater.from(getContext()).inflate(
                      R.layout.earthquake_list_item, parent, false); } // 在地震列表中的给定位置找到地震 Earthquake currentEarthquake = getItem(position); // 找到视图 ID 为 magnitude 的 TextView TextView magnitudeView = (TextView) listItemView.findViewById(R.id.magnitude); // 在该 TextView 中显示目前地震的震级 magnitudeView.setText(currentEarthquake.getMagnitude()); // 找到视图 ID 为 location 的 TextView TextView locationView = (TextView) listItemView.findViewById(R.id.location); // 在该 TextView 中显示目前地震的位置 locationView.setText(currentEarthquake.getLocation()); // 根据地震时间(以毫秒为单位)创建一个新的 Date 对象 Date dateObject = new Date(currentEarthquake.getTimeInMilliseconds()); // 找到视图 ID 为 date 的 TextView TextView dateView = (TextView) listItemView.findViewById(R.id.date); // 设置日期字符串的格式(即 "Mar 3, 1984") String formattedDate = formatDate(dateObject); // 在该 TextView 中显示目前地震的日期 dateView.setText(formattedDate); // 找到视图 ID 为 time 的 TextView TextView timeView = (TextView) listItemView.findViewById(R.id.time); // 设置时间字符串的格式(即 "4:30PM") String formattedTime = formatTime(dateObject); // 在该 TextView 中显示目前地震的时间 timeView.setText(formattedTime); // 返回目前显示适当数据的列表项视图 return listItemView; } 

以上代码包含两个辅助方法,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); } 

做出这些更改后,在设备上运行该应用时, 应类似于以下屏幕截图。请注意, 需要多次尝试和更改才能使格式看起来与 我们预期中的格式完全一致。一次尝试不足以令所有代码 完美运行。

干得好!现在,地震的日期和时间 看上去更易懂了!

 
20

现在让我们开始讨论位置。我们可以重新访问设计模拟来查看 应如何显示每次地震的位置。设计 分两行显示距离信息,第一行显示 与城市之间的距离,第二行指定具体的城市。由于 两行文本的字体样式和文字颜色不同, 这里最好使用两个不同的 TextView。

将一个字符串拆分成两个单独的字符串

如果查看从 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")。

字符串操控

本任务练习操控字符串。即学习将字符串拆分成不同的部分并组合到一起, 以便能够按照所需格式创建字符串。Java 中 的 String 类提供了多种方法用于执行这些任务。

其中的许多方法都假设你已知字符串 只是一串字符。对于 "hello" 这样的字符串,长度为 5 个 字符,第一个字符 "h" 位于索引 0, "e" 位于索引 1,"l" 位于索引 2,以此类推。

你也可以参阅文档中的CharSequence。CharSequence 是一组字符,字符串是一种更具体的 CharSequence (String 类是 CharSequence 类的 扩展类)。如果某个方法要求使用 CharSequence 作为输入,你可以 传递一个字符串。 以下是一些可以在 String 对象上调用的其他方法:

length() - 返回字符串中的 字符数

contains(CharSequence cs) - 根据 原始字符串中是否包含输入 CharSequence (或输入 String)返回 true 或 false

indexOf(String string) - 返回输入 String 首次在原始 String 中出现的位置的索引,如果 原始 String 中不存在输入 String,则返回 -1

split(String string) - 根据输入 String 中 指定的位置对原始 String 进行拆分, 并返回包含拆分后 String 部分的数组。

substring(int start, int end) - 返回 一个新的 String,该 String 为从开始索引到结束索引 之间的内容(包括开始索引但不包括结束索引)。

你需执行的操作

在此编码任务中,需要将位置文本拆分成 位置偏移("74km NW of ")和主要位置("Rumoi, Japan")并在两个单独的 TextView 中显示这两个 String。如果 没有位置偏移,使用 "Near the" 加上主要位置 ("Pacific-Antarctic Ridge")。

由于该逻辑与信息在 UI 中的显示方式有关, 请在 EarthquakeAdapter 类中对代码做出更改。从技术上来讲, 你可以在 Earthquake 类中执行代码更改, 最后由开发者根据个人偏好 确定代码的位置。在本例中,我们喜欢将 原始位置 String 存储在 Earthquake 类中, 并将所有针对地震数据执行的 UI 相关更改存放在 EarthquakeAdapter 类中。以后,如果我们的设计师想要 在 UI 中显示整个位置字符串,我们可以 原样保留 Earthquake 类,而只修改 EarthquakeAdapter 中关于位置显示方式的逻辑。

完成后,应用应类似于以下屏幕截图:

这个任务很复杂,涉及到使用一些你可能 从未用过的 String 操控方法,请随意反复进行试验。 有多种方法可以达成预期的结果。加油!

提示:如果遇到困难,请参阅这篇 StackOverflow 文章

21

如果已看到服务器返回的 JSON 响应中的震级值, 就会注意到震级有时带有一位或者两位小数, 如 "7.2" 或 "7.29"。但是根据 设计模拟,我们最好以带有一位小数的字符串形式 显示地震震级(即 "7.2"),以便震级值可以居中显示在 颜色漂亮的圆圈中。

 
 

DecimalFormat

你猜怎么着?Android 中有一个类可以帮到我们!这个类叫做 DecimalFormat 类。顺便提一句, 还有 NumberFormat 类可用于处理所有 类型的数字的格式,但这是一个抽象类, 而 DecimalFormat 是一个具体类,我们可以与之交互。

要初始化 DecimalFormat 对象,你要传入一个 与已定义模式相匹配的格式字符串(与 SimpleDateFormat 类 的 year、month、date 符号类似)。特殊符号具有 特殊的意义(如 0 表示数字的占位符、# 也表示 数字,但是不显示前导零)。在 DecimalFormat 文档页面的表中有完整的列表。

以下是我们使用 DecimalFormat 格式方法的示例。

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

由于我们指定 DecimalFormat 显示三位数字 ("0.00"), 因此,对 2.3234 这样的数字进行格式化会将其缩短到 两位小数,从而输出字符串值 "2.32"。

如果未自动导入此类,你可能需要在文件顶部 导入这个类。

 import java.text.DecimalFormat;

Double 数据类型

你可能已注意到,DecimalFormat format() 方法 以双精度值作为输入。"Double" 是 Java 中的另外一种原始 数据类型。你可以将其视为一个十进制数值,而 "int" 数据类型是一个整数。

注释:如果想要更有效地存储十进制数值, 也可以将 "float" 数据类型用于小数。"float" 与 "double" 之间的细微差别 可在官方 Java 文档网站 此处中找到。

你需执行的操作

在接下来的测验中,你需要将震级存储为 double 数据类型。 开始时需要在解析 JSON 响应时以 double 形式 提取十进制震级值。

在 QueryUtils extractEarthquakes() 中:

 // 提取名为 "mag" 的键的值
 double magnitude = properties.getDouble("mag");

这会在应用的其他部分造成连锁反应,你需要 更新 Earthquake 类,例如为了处理 double 值形式的震级的存储问题。同时在 EarthquakeAdapter 中, 当你尝试显示震级时,请使用 DecimalFormat 类 对震级进行格式化,以只显示一位小数。加油!

 
 

练习题

将每个地震的震级存储为 double 数据类型,并 对震级进行格式化,以显示给用户:

  • 在 QueryUtils 类中,解析 JSON 响应时将震级提取为 double 数据类型

  • 在 Earthquake 类中,将震级存储为 double

  • 在 EarthquakeAdapter 类中,使用 DecimalFormat,以始终显示一位小数

提交
 

首先你应该修改解析 JSON 响应中震级值的方式, 以得到一个 double 值。

在 QueryUtils extractEarthquakes() 中:

 // 提取名为 "mag" 的键的值
 double magnitude = properties.getDouble("mag");

Android Studio 会显示一条错误,因为我们不能将 double 值传递到 Earthquake 构造函数中,因此需要 更新 Earthquake 类。在 Earthquake 类中: magnitude 全局变量应该是 double, 构造函数应当接受 double 值作为输入 , 并且 magnitude getter 方法应该返回一个 double。

请注意省略号 (...) 表示我们略过了 一部分代码,因此更容易查看代码的变动,但是 如果检查完整文件的话,其他部分的代码仍然存在。

在 Earthquake.java 中:

 /** 地震震级 */
 private double mMagnitude;

 …

 /**
  * 构造一个新 {@link Earthquake} 对象。 * * @param magnitude 表示地震的震级(大小) * @param location 表示地震发生的位置 * @param timeInMilliseconds 表示地震发生时以毫秒(根据 Epoch)计的时间 */ public Earthquake(double magnitude, String location, long timeInMilliseconds) { mMagnitude = magnitude; mLocation = location; mTimeInMilliseconds = timeInMilliseconds; } … /** * 返回地震的震级。 */ public double getMagnitude() { return mMagnitude; } 

以 double 形式存储地震震级的另一个优势在于 可以更容易地进行数学计算。例如,如果我们想 查找所有地震的平均震级,可以轻松地 将所有震级相加,并除以地震次数。如果 震级值是文本字符串,则无法进行 数学计算。

在 EarthquakeAdapter 中,我们将数值格式化为 UI 所需的形式。在这里,我们仅需要显示 一位小数。我们创建了一个名为 formatMagnitude() 的辅助方法, 该方法接收 double 值作为输入,并返回格式化后的字符串。 该帮助方法使用模式字符串 "0.0" 初始化 DecimalFormat 对象实例。然后在适配器的 getView() 方法中, 我们可以从当前的 Earthquake 对象中读取震级值, 将数值格式化为字符串,并更新 TextView 以显示这个值。

在 EarthquakeAdapter.java 中:

 import java.text.DecimalFormat;

 …

 @Override
 public View getView(int position, View convertView, ViewGroup parent) { … // 使用视图 ID magnitude 找到 TextView TextView magnitudeView = (TextView) listItemView.findViewById(R.id.magnitude); // 格式化震级使其显示一位小数 String formattedMagnitude = formatMagnitude(currentEarthquake.getMagnitude()); // 在该 TextView 中显示目前地震的震级 magnitudeView.setText(formattedMagnitude); … } /** * 从十进制震级值返回格式化后的仅显示一位小数的震级字符串 * (如“3.2”)。 */ private String formatMagnitude(double magnitude) { DecimalFormat magnitudeFormat = new DecimalFormat("0.0"); return magnitudeFormat.format(magnitude); } 

完成后,应用的显示应该是这样。

 
23

现在,可以将你所学到的有关 switch 语句的内容 应用到 Quake Report 应用了!让我先来提供一个环境 并指导你针对下一个编码任务设置你的应用。

在最终的设计中,你会注意到每个列表项的震级值后边 均有一个带颜色的圆圈。

 
 

我们的设计师为我们提供该指南说明圆圈应为 何种颜色。这些颜色从蓝色(低震级值)到红色(高 震级值)。大于 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"。

 
 
 

之后,项目目录面板中应当会出现 magnitude_circle.xml 文件。

 
 

将 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 代码中对颜色进行操纵, 以进一步减少所需的资源数量。项目中图像文件越少 意味着应用越小,这对于最终用户来说 是好事!

shape drawable 主题的详细内容不在本课的讨论范围内, 但是你可以参阅此文档链接 或这篇 Codepath 教程获取更多信息。

现在,修改列表项布局,使震级 TextView 的 背景属性引用我们刚刚定义的新 drawable 资源 (android:background="@drawable/magnitude_circle")。也可以对 TextView 的外观进行其他改动,例如字体大小和颜色, 使其与设计模拟相符。

在 earthquake_list_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_list_item.xml 中:

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

最后,你需要修改 EarthquakeAdapter,以将 每个列表项的背景圆圈设置为正确的颜色。此代码段 假设 magnitudeView 是列表项布局中 R.id.magnitude TextView 的参考。你还需要导入 GradientDrawable 类。

在 EarthquakeAdapter.java 中:

 import android.graphics.drawable.GradientDrawable; 

在 EarthquakeAdapter getView() 中:

 // 为震级圆圈设置正确的背景颜色。
 // 从 TextView 获取背景,该背景是一个 GradientDrawable。
 GradientDrawable magnitudeCircle = (GradientDrawable) magnitudeView.getBackground();

 // 根据当前的地震震级获取相应的背景颜色
 int magnitudeColor = getMagnitudeColor(currentEarthquake.getMagnitude());

 // 设置震级圆圈的颜色 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);

有关测试的注意事项:你认为代码可用后,可通过修改 QueryUtils 类中的样例 JSON 响应对其进行测试。更改 QueryUtils SAMPLE_JSON_RESPONSE 常数值,以使 地震暂时具有从 0 到 10 的不同震级 值。使用示例 SAMPLE_JSON_RESPONSE 代码片段 。当你在设备上运行该应用时, 应显示全部震级颜色值。

 
 

练习题

通过定义名为 getMagnitudeColor(double magnitude) 的专用辅助方法,基于当前的地震震级返回正确的颜色值来实现此设计更改。

  • 依据指导中提供的步骤进行练习(定义新 颜色、修改列表项布局、添加形状 可绘制对象)。

  • 在 EarthquakeAdapter 中,定义新的辅助方法。使用 switch 语句根据震级值查找与之对应的颜色资源。

提交
 

在本练习中,我们会要求你在 EarthquakeAdapter 类中定义 getMagnitudeColor() 辅助方法。我们同样鼓励使用 switch 语句。这是你实践此方法的一种方式。因为我们希望只有一个case被执行,所以切记在各case间添加 break 语句。

在 EarthquakeAdapter.java 中:

 …

 import android.support.v4.content.ContextCompat;

 … 

 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; } return ContextCompat.getColor(getContext(), magnitudeColorResourceId); } 

我们为你提供了方法签名,以便该方法将 双精度震级值作为输入并返回颜色整数 值。根据文档所述,switch 语句无法 接受双精度值,因此我们应将十进制震级 值转换为整数。此时,小数部分的精度同样不 重要,因为我们只需要知道震级 是否落在 1 到 10 之间的段中。

我们可以使用 Math 类进行一些简单的数学 计算。在此情况下,我们可以计算十进制震级值的 "floor"。这意味着 找到的最近整数小于十进制值。值 1.2 的底值为整数 1。通常,对于正 小数,可以将其看作截去小数点后的 数字部分。

 int magnitudeFloor = (int) Math.floor(magnitude);

将震级表示为整数形式后(存储在 magnitudeFloor 变量中),我们可以编写基于 magnitudeFloor 值执行不同逻辑的 switch 语句。

 switch (magnitudeFloor) {
   ...
 }

在每个条件中,我们将 magnitudeColorResourceId 变量的值设为在 colors.xml 文件中定义的颜色资源之一。对于条件 0 和 1,我们归为要使用 R.color.magnitude1 颜色的同一逻辑。设计决定 对震级小于 2 的地震使用同一 颜色。我们还具有默认条件,即所有震级高于 10 的地震将使用 R.color.magnitude10plus 颜色 资源。

找到对应的颜色资源 ID 后,我们仍需进行下一步, 将其转换为实际颜色值。切记:颜色资源 ID 仅指向我们定义的资源,而非颜色的 值。例如,参考 R.layout.earthquake_list_item 会 告诉我们布局所在。但仅为数字,并非完整的 XML 布局。

可以调用 ContextCompat getColor() 以将颜色资源 ID 转换为 实际整数颜色值,并将结果作为 getMagnitudeColor() 辅助方法的返回值返回。

 ContextCompat.getColor(getContext(), magnitudeColorResourceId);

在我们之前提供的 EarthquakeAdapter getView() 代码中, 我们将颜色值设置为以震级 TextView 的背景而设置的 GraidentDrawable。现在你也可以做到了!在 UI 中, 系统会自动为震级圆设置适合的颜色!

练习完成前后的差异

24

我们计划在此列表中添加一些可视化增强。我们通常 会在构建应用结束时执行此步骤,但是因为我们已经 在本课中关注了 UI 变化,因此现在将进行复习。

新颜色

1.将这些新颜色添加到 res/values/colors.xml 文件。稍后将在 我们为你提供的 earthquake_list_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="textColorEarthquakeLocation">#2B3D4D</color> 

列表项布局

2.使用提供的布局更新 earthquake_list_item.xml 文件。 我们已设定了用于反映设计模拟的字体样式、内边距 和视图的定位。你可以使用我们提供的视图 ID, 或者修改布局文件以使用 Java 代码所依据的 视图 ID。

<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:paddingEnd="16dp" android:paddingLeft="16dp" android:paddingRight="16dp" android:paddingStart="16dp"> <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" /> <LinearLayout android:layout_width="0dp" android:layout_height="wrap_content" android:layout_gravity="center_vertical" android:layout_marginLeft="16dp" android:layout_marginStart="16dp" android:layout_weight="1" android:orientation="vertical"> <TextView android:id="@+id/location_offset" 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_location" android:layout_width="wrap_content" android:layout_height="wrap_content" android:ellipsize="end" android:maxLines="2" android:textColor="@color/textColorEarthquakeLocation" 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_marginLeft="16dp" android:layout_marginStart="16dp" android:orientation="vertical"> <TextView android:id="@+id/date" 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" 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> 

要预览布局,可以在 Android Studio 中切换到设计面板,或者可在设备上运行该应用。

注意: 有关特定属性的详细信息,请查看 TextView 文档。你可能对 ellipsize 和 maxLines 属性尤其陌生。这两个 属性表示:如果 TextView 中的文本长度超过了 maxLines 数,我们就可以在 文本(如文本结尾处)中添加省略号 (“...”) 。 很多定位都通过 layout_gravity 属性完成,该属性是 LinearLayout 布局参数,就像 layout_height 和 layout_width 一样。

隐藏列表项间的分隔线

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"/> 

练习完成前后的差异

结果

完成以上所有代码更改后,你的应用应如下所示!

 
 

转载于:https://www.cnblogs.com/infocodez/p/8383902.html

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值