创建一个应用程序,你会在 Google 地图上使用 SQLite 数据库和
Google 地图 Overlays 来绘制数据记录。
应用程序:找一个朋友
关键技能 & 概念
● 创建一个 SQLite 数据库
● 创建一个定制内容提供者
● 从数据库检索条目并且传递到一个 Google Maps Overlay146
这是你将创建应用程序的最后一章,但是会是本书介绍的最大的一个应用程序。
我将介绍一些到目前为止没有遇到过的话题,而且你会用到这些话题所谈到的技
能创建一个非常健全的应用程序。
在本章中,会学习如何在 Android 模拟器中创建 SQLite 数据库。我会向你展示
如何在定制的数据库中读取,写入并且输出数据。这个过程包括创建并使用你自
己的 Content Provider 来和数据库一同工作。然后,你拿取存储在数据库中的
数据并写入到 Google Maps Overlay 中。当你在前一章用 Google 地图时,还没
有使用 Overlay。使用 Google Maps Overlays 在地图上绘制形状并且写文本,
得到一个有信息量的地图。在这个项目中,将创建一个两部分应用程序。应用程
序的第一部分将允许用户输入“friends”到移动数据库中。(一个 friend 由姓
名和地理坐标位置组成)。用户将能增加,修改并且删除 friends.
第二个部分将包括一个菜单项目。当用户选择这个菜单项目,应用程序将显示一
个 Google 地图。这个 Google 地图和第九章创建的 Google 地图不同之处就是,
这个地图会包含一个 Google Maps Overlay,它会允许你在 Google 地图的标题
处写入姓名,给定信息并且绘制项目。
要开始,在 Eclipse 内创建一个新的 Android 项目,命名为 FindFriend,使用
下图的设定(略)。
现在,你应该对创建一个 Android 应用程序非常的熟悉了,创建本项目会需要一
点小小的帮助。Google 在 Android SDK 里有一个应用程序叫 NotePad,非常简单
但是允许你储存,修改并且删除数据库里的“notes”。你会去修改这个例子的一
些代码来创建 Friends 数据库。
如果你想要知道 Google NotePad 如何工作,在继续之前,在 Eclipse 中装载项
目并且在模拟器中运行它。不久将会开始修改这个代码,但是首先,在下一节里 ,
将创建你的第一个 SQLite 数据库。
创建一个 SQLite 数据库
创建一个 SQLite SQLite SQLite SQLite 数据库 第十一章(2) (2) (2) (2)
Android 设备将发布时会有一个内部的 SQLite 数据库。这个数据库的目的是允
许用户和开发者一个可以在活动中储存信息的地方。
如果你用过 Microsoft SQL 服务器或者 SQLite,使用 Andorid 的 SQLite 数据库
的结构和过程对你将不会陌生。不管你有多少的经验,这个部分将涵盖所有需要
创建和使用全功能 SQlite 数据库的技能。你将要在 Android 模拟器上创建一个
数据库。要实现这个,你需要进入 Android SDK 命令行编辑器工具并使用 shell
命令来进入 Android 服务器。
提示
参考第三章来重拾你的记忆关于路径声明和使用命令行工具。
一旦你进入服务器,你需要导航到数据库的位置。所有的 Android SQLite 数据
库的位置是在 data/data/<package>/147
databases 目录。使用 cd 命令来从当前的目录改变到 data 目录,并且再到
<package>目录。如果你不确定<package>目录的名称,使用 ls 来列出文件和当
前目录。改变目录至<package>android_programmers_
guide.FindAFriend,如下所示(略)
警告
如果你没有 android_programmers_guide.FindAFriend 目录,按照前一部分描述
的方式创建你的应用程序并且运行“Hello World!”默认的由项目创建的应用程
序,那样会确保你有个正确的目录。
找到 android_programmers_guide.FindAFriend 目录后,运行 Is 命令。这个命
令列出特定文件夹内所有的文件和目录。改命令应当返回空的内容。因为,此时
在该目录内没有文件和文件夹。
假定 SQLite 数据库必须在本目录下的一个数据库目录内,是时候来创建一个了。
mkdir 工具为你创建目录。因此,运行 mkdir databases 命令。它将创建保留数
据库的目录。
警告
现在,你几乎是在服务器的根目录上。因此你刚刚创建的目录将被作为根目录进
入。当你运行活动时,可能会出问题,因为每一个活动有一个不同的用户。出于
开发的目的,要解决这个问题,运行 chmod 777 databases 来准许每个人都能进
入到数据库目录。将来,你必须对给予每个人的权力到一些敏感的 Android 条目
非常谨慎才行。只给予特定的用户需要使用特定条目的权力。
已经创建了数据库目录了,可以创建数据库了。使用 cd 命令导航到数据库目录。
在数据库目录后,使用 sqlite3 工具来创建数据库并命名它为 friends.db,如
下:
如果执行命令成功,你应当能看到一个 SQLite3 版本信息,本例是 3.5.0,和一
个 SQLite3 prompt—sqlite>。这说明数据库已被建立但是是空的。数据库没有
包含表格和数据。记住,下一步是为活动数据创建一个表格。
你 需 要 创 建 一 个 名 为 friends 的 表 格 。 这 个 表 将 保 留
id,name,location,created, 和 modified 字段。这些字段将为你的项目提供足
够的信息。
提示
如果你对 SQLite 不熟悉,一个 SQLite 命令必须以分号结束。如果你想要跨越一
个命令这个会有帮助。没有终止 SQLite 命令的情况下,按下 ENTER 键会继续给
你一个提示符,…>。你不能在提示符继续输入命令,除非你使用分号。一旦分
号被使用,SQLite 将把连续的命令作为一个完整的命令。
要在数据库内创建 friends 表格,在 sqlite>提示符输入下列命令:
# sqlite friends.db
CREATE TABLE friends (_id INTEGER PRIMARY KEY, name TEXT, location TEXT,
created INTEGER, modified INTEGER);148
如果命令执行成功,将返回到 sqlite>提示符,如下图所示(略)。
数据库现在可以被使用了,你可以退出 SQLite 了。使用.exit 来退出。然后可
以退出 shell 部分返回到 Eclipse.
创建数据库是创建应用程序的第一步。现在数据库和相应的表格已经被创建,你
需要一个方法来存储数据。受雇 Android 数据的存储方式是一个 Content
Provider 。 下 面 的 部 分 带 你 走 进 如 何 为 新 数 据 库 创 建 一 个 定 制 Content
Provider 并存储数据。
创建一个定制的 Content Provider
创建一个定制的 Content Content Content Content Provider Provider Provider Provider 第十一章(3) (3) (3) (3)
Android 使用 Content Provider 来存 储 数 据 。你 在第 九 章使 用 过 Content
Provider 存储并从一个 GPS 中读取坐标信息。同样的过程应用于数据库。有这
样一些 Content Provider Contact Lists,IMs,和 Recent
Calls。总之,没有为你数据库准备的 Content Provider。Android 是非常之灵
活并且允许你为定制的数据创建定制的 Content Provider。在本节,将创建一
个和 Friends 数据工作的 Content Provider。这是存取 friend 数据和最终在屏
幕上显示它们的关键所在。
下一节,让我们来编辑 string.xml 文件。这个文件保留全局遍及活动的字符串
内容。
编辑 string.xml 文件
首先,为项目编辑 string.xml 文件。string.xml 文件由每个项目创建但是你还
没有使用过它。这个文件保留可以在活动中使用的静态字符串。
通常,你不大可能在写这些字符串之前仔细查看所有的部分。那就是,当你构造
活动时,你通常增加到 string.xml 的入口。因为那样会打断本书的流程,所以
我预先给你所有 string.xml 文件的所有内容。编辑 string.xml 文件使它看上去
像这样:
<?xml version="1.0" encoding="utf-8"?>
<resources>
<string name="app_name">FindAFriend</string>
<string name="menu_delete">Delete</string>
<string name="menu_insert">Add Friend</string>
<string name="find_friends">Find Friends</string>
<string name="menu_revert">Revert</string>
<string name="menu_discard">Discard</string>
<string name="resolve_edit">Edit location</string>
<string name="resolve_title">Edit name</string>
<string name="title_create">Create Friend</string>
<string name="title_edit">Edit Friend</string>149
完成 string.xml 文件后,需要创建一个.java 文件来保留你的代码。应该命名
这个文件为 FriendsProvider.java。还要创建另外一个.java 文件来保留数据定
义。命名这个文件为 Friends.java,因为它会定义一个 Friends 数据结构像什
么样子并且允许你的 Content Provider 正常进入。(因为 provider 将会是项目
中的一个类,没有必要来构建一个相应的.xml 布局文件)。
提示
技术上,对于应用程序,你定制的 Content Provider 不需要定居在同一项目或
包装内作为代码的剩余部分。为了简单明了,我在 FindAFriend 项目里做了一个
类。假如你计划创建一个可能用户多重项目的 Content Provider,在单独的包
装中创建它吧。这样,当你只想要使用 Content Provider 时,会允许你呼叫一
个包装。
让我们开始 Friends.java 文件。你只要为相关的类需要输入两个包装:
BaseColumns 将被一个从主 Friends 类中的 subclass 执行。命名这个 subclass
为 Friend,因为它代表 Friends 数据集中的一个 friend。下面的代码显示如何
设置类的概要:
这个类将保留一下静态变量,它们定义 Friends 数据库中的每一列,Content
URI,和记录的默认排序。
提示
Content URI 被用于识别将要处理的内容。这个数值必须唯一。
需要定义的字符串如下:
<string name="title_notes_list">Friends</string>
<string name="title_note">Location</string>
<string name="title_edit_title">Friend Name:</string>
<string name="button_ok">OK</string>
<string name="error_title">Error</string>
<string name="error_message">Error loading note</string>
</resources>
import android.net.Uri;
import android.provider.BaseColumns;
public final class Friends {
public static final class Friend implements BaseColumns {
}
}
public static final Uri CONTENT_URI
=
Uri.parse("content://android_programmers_guide.FindAFriend.Friends/friend");
public static final String DEFAULT_SORT_ORDER = "modified DESC";
public static final String NAME = "name";150
有了变量的设定以后,Friends 类放在一起应该是这样的:
创建 Content Provider
创建 Content Content Content Content Provider Provider Provider Provider 第十一章(4) (4) (4) (4)
使用 Eclipse 打开将会成为项目 Content Provider 的 FriendsProvider.java
文件。你将要在活动中使用这个定制的 Content Provider 来从 Friends 数据库
中检索数据。
和往常一样。让我们从导入文件开始。你需要输入 Friends 类和一些其它的类:
如你所见,你输入类的大多数和 SQL 打交道。当你用这些包装的时候,我再向你
解释。
public static final String LOCATION = "location";
public static final String CREATED_DATE = "created";
public static final String MODIFIED_DATE = "modified";
package android_programmers_guide.FindAFriend;
import android.net.Uri;
import android.provider.BaseColumns;
public final class Friends {
public static final class Friend implements BaseColumns {
public static final Uri CONTENT_URI
=
Uri.parse("content://android_programmers_guide.FindAFriend.Friends/friend");
public static final String DEFAULT_SORT_ORDER = "modified DESC";
public static final String NAME = "name";
public static final String LOCATION = "location";
public static final String CREATED_DATE = "created";
public static final String MODIFIED_DATE = "modified";
}
}
import android_programmers_guide.FindAFriend.Friends;
import android.content.*;
import android.database.Cursor;
import android.database.SQLException;
import android.database.sqlite.SQLiteOpenHelper;
import android.database.sqlite.SQLiteDatabase;
import android.database.sqlite.SQLiteQueryBuilder;
import android.net.Uri;
import android.text.TextUtils;
import android.util.Log;
import java.util.HashMap;151
第一个将要使用的包装是 android.content。要成为一个 Content Provider,需
要利用并优先要求的方法,你的 FriendsProvider 类需要扩展 ContentProvider。
看一下下面类的概要,它们包括一些将要使用在 Provider 的变量定义:
Content Provider 包含一些需要优先的方法,包括 onCreate( ), query( ),
insert( ), delete( ), 和 update( )。因为这些方法将被活动使用 Content
Provider 呼叫,你必须优先使用它们来进入 Friends 数据库。
你将优先 onCreate( ) 方法呼叫一个 SQLiteOpenHelper。因此,在能优先
ContentProvider 的 onCreate() 之 前 , 你 不 得 不 创 建 一 个 类 扩 展
SQLiteIpenHelper。
代码块跟从的是一个 Content Provider 的子类。它扩展 SQLiteOpenHelper:
刚创建的 DatabaseHelper 类包含两个优先方法:onCreater()和 onUpgrade()。
onCreate()方法被用于当从代码中创建数据库,或者表格定义不存在的示例中。
注意
假 定 你 从 adb 壳 中 创 建 数 据 库 结 构 , 你 不 会 依 赖 于 DatabaseHelper 的
public class FriendsProvider extends ContentProvider {
private SQLiteDatabase mDB;
private static final String TAG = "FriendsProvider";
private static final String DATABASE_NAME = "friends";
private static final int DATABASE_VERSION = 2;
private static HashMap<String, String> FRIENDS_PROJECTION_MAP;
private static final int FRIENDS = 1;
private static final int FRIENDS_ID = 2;
private static final UriMatcher URL_MATCHER;}
private static class DatabaseHelper extends SQLiteOpenHelper {
@Override
public void onCreate(SQLiteDatabase db) {
db.execSQL("CREATE TABLE friends (_id INTEGER PRIMARY KEY,"
+ "name TEXT," + "location TEXT," + "created INTEGER,"
+ "modified INTEGER" + ");");
}
@Override
public void onUpgrade(SQLiteDatabase db, int oldVersion, int
newVersion) {
Log.w(TAG, "Upgrading database from version " + oldVersion + "to "
+ newVersion + ", which will destroy all old data");
db.execSQL("DROP TABLE IF EXISTS friends");
onCreate(db);
}
}152
onCreate()方法来建立你的数据库。
DatabaseHelper 类创建后,你现在可以为 Content Provider 优先 onCreate()
方法了:
这是个非常之简单的方法,结果就是返回一个布尔值代表你的数据库是否可以被
打开。你使用在兄弟类中创建的 SQLiteOpenHelper 来打开 Friends 数据库。注
意你传递数据库名称到 DatabaseHelper 类。如果数据库对象——mDB 返回的不
是 null,然后数据库就成功的被打开并可以查询了。
下一步,优先 ContentProvider 类的 query()方法。这将是 Content Provider
的主要部分。query()方法是从活动通过 Content Provider 被呼叫来从数据库
中获得记录。看下优先版本的 query()方法代码:
@Override
public boolean onCreate() {
DatabaseHelper dbHelper = new DatabaseHelper();
mDB = dbHelper.openDatabase(getContext(), DATABASE_NAME, null,
DATABASE_VERSION);
return (mDB == null) ? false : true;
}
@Override
public Cursor query(Uri url, String[] projection, String selection,
String[] selectionArgs, String sort) {
SQLiteQueryBuilder qb = new SQLiteQueryBuilder();
switch (URL_MATCHER.match(url)) {
case FRIENDS:
qb.setTables("friends");
qb.setProjectionMap(FRIENDS_PROJECTION_MAP);
break;
case FRIENDS_ID:
qb.setTables("friends");
qb.appendWhere("_id=" + url.getPathSegments().get(1));
break;
default:
throw new IllegalArgumentException("Unknown URL " + url);
}
String orderBy;
if (TextUtils.isEmpty(sort)) {
orderBy = Friends.Friend.DEFAULT_SORT_ORDER;
} else {
orderBy = sort;
}
Cursor c = qb.query(mDB, projection, selection, selectionArgs, null,153
query()方法做了一点家务管理之类的事宜,通过检查传递到其中的数据库 URL
的有效性和定义一个查询分类序列达到的。URL 检查是为了确保你只是要进入
Friends 数据库。如果你试图从其它活动进入数据库,或者从其它的 Content
Provider,query()方法投递一个例外。
到方法的结尾,你使用 SQLiteQueryBuilder 来执行一个查询。通过下面的代码,
导致的数据集被赋值到一个光标:
注意
光标是一个设备允许你移动记录并从数据列中返回信息。
update( ),delete( ), 和 insert( ) 方法同样的简单。看一下这三个方法,
应当优先它们:
null, orderBy);
c.setNotificationUri(getContext().getContentResolver(), url);
return c;
}
Cursor c = qb.query(mDB, projection, selection, selectionArgs, null,
null, orderBy);
@Override
public Uri insert(Uri url, ContentValues initialValues) {
long rowID;
ContentValues values;
if (initialValues != null) {
values = new ContentValues(initialValues);
} else {
values = new ContentValues();
}
if (URL_MATCHER.match(url) != FRIENDS) {
throw new IllegalArgumentException("Unknown URL " + url);
}
Long now = Long.valueOf(System.currentTimeMillis());
Resources r = Resources.getSystem();
if (values.containsKey(Friends.Friend.CREATED_DATE ) == false) {
values.put(Friends.Friend.CREATED_DATE, now);
}
if (values.containsKey(Friends.Friend.MODIFIED_DATE) == false) {
values.put(Friends.Friend.MODIFIED_DATE, now);
}
if (values.containsKey(Friends.Friend.NAME) == false) {
values.put(Friends.Friend.NAME,
r.getString(android.R.string.untitled));154
}
if (values.containsKey(Friends.Friend.LOCATION) == false) {
values.put(Friends.Friend.LOCATION , "");
}
270 Android: A Programmer’s Guide
rowID = mDB.insert("friends", "friend", values);
if (rowID > 0) {
Uri uri = ContentUris.withAppendedId(Friends.Friend.CONTENT_URI
, rowID);
getContext().getContentResolver().notifyChange(uri, null);
return uri;
}
throw new SQLException("Failed to insert row into " + url);
}
@Override
public int delete(Uri url, String where, String[] whereArgs) {
int count;
long rowId = 0;
switch (URL_MATCHER.match(url)) {
case FRIENDS:
count = mDB.delete("friends", where, whereArgs);
break;
case FRIENDS_ID:
String segment = url.getPathSegments().get(1);
rowId = Long.parseLong(segment);
count = mDB
.delete("friends", "_id="
+ segment
+ (!TextUtils.isEmpty(where) ? " AND (" + where
+ ')' : ""), whereArgs);
break;
default:
throw new IllegalArgumentException("Unknown URL " + url);
}
getContext().getContentResolver().notifyChange(url, null);
return count;
}
@Override
public int update(Uri url, ContentValues values, String where, String[]
whereArgs) {
int count;
switch (URL_MATCHER.match(url)) {
case FRIENDS:
count = mDB.update("friends", values, where, whereArgs);155
这些方法的代码应当不需要再加以说明了。如果看过在每个方法的处理,代码的
核心就是发出一个数据库声明来执行要求的动作——更新,删除,或者插入。
最后的 Content Provider 的部分就是一个 getType()方法,它返回 Friends 数
据类型。当创建自己的类型,应当跟从一下协议:
这样就完成了新的定制的 Content Provider。看一下完成的 FriendsProvider
代码:
break;
case FRIENDS_ID:
String segment = url.getPathSegments().get(1);
count = mDB
.update("friends", values, "_id=" + segment
Chapter 11: Application: Find a Friend 271
+ (!TextUtils.isEmpty(where) ? " AND (" + where + ')' : ""), whereArgs);
break;
default:
throw new IllegalArgumentException("Unknown URL " + url);
}
getContext().getContentResolver().notifyChange(url, null);
return count;
}
vnd.android.cursor.dir/vnd.<package>
Take a look at the getType( ) method:
@Override
public String getType(Uri url) {
switch (URL_MATCHER.match(url)) {
case FRIENDS:
return
"vnd.android.cursor.dir/vnd.android_programmers_guide.friend";
case FRIENDS_ID:
return
"vnd.android.cursor.item/vnd.android_programmers_guide.friend";
default:
throw new IllegalArgumentException("Unknown URL " + url);
}
}
package android_programmers_guide.FindAFriend;
import android_programmers_guide.FindAFriend.Friends;
import android.content.*;
import android.database.Cursor;
import android.database.SQLException;
import android.database.sqlite.SQLiteOpenHelper;156
import android.database.sqlite.SQLiteDatabase;
import android.database.sqlite.SQLiteQueryBuilder;
import android.net.Uri;
import android.text.TextUtils;
import android.util.Log;
import java.util.HashMap;
public class FriendsProvider extends ContentProvider {
private SQLiteDatabase mDB;
private static final String TAG = "FriendsProvider";
private static final String DATABASE_NAME = "friends";
private static final int DATABASE_VERSION = 2;
private static HashMap<String, String> FRIENDS_PROJECTION_MAP;
private static final int FRIENDS = 1;
private static final int FRIENDS_ID = 2;
private static final UriMatcher URL_MATCHER;
private static class DatabaseHelper extends SQLiteOpenHelper {
@Override
public void onCreate(SQLiteDatabase db) {
db.execSQL("CREATE TABLE friends (_id INTEGER PRIMARY KEY,"
+ "name TEXT," + "location TEXT," + "created INTEGER,"
+ "modified INTEGER" + ");");
}
@Override
public void onUpgrade(SQLiteDatabase db, int oldVersion, int
newVersion) {
Log.w(TAG, "Upgrading database from version " + oldVersion + "to "
+ newVersion + ", which will destroy all old data");
db.execSQL("DROP TABLE IF EXISTS friends");
onCreate(db);
}
}
@Override
public boolean onCreate() {
DatabaseHelper dbHelper = new DatabaseHelper();
mDB = dbHelper.openDatabase(getContext(), DATABASE_NAME, null,
DATABASE_VERSION);
return (mDB == null) ? false : true;
}
@Override
public Cursor query(Uri url, String[] projection, String selection,
String[] selectionArgs, String sort) {
SQLiteQueryBuilder qb = new SQLiteQueryBuilder();
switch (URL_MATCHER.match(url)) {
case FRIENDS:157
qb.setTables("friends");
qb.setProjectionMap(FRIENDS_PROJECTION_MAP);
break;
case FRIENDS_ID:
qb.setTables("friends");
qb.appendWhere("_id=" + url.getPathSegments().get(1));
break;
default:
throw new IllegalArgumentException("Unknown URL " + url);
}
String orderBy;
if (TextUtils.isEmpty(sort)) {
orderBy = Friends.Friend.DEFAULT_SORT_ORDER;
} else {
orderBy = sort;
}
Cursor c = qb.query(mDB, projection, selection, selectionArgs, null,
null, orderBy);
c.setNotificationUri(getContext().getContentResolver(), url);
return c;
}
@Override
public String getType(Uri url) {
switch (URL_MATCHER.match(url)) {
case FRIENDS:
return
"vnd.android.cursor.dir/vnd.android_programmers_guide.friend";
case FRIENDS_ID:
return
"vnd.android.cursor.item/vnd.android_programmers_guide.friend";
default:
throw new IllegalArgumentException("Unknown URL " + url);
}
}
@Override
public Uri insert(Uri url, ContentValues initialValues) {
long rowID;
ContentValues values;
if (initialValues != null) {
values = new ContentValues(initialValues);
} else {
values = new ContentValues();
}
if (URL_MATCHER.match(url) != FRIENDS) {158
throw new IllegalArgumentException("Unknown URL " + url);
}
Long now = Long.valueOf(System.currentTimeMillis());
Resources r = Resources.getSystem();
if (values.containsKey(Friends.Friend.CREATED_DATE ) == false) {
values.put(Friends.Friend.CREATED_DATE, now);
}
if (values.containsKey(Friends.Friend.MODIFIED_DATE) == false) {
values.put(Friends.Friend.MODIFIED_DATE, now);
}
if (values.containsKey(Friends.Friend.NAME) == false) {
values.put(Friends.Friend.NAME,
r.getString(android.R.string.untitled));
}
if (values.containsKey(Friends.Friend.LOCATION) == false) {
values.put(Friends.Friend.LOCATION , "");
}
rowID = mDB.insert("friends", "friend", values);
if (rowID > 0) {
Uri uri = ContentUris.withAppendedId(Friends.Friend.CONTENT_URI
, rowID);
getContext().getContentResolver().notifyChange(uri, null);
return uri;
}
throw new SQLException("Failed to insert row into " + url);
}
@Override
public int delete(Uri url, String where, String[] whereArgs) {
int count;
long rowId = 0;
switch (URL_MATCHER.match(url)) {
case FRIENDS:
Chapter 11: Application: Find a Friend 275
count = mDB.delete("friends", where, whereArgs);
break;
case FRIENDS_ID:
String segment = url.getPathSegments().get(1);
rowId = Long.parseLong(segment);
count = mDB
.delete("friends", "_id="
+ segment
+ (!TextUtils.isEmpty(where) ? " AND (" + where
+ ')' : ""), whereArgs);
break;159
有了最根本的数据素材(数据库,定义和 Content Provider),你可以开始来建
default:
throw new IllegalArgumentException("Unknown URL " + url);
}
getContext().getContentResolver().notifyChange(url, null);
return count;
}
@Override
public int update(Uri url, ContentValues values, String where, String[]
whereArgs) {
int count;
switch (URL_MATCHER.match(url)) {
case FRIENDS:
count = mDB.update("friends", values, where, whereArgs);
break;
case FRIENDS_ID:
String segment = url.getPathSegments().get(1);
count = mDB
.update("friends", values, "_id=" + segment
+ (!TextUtils.isEmpty(where) ? " AND (" + where + ')' : ""), whereArgs);
break;
default:
throw new IllegalArgumentException("Unknown URL " + url);
}
getContext().getContentResolver().notifyChange(url, null);
return count;
}
static {
URL_MATCHER = new UriMatcher(UriMatcher.NO_MATCH);
URL_MATCHER.addURI("android_programmers_guide.FindAFriend.Friends",
"friend", FRIENDS);
URL_MATCHER.addURI("android_programmers_guide.FindAFriend.Friends",
"friend/#", FRIENDS_ID);
FRIENDS_PROJECTION_MAP = new HashMap<String, String>();
FRIENDS_PROJECTION_MAP.put(Friends.Friend._ID, "_id");
FRIENDS_PROJECTION_MAP.put(Friends.Friend.NAME, "name");
FRIENDS_PROJECTION_MAP.put(Friends.Friend.LOCATION, "location");
FRIENDS_PROJECTION_MAP.put(Friends.Friend.CREATED_DATE, "created");
FRIENDS_PROJECTION_MAP.put(Friends.Friend.MODIFIED_DATE,
"modified");
}
}160
造周围的活动。记住,活动将使用数据库内的数据,显示到列表,并允许用户启
动另一个活动来放置数据库条目到一个 Google Maps Overlay。在下一节,将构
建活动并完成 FindFriend 应用程序。
创建 FindAFriend 活动
创建 FindAFriend FindAFriend FindAFriend FindAFriend 活动 第十一章(5) (5) (5) (5)
如果你花了些时间运行 Google 的 NotePad 示例,那么你就对活动的布局非常熟
悉 了 。 你 将 修 改 NotePad 的 接 口 来 使 用 Friends 数 据 库 和 Google 地 图 。
FindAFriend 活动将和一些小的活动交互:NameEditor, LocationEditor, 和
FriendsMap。下面的章节中你将构建所有的这些活动。
注意
除了 NotePad,Google 提供了一些写的非常好的示例活动,展示了多编程状态的
基本技术。
如你所做的过去的几个活动,从文件 AndroidManifest.xml 开始。要想熟悉复杂
的应用程序,你需要多次更改 AndroidManifest.xml 文件。
编辑 AndroidManifest.xml
看看下面为 FindAFriend 项目准备的 AndroidManifest.xml 文件。需要为新活动
增加一些 Intent 过滤器,包括一个编辑 friend 的姓名,并且启动你的 Google
地图。
同样,需要仔细注意每个 Intent 过滤器的动作。这些动作会被传递到每个活动
并处理 Intent。最后,不要忘记增加 Access_Location and Access_GPS 许 可 ,
这样就可以增加你的当前闻之了。完整的 AndroidManifest.xml 文件应当像这样
显示:
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="android_programmers_guide.FindAFriend">
<application android:icon="@drawable/icon">
<provider android:name="FriendsProvider"
android:authorities="android_programmers_guide.FindAFriend.Friends" />
<activity android:name=".FindAFriend"
android:label="@string/app_name">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
<intent-filter>
<action android:name="android.intent.action.VIEW" />
<action android:name="android.intent.action.EDIT" />
<action android:name="android.intent.action.PICK" />161
<category android:name="android.intent.category.DEFAULT" />
<dataandroid:mimeType="vnd.android.cursor.dir/
vnd.android_programmers_guide.friend" />
</intent-filter>
<intent-filter>
<action android:name="android.intent.action.GET_CONTENT" />
<category android:name="android.intent.category.DEFAULT" />
<dataandroid:mimeType="vnd.android.cursor.item/
vnd.android_programmers_guide.friend" />
</intent-filter>
</activity>
<activity android:name=".FriendsMap" android:label="FriendsMap">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER"
/>
</intent-filter>
</activity>
<activity android:name="LocationEditor"
android:label="@string/title_note">
<intent-filter android:label="@string/resolve_edit">
<action android:name="android.intent.action.VIEW" />
<action android:name="android.intent.action.EDIT" />
<action
android:name="com.google.android.notepad.action.EDIT_LOCATION" />
<category android:name="android.intent.category.DEFAULT" />
<dataandroid:mimeType="vnd.android.cursor.item/
vnd.android_programmers_guide.friend" />
</intent-filter>
<intent-filter>
<action android:name="android.intent.action.INSERT" />
<category android:name="android.intent.category.DEFAULT" />
<dataandroid:mimeType="vnd.android.cursor.dir/
vnd.android_programmers_guide.friend" />
</intent-filter>
</activity>
<activity android:name="NameEditor"
android:label="@string/title_edit_title"
android:theme="@android:style/Theme.Dialog">
<intent-filter android:label="@string/resolve_title">
<action android:name="com.google.android.notepad.action.EDIT_NAME"
/>
<category android:name="android.intent.category.DEFAULT" />
<category android:name="android.intent.category.ALTERNATIVE" />162
在下一节中,将为 NameEditor 项目创建第一个活动。正如名称所示,当用户期
望编辑一个 friend 的名称时,这个活动将被启动。
创建 NameEditor 活动
创建 NameEditor NameEditor NameEditor NameEditor 活动 第十一章(6) (6) (6) (6)
在本节中,你将为 FindAFriend 项目创建 NameEditor 活动。这个活动将被从主
活动 FindAFriend 菜单项目中启动(还没有创建)。NameEditor 活动的目的是
修改一个 Friend 记录的姓名字段。
增加一个 name_editor.xml 布局文件并且一个相应的 NameEditor.java 文件到应
用程序中。你将编辑这些文件来创建你的活动。
首先,编辑 name_editor.xml 文件来为活动创建布局文件。活动将有一个
EditText 和一个 Button。EditText 将允许你修改名称字段,Button 将写入结果
并退出。如果你一开始就跟从本书的话,你已经增加了不少的 View 布局到 XML
文件中。因此,我可以免去细节,玩战的 name_editor.xml 文件应当如下所示:
<category android:name="android.intent.category.SELECTED_ALTERNATIVE" />
<dataandroid:mimeType="vnd.android.cursor.item/
vnd.android_programmers_guide.friend" />
</intent-filter>
</activity>
</application>
<uses-permission android:name="android.permission.ACCESS_GPS">
</uses-permission><uses-permission
android:name="android.permission.ACCESS_LOCATION">
</uses-permission></manifest>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:orientation="vertical"
android:paddingLeft="6dip"
android:paddingRight="6dip"
android:paddingBottom="3dip">
<EditText android:id="@+id/name"
android:maxLines="1"
android:layout_marginTop="2dip"
android:layout_width="wrap_content"
android:ems="25"
android:layout_height="wrap_content"
android:autoText="true"
android:capitalize="sentences"163
现在,编辑 NameEditor.java 并开始写代码。需要导入前一节的 Friends 类并且
导入 Cursor 包装来帮助你使用数据库记录:
应当建立活动来执行 View.OnClickListener()。这将允许你在活动中优先
OnClickListener()方法。这部分代码显示 NameEditor 类的概要和一些你需要的
变量定义:
下一步,需要优先一些方法,从 onCreate()开始。已经在其它章节里看过方法
被优先。通常,当活动被创建,它保留所有被执行的代码:
android:scrollHorizontally="true" />
<Button android:id="@+id/ok"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="right"
android:text="@string/button_ok" />
</LinearLayout>
import android.app.Activity;
import android.database.Cursor;
import android.net.Uri;
import android.os.Bundle;
import android.view.View;
import android.widget.Button;
import android.widget.EditText;
public class NameEditor extends Activity implements View.OnClickListener {
public static final String EDIT_NAME_ACTION =
"android_programmers_guide.FindAFriend.action.EDIT_NAME";
private static final int NAME_INDEX = 1;
private static final String[] PROJECTION = new String[] {
Friends.Friend._ID,
Friends.Friend.NAME,
};
Cursor mCursor;
EditText mText;
}
public void onCreate(Bundle icicle) {
super.onCreate(icicle);
setContentView(R.layout.name_editor);
Uri uri = getIntent().getData();
mCursor = managedQuery(uri, PROJECTION, null, null);
mText = (EditText) this.findViewById(R.id.name);
mText.setOnClickListener(this);
Button b = (Button) findViewById(R.id.ok);164
注意,在前面的代码示例中,你赋值布局到各自的 Views 并且初始化你的变量。
可是,你可能想知道为姓名字段准备的数据在哪里。那就是,你已经创建了一个
光标,但是从中还没有检索任何东西。为此,你将使用 onResume()方法。
下面两个优先的方法是,onResume()和 onPause(),作用是独自的读取和写入数
据库。在 Android 生命周期中,当活动被打开并且在焦点之上,onResume()被呼
叫。onPause()被呼叫的情况是当活动被打开,但是在焦点被传递到另一个活动
之前。
优先 onResume()方法来读取数据库并检索姓名字段:
在这种方式下,你移动光标到第一个记录,使用前面赋值的索引从中读取姓名字
段,然后设置 EditText 到姓名字段的内容。下一步,修改 onPause()方法来写
回 EditText 内容到数据库:
最后,从 onClick 句柄呼叫活动方法 finish()。它将清除并关闭活动。完成后
的 NameEditor.java 文件应当如下:
b.setOnClickListener(this);
}
protected void onResume() {
super.onResume();
if (mCursor != null) {
mCursor.first();
String title = mCursor.getString(NAME_INDEX);
mText.setText(title);
}
}
protected void onPause() {
super.onPause();
if (mCursor != null) {
String title = mText.getText().toString();
mCursor.updateString(NAME_INDEX, title);
mCursor.commitUpdates();
}
}
package android_programmers_guide.FindAFriend;
import android_programmers_guide.FindAFriend.Friends;
import android.app.Activity;
import android.database.Cursor;
import android.net.Uri;
import android.os.Bundle;
import android.view.View;
import android.widget.Button;
import android.widget.EditText;165
public class NameEditor extends Activity implements View.OnClickListener {
public static final String EDIT_NAME_ACTION =
"android_programmers_guide.FindAFriend.action.EDIT_NAME";
private static final int NAME_INDEX = 1;
private static final String[] PROJECTION = new String[] {
Friends.Friend._ID,
Chapter 11: Application: Find a Friend 281
Friends.Friend.NAME,
};
Cursor mCursor;
EditText mText;
@Override
public void onCreate(Bundle icicle) {
super.onCreate(icicle);
setContentView(R.layout.name_editor);
Uri uri = getIntent().getData();
mCursor = managedQuery(uri, PROJECTION, null, null);
mText = (EditText) this.findViewById(R.id.name);
mText.setOnClickListener(this);
Button b = (Button) findViewById(R.id.ok);
b.setOnClickListener(this);
}
@Override
protected void onResume() {
super.onResume();
if (mCursor != null) {
mCursor.first();
String title = mCursor.getString(NAME_INDEX);
mText.setText(title);
}
}
@Override
protected void onPause() {
super.onPause();
if (mCursor != null) {
String title = mText.getText().toString();
mCursor.updateString(NAME_INDEX, title);
mCursor.commitUpdates();
}
}
public void onClick(View v) {
finish();
}
}166
这样,你可以在 Friends 数据库编辑姓名的值。总之,数据库中的两个字段重要 ,
姓名和位置。下一节,将为位置字段创建编辑器。
创建 LocationEditor 活动
创建 LocationEditor LocationEditor LocationEditor LocationEditor 活动 第十一章(7) (7) (7) (7)
在本节中,你将为 Friends 数据库的“位置”字段创建一个编辑器。做这个活动 ,
你将会从 NameEditor 活动做一点小的修改。因此,代码和过程不同。
如果你浏览了 Google NotePad 演示版,你应当注意到“notes”编辑器是一个白
色屏幕,带有动态自身重复划线。这是个使用定制 View 执行的结果。你会用这
个相同的 View 来制作 LocationEditor。
location_editor.xml
第一步是独自创建 location_editor.xml 和 LocationEditor.java 文件的布局和
代码。布局文件应当包含一个到定制 View 布局的呼叫。完整的布局文件如下:
LocationEditor 还会包含一个菜单系统,允许用户废弃,删除或者恢复他们做
的任何改变。这会是一个非常复杂的活动。因此,最好从头开始,那就是
LocationEditor.java 文件的输入部分。
LocationEditor.java
看看这个活动的输入,很多是处理屏幕上 View 的图样:
<?xml version="1.0" encoding="utf-8"?>
<view xmlns:android="http://schemas.android.com/apk/res/android"
class="android_programmers_guide.FindAFriend.LocationEditor$MyEditText"
android:id="@+id/location"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:background="#ffffff"
android:padding="10dip"
android:scrollbars="vertical"
android:fadingEdge="vertical" />
import android.app.Activity;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.database.Cursor;
import android.graphics.Canvas;
import android.graphics.Paint;
import android.graphics.Rect;
import android.net.Uri;
import android.os.Bundle;167
下一步,设置活动的主类概要。使用 LocationEditor 期间,这里有不少的变量
需要定义:
所有本章上一节执行的任务,变量的定义是无需加以说明的。
下一小块的代码展示需要创建的子类。这个子类将把 EditText 画在屏幕上。你
把它分开,这样可以从活动中按照需要呼叫它。记住,你会在屏幕上根据用户的
需要动态的绘制一个新的 EditText。特别注意需要优先的 onDraw 类:
import android.util.AttributeSet;
import android.view.Menu;
import android.widget.EditText;
import java.util.Map;
public class LocationEditor extends Activity {
private static final String TAG = "Friends";
private static final int FRIEND_INDEX = 1;
private static final int NAME_INDEX = 2;
private static final int MODIFIED_INDEX = 3;
private static final String[] PROJECTION = new String[] {
Friends.Friend._ID, // 0
Friends.Friend.LOCATION, // 1
Friends.Friend.NAME, // 2
Friends.Friend.MODIFIED_DATE // 3
};
private static final String ORIGINAL_CONTENT = "origContent";
private static final int REVERT_ID = Menu.FIRST;
private static final int DISCARD_ID = Menu.FIRST + 1;
private static final int DELETE_ID = Menu.FIRST + 2;
private static final int STATE_EDIT = 0;
private static final int STATE_INSERT = 1;
private int mState;
private boolean mNoteOnly = false;
private Uri mURI;
private Cursor mCursor;
Chapter 11: Application: Find a Friend 285
private EditText mText;
private String mOriginalContent;
}
public static class MyEditText extends EditText {
private Rect mRect;
private Paint mPaint;
public MyEditText(Context context, AttributeSet attrs, Map params) {
super(context, attrs, params);
mRect = new Rect();168
还有,看上去好像很多的代码,但是都不陌生。这个子类只是绘制一个所需的新
EditText 到屏幕上。
和 NameEditor 一样,你会使用 onResume()和 onPause()方法来使用数据库。看
看下面每个的代码:
mPaint = new Paint();
mPaint.setStyle(Paint.Style.STROKE);
mPaint.setColor(0xFF0000FF);
}
@Override
protected void onDraw(Canvas canvas) {
int count = getLineCount();
Rect r = mRect;
Paint paint = mPaint;
for (int i = 0; i < count; i++) {
int baseline = getLineBounds(i, r);
canvas.drawLine(r.left, baseline + 1, r.right, baseline + 1,
paint);
}
super.onDraw(canvas);
}
}
protected void onResume() {
super.onResume();
if (mCursor != null) {
mCursor.first();
if (mState == STATE_EDIT) {
setTitle(getText(R.string.title_edit));
} else if (mState == STATE_INSERT) {
setTitle(getText(R.string.title_create));
}
String note = mCursor.getString(FRIEND_INDEX);
mText.setTextKeepState(note);
if (mOriginalContent == null) {
mOriginalContent = note;
}
} else {
setTitle(getText(R.string.error_title));
mText.setText(getText(R.string.error_message));
}
}
protected void onPause() {
super.onPause();169
和 NameEditor 很像,在 onResume()期间从数据库读取,在 onPause()期间写回
到数据库。在 LocationEditor 中增加的一个和 NameEditor 相反的特性是当你修
改时,还写完修改的数据。
最后,需要两个方法来取消和删除 friends。这些方法将被从菜单系统中呼叫:
if (mCursor != null) {
String text = mText.getText().toString();
int length = text.length();
if (isFinishing() && (length == 0) && !mNoteOnly) {
setResult(RESULT_CANCELED);
deleteFriend();
} else {
if (!mNoteOnly) {
mCursor.updateLong(MODIFIED_INDEX,
System.currentTimeMillis());
if (mState == STATE_INSERT) {
String title = text.substring(0, Math.min(30,
length));
Chapter 11: Application: Find a Friend 287
if (length > 30) {
int lastSpace = title.lastIndexOf(' ');
if (lastSpace > 0) {
title = title.substring(0, lastSpace);
}
}
mCursor.updateString(NAME_INDEX, title);
}
}
mCursor.updateString(FRIEND_INDEX, text);
managedCommitUpdates(mCursor);
}
}
}
private final void cancelFriend() {
if (mCursor != null) {
if (mState == STATE_EDIT) {
mCursor.updateString(FRIEND_INDEX, mOriginalContent);
mCursor.commitUpdates();
mCursor.deactivate();
mCursor = null;
} else if (mState == STATE_INSERT) {
deleteFriend();
}170
假 定 你 已 经 学 会 了 第 八 章 中 的 创 建 菜 单 系 统 , 简 单 的 检 查 一 下 完 整 的
LocationEditor.java 文件来所有的这些方法和子类如何一起工作:
}
setResult(RESULT_CANCELED);
finish();
}
private final void deleteFriend() {
if (mCursor != null) {
mText.setText("");
mCursor.deleteRow();
mCursor.deactivate();
mCursor = null;
}
}
package android_programmers_guide.FindAFriend;
import android.app.Activity;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.database.Cursor;
import android.graphics.Canvas;
import android.graphics.Paint;
import android.graphics.Rect;
import android.net.Uri;
import android.os.Bundle;
import android.util.AttributeSet;
import android.view.Menu;
import android.widget.EditText;
import java.util.Map;
public class LocationEditor extends Activity {
private static final String TAG = "Friends";
private static final int FRIEND_INDEX = 1;
private static final int NAME_INDEX = 2;
private static final int MODIFIED_INDEX = 3;
private static final String[] PROJECTION = new String[] {
Friends.Friend._ID, // 0
Friends.Friend.LOCATION, // 1
Friends.Friend.NAME, // 2
Friends.Friend.MODIFIED_DATE // 3
};
private static final String ORIGINAL_CONTENT = "origContent";
private static final int REVERT_ID = Menu.FIRST;
private static final int DISCARD_ID = Menu.FIRST + 1;171
private static final int DELETE_ID = Menu.FIRST + 2;
private static final int STATE_EDIT = 0;
private static final int STATE_INSERT = 1;
private int mState;
288 Android: A Programmer’s Guide
Chapter 11: Application: Find a Friend 289
private boolean mNoteOnly = false;
private Uri mURI;
private Cursor mCursor;
private EditText mText;
private String mOriginalContent;
public static class MyEditText extends EditText {
private Rect mRect;
private Paint mPaint;
public MyEditText(Context context, AttributeSet attrs, Map params) {
super(context, attrs, params);
mRect = new Rect();
mPaint = new Paint();
mPaint.setStyle(Paint.Style.STROKE);
mPaint.setColor(0xFF0000FF);
}
@Override
protected void onDraw(Canvas canvas) {
int count = getLineCount();
Rect r = mRect;
Paint paint = mPaint;
for (int i = 0; i < count; i++) {
int baseline = getLineBounds(i, r);
canvas.drawLine(r.left, baseline + 1, r.right, baseline + 1,
paint);
}
super.onDraw(canvas);
}
}
@Override
protected void onCreate(Bundle icicle) {
super.onCreate(icicle);
final Intent intent = getIntent();
final String type = intent.resolveType(this);
final String action = intent.getAction();
if (action.equals(Intent.EDIT_ACTION)) {
mState = STATE_EDIT;
mURI = intent.getData();
} else if (action.equals(Intent.INSERT_ACTION)) {172
mState = STATE_INSERT;
290 Android: A Programmer’s Guide
mURI = getContentResolver().insert(intent.getData(), null);
if (mURI == null) {
finish();
return;
}
setResult(RESULT_OK, mURI.toString());
} else {
finish();
return;
}
setContentView(R.layout.location_editor);
mText = (EditText) findViewById(R.id.location);
mCursor = managedQuery(mURI, PROJECTION, null, null);
if (icicle != null) {
mOriginalContent = icicle.getString(ORIGINAL_CONTENT);
}
}
@Override
protected void onResume() {
super.onResume();
if (mCursor != null) {
mCursor.first();
if (mState == STATE_EDIT) {
setTitle(getText(R.string.title_edit));
} else if (mState == STATE_INSERT) {
setTitle(getText(R.string.title_create));
}
String note = mCursor.getString(FRIEND_INDEX);
mText.setTextKeepState(note);
if (mOriginalContent == null) {
mOriginalContent = note;
}
} else {
setTitle(getText(R.string.error_title));
mText.setText(getText(R.string.error_message));
}
}
Chapter 11: Application: Find a Friend 291
@Override
protected void onFreeze(Bundle outState) {
outState.putString(ORIGINAL_CONTENT, mOriginalContent);
}173
@Override
protected void onPause() {
super.onPause();
if (mCursor != null) {
String text = mText.getText().toString();
int length = text.length();
if (isFinishing() && (length == 0) && !mNoteOnly) {
setResult(RESULT_CANCELED);
deleteFriend();
} else {
if (!mNoteOnly) {
mCursor.updateLong(MODIFIED_INDEX,
System.currentTimeMillis());
if (mState == STATE_INSERT) {
String title = text.substring(0, Math.min(30,
length));
if (length > 30) {
int lastSpace = title.lastIndexOf(' ');
if (lastSpace > 0) {
title = title.substring(0, lastSpace);
}
}
mCursor.updateString(NAME_INDEX, title);
}
}
mCursor.updateString(FRIEND_INDEX, text);
managedCommitUpdates(mCursor);
}
}
}
@Override
public boolean onCreateOptionsMenu(Menu menu) {
super.onCreateOptionsMenu(menu);
if (mState == STATE_EDIT) {
menu.add(0, REVERT_ID, R.string.menu_revert).setShortcut('0',
'r');
if (!mNoteOnly) {
menu.add(0, DELETE_ID,
R.string.menu_delete).setShortcut('1', 'd');
}
} else {
menu.add(0, DISCARD_ID, R.string.menu_discard).setShortcut('0',
'd');
}174
if (!mNoteOnly) {
Intent intent = new Intent(null, getIntent().getData());
intent.addCategory(Intent.ALTERNATIVE_CATEGORY);
menu.addIntentOptions(
Menu.ALTERNATIVE, 0,
new ComponentName(this, LocationEditor.class), null,
intent, 0, null);
}
return true;
}
@Override
public boolean onOptionsItemSelected(Menu.Item item) {
switch (item.getId()) {
case DELETE_ID:
deleteFriend();
finish();
break;
case DISCARD_ID:
cancelFriend();
break;
case REVERT_ID:
cancelFriend();
break;
}
return super.onOptionsItemSelected(item);
}
private final void cancelFriend() {
if (mCursor != null) {
if (mState == STATE_EDIT) {
mCursor.updateString(FRIEND_INDEX, mOriginalContent);
mCursor.commitUpdates();
mCursor.deactivate();
mCursor = null;
} else if (mState == STATE_INSERT) {
deleteFriend();
}
}
setResult(RESULT_CANCELED);
finish();
}
private final void deleteFriend() {
if (mCursor != null) {
mText.setText("");
292 Android: A Programmer’s Guide175
在下一节,会创建绘制 Google Maps Overlay 的活动,FriendsMap 活动将从
Friends 数据库中读取完整的 friends 记录集并把每一个写入 Overlay。
创建 FriendsMap FriendsMap FriendsMap FriendsMap 活动 第十一章(8) (8) (8) (8)
FriendsMap 是最后一个可以从主程序中呼叫的活动。本活动将从 Friends 数据库中呼叫数
据集并且在 Google Map 上为每一个 friend 画一个圆圈。该活动还将为你的当前位置画一个
圆圈。
从增加两个新文件来开始本项目,friendsmap.xml 和 FriendsMap.java 文件。因为你已经在
第九章内看过 friendsmap.xml 文件了,所以没有必要再解释一遍。使用的是一个 RelativeL
ayout 来把 4 个按钮放在一个 Google Map 上的。完整的 friendsmap.xml 文件应当看上去如
下:
mCursor.deleteRow();
mCursor.deactivate();
mCursor = null;
}
}
}
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
>
<view class="com.google.android.maps.MapView"
android:id="@+id/myMap"
android:layout_width="wrap_content"
android:layout_height="wrap_content"/>
<Button android:id="@+id/buttonZoomIn"
style="?android:attr/buttonStyleSmall"
android:text="+"
android:layout_width="wrap_content"
android:layout_height="wrap_content" />
<Button android:id="@+id/buttonMapView"
style="?android:attr/buttonStyleSmall"
android:text="Map"
android:layout_alignRight="@+id/myMap"
android:layout_width="wrap_content"
android:layout_height="wrap_content" />
<Button android:id="@+id/buttonSatView"
style="?android:attr/buttonStyleSmall"176
因为在第九章已经看过绝大多数的 FriendsMap.java 文件了,我不会再阐述每一个细节。但
是,有一个方法需要解释。
你会创建一个叫做 LoadFriends()的方法来存取数据库,读取记录,并且绘制 Overlay。看
一下 LoadFriends()的代码。注意,你打开数据库,匹配并分析位置字段,从位置字段的纬
度和经度创建点,并且绘制点到 Overlay 中。这个方法最后所做的事情就是从 GPS 上抓取
坐标并且在 Overlay 上绘制出来,用标签“ME”表示。
android:text="Sat"
android:layout_alignRight="@+id/myMap"
android:layout_alignBottom="@+id/myMap"
android:layout_width="wrap_content"
android:layout_height="wrap_content" />
<Button android:id="@+id/buttonZoomOut"
style="?android:attr/buttonStyleSmall"
android:text="-"
android:layout_alignBottom="@+id/myMap"
android:layout_width="wrap_content"
android:layout_height="wrap_content" />
</RelativeLayout>
public void LoadFriends(MapView mv, MapController mc, Cursor c){
Point myLocation = null;
Double latPoint = null;
Double lngPoint = null;
c.first();
do{
if (c.getString(c.getColumnIndex("location")) != null) {
final String geoPattern = "(geo:[\\-]?[0-9]{1,3}\\.[0
9]{1,6}\\,[\\-]?[0-9]{1,3}\\.[0-9]{1,6}\\#)";
Pattern pattern = Pattern.compile(geoPattern);
CharSequence inputStr =
c.getString(c.getColumnIndex("location"));
Matcher matcher = Pattern.matcher(inputStr);
boolean matchFound = matcher.find();
if (matchFound) {
String groupStr = matcher.group(0);
latPoint =
Double.valueOf(groupStr.substring(groupStr.indexOf(":") + 1,
groupStr.indexOf(","))) ;
lngPoint =
Double.valueOf(groupStr.substring(groupStr.indexOf(",") + 1,
groupStr.indexOf("#"))) ;
Point friendLocation = new
Point(latPoint.intValue(),lngPoint.intValue());177
剩余的 FriendsMap.java 文件操控第十章介绍的缩放和棒性按钮:
294 Android: A Programmer’s Guide
drawFriendsOverlay.addNewFriend(c.getString(c.getColumnIndex("name")),
friendLocation);
}
}
}while(c.next());
LocationManager myManager = (LocationManager)
getSystemService(Context.LOCATION_SERVICE);
Double myLatPoint =
myManager.getCurrentLocation("gps").getLatitude()*1E6;
Double myLngPoint =
myManager.getCurrentLocation("gps").getLongitude()*1E6;
myLocation = new Point(myLatPoint.intValue(),myLngPoint.intValue());
drawFriendsOverlay.addNewFriend("Me", myLocation);
mc.centerMapTo(myLocation, false);
mc.zoomTo(9);
mv = null;
}
package android_programmers_guide.FindAFriend;
import android.os.Bundle;
import android.location.LocationManager;
import android.view.View;
import android.content.Context;
import android.content.Intent;
import android.database.Cursor;
import android.widget.Button;
import java.util.regex.Pattern;
import java.util.regex.Matcher;
import android.graphics.Canvas;
import android.graphics.RectF;
import android.graphics.Paint;
import com.google.android.maps.MapActivity;
import com.google.android.maps.MapView;
import com.google.android.maps.Point;
import com.google.android.maps.MapController;
import com.google.android.maps.Overlay;
import com.google.android.maps.OverlayController;
public class FriendsMap extends MapActivity {
private static final String[] PROJECTION = new String[] {
Friends.Friend.NAME, Friends.Friend.LOCATION};
public Cursor mCursor;
DrawFriendsOverlay drawFriendsOverlay = new DrawFriendsOverlay();
@Override178
public void onCreate(Bundle icicle) {
super.onCreate(icicle);
setContentView(R.layout.friendsmap);
Intent intent = getIntent();
if (intent.getData() == null) {
intent.setData(Friends.Friend.CONTENT_URI);
}
mCursor = managedQuery(getIntent().getData(), PROJECTION, null,null);
final MapView myMap = (MapView) findViewById(R.id.myMap);
final MapController myMapController = myMap.getController();
LoadFriends(myMap, myMapController, mCursor);
OverlayController myOverlayController =
myMap.createOverlayController();
myOverlayController.add(drawFriendsOverlay, true);
final Button zoomIn = (Button) findViewById(R.id.buttonZoomIn);
zoomIn.setOnClickListener(new Button.OnClickListener() {
public void onClick(View v){
ZoomIn(myMap,myMapController);
}});
final Button zoomOut = (Button) findViewById(R.id.buttonZoomOut);
zoomOut.setOnClickListener(new Button.OnClickListener() {
public void onClick(View v){
ZoomOut(myMap,myMapController);
}});
final Button viewMap = (Button) findViewById(R.id.buttonMapView);
viewMap.setOnClickListener(new Button.OnClickListener() {
public void onClick(View v){
ShowMap(myMap,myMapController);
}});
final Button viewSat = (Button) findViewById(R.id.buttonSatView);
viewSat.setOnClickListener(new Button.OnClickListener() {
public void onClick(View v){
ShowSat(myMap,myMapController);
}});
}
public void LoadFriends(MapView mv, MapController mc, Cursor c){
Point myLocation = null;
Double latPoint = null;
Double lngPoint = null;
c.first();
do{
if (c.getString(c.getColumnIndex("location")) != null) {
final String geoPattern = "(geo:[\\-]?[0-9]{1,3}\\.[0
9]{1,6}\\,[\\-]?[0-9]{1,3}\\.[0-9]{1,6}\\#)";179
Pattern pattern = Pattern.compile(geoPattern);
CharSequence inputStr =
c.getString(c.getColumnIndex("location"));
Matcher matcher = pattern.matcher(inputStr);
boolean matchFound = matcher.find();
if (matchFound) {
String groupStr = matcher.group(0);
latPoint =
Double.valueOf(groupStr.substring(groupStr.indexOf(":") + 1,
groupStr.indexOf(","))) ;
lngPoint =
Double.valueOf(groupStr.substring(groupStr.indexOf(",") + 1,
groupStr.indexOf("#"))) ;
Point friendLocation = new
Point(latPoint.intValue(),lngPoint.intValue());
drawFriendsOverlay.addNewFriend(c.getString(c.getColumnIndex("name")),
friendLocation);
}
}
}while(c.next());
LocationManager myManager = (LocationManager)
getSystemService(Context.LOCATION_SERVICE);
Double myLatPoint =
myManager.getCurrentLocation("gps").getLatitude()*1E6;
Double myLngPoint =
myManager.getCurrentLocation("gps").getLongitude()*1E6;
myLocation = new Point(myLatPoint.intValue(),myLngPoint.intValue());
drawFriendsOverlay.addNewFriend("Me", myLocation);
mc.centerMapTo(myLocation, false);
mc.zoomTo(9);
mv = null;
}
public void ZoomIn(MapView mv, MapController mc){
if(mv.getZoomLevel()!=21){
mc.zoomTo(mv.getZoomLevel()+ 1);
}
}
public void ZoomOut(MapView mv, MapController mc){
if(mv.getZoomLevel()!=1){
mc.zoomTo(mv.getZoomLevel()- 1);
}
}
public void ShowMap(MapView mv, MapController mc){
if (mv.isSatellite()){180
完成本项目的最后一个任务是创建主活动,FindAFriend。该活动被放置在一个壳内来呼叫
本章创建的活动。
mv.toggleSatellite();
}
}
public void ShowSat(MapView mv, MapController mc){
if (!mv.isSatellite()){
mv.toggleSatellite();
}
}
protected class DrawFriendsOverlay extends Overlay{
public String[] friendName = new String[0];
public Point[] friendPoint = new Point[0];
final Paint paint = new Paint();
@Override
public void draw(Canvas canvas, PixelCalculator calculator, Boolean
shadow){
for(int x=0;x<friendPoint.length; x++){
int[] coords = new int[2];
calculator.getPointXY(friendPoint[x], coords);
RectF oval = new RectF(coords[0] - 7, coords[1] + 7,
coords[0] + 7, coords[1] - 7);
paint.setTextSize(14);
canvas.drawText(friendName[x],
coords[0] +9, coords[1], paint);
canvas.drawOval(oval, paint);
}
}
public void addNewFriend(String name,Point point ){
int x = friendPoint.length;
String[] friendNameB = new String[x + 1];
Point[] friendPointB = new Point[x + 1];
System.arraycopy(friendName, 0, friendNameB, 0, x );
System.arraycopy(friendPoint, 0, friendPointB, 0, x);
friendNameB[x] = name;
friendPointB[x]= point;
friendName = new String[x + 1];
friendPoint = new Point[x + 1];
System.arraycopy(friendNameB, 0, friendName, 0, x + 1 );
System.arraycopy(friendPointB, 0, friendPoint, 0, x + 1 );
}
}
}181
创建 FindAFriend FindAFriend FindAFriend FindAFriend 活动 第十一章(9) (9) (9) (9)
要开始本节,创建两个文件,findafriend.xml 和 FindAFriend.java。再说一次,这些文件将
为当前部分独自保留你的布局和代码。布局文件非常的基本并且只有一个 TextView。这个T
extView 将被用来写入到 friends 的列表中。完整的 findafriend.xml 文件应当显示如下:
完整的 FindAFriend.java 文件如下。文件中的所有代码在本章中已经讨论过。首先,读取
数据库并且写入结果到一个 ListView。给予用户一个菜单项目来编辑或者删除条目,或者启
动 FriendsMap 活动。很简单,对不对?
<?xml version="1.0" encoding="utf-8"?>
<TextView xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@android:id/text1"
android:layout_width="fill_parent"
android:layout_height="?android:attr/listPreferredItemHeight"
android:textAppearance="?android:attr/textAppearanceLargeInverse"
android:gravity="center_vertical"
android:paddingLeft="27dip"
/>
package android_programmers_guide.FindAFriend;
import android_programmers_guide.FindAFriend.Friends;
import android.app.ListActivity;
import android.content.ComponentName;
import android.content.Intent;
import android.content.ContentUris;
import android.database.Cursor;
import android.graphics.Color;
import android.net.Uri;
import android.os.Bundle;
import android.view.Menu;
import android.view.View;
import android.view.View.MeasureSpec;
import android.widget.ListAdapter;
import android.widget.ListView;
import android.widget.SimpleCursorAdapter;
import android.widget.TextView;
public class FindAFriend extends ListActivity {
300 Android: A Programmer’s Guide
public static final int DELETE_ID = Menu.FIRST;
public static final int INSERT_ID = Menu.FIRST + 1;
public static final int FIND_FRIENDS = Menu.FIRST + 2;
private static final String[] PROJECTION = new String[] {
Friends.Friend._ID, Friends.Friend.NAME};182
private Cursor mCursor;
@Override
protected void onCreate(Bundle icicle) {
super.onCreate(icicle);
setDefaultKeyMode(SHORTCUT_DEFAULT_KEYS);
Intent intent = getIntent();
if (intent.getData() == null) {
intent.setData(Friends.Friend.CONTENT_URI);
}
setupList();
mCursor = managedQuery(getIntent().getData(), PROJECTION, null,
null);
ListAdapter adapter = new SimpleCursorAdapter(this,
R.layout.findafriend_item, mCursor,
new String[] {Friends.Friend.NAME}, new int[]
{android.R.id.text1});
setListAdapter(adapter);
}
private void setupList() {
View view = getViewInflate().inflate(
android.R.layout.simple_list_item_1, null, null);
TextView v = (TextView) view.findViewById(android.R.id.text1);
v.setText("X");
getListView().setBackgroundColor(Color.GRAY);
v.measure(MeasureSpec.makeMeasureSpec(View.MeasureSpec.EXACTLY,
100),
MeasureSpec.makeMeasureSpec(View.MeasureSpec.UNSPECIFIED,
0));
}
@Override
public boolean onCreateOptionsMenu(Menu menu) {
super.onCreateOptionsMenu(menu);
menu.add(0, INSERT_ID, R.string.menu_insert).setShortcut('3', 'a');
Intent intent = new Intent(null, getIntent().getData());
intent.addCategory(Intent.ALTERNATIVE_CATEGORY);
menu.addIntentOptions(
Menu.ALTERNATIVE, 0, new ComponentName(this, FindAFriend.class),
null, intent, 0, null);
return true;
}
@Override
public boolean onPrepareOptionsMenu(Menu menu) {
super.onPrepareOptionsMenu(menu);
final boolean haveItems = mCursor.count() > 0;183
if (haveItems) {
Uri uri = ContentUris.withAppendedId(getIntent().getData(),
getSelectedItemId());
Intent[] specifics = new Intent[1];
specifics[0] = new Intent(Intent.EDIT_ACTION, uri);
Menu.Item[] items = new Menu.Item[1];
Intent intent = new Intent(null, uri);
intent.addCategory(Intent.SELECTED_ALTERNATIVE_CATEGORY);
menu.addIntentOptions(Menu.SELECTED_ALTERNATIVE, 0, null,
specifics, intent, 0, items);
menu.add(Menu.SELECTED_ALTERNATIVE, DELETE_ID,
R.string.menu_delete)
.setShortcut('2', 'd');
menu.add(Menu.SELECTED_ALTERNATIVE, FIND_FRIENDS,
R.string.find_friends).setShortcut('4', 'f');
if (items[0] != null) {
items[0].setShortcut('1', 'e');
}
} else {
menu.removeGroup(Menu.SELECTED_ALTERNATIVE);
}
menu.setItemShown(DELETE_ID, haveItems);
return true;
}
@Override
public boolean onOptionsItemSelected(Menu.Item item) {
switch (item.getId()) {
case DELETE_ID:
deleteItem();
return true;
case INSERT_ID:
insertItem();
return true;
case FIND_FRIENDS:
Intent findfriends = new Intent(this, FriendsMap.class);
startActivity(findfriends);
return true;
}
return super.onOptionsItemSelected(item);
}
@Override
protected void onListItemClick(ListView l, View v, int position, long
id) {
Uri url = ContentUris.withAppendedId(getIntent().getData(), id);184
这个是本书中最长的一个活动,需要注意的是你所做的相关所需的编程工作还是非常的少。
下一步,运行这个活动并且查看所有工作的成果。
运行 FindAFriend FindAFriend FindAFriend FindAFriend 活动 第十一章(10) (10) (10) (10)
在 Android 模拟器中运行 FindAFriend 活动。应当从一个空的列表开始,如下图
(略)。要增加第一个朋友,点击菜单按钮并选择 Add Friend 选项。
本选项启动你创建的定制 View。在提供的行处输入一个朋友的名称,点击返回
按钮返回到主活动。
现在在 ListView 中应当有一个朋友的名称。再次点击菜单按钮,显然你拥有更
多的选项了。如下图(略)。选择 Edit Location 选项。会再次来到定制控制。
输入坐标信息。
最后,返回到主活动并选择 Find Friend 选项。这样分别会清除在旧金山的位置
和你朋友在非洲海岸的位置。
试试这个:实时位置更新
试着修改 FindAFriend 应用程序,当你移动时,来更新“ME”标记。这个应该很
容易通过使用 update()方法实现。
问专家
Q:SQLite 数据库可以通过代码的方式创建吗?
A:是的。但是,为了更好的了解 Android 的目的,我选择了手工创建数据库例子 。
可以在 FriendsProvider Content Provider 通过代码创建数据库的 creation
方法自行修改本项目。
String action = getIntent().getAction();
if (Intent.PICK_ACTION.equals(action)
|| Intent.GET_CONTENT_ACTION.equals(action)) {
setResult(RESULT_OK, url.toString());
} else {
startActivity(new Intent(Intent.EDIT_ACTION, url));
}
}
private final void deleteItem() {
mCursor.moveTo(getSelectedItemPosition());
mCursor.deleteRow();
}
private final void insertItem() {
startActivity(new Intent(Intent.INSERT_ACTION,
getIntent().getData()));
}
}Q:需要一个分开的类来执行 BaseColumns?
A:不。可以直接从 Friends 类中定义条目。如果你创建的一个 Content Provider
被其它开发者使用,但是他们不知道数据库的底层结构,那么你就需要提供一个
定义的类。