您的位置:首页 > 移动开发 > Android开发

[译文]Notepad Exercise 1 – Android SDK Tutorials系列

2011-12-14 00:11 549 查看

Notepad 练习1

这个练习里,你将创建一个简单的笔记列表,用户只能添加而不能编辑笔记。练习展示了:

ListActivity
的基础,创建和处理菜单选项。

如何使用SQLite数据库存储笔记。

如何使用SimpleCursorAdapter将数据从数据库游标绑定到ListView 。

屏幕布局基础:如何排版一个list view,如何向activity的菜单里添加项目,以及activity如何处理这些菜单被选中的动作。

[Exercise 1] [Exercise 2] [Exercise 3] [Extra Credit]

步骤1

在Eclipse里面打开工程
Notepadv1


Notepadv1
是一个工程,作为本次练习的起点。它完成了一些初始的基础工作,如果你已经阅读了Hello, World 教程,应该已经熟悉这些基础工作了。

创建一个新工程File > New > Android Project.

在创建Android新工程对话框里,选择从已有源码创建工程([b]Create project from existing source)[/b]。

点击浏览(Browse),然后找到你拷贝的
NotepadCodeLab
目录(从Notepad介绍下载的),然后选择
Notepadv1。


工程名和其他属性应该都已经给你填好了。你必须选择编译目标 - 我们建议选择已有最低版本的平台作为编译目标。同时在最低SDK版本一栏填写与选择的目标平台相匹配的API等级数。

点击结束。应该能在Eclipse的package explorer看到打开的
Notepadv1
工程了。

如果发现
AndroidManifest.xml
有错误,或者一些跟Android zip文件有关的问题,右键点击工程,选择Android Tools > Fix Project Properties. (工程在错误的位置查找库文件,这个操作将帮你修复这个问题。)

步骤2

访问、修改数据

这个练习里,我们使用SQLite数据库来保存数据。这在只有你的应用会访问、修改数据的时候是有效的。如果你希望其他Activity也能访问、修改数据,你必须通过
ContentProvider
来提供数据。

如果你感兴趣,可以了解更多关于content providers 或者整个Data Storage的内容。在SDK的
samples/
目录下的NotePad示例也讲述了如何创建一个ContentProvider。

查看一下类
NotesDbAdapter
- 这个类用来封装SQLite数据库的数据访问,这个数据库将保存我们的笔记数据并允许我们更新它。

在这个类的最上面,定义了一些常量,用来保存数据库里对应的字段名,应用可以通过这些字段名来查找数据。那儿也同时定义了一条数据库创建语句,在数据库不存在的时候创建一个新的数据库。

我们的数据库名称是
data
, 里面只有一个表名字是
notes
, 表里面有3个字段:
_id
,
title
body。
_id
以下划线惯例命名,这个惯例在Android SDK里面多个地方用到,用来帮助追踪状态。在查询或修改数据库(预先判断哪些列等等)的时候通常必须提供
_id
。其他两个字段是简单的文本字段,用来保存数据。

NotesDbAdapter
的构造函数拥有一个Context,它允许
NotesDbAdapter
跟Android操作系统的某些部分进行交流。类需要以某种方式跟Android系统接触是很正常的事情。Activity是类Context的一个实现,因此通常在需要Context的时候只要从你的Activity传递
this
就行了。

open()
方法创建一个DatabaseHelper的实例,DatabaseHelper是类SQLiteOpenHelper 的一个本地实现。
open()
方法方法调用
getWritableDatabase()为我们创建并打开一个数据库。


close()
就是关闭数据库,释放跟数据库连接相关的资源。

createNote()
接受两个字符串参数:新笔记的标题和内容,然后在数据库里创建这个笔记。假设创建笔记成功,这个方法会返回新创建笔记的行号
_id


deleteNote()
接受一个给定笔记的行号rowId ,然后从数据库删除这个笔记。

fetchAllNotes()
生成一个查询来返回数据库里所有笔记的游标。
query()
的调用很值得研究。第一个参数是要查询的数据库表名(这里的
DATABASE_TABLE
是”notes”)。下一个参数是一系列我们需要返回的列名,这里我们要返回
_id
,
title
body
,将它们放到字符串数组里面。剩下的参数依次是:
selection
,
selectionArgs
,
groupBy
,
having
orderBy
.把它们全部设置为
null
意味着我们需要返回全部数据,不要分组,以及使用默认排列顺序。需要更详细的内容请参考
SQLiteDatabase


注意: 返回游标而不是所有的行。这让Android能高效地使用资源 - 游标并不是把大量数据直接堆到内存里,而是只获取、释放需要的数据,这对拥有大量记录的表尤其高效。

fetchNote()
fetchAllNotes()
相似,不过只获取我们指定行号rowId 对应的笔记。它使用的
SQLiteDatabase 的query()
的版本稍稍有点不同。第一个参数(设置为true)表示我们只对唯一的结果感兴趣。selection 参数(第四个参数)被设置成只搜索行号等于我们传入的行号rowId 的行。因此我们获得了唯一行的游标。

最后,
updateNote()
接受三个参数:rowId, title 和body,然后使用一个
ContentValues的实例来更新给定行号rowId的笔记。


步骤3

布局和Activity

大部分Activity类都有一个与之关联的布局。布局就是Activity展示给用户看的”脸面”。这里我们的布局占据整个屏幕,展示一系列的笔记。

然而,全屏布局并不是一个Activity唯一的选择。你也可以使用悬浮布局floating layout(例如:一个对话框和提醒dialog or alert),或者你也可能根本不需要一个布局(如果你不为Activity指定某种布局,Acitivity对用户来说就是不可见的。)

打开看看
res/layout
下的
notepad_list.xml
文件。(你可能需要点击底部的xml标签来查看XML源码。)

这是个几乎为空的布局定义文件。下面是几个你应该知道的关于布局文件的东西:

所有的Android布局文件都必须以这个XML头作为开头:
<?xml version="1.0" encoding="utf-8"?>
.

第一个定义一般(并不总是)是某种布局的定义,这里是
LinearLayout。


必须总是在顶层的部件或者布局里面定义Android的XML命名空间,因而在剩下的整个文件里都可以使用
android:
标签:
xmlns:android=http://schemas.android.com/apk/res/android


步骤4

我们需要创建一个布局来展示我们的列表。在
LinearLayout
元素里面添加代码,结果如下:

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android=http://schemas.android.com/apk/res/android

android:layout_width="wrap_content"

android:layout_height="wrap_content">

<ListView android:id="@android:id/list"

android:layout_width="wrap_content"

android:layout_height="wrap_content"/>

<TextView android:id="@android:id/empty"

android:layout_width="wrap_content"

android:layout_height="wrap_content"

android:text="@string/no_notes"/>
</LinearLayout>


ListView
TextView
节点里面的id字符串有@符号,表示XML解析器需要解析和扩展id字符串,并使用ID资源。

ListView
TextView
可以被看成是两个交替的View,每次只能显示其中一个。如果有笔记需要显示就使用ListView ,而如果没有任何笔记需要显示就使用TextView (它有一个默认的值"No Notes Yet!",作为一个字符串资源在
res/values/strings.xml
里面被定义)。

list
empty
ID是Android平台为我们提供的,因此,我们必须为
id
加上前缀
android:(例如:@android:id/list
)

ListAdapter没有数据供给,ListView会自动使用empty
ID,ListAdapter默认会去查找这个名字。或者,通过在ListView调用
setEmptyView(View)
来更改默认的空View。

更明白地讲,类
android.R里是系统为你预先定义的资源的集合,而你工程里的类R
是你的工程已定义资源的集合。只要加上
android:
命名空间前缀,就可以在XML文件里使用在类
android.R
里能找到的资源(正如我们在这里看到的)。

步骤5

资源和类R

Eclipse工程里,res/下的目录存放资源文件。在res/目录下的目录和文件有特定的结构(specific structure).

定义在这些目录和文件里的资源,在类R里面有对应的入口,允许你的应用很方便的访问和使用它们。类R是Eclipse插件根据 res/目录下的内容自动生成的(或者,由aapt生成,如果你使用命令行开发工具)。另外,它们也被打包并部署为应用的一部分。

为了在ListView显示笔记的列表,我们也需要为每一行定义一个View:

res/layout
下创建一个新文件
notes_row.xml
.

添加以下内容(注意,又一次使用了XML头,在第一个节点定义了Android XML命名空间)

<?xml version="1.0" encoding="utf-8"?>
<TextView android:id="@+id/text1"

xmlns:android=http://schemas.android.com/apk/res/android

android:layout_width="wrap_content"

android:layout_height="wrap_content"/>

这就是要用在每条记录标题行的View - 只有一个文本域在里面。

这次我们创建一个新的ID
text1
。@符号后面的+符号(加号)表示,这个id应该被看成一个资源,如果不存在,应该自动创建它。

3. 保存文件。

打开看看
R.java
文件,应该能看到新定义的
notes_row
text1
(我们新定义的),意味着我们现在就能够在代码里面访问它们了。

步骤6

下面,打开类
Notepadv1
。下面的步骤里,我们将修改这个类,将它变成一个ListAdapter来显示我们的笔记,并允许我们添加新的笔记。

Notepadv1
将继承自
Activity
的一个子类叫做
ListActivity
, 它有额外的能力,像列表一样能容纳一些东西。例如:在屏幕上以行的方式显示任意数量的列表项,在列表项之间移来移去,并允许选中它们。

看一遍类
Notepadv1
里面已有的代码。里面有一个现在还没用到的私有成员
mNoteNumber
,我们将用它来创建带编号的笔记标题。

也定义了3个重写的方法:
onCreate
,
onCreateOptionsMenu
onOptionsItemSelected
; 我们需要重写它们的代码:

Activity启动的时候会调用
onCreate()
方法 - 它有点像Activity的”main”方法。当Activity运行的时候,我们用它来初始化资源和状态。

调用
onCreateOptionsMenu()
来为Activity创建菜单。当用户点击菜单(menu)按钮时会显示菜单,菜单里有一系列选项可供选择(例如”Create Note”)。

onOptionsItemSelected()
is the other half of the menu equation, it is used to handle events generated from the menu (e.g., when the user selects the "Create Note" item).

onOptionsItemSelected()
是菜单等式的另一半,用来处理菜单引发的事件(例如,当用户选择了”Create Note”项)。

步骤7

Notepadv1
的基类从
Activity
改为
ListActivity
:

public class Notepadv1 extends ListActivity

注意:在你完成上面的修改以后,你必须在类Notepadv1里导入
ListActivity
,在Windows或Linux用快捷键ctrl-shift-O ,或者在Mac上用cmd-shift-O (组织包的导入)来进行导入。

步骤8

为onCreate()
函数体编写代码。

这里我们将为Activity设置标题(显示在屏幕顶端),利用我们创建的XML布局
notepad_list
,创建
NotesDbAdapter
实例来访问笔记数据,以及用已有的笔记标题填充列表:

onCreate()
方法里,调用
super.onCreate()
方法,参数是传入的
savedInstanceState


调用
setContentView()
,参数是
R.layout.notepad_list
.

在类的顶部,创建一个类型为
NotesDbAdapter的
私有成员
mDbHelper


回到
onCreate()
方法,构建一个
NotesDbAdapter
的实例,并把它赋值给成员
mDbHelper
(将
this
传递给
DBHelper
的构造函数)

调用
mDbHelper
open()
方法打开(或创建)数据库。

最后,调用一个新方法
fillData()
, 该方法获得数据,并通过
mDbHelper
填充ListView - 我们还没定义这个方法呢。

onCreate()
内容如下:

@Override

public void onCreate(Bundle savedInstanceState) {

super.onCreate(savedInstanceState);

setContentView(R.layout.notepad_list);

mDbHelper = new NotesDbAdapter(this);

mDbHelper.open();

fillData();

}


同时确认你定义了成员
mDbHelper
(就在mNoteNumber 定义的下面):

private NotesDbAdapter mDbHelper;

步骤9

更多关于菜单的内容

我们正在构建的notepad应用只触及到了菜单menu的皮毛。

你也可以为菜单项添加快捷键创建子菜单,甚至为其他应用添加菜单项

onCreateOptionsMenu()编写代码。


我们现在创建"Add Item" 按钮,通过点击手机上的menu按钮可以看到它。我们设定它位于菜单第一的位置。

在资源文件
strings.xml
(目录
res/values
下面)里添加一个新的名为"menu_insert"的字符串,内容设置为
Add Item
:
<string name="menu_insert">Add Item</string>

保存文件,回到
Notepadv1。


在类顶部创建一个菜单选项常量:

public static final int INSERT_ID = Menu.FIRST;


在方法
onCreateOptionsMenu()
里,修改
super
调用返回布尔值
result
. 我们在最后返回这个值。

然后调用
menu.add()
添加菜单项。

整个函数内容如下:

@Override

public boolean onCreateOptionsMenu(Menu menu) {

boolean result = super.onCreateOptionsMenu(menu);

menu.add(0, INSERT_ID, 0, R.string.menu_insert);

return result;

}

add()
的参数说明:菜单的分组ID (这里是空),唯一的ID(上面定义的),菜单项的顺序(0表示没有特例),以及菜单项用到的字符串资源。

步骤10

onOptionsItemSelected()
方法编写代码。

这个函数将处理我们新增加的"Add Note"(译者注:跟前面一致应该是"Add Item")菜单项。当它被选中时,
onOptionsItemSelected()
方法将被调用,
item.getItemId()
被设置成
INSERT_ID
(我们用来定位菜单项的常量)。我们可以检测它的值,并采取适当的行动。

super.onOptionsItemSelected(item)
方法的调用放在最后面 - 我们需要先捕获我们的事件。

item.getItemId()
使用switch语句。
如果结果是INSERT_ID,就调用一个新方法
createNote()
, 然后返回true,因为我们已经处理了这个事件,并且不想让它传播给整个系统。

最后返回基类调用
onOptionsItemSelected()
方法的结果。

整个
onOptionsItemSelect()
方法的内容如下:

@Override

public boolean onOptionsItemSelected(MenuItem item) {

switch (item.getItemId()) {

case INSERT_ID:

createNote();

return true;

}

return super.onOptionsItemSelected(item);

}

步骤11

添加新方法
createNote():


在我们的应用第一版本里,
createNote()
方法将不会有什么实际功用。我们只是简单的创建一个笔记,它的标题基于一个计数器生成("Note 1", "Note 2"...) ,内容为空。目前我们还无法编辑笔记的内容,所以我们暂时把笔记的内容都设置成某个默认值。

使用"Note"和我们定义的计数器来构建标题:
String noteName = "Note " + mNoteNumber++


调用
mDbHelper.createNote()
,将
noteName
作为标题,
""
作为内容。

调用
fillData()
填充笔记列表(低效但是简单) - 我们将在下一步定义这个方法。

整个
createNote()
方法内容如下:

private void createNote() {

String noteName = "Note " + mNoteNumber++;

mDbHelper.createNote(noteName, "");

fillData();

}

步骤12

List适配器

我们的例子使用
SimpleCursorAdapter
将数据库游标
Cursor
绑定到一个ListView,这是使用
ListAdapter
的通常做法。其他的选择如
ArrayAdapter
,同样可以将内存数据的List或者Array绑定到一个ListView (译者注:原文是List)。

定义
fillData()
方法:

这个方法使用
SimpleCursorAdapter,它持有一个数据库游标Cursor
,并把它绑定到布局里提供的显示字段。这些字段定义了列表的行元素(这里我们使用布局文件
notes_row.xml
里的
text1
字段),因此,我们可以很方便的使用数据库记录来填充列表。

为了实现这个目标,我们必须提供从返回的游标里面的
title
字段,到我们的
text1
TextView的一个映射。通过两个Array来实现此功能:第一个,一个几个列的字符串数组作为映射源(这里只有”title”,来自常量
NotesDbAdapter.KEY_TITLE
);第二个,一个保存了View的引用的整型数组作为数据绑定目标(
R.id.text1
TextView)。

这是一大段代码,我们实现阅读一下:

private void fillData() {

// Get all of the notes from the database and create the item list

Cursor c = mDbHelper.fetchAllNotes();

startManagingCursor(c);

String[] from = new String[] { NotesDbAdapter.KEY_TITLE };

int[] to = new int[] { R.id.text1 };

// Now create an array adapter and set it to display using our row

SimpleCursorAdapter notes =

new SimpleCursorAdapter(this, R.layout.notes_row, c, from, to);         setListAdapter(notes);     }

我们已完成的工作如下:

mDbHelper.fetchAllNotes()
获得游标后,调用了Activity的方法
startManagingCursor()
,让Android系统处理游标的生命周期,而不用我们自己去关心它。(我们将在练习3涉及到生命周期的内幕,但是,暂时只要知道这样做是让Android为我们做一些资源管理的工作。)

然后我们创建了一个字符串数组,在里面我们我们声明了要映射的列(这里只有标题),和一个整型数组,在里面定义了我们需要绑定列的View(顺序必须和字符串数组保持一致,但这里我们两个数组都只有一个元素)。

下面创建SimpleCursorAdapter的一个实例。跟很多Android的类一样,SimpleCursorAdapter 需要一个Context来完成它的工作,因此我们传递
this
作为Context(因为基类Activity 是Context的一个实现)。传递
我们创建的notes_row
View作为数据的容器,下一个参数是我们刚刚创建的游标Cursor,最后两个参数是我们的两个数组。

将来,要记住from列和to资源的映射需要保持两个数组内容的顺序一致。如果我们有更多列需要绑定到更多的View上,我们需要按顺序设置它们,例如,我们可能使用
{ NotesDbAdapter.KEY_TITLE, NotesDbAdapter.KEY_BODY }
{ R.id.text1, R.id.text2 }
来绑定两个字段到列表的行(我们也需要在notes_row.xml里定义text2来显示笔记内容)。这就是如何将多个字段绑定到一个列表行的方法(同时也需要定制行的布局)。

如果遇到类未找到的编译错误,按快捷键ctrl-shift-O 或者(在MAC上cmd-shift-O) 来组织导入包。

步骤13

运行应用!

右键点击
Notepadv1
工程。

在弹出菜单里,选择Run As > Android Application.

如果看到有对话框弹出,选择Android Application作为应用运行的方式。

点击菜单menu按钮(译者注:模拟器menu按钮或者手机实体menu按钮),选择Add Item 菜单项来添加新笔记。

解决方案和后面的步骤

你可以在zip文件里面的
Notepadv1Solution
里面找到这个类的解决方案,把它跟你自己写的比较一下。

一旦你完成上面的练习,请移步到Tutorial Exercise 2 ,增加创建、编辑和删除笔记的功能。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: