2008年12月16日星期二

教程:Notepad练习1

教程:Notepad练习1

在这个练习中,你将会构建一个简单的便签列表,让用户增加新便签,而不能编辑。这个练习展示了:
* ListActivities的基本用法,创建和处理菜单选项
* 怎样使用SQLite数据库来存储便签
* 怎样使用SimpleCursorAdapter来绑定数据库游标上的数据到ListView上
* 界面布局的基本用法,包括怎样摆放一个列表视图,怎样增加条目到活动菜单中,活动怎样处理菜单选择事件。

第一步

在Eclipse中打开Notepadv1项目

Notepadv1是作为起始点提供的项目。该工程项目关注一些样板工作,正如你在“Hello Android tutorial”中看到的一样。
1、通过点击菜单File > New > Android Project进入一个新的Android项目
2、在New Android Project对话框中,选择Create project from existing source
3、点击Browse,然后选择你存放NotepadCodeLab的目录。选择Notepadv1,然后点击Choose。
4、你会在Project name中看到Notepadv1,也会看到填充了你选择的路径的Location。
5、点击Finish。Notepadv1项目应该打开了,并在Eclipse package explorer(包浏览器)中可见

如果你看到关于AndroidManifest.xml的错误,或者关于Android zip文件的问题,在项目上右键,选择菜单Android Tools > Fix Project Properties。(这个项目看起来库文件处于错误的位置,这种办法会替你纠正它。)

第二步

看看NotesDbAdapter类——这个类提供SQLite数据库访问的封装,将会保存我们的便签数据,允许我们修改它。
这个类顶部是一些常量定义,程序使用这些常量来查找数据库中合适字段的数据。也定义了一个数据库创建字串,用于创建新数据库表格,如果不存在的话。
我们的数据库的名称为data,还有一个成为notes的表,按次序有三个字段:_id,title,body。_id命名使用下划线规则,这个规则用于许多地方,包括AndroidSDK,有助于保持状态跟踪。通常,当查询或者修改数据库(在列预测等等)时_id必须定义。其他两个字段是存储数据的简单的文本字段。
NotesDbAdapter的构造函数使用了Context,Context允许和Android操作系统的各个方面通讯。对于需要以某种方式和Android系统接触的类,这是很常见的。Activity类重新实现了Context类,因此,当你需要Context时,通常你只需要从Activity传递Context。
open()方法调用一个DatabaseHelper实例,DatabaseHelper是SQLiteOpenHelper类的本地实现。它调用getWritableDatabase()来处理创建、打开数据库的操作。
close()仅仅关闭数据库,释放和连接相关的资源。
createNote()携带标题文本字串和新便签的内容,然后再数据库中创建那个便签。假如新便签成功创建,这个方法返回新创建的便签所在行的_id的值。
deleteNote()携带指向一个特定便签的rowId,然后从数据库中删除那条便签。
fetchAllNotes()发出一个查询,返回数据库中所有便签的游标。query()调用是值得研究和理解。第一个字段是查询的数据表的名称(这个例子DATABASE_TABLE是“notes”)。下面是我们想返回的列的列表,这个例子就是_id、title和列的数据体,因此被指定放到String数组中。剩下的字段依次是:selection、selectionArgs、groupBy、having和orderBy。这些字段都允许空意味著我们允许存放所有的数据,不必分组,采用默认的排序方式。更多细节请参见“SQLiteDatabase”。

注意:返回的是游标而不是记录集。这允许Android有效使用资源——避免直接加载大量数据到内容中,当需要的时候,游标会获取和释放数据,这对于大表是非常有效的。

fectchNote()类似于fectchAllNotes(),仅仅通过我们指定的rowId获取一条便签。他使用SQLiteDatabase的query()方法的轻量级版本。第一个参数(设置为true)表示我们对一个明显的结果感兴趣(只返回唯一的一行)。selection参数(第四个)有值表示仅仅返回符合“where _id=我们传送的rowId”的记录。因此,我们获得指向某行的游标。
最后,updateNote()针对rowId、title和body,使用ContentValue实例修改对应rowId的便签。

第三步

打开res/layout目录下的notepad_list.xml文件,仔细看看。(为了浏览XML标记你可能必须点击XML便签页,在底部)
这是一个大部分空着的界面定义文件。下面是你应该知道的关于界面文件的一些事情:
*所有界面文件必须以作为第一行
*下一个定义通常但不总是某种界面元素定义,比如LinearLayout
*XML名称空间应该总是定义在XML文件中的顶级组件或者界面,因此文件的其余部分总是能看到android标签:
xmlns:android="http://schemas.android.com/apk/res/android"

界面与活动
多数活动类会有一个与之联系的界面。界面是活动的呈现给用户的脸面,这个例子中,我们的界面占据整个屏幕,显示一个便签列表。
但是,对于一个活动来讲,全屏幕界面不是唯一选项。你也可能想使用一个浮动的界面(例如对话框和警告提示框),或者也许你根本不需要一个界面(如果你不指定活动使用的某种界面,则活动将是用户不可见的)。

第四步

我们需要创建界面来容纳我们的列表。增加包含LinearLayout元素,整个文件看起来像这样:

android:layout_width="wrap_content"
android:layout_height="wrap_content">
android:layout_width="wrap_content"
android:layout_height="wrap_content"/>
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/no_notes"/>

*ListView和TextView标签的id串中的@符号意思是:XML语法分析器应该解析和扩展id串的剩余部分,使用一个ID资源扩展。
*ListView和TextView可以作为两个可互相替换的视图,仅仅其中一个会被立即显示。
*通过Android平台列表和空的ID提供给我们,因此,我们必须使用android作为id的前缀。例如,@android:id/list
*当ListAdapter没有数据提供给ListView时,系统自动使用携带空id的视图。默认情况下,ListAdapter知道寻找这个名字。另外,你应该使用ListView的serEmptyView(VIew)方法来改变默认的空视图。
基本上说,android.R类是平台提供的一组预定义的资源,而你的项目的R类是你自己项目已经定义的一组资源。在android.R资源类找到的资源能被用于XML文件,通过使用android:名称空间前缀。

第五步

为了在ListView中创建便签列表,我们也需要为每一行定义一个VIew:
1、在res/layout目录下创建一个新文件,取名为notes_row.xml
2、增加如下的内容(注意:XML头部又被使用,第一个节点定义了android XML名称空间)

xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="wrap_content"
android:layout_height="wrap_content"/>
这是用于每个便签标题行的View——仅有一个文本字段。
在这个例子中,我们创建一个新的id,称为text1.@号后面的+表示如果id不存在那么自动作为资源创建,因此我们在text1,然后使用它。
3、保存文件
在项目中打开R.java类看看它,你将会看到新的定义:notes_row和text1,意味着我们现在能在代码中访问这些。

资源和R类
res/文件夹用于资源。res/下面有对于文件夹和文件的特别结构。
这些文件夹和文件中定义的资源将会对应R类中的条目,以便允许他们容易被访问和使用。R类由eclipse插件根据res/的内容自动产生(或者,在命令行工具中使用aapt命令)。作为应用程序的一部分,他们可展开和收集。

第六步
下面,在源代码中打开Noteadv1类。下面的步骤中,我们打算替换这个类,让其变成列表适配器,显示我们的便签,也会允许增加新的便签。
Notepadv1将会继承Activity的子类ListActivity,ListActivity有额外的功能以容纳多种可能加入的列表功能,例如:在每行的列表条目上显示任意的数字、移动列表条目、选择列表条目。
仔细查看Notepadv1类的已存在的代码。有一个还没有使用的私有字段叫做mNoteNumber,我们将会用来创建编号了的便签的标题。
还有三个重载方法:onCreate,OnCreateOptionsMenu和OnOptionsItemSelected,我们需要填充他们。
* onCreate()在活动启动的时候调用——有点像活动的“main”方法。运行时使用这个方法来设置资源和状态。
*onCreateOptionsMenu()用于增加活动的菜单。当用户点击菜单按钮时将会表现出来,有一个选项列表能选择。
*onOptionsItemSelected()是菜单等式的另外一半,用于处理菜单事件(例如,在用户选择“Create NOte”子菜单时)

第七步

修改Notepadv1的继承关系,从Activity改为ListActivity:
public class Notepadv1 extends ListActivity
注意:你必须使用Eclipse导入ListActivity到Notepadv1类中,Windows 和Linux的热键为ctrl-shift-O,Mac的热键cmd-shift-O。

第八步

填充onCreate()方法的代码。
这里我们会设置活动的标题(显示在屏幕的顶部),使用我们在XML文件中创建的notepad_list界面,设置NotesDbAdapter实例以便将来访问便签数据,使用可获取的便签标题来增加列表:
1、在onCreate方法中,携带savedInstanceState参数调用super()
2、调用setContentView(),传递R.layout.notepad_list
3、在类的顶部,创建一个NotesDbAdapter类的私有属性,称为mDbHelper
4、在onCreate方法中,构建一个新NotesDbAdapter实例,绑定到mDbHelper字段(传递给DBHelper的构造函数)
5、在mDbHelper上调用open()方法,以打开数据库
6、最后,调用新方法fillData(),作用是:获取数据,使用helper增加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;

第九步

填写onCreateOptionsMenu()的代码。
现在我们会创建“Add Item”按钮,这个按钮能在设备上点击。我们会指定他在菜单上占据的位置.
1、strings.xml中增加一个名叫“menu_insert”的新字串,该字串的值为Add Item:
Add Item
然后保存文件并返回到Notepadv1.
2、在类的顶部创建菜单位置常量
public static final int INSERT_ID = Menu.FIRST;
3、OnCreateOptionsMenu()中修改super的调用,因此我们捕获布尔变量作为结果返回。我们将会在最后返回这个值。
4、然后,使用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(如上的定义),菜单项的顺序(o表示没有优先级),用于这个条目的资源串。

关于菜单的更多知识


我们构造的notepad程序仅仅接触了菜单的表面。
你还能“增加菜单项的快捷键”、“创建子菜单”,甚至增加菜单项到其他应用。

第十步

填充onOptionsItemSelected()方法的代码。
打算处理我们新的Add Note菜单项。当菜单被选择时,onOptionsItemSelected方法携带item.getId(),设置到INSERT_ID(这个常数我们用来识别菜单项)。我们能检测这个选择事件,作出适当的动作:
1、super.onOptionsItemSelected(item)方法的调用发生在这个方法的最后——我们想首先捕获我们的事件。
2、在item.getItemID()中写一个转换状态。
INSERT_ID的例子中,调用一个新的方法,createNote(),返回true,因为我们已经处理了这个时间,不想通过系统传递它。
3、最后,返回超类的onOptionsItemSelected方法的结果
onOptionsItemSelected方法应该看起来如下:
@Override
public boolean onOptionsItemSelected(MenuItem item) {
switch (item.getItemId()) {
case INSERT_ID:
createNote();
return true;
}

return super.onOptionsItemSelected(item);
}

第11步

增加一个新的createNote方法:
我们的程序的第一个版本,createNote()不会很有用。我们将会简单地创建新的带有标题的便签,假定标题基于计数器、便签内容是空的。目前,我们无法编辑便签的内容,因此现在我们毫无疑问是设置默认值的内容:
1、使用Note和计数器来构造名称,我们在类中定义String noteName = "Note " + mNoteNumber++
2、调用mDbHelper.createNote(),使用noteName作为标题,内容就写为""
3、调用fillData(),组装便签列表(低效但是简单)——我们将会创建这个方法
createNote方法看起来如下:
private void createNote() {
String noteName = "Note " + mNoteNumber++;
mDbHelper.createNote(noteName, "");
fillData();
}

第12步

定义fillData方法:
这个方法使用SimpleCursorAdapter,SimpleCursorAdapter获取一个数据库游标,绑定到界面中提供的字段上。这个字段定义了我们列表的行元素(这个例子中,我们使用notes_row.xml界面的text1字段),因此允许我们轻易地使用数据库中的数据项来组装列表。
我们必须提供一个映射,从返回的游标的标题字段映射到我们的text1文本视图中,定义了两个数组:第一个,字串数组,映射到列(这个例子中,仅仅标题,来自常量NotesDbAdapter.KEY_TITLE),第二个,整型数组,包含到绑定了数据的视图(R.id.text1文本视图)的引用
这是一个较大块的代码,因此让我们首先看看:
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);
}
以下是我们已经做得事情:
1、当从mDbHelper.fetchAllNotes()获得游标后,我们使用一个称为startManagingCursor()的活动的方法,该方法允许Android照看游标的生命周期,而不需要我们担心她。(我们将会在练习3中牵涉生命周期,但是现在仅仅知道:这让Android为我们做一些资源管理的事情)
2、然后,我们创建一个字符串数组映射到标题,一个整型数组映射到视图
3、下一步是实例化SimpleCursorAdapter。象Android的许多类一样,SimpleCursorAdapter需要Context,因此,我们传入上下文环境参数(因为Activity的子类实现了Context)。我们传递notes_row视图(作为数据容器创建),游标(刚刚创建的),数组。
将来记住,从列到资源的映射用于决定两个数组的顺序。如果我们想绑定有更多的列,对应的有更多的视图绑定,那么我们会按顺序指定它们,例如:我们可能使用{ NotesDbAdapter.KEY_TITLE, NotesDbAdapter.KEY_BODY }和{ R.id.text1, R.id.text2 }来绑定两个字段进入记录行(而且,我们也会需要在notes_row.xml中定义text2,针对内容字符串)。这是绑定多个字段到单记录的方法(而且,也获得一个自定义的记录行的界面)。
If you get compiler errors about classes not being found, ctrl-shift-O or (cmd-shift-O on the mac) to organize imports.
如果你看到这个类的编译错误是没有找到,那么按ctrl-shift-O 来组织import。

第13步
运行你的程序
1、右击Notepadv1项目
2、从弹出菜单中选择Run As > Android Application.
3、如果你看到一个对话框出现,选择Android发射器作为运行程序的方法(你也能使用对话框顶部附近的链接,设置你的工作区的默认启动方式:这种方式是推荐的因为每次回询问你是否暂停插件)
4、通过点击menu按钮选择Add Item菜单,来增加新的便签

没有评论: