创建一个地震Viewer
接下来的例子,你将要创建一个使用USGS地震种子来显示最近地震列表的工具。
你将数次回到这个地震应用程序,第一次是在第6章中的用一个Content Provider保存和共享地震数据,再一次是在第7章和第8章中,添加映射支持和在后台服务中更新地震数据。
在这个例子中,你将要创建一个基于List的Activity,它连向一个地震种子,并显示位置、等级以及地震的时间。另外,你还将使用一个AlertDialog来提供一个细节的窗口,包含一个链向USGS站点的可链接TextView。
1. 创建一个地震工程,包含一个Earthquake Activity。修改main.xml layout资源包含一个ListView控件——确保要命名它,因为在Activity的代码中你要引用它。
<?xml version=”1.0” encoding=”utf-8”?>
<LinearLayout xmlns:android=”http://schemas.android.com/apk/res/android”
android:orientation=”vertical”
android:layout_width=”fill_parent”
android:layout_height=”fill_parent”>
<ListView
android:id=”@+id/earthquakeListView”
android:layout_width=”fill_parent”
android:layout_height=”wrap_content”
/>
</LinearLayout>
2. 创建一个新的Quake公共类。这个类用于储存每个地震的细节(时间,细节,位置,等级和链接等)。重写toString方法来提供在ListView中使用的每个Quake的字符串。
package com.paad.earthquake;
import java.util.Date;
import java.text.SimpleDateFormat;
import android.location.Location;
public class Quake {
private Date date;
private String details;
private Location location;
private double magnitude;
private String link;
public Date getDate() { return date; }
public String getDetails() { return details; }
public Location getLocation() { return location; }
public double getMagnitude() { return magnitude; }
public String getLink() { return link; }
public Quake(Date _d, String _det, Location _loc, double _mag,String _link) {
date = _d;
details = _det;
location = _loc;
magnitude = _mag;
link = _link;
}
@Override
public String toString()
{
SimpleDateFormat sdf = new SimpleDateFormat(“HH.mm”);
String dateString = sdf.format(date);
return dateString + “: “ + magnitude + “ “ + details;
}
}
3. 在Earthquake Activity里,重写onCreate方法来储存一个Quake对象的ArrayList,并使用一个ArrayAdapter绑定其到ListView上。
package com.paad.earthquake;
import java.io.IOException;
import java.io.InputStream;
import java.net.HttpURLConnection;
import java.net.MalformedURLException;
import java.net.URL;
import java.net.URLConnection;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Date;
import java.util.GregorianCalendar;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.NodeList;
import org.xml.sax.SAXException;
import android.app.Activity;
import android.app.Dialog;
import android.location.Location;
import android.os.Bundle;
import android.view.Menu;
import android.view.View;
import android.view.WindowManager;
import android.view.MenuItem;
import android.widget.AdapterView;
import android.widget.ArrayAdapter;
import android.widget.ListView;
import android.widget.TextView;
import android.widget.AdapterView.OnItemClickListener;
public class Earthquake extends Activity {
ListView earthquakeListView;
ArrayAdapter<Quake> aa;
ArrayList<Quake> earthquakes = new ArrayList<Quake>();
@Override
public void onCreate(Bundle icicle) {
super.onCreate(icicle);
setContentView(R.layout.main);
earthquakeListView =(ListView)this.findViewById(R.id.earthquakeListView);
int layoutID = android.R.layout.simple_list_item_1;
aa = new ArrayAdapter<Quake>(this, layoutID , earthquakes);
earthquakeListView.setAdapter(aa);
}
}
4. 接下来,处理地震种子。在这个例子中,使用的种子是1天的USGS种子,并且等级大于2.5级。
以外部字符串资源的方式添加种子的位置。这种方式的优点是对于不同的用户位置来说可以指定不同的种子。
<?xml version=”1.0” encoding=”utf-8”?>
<resources>
<string name=”app_name”>Earthquake</string>
<string name=”quake_feed”>
http://earthquake.usgs.gov/eqcenter/catalogs/1day-M2.5.xml
</string>
</resources>
5. 在你的程序能访问Internet之前,需要授予Internet的权限。在manifest中添加uses-permission节点。
<uses-permission xmlns:android=”http://schemas.android.com/apk/res/android”
android:name=”android.permission.INTERNET”>
</uses-permission>
6. 回到Earthquake Activity中,创建一个新的refreshEarthquakes方法来连接,解析地震种子。提取每个earthquake,并解析细节来获得时间、等级、链接和位置。当你完成每个earthquake的解析,传递它到新的addNewQuake方法中。
XML的解析如下所示,没有太多的解释。
private void refreshEarthquakes()
{
// Get the XML
URL url;
try
{
String quakeFeed = getString(R.string.quake_feed);
url = new URL(quakeFeed);
URLConnection connection;
connection = url.openConnection();
HttpURLConnection httpConnection = (HttpURLConnection)connection;
int responseCode = httpConnection.getResponseCode();
if (responseCode == HttpURLConnection.HTTP_OK)
{
InputStream in = httpConnection.getInputStream();
DocumentBuilderFactory dbf;
dbf = DocumentBuilderFactory.newInstance();
DocumentBuilder db = dbf.newDocumentBuilder();
// Parse the earthquake feed.
Document dom = db.parse(in);
Element docEle = dom.getDocumentElement();
// Clear the old earthquakes
earthquakes.clear();
// Get a list of each earthquake entry.
NodeList nl = docEle.getElementsByTagName(“entry”);
if (nl != null && nl.getLength() > 0)
{
for (int i = 0 ; i < nl.getLength(); i++)
{
Element entry = (Element)nl.item(i);
Element title =
(Element)entry.getElementsByTagName(“title”).item(0);
Element g =
(Element)entry.getElementsByTagName(“georss:point”).item(0);
Element when =
(Element)entry.getElementsByTagName(“updated”).item(0);
Element link =
(Element)entry.getElementsByTagName(“link”).item(0);
String details = title.getFirstChild().getNodeValue();
String hostname = “http://earthquake.usgs.gov/”;
String linkString = hostname + link.getAttribute(“href”);
String point = g.getFirstChild().getNodeValue();
String dt = when.getFirstChild().getNodeValue();
SimpleDateFormat sdf;
sdf = new SimpleDateFormat(“yyyy-MM-dd’T’hh:mm:ss’Z’”);
Date qdate = new GregorianCalendar(0,0,0).getTime();
try
{
qdate = sdf.parse(dt);
}
catch (ParseException e)
{
e.printStackTrace();
}
String[] location = point.split(“ “);
Location l = new Location(“dummyGPS”);
l.setLatitude(Double.parseDouble(location[0]));
l.setLongitude(Double.parseDouble(location[1]));
String magnitudeString = details.split(“ “)[1];
int end = magnitudeString.length()-1;
double magnitude;
magnitude = Double.parseDouble(magnitudeString.substring(0, end));
details = details.split(“,”)[1].trim();
Quake quake = new Quake(qdate, details, l,
magnitude, linkString);
// Process a newly found earthquake
addNewQuake(quake);
}
}
}
}
catch (MalformedURLException e)
{
e.printStackTrace();
}
catch (IOException e)
{
e.printStackTrace();
}
catch (ParserConfigurationException e)
{
e.printStackTrace();
}
catch (SAXException e)
{
e.printStackTrace();
}
finally
{
}
}
private void addNewQuake(Quake _quake) {
// TODO: Add the earthquakes to the array list.
}
7. 更新addNewQuake方法,让它可以带一个刚处理过的Quake参数并将其添加Earthquake的ArrayList中。它还应该通知ArrayAdapter底层数据发生了变化。
private void addNewQuake(Quake _quake) {
// Add the new quake to our list of earthquakes.
earthquakes.add(_quake);
// Notify the array adapter of a change.
aa.notifyDataSetChanged();
}
8. 修改onCreate方法在启动时调用refreshEarthquakes。
@Override
public void onCreate(Bundle icicle) {
super.onCreate(icicle);
setContentView(R.layout.main);
earthquakeListView =
(ListView)this.findViewById(R.id.earthquakeListView);
int layoutID = android.R.layout.simple_list_item_1;
aa = new ArrayAdapter<Quake>(this, layoutID , earthquakes);
earthquakeListView.setAdapter(aa);
refreshEarthquakes();
}
Internet查询目前发生在UI主线程。这是一种糟糕的方式,当查询需要花费超过几秒的时间时应用程序会变得无响应。在第8章,你将学习如何将这样的耗时的操作移到后台线程中。
9. 如果你运行你的工程,你应该看到一个ListView中显示了过去24小时里等级大于2.5的地震,如图5-6所示。
图5-6
10. 还剩下两步来能你的程序变得更加有用。第一,创建一个菜单项来让用户在需要的时候更新一下地址种子。
10.1 为菜单选择添加外部字符串。
<string name=”menu_update”>Refresh Earthquakes</string>
10.2 然后重写Activity的onCreateOptionsMenu和onOptionsItemSelected方法,来显示和处理菜单项的更新操作。
static final private int MENU_UPDATE = Menu.FIRST;
@Override
public boolean onCreateOptionsMenu(Menu menu) {
super.onCreateOptionsMenu(menu);
menu.add(0, MENU_UPDATE, Menu.NONE, R.string.menu_update);
return true;
}
@Override
public boolean onOptionsItemSelected(MenuItem item) {
super.onOptionsItemSelected(item);
switch (item.getItemId())
{
case (MENU_UPDATE):
{
refreshEarthquakes();
return true;
}
}
return false;
}
11. 现在,添加一些用户交互。当用户从地震列表中选择一项时,打开一个对话框,显示更多的地震细节。
11.1 创建新的quake_details.xml layout资源,作为用户点击项目时显示的对话框的布局。
<?xml version=”1.0” encoding=”utf-8”?>
<LinearLayout
xmlns:android=”http://schemas.android.com/apk/res/android”
android:orientation=”vertical”
android:layout_width=”fill_parent”
android:layout_height=”fill_parent”
android:padding=”10sp”>
<TextView
android:id=”@+id/quakeDetailsTextView”
android:layout_width=”fill_parent”
android:layout_height=”fill_parent”
android:textSize=”14sp”
/>
</LinearLayout>
11.2 然后,修改onCreate方法,为ListView添加一个onItemClickListener,当一个地震项目选择时显示一个对话框。
static final private int QUAKE_DIALOG = 1;
Quake selectedQuake;
@Override
public void onCreate(Bundle icicle) {
super.onCreate(icicle);
setContentView(R.layout.main);
earthquakeListView =
(ListView)this.findViewById(R.id.earthquakeListView);
earthquakeListView.setOnItemClickListener(new OnItemClickListener() {
public void onItemClick(AdapterView _av, View _v, int _index, long arg3) {
selectedQuake = earthquakes.get(_index);
showDialog(QUAKE_DIALOG);
}
});
int layoutID = android.R.layout.simple_list_item_1;
aa = new ArrayAdapter<Quake>(this, layoutID , earthquakes);
earthquakeListView.setAdapter(aa);
refreshEarthquakes();
}
11.3 现在,重写onCreateDialog和onPrepareDialog方法来创建对话框和填入地震细节。
@Override
public Dialog onCreateDialog(int id) {
switch(id) {
case (QUAKE_DIALOG) :
LayoutInflater li = LayoutInflater.from(this);
View quakeDetailsView = li.inflate(R.layout.quake_details, null);
AlertDialog.Builder quakeDialog = new AlertDialog.Builder(this);
quakeDialog.setTitle(“Quake Time”);
quakeDialog.setView(quakeDetailsView);
return quakeDialog.create();
}
return null;
}
@Override
public void onPrepareDialog(int id, Dialog dialog) {
switch(id) {
case (QUAKE_DIALOG) :
SimpleDateFormat sdf;
sdf = new SimpleDateFormat(“dd/MM/yyyy HH:mm:ss”);
String dateString = sdf.format(selectedQuake.getDate());
String quakeText = “Mangitude “ + selectedQuake.getMagnitude() +
“\n” + selectedQuake.getDetails() + “\n” + selectedQuake.getLink();
AlertDialog quakeDialog = (AlertDialog)dialog;
quakeDialog.setTitle(dateString);
TextView tv =
(TextView)quakeDialog.findViewById(R.id.quakeDetailsTextView);
tv.setText(quakeText);
break;
}
}
11.4 最后一步,为对话框制作超链接指向USGS。调整对话框的XML layout资源定义,包含一个autolink特性。
<?xml version=”1.0” encoding=”utf-8”?>
<LinearLayout
xmlns:android=”http://schemas.android.com/apk/res/android”
android:orientation=”vertical”
android:layout_width=”fill_parent”
android:layout_height=”fill_parent”
android:padding=”10sp”>
<TextView
android:id=”@+id/quakeDetailsTextView”
android:layout_width=”fill_parent”
android:layout_height=”fill_parent”
android:textSize=”14sp”
android:autoLink=”all”
/>
</LinearLayout>
再次运行你的Activity。当你点击某个地震项目时,一个对话框会显示出来,并使列表部分模糊,如图5-7所示。
图5-7
Sample Code:
https://files.cnblogs.com/xirihanlin/DL090804@cc-Earthquake.zip
Sample图示: