记事本练习 1
在这个练习中,你将要创建一个简单的记事本列表,用户可以添加新的记事但是不能编辑他们。这个练习示范了如下内容:
列表活动创建并操作菜单项的基本知识。怎样在SQLite数据库中存取记事。怎样将数据绑定到一个使用了数组适配器的列表视图(最简单的一种绑定到列表视图的方式)。关于屏幕布局,包括怎样布局一个列表视图,怎样将一个项目加入活动的菜单,活动是怎样操作那些菜单选择,这些内容的基础知识。 [Exercise 1] [Exercise 2] [Exercise 3] [Extra Credit]
第一步 在Eclipse中打开Notepadv1项目。
Notepadv1项目是一个起点,它关注一些样板性的工作,如果你完成了Hello Android教程,那么你就曾经见过它们。
右击包资源管理器,选择把.../General/Existing Projects导入到工作区。按浏览键,转到你拷贝的那三个练习所在的文件夹,选择Notepadv1然后点OK。你应该看到Notepadv1列在项目表中,并且有个检查标记在它旁边,点完成。练习项目应该已经打开。如果AndroidManisfest.xml 或者Android zip 文件有什么错误,右击项目然后选择Android Tools->Fix Project Properties(项目在错误的路径里查找库文件,上面的操作会替你修复它)。
在这个练习中,我们仅打算直接用SQLite 数据库来存储我们的数据,但是在一个实际的应用程序中,也许写一个合适的内容提供器(Content Provider)来封装这个行为会更好。
如果你感兴趣,你可以找到更多关于内容提供器或者介绍存储,查找,显示数据的整个主题。
看一下DBHelper类-它用来封装对SQLite数据库的数据访问,这个数据库将保存我们的记事本数据并且允许我们更新它。
通常你将利用一个内容提供器来实现,事实上在SDK中包含的那个完整的记事本程序确实实现了这样一个 ContentProvider。不管怎样,没理由不让你直接用自己的SQLite数据,像我们在这里做的一样。最主要的事是注意这个类是怎样为我们存储,查找,更新SQLite数据库中的数据。对于查找行,基于行id查找,创建新行,删除已存在的行和更新一行,这里有一些方法。如果你想迅速了解如何在你的程序中使用SQLite数据库的基础知识,或者看一下这个例子中的类,或者更好的,看一下完整的记事本应用程序。它在SDK的samples/目录中,作为一个使用ContentProvider的例子。
布局和活动
大多数活动会有一个相联系的布局。布局是活动对用户的直观表现。在这个例子中,我们的布局将占据整个屏幕并提供一个记事列表。
全屏的布局并不是活动的唯一选择。你也许想知道如何用浮动布局(例如,对话框或者警告),或者也许你完全不需要一个布局(这类活动对用户将是不可见的,除非你为它指定了某种布局)
打开res/layout中的notepad_list.xml文件,看一下:
这是一个布局定义文件,其中包含了默认的起始项。我们提供这些,是为了便于你迅速开始
xmlns:android="http://schemas.android.com/apk/res/android"
我们需要创建一个布局来容纳我们的列表。在LinearLayout标签中添加代码,使得这个文件看起来像下面的样子:(你得敲Source tab按钮来编辑XML文件)
<?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 id="@id/android:list" android:layout_width="wrap_content" android:layout_height="wrap_content"/> <TextView id="@id/android:empty" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="@string/no_notes"/> </LinearLayout>
在Eclipse项目res/下的文件夹很特殊。在这个文件夹下的子文件夹和文件有特定的结构。
尤其,定义在这些文件夹和文件里的资源会在R类中有相应的入口。这样你的程序就可以轻松的访问到这些资源。更进一步的,它们会被绑定,作为程序的一部分。
要产生一个列表视图,我们需要定义表中各行的显示:
<?xml version="1.0" encoding="utf-8"?> <TextView id="@+id/text1" xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="wrap_content" android:layout_height="wrap_content"/>
然后,打开源码中的Notepadv1类。我们将要替换这个类,成为列表适配器来显示我们的记事,同时允许我们添加新的记事。
Notepadv1将成为Activity的一个子类,称为ListActivity,它拥有一些适合做与列表相关的额外功能,例如:在屏幕上按行显示列表项的数字,在列表项中移动,允许他们被选择。
看一下Notepadv1类中的现有代码。在最上面有一些常量定义,接着的私有区域里我们将创建数字形式的记事标题,并且还有一些从超类重载的方法。
将Notepadv1的父类从Activity改为ListActivity:
public class Notepadv1 extends ListActivity
注:你必须用Eclipse把ListActivity导入到Notepadv1类中,在Windows或Linux按crtl-shift-O,或者在Mac下按cmd-shift-O。
这里定义了三个重载方法:onCreate,onCreateOptionsMenu和onOptionsItemSelected,我们把需要的内容详细说明一下:
详细说明onCreate()方法的主体。
在这儿,我们将要为活动设定一个名称(显示在屏幕的最上面),利用我们先前为活动创建的notepad_list布局来显示内容,建立DBHelper实例来访问记事数据,然后产生带有有效记事标题的列表:
@Override public void onCreate(Bundle icicle) { super.onCreate(icicle); setContentView(R.layout.notepad_list); dbHelper = new DBHelper(this); fillData(); }
记住添加DBHelper的定义(在noteNumber定义的下面):
private DBHelper dbHelper;
我们构建的记事本程序仅仅涉及了菜单的表面。
你可以为菜单项添加快捷键,创造子菜单甚至将菜单添加到别的应用程序中onCreateOptionMenu()方法的主体
我们现在要添加一个菜单项,“Add Item”,它会用到我们在strings.xml中创建的字符串,并且用到一个常量定义,这个常量我们在类的顶部创建来标志Add项的操作。
@Override public boolean onCreateOptionsMenu(Menu menu) { boolean result = super.onCreateOptionsMenu(menu); menu.add(0, INSERT_ID, R.string.menu_insert); return result; }
onOptionsItemSelected()方法:
这个方法将要处理我们新的“添加记事”菜单项。当选中这个菜单项时,onOptionsItemSelected()方
法就将被调用,同时item.getId()的值会被设为INSERT_ID(我们用这个常量来标志菜单项)。我们能
检查这个,然后选择适当的动作:
@Override public boolean onOptionsItemSelected(Item item) { switch (item.getId()) { case INSERT_ID: createNote(); break; } return super.onOptionsItemSelected(item); }
添加一个createNote()方法:在我们应用程序的这个第一版里,createNote()不会很有用。我们只是简单的创建一个带有标题的记事,
这个标题被赋值为(“Note1”,“Note2”。。。)这类基于计数的东东,但是记事的内容为空。现在我
们还不能修改记事的内容,所以我们现在只能满足于一些默认值:
private void createNote() { String noteName = "Note " + noteNumber++; dbHelper.createRow(noteName, ""); fillData(); }
列表适配器
我们的例子使用了一个非常简单的数组适配器,它将一个项目的数组或列表添加至列表视图。在Android中更常见的是,列表适配器和ContenProviders一起运行,并且这也是列表的一种非常简单的用法。
将ContentProvider捆绑至ListView,你可以用android.widget.SimpleCursorAdapter。
定义fillData()方法。这个方法很长:
这个方法使用了ArrayAdapter,它是将数据放入ListView的最简单方法。ArrayAdapter读取一个列表或者字串数组,并且将他们绑定到一个文本视图,在布局中提供了这个为列表行定义的文本视图(在我们的notes_row.xml布局文件中的text1域)。这个方法简单的从database helper中获取一列记事,利用从每行得到的标题字串构造了一个字符串列表,然后通过这些items创建一个ArrayAdapter,并使用我们定义过的notes_row
注:在这个练习中我们使用了ArrayAdapter,这并不是好的解决方案,更典型的,一个SimpleCursorAdapter应该和ContenProvider一起用或者至少是一个从查询返回的Cursor。看侧边的列表适配器获得更多信息。
运行!
方案和下一步
你可以在zip文件Notepadv1Solution中找到这个类的解决方案,对照一下你自己的。一旦你准备好,继续入门指引练习2为程序添加新建,编辑删除记事的能力。返回入门指引首页。