本练习中,你将会使用调试器来查看你在练习3中做的工作。本练习证明:
1、怎么设置断点来观察执行情况
2.怎么运行你的应用程序在调试模式下。
第一步
使用加工过的Notepadv3,在NoteEdit类的onCreate(), onPause(), onSaveInstanceState() 和 onResume()方法的开头处,设置断点。(如果不熟悉Eclipse,在你想设置断点的行上,对应编辑窗口的左边的窄窄的灰色边框里面右击,选择Toggle Breakpoint,你应该会看到蓝点显示)
第二步
现在,在调试模式下启动notepad示例程序
1、在Notepadv3项目上右击,从调试菜单选择Debug As -> Android Application
2.Android模拟器应该简单地说“waiting for debugger to connect(等待调试器链接)”,然后运行应用程序
3、如果一直停留在“waiting...”屏幕下,退出模拟器和Eclipse,从命令行使用adb销毁服务器,然后重新启动。
第三步
当你编辑或者创建新标签的时候,你应该看到断点命中,执行停止。
第四步
点击Resume按钮让执行继续(在顶部的Eclipse工具栏右边带有绿色三角形的黄色矩形)
第五步
实验确认和回退按钮,试着按Home键,改为其他模式。观察生命周期时间产生了什么,什么时候触发。
ADT插件不仅提供优良的调试支持,而且卓越的剖析能力支持。你能试着使用TraceView来剖析你的应用程序。如果你的应用成运行很慢,这能帮助你发现瓶颈,并纠正问题。
2008年12月30日星期二
教程:Notepad练习3
教程:Notepad练习3
这个练习中,你将会使用生命周期事件回调来保存和取回应用程序状态数据。这个练习说明:
1、生命周期事件和你的程序怎么能使用生命周期事件
2、维持应用程序状态的技术
第一步
导入Notepadv3进入Eclipse。这个练习的开始点正好是Notepadv2最后停止的地方。
目前这个程序有一些问题——当编辑导致崩溃时点击回退按钮,其他在编辑期间发生的任何事情将会导致编辑数据丢失。
为了修正这个问题,我们将会把创建和编辑便签的大部分功能移到NoteEdit类,针对编辑便签功能介绍一个完整的生命周期。
1、删除NoteEdit中从额外Bundle分析标题和内容的代码
作为替换,我们打算使用DBHelper类来从数据库中直接访问便签。我们需要传入到NoteEdit活动的参数只有mRowId(但是仅仅在编辑时,创建时我们不传入任何参数)。删除下面的行:
String title = extras.getString(NotesDbAdapter.KEY_TITLE);
String body = extras.getString(NotesDbAdapter.KEY_BODY);
2、我们也会除去在额外Bundle中传入的属性,这些属性用于设置UI中标题和内容文本编辑框的值。因此删除:
if (title != null) {
mTitleText.setText(title);
}
if (body != null) {
mBodyText.setText(body);
}
第二步
在NoteEdit类的顶部,创建NotesDbAdapter的私有属性:
private NotesDbAdapter mDbHelper;
也在onCreate()方法中增加NotesDbAdapter的实例(在super.onCreate()调用的右下面)
mDbHelper = new NotesDbAdapter(this);
mDbHelper.open();
第三步
在NoteEdit中,我们需要为了mRowId检查savedInstanceState,万一正在编辑的便签包含Bundle的一个已经保存的状态,我们应该恢复这个状态(如果Activity失去焦点然后重新启动,恢复动作将会发生)
1、替换目前的初始化mRowId的代码:
mRowId = null;
Bundle extras = getIntent().getExtras();
if (extras != null) {
mRowId = extras.getLong(NotesDbAdapter.KEY_ROWID);
}
为:
mRowId = savedInstanceState != null ? savedInstanceState.getLong(NotesDbAdapter.KEY_ROWID)
: null;
if (mRowId == null) {
Bundle extras = getIntent().getExtras();
mRowId = extras != null ? extras.getLong(NotesDbAdapter.KEY_ROWID)
: null;
}
2、注意检查savedInstanceState的null值,我们仍然需要从额外Bundle中加载mRowID,如果mRowID没有被savedInstanceState提供。这是一个三元操作符的简写,以便保证不是使用这个值就是null,不管它是否满足条件。
第四步
下面,我们需要填充基于mRowID的字段:
populateFields();
这行代码在confirmButton.setOnClickListener()之前运行。我们会待会儿定义这个方法。
第五步
从onClick()处理函数中除去Bundle创建和Bundle值设置.Activity不再需要返回任何附加信息到调用者。因为我们不再有返回的Intent,我们会使用setResult()的更简版本:
public void onClick(View view) {
setResult(RESULT_OK);
finish();
}
我们会实现功能:保存修改或者新建的便签到数据库中,使用生命周期方法。
onCreate()的代码如下:
super.onCreate(savedInstanceState);
mDbHelper = new NotesDbAdapter(this);
mDbHelper.open();
setContentView(R.layout.note_edit);
mTitleText = (EditText) findViewById(R.id.title);
mBodyText = (EditText) findViewById(R.id.body);
Button confirmButton = (Button) findViewById(R.id.confirm);
mRowId = savedInstanceState != null ? savedInstanceState.getLong(NotesDbAdapter.KEY_ROWID)
: null;
if (mRowId == null) {
Bundle extras = getIntent().getExtras();
mRowId = extras != null ? extras.getLong(NotesDbAdapter.KEY_ROWID)
: null;
}
populateFields();
confirmButton.setOnClickListener(new View.OnClickListener() {
public void onClick(View view) {
setResult(RESULT_OK);
finish();
}
});
第六步
定义populateFields()方法
private void populateFields() {
if (mRowId != null) {
Cursor note = mDbHelper.fetchNote(mRowId);
startManagingCursor(note);
mTitleText.setText(note.getString(
note.getColumnIndexOrThrow(NotesDbAdapter.KEY_TITLE)));
mBodyText.setText(note.getString(
note.getColumnIndexOrThrow(NotesDbAdapter.KEY_BODY)));
}
}
这个方法使用NotesDbAdapter.fetchNote()方法来找到编辑的便签,然后调用startManagingCursor(),这个方法是Android共用程序,负责Cursor的生命周期。在Activity生命周期指示下这个方法会释放和重建资源,因此,我们不必担心这些,做自己该做的事情。之后,我们只从游标中查找标题和内容的值,并用来填充View元素。
第七步
处理生命周期事件为什么是重要的?
如果你过去习惯于总是控制你的程序,那么你可能不理解生命周期所有事情为什么必须的。原因是,在Android中,你不能控制你的Activity,而是操作系统控制。
正如我们已经看到的那样,Android模型基于可以互相调用的活动。当一个活动调用另外一个时,目前的活动暂停,如果系统运行在低资源环境下,可能被遗弃杀掉。如果被杀掉,你的活动将不得不保存充足的状态,以便将来恢复,我们希望恢复时的状态和被杀掉的状态相同。
Android有一个定义明确的生命周期。即使你不显式控制另外一个活动,生命周期时间也能发生。例如,也许一个电话到达到话筒,如果电话到达,你的Activity正在运行,那么这个活动会被换出,而由呼叫活动替换。
仍旧在NoteEdit类中,我们重载onSaveInstanceState()、onPause() 和 onResume()方法。这些是我们自己的生命周期方法(连同我们已经有的onCreate())。
如果Activity被停止,onSaveInstanceState()被Android自动调用,在重新开始以前可以被杀掉。这意味着,应该保存任何必须的状态以保证Activity重新启动时重新初始化到同样的环境。这是到onCreate()方法的副本,实际上,传入到onCreate()的savedInstanceState Bundle是和onSaveInstanceState()的outState相同的Bundle.
onPause() 和 onResume()是免费赠送的方法。当Activity结束的时候,onPause()总是被调用,及时我们主动(例如调用finish())。我们会使用来保存当前的标签到数据库。最佳办法是释放在onPause()内部能不释放的任何资源,在被动状态占用更少的资源。基于这个原因,我们将会关闭DBHelper类,设置属性到空,以便能进行垃圾回收。另外一个方面,onResume()会重建mDbHelper实例,因此我们能使用它,然后又读取数据库的便签数据,重新填充这些字段。
因此,在populateFields()方法后增加一些空间,并且增加下面的生命周期方法:
1. onSaveInstanceState():
@Override
protected void onSaveInstanceState(Bundle outState) {
super.onSaveInstanceState(outState);
outState.putLong(NotesDbAdapter.KEY_ROWID, mRowId);
}
2. onPause():
@Override
protected void onPause() {
super.onPause();
saveState();
}
We'll define saveState() next.
3. onResume():
@Override
protected void onResume() {
super.onResume();
populateFields();
}
第八步
定义saveState()方法,从数据库中取出数据
private void saveState() {
String title = mTitleText.getText().toString();
String body = mBodyText.getText().toString();
if (mRowId == null) {
long id = mDbHelper.createNote(title, body);
if (id > 0) {
mRowId = id;
}
} else {
mDbHelper.updateNote(mRowId, title, body);
}
}
注意:我们从creaeNote()捕获返回值,如果返回了一个有效的行号,我们保存在mRowID属性中,以便我们将来能修改便签,而不是创建一个新的便签(另外,如果生命周期时间被触发,那么创建可能发生)
第九步
现在,在Notepadv3类中从onActivityResult()中剥离出先前的处理代码
在NoteEdit生命周期内发生所有便签查询和修改操作,因此,onActivityResult()方法需要做的事情是修改数据视图,其他工作不需要。最终的代码如下
@Override
protected void onActivityResult(int requestCode, int resultCode,
Intent intent) {
super.onActivityResult(requestCode, resultCode, intent);
fillData();
}
因为其他类做这个工作,必须做的事情是刷新数据。
第十步
也要从onListItemClick()中移去设置标题和内容的行(他们不再需要,仅仅需要mRowID):
Cursor c = mNotesCursor;
c.moveToPosition(position);
同样要移去:
i.putExtra(NotesDbAdapter.KEY_TITLE, c.getString(
c.getColumnIndex(NotesDbAdapter.KEY_TITLE)));
i.putExtra(NotesDbAdapter.KEY_BODY, c.getString(
c.getColumnIndex(NotesDbAdapter.KEY_BODY)));
因此剩下的代码应该是:
super.onListItemClick(l, v, position, id);
Intent i = new Intent(this, NoteEdit.class);
i.putExtra(NotesDbAdapter.KEY_ROWID, id);
startActivityForResult(i, ACTIVITY_EDIT);
现在,你也能移去mNotesCursor属性,使用fillData()方法中的本地变量:
Cursor notesCursor = mDbHelper.fetchAllNotes();
注意:mNotesCursor中的m表示一个成员变量,因此当我们产生本地变量notesCursor的时候,我们销毁m。
运行程序!(右键菜单中,点击Run As -> Android Application)
这个练习中,你将会使用生命周期事件回调来保存和取回应用程序状态数据。这个练习说明:
1、生命周期事件和你的程序怎么能使用生命周期事件
2、维持应用程序状态的技术
第一步
导入Notepadv3进入Eclipse。这个练习的开始点正好是Notepadv2最后停止的地方。
目前这个程序有一些问题——当编辑导致崩溃时点击回退按钮,其他在编辑期间发生的任何事情将会导致编辑数据丢失。
为了修正这个问题,我们将会把创建和编辑便签的大部分功能移到NoteEdit类,针对编辑便签功能介绍一个完整的生命周期。
1、删除NoteEdit中从额外Bundle分析标题和内容的代码
作为替换,我们打算使用DBHelper类来从数据库中直接访问便签。我们需要传入到NoteEdit活动的参数只有mRowId(但是仅仅在编辑时,创建时我们不传入任何参数)。删除下面的行:
String title = extras.getString(NotesDbAdapter.KEY_TITLE);
String body = extras.getString(NotesDbAdapter.KEY_BODY);
2、我们也会除去在额外Bundle中传入的属性,这些属性用于设置UI中标题和内容文本编辑框的值。因此删除:
if (title != null) {
mTitleText.setText(title);
}
if (body != null) {
mBodyText.setText(body);
}
第二步
在NoteEdit类的顶部,创建NotesDbAdapter的私有属性:
private NotesDbAdapter mDbHelper;
也在onCreate()方法中增加NotesDbAdapter的实例(在super.onCreate()调用的右下面)
mDbHelper = new NotesDbAdapter(this);
mDbHelper.open();
第三步
在NoteEdit中,我们需要为了mRowId检查savedInstanceState,万一正在编辑的便签包含Bundle的一个已经保存的状态,我们应该恢复这个状态(如果Activity失去焦点然后重新启动,恢复动作将会发生)
1、替换目前的初始化mRowId的代码:
mRowId = null;
Bundle extras = getIntent().getExtras();
if (extras != null) {
mRowId = extras.getLong(NotesDbAdapter.KEY_ROWID);
}
为:
mRowId = savedInstanceState != null ? savedInstanceState.getLong(NotesDbAdapter.KEY_ROWID)
: null;
if (mRowId == null) {
Bundle extras = getIntent().getExtras();
mRowId = extras != null ? extras.getLong(NotesDbAdapter.KEY_ROWID)
: null;
}
2、注意检查savedInstanceState的null值,我们仍然需要从额外Bundle中加载mRowID,如果mRowID没有被savedInstanceState提供。这是一个三元操作符的简写,以便保证不是使用这个值就是null,不管它是否满足条件。
第四步
下面,我们需要填充基于mRowID的字段:
populateFields();
这行代码在confirmButton.setOnClickListener()之前运行。我们会待会儿定义这个方法。
第五步
从onClick()处理函数中除去Bundle创建和Bundle值设置.Activity不再需要返回任何附加信息到调用者。因为我们不再有返回的Intent,我们会使用setResult()的更简版本:
public void onClick(View view) {
setResult(RESULT_OK);
finish();
}
我们会实现功能:保存修改或者新建的便签到数据库中,使用生命周期方法。
onCreate()的代码如下:
super.onCreate(savedInstanceState);
mDbHelper = new NotesDbAdapter(this);
mDbHelper.open();
setContentView(R.layout.note_edit);
mTitleText = (EditText) findViewById(R.id.title);
mBodyText = (EditText) findViewById(R.id.body);
Button confirmButton = (Button) findViewById(R.id.confirm);
mRowId = savedInstanceState != null ? savedInstanceState.getLong(NotesDbAdapter.KEY_ROWID)
: null;
if (mRowId == null) {
Bundle extras = getIntent().getExtras();
mRowId = extras != null ? extras.getLong(NotesDbAdapter.KEY_ROWID)
: null;
}
populateFields();
confirmButton.setOnClickListener(new View.OnClickListener() {
public void onClick(View view) {
setResult(RESULT_OK);
finish();
}
});
第六步
定义populateFields()方法
private void populateFields() {
if (mRowId != null) {
Cursor note = mDbHelper.fetchNote(mRowId);
startManagingCursor(note);
mTitleText.setText(note.getString(
note.getColumnIndexOrThrow(NotesDbAdapter.KEY_TITLE)));
mBodyText.setText(note.getString(
note.getColumnIndexOrThrow(NotesDbAdapter.KEY_BODY)));
}
}
这个方法使用NotesDbAdapter.fetchNote()方法来找到编辑的便签,然后调用startManagingCursor(),这个方法是Android共用程序,负责Cursor的生命周期。在Activity生命周期指示下这个方法会释放和重建资源,因此,我们不必担心这些,做自己该做的事情。之后,我们只从游标中查找标题和内容的值,并用来填充View元素。
第七步
处理生命周期事件为什么是重要的?
如果你过去习惯于总是控制你的程序,那么你可能不理解生命周期所有事情为什么必须的。原因是,在Android中,你不能控制你的Activity,而是操作系统控制。
正如我们已经看到的那样,Android模型基于可以互相调用的活动。当一个活动调用另外一个时,目前的活动暂停,如果系统运行在低资源环境下,可能被遗弃杀掉。如果被杀掉,你的活动将不得不保存充足的状态,以便将来恢复,我们希望恢复时的状态和被杀掉的状态相同。
Android有一个定义明确的生命周期。即使你不显式控制另外一个活动,生命周期时间也能发生。例如,也许一个电话到达到话筒,如果电话到达,你的Activity正在运行,那么这个活动会被换出,而由呼叫活动替换。
仍旧在NoteEdit类中,我们重载onSaveInstanceState()、onPause() 和 onResume()方法。这些是我们自己的生命周期方法(连同我们已经有的onCreate())。
如果Activity被停止,onSaveInstanceState()被Android自动调用,在重新开始以前可以被杀掉。这意味着,应该保存任何必须的状态以保证Activity重新启动时重新初始化到同样的环境。这是到onCreate()方法的副本,实际上,传入到onCreate()的savedInstanceState Bundle是和onSaveInstanceState()的outState相同的Bundle.
onPause() 和 onResume()是免费赠送的方法。当Activity结束的时候,onPause()总是被调用,及时我们主动(例如调用finish())。我们会使用来保存当前的标签到数据库。最佳办法是释放在onPause()内部能不释放的任何资源,在被动状态占用更少的资源。基于这个原因,我们将会关闭DBHelper类,设置属性到空,以便能进行垃圾回收。另外一个方面,onResume()会重建mDbHelper实例,因此我们能使用它,然后又读取数据库的便签数据,重新填充这些字段。
因此,在populateFields()方法后增加一些空间,并且增加下面的生命周期方法:
1. onSaveInstanceState():
@Override
protected void onSaveInstanceState(Bundle outState) {
super.onSaveInstanceState(outState);
outState.putLong(NotesDbAdapter.KEY_ROWID, mRowId);
}
2. onPause():
@Override
protected void onPause() {
super.onPause();
saveState();
}
We'll define saveState() next.
3. onResume():
@Override
protected void onResume() {
super.onResume();
populateFields();
}
第八步
定义saveState()方法,从数据库中取出数据
private void saveState() {
String title = mTitleText.getText().toString();
String body = mBodyText.getText().toString();
if (mRowId == null) {
long id = mDbHelper.createNote(title, body);
if (id > 0) {
mRowId = id;
}
} else {
mDbHelper.updateNote(mRowId, title, body);
}
}
注意:我们从creaeNote()捕获返回值,如果返回了一个有效的行号,我们保存在mRowID属性中,以便我们将来能修改便签,而不是创建一个新的便签(另外,如果生命周期时间被触发,那么创建可能发生)
第九步
现在,在Notepadv3类中从onActivityResult()中剥离出先前的处理代码
在NoteEdit生命周期内发生所有便签查询和修改操作,因此,onActivityResult()方法需要做的事情是修改数据视图,其他工作不需要。最终的代码如下
@Override
protected void onActivityResult(int requestCode, int resultCode,
Intent intent) {
super.onActivityResult(requestCode, resultCode, intent);
fillData();
}
因为其他类做这个工作,必须做的事情是刷新数据。
第十步
也要从onListItemClick()中移去设置标题和内容的行(他们不再需要,仅仅需要mRowID):
Cursor c = mNotesCursor;
c.moveToPosition(position);
同样要移去:
i.putExtra(NotesDbAdapter.KEY_TITLE, c.getString(
c.getColumnIndex(NotesDbAdapter.KEY_TITLE)));
i.putExtra(NotesDbAdapter.KEY_BODY, c.getString(
c.getColumnIndex(NotesDbAdapter.KEY_BODY)));
因此剩下的代码应该是:
super.onListItemClick(l, v, position, id);
Intent i = new Intent(this, NoteEdit.class);
i.putExtra(NotesDbAdapter.KEY_ROWID, id);
startActivityForResult(i, ACTIVITY_EDIT);
现在,你也能移去mNotesCursor属性,使用fillData()方法中的本地变量:
Cursor notesCursor = mDbHelper.fetchAllNotes();
注意:mNotesCursor中的m表示一个成员变量,因此当我们产生本地变量notesCursor的时候,我们销毁m。
运行程序!(右键菜单中,点击Run As -> Android Application)
2008年12月28日星期日
教程:Notepad练习2
Tutorial: Notepad Exercise 2
教程:Notepad练习2
In this exercise, you will add a second Activity to your notepad application, to let the user create, edit, and delete notes. The new Activity assumes responsibility for creating new notes by collecting user input and packing it into a return Bundle provided by the intent. This exercise demonstrates:
在这个练习之中,你会增加第二个活动进入你的notepad程序,让用户创建、编辑、删除便签。新的Activity呈现的职责是创建新的便签来收集用户输入、打包进入一个返回的由intent提供的Bundle,这个练习证明了:
* Constructing a new Activity and adding it to the Android manifest
1、构造新Activity,增加进入Android manifext
* Invoking another Activity asynchronously using startActivityForResult()
2、使用startActivityForResult()异步调用另外一个Activity
* Passing data between Activity in Bundle objects
3、在Activity间使用Bundle对象传递数据
* How to use a more advanced screen layout
4、怎么使用更多的高级界面布局
Step 1
第一步
Create a new Android project using the sources from Notepadv2 under the NotepadCodeLab folder, just like you did for the first exercise. If you see an error about AndroidManifest.xml, or some problems related to an android.zip file, right click on the project and select Android Tools > Fix Project Properties.
创建新Android项目,使用NotepadCodeLab文件夹的Notepadv2源代码,和练习1的做法相同。如果你看到AndroidManifest.xml的错误,或者android.zip的问题,在project上右击,选择Android Tools > Fix Project Properties
Open the Notepadv2 project and take a look around:
打开Notepadv2项目,全部看一看:
* Open and look at the strings.xml file under res/values — there are several new strings which we will use for our new functionality
1、打开strings.xml 并查看——新功能会用到几个新的字符串
* Also, open and take a look at the top of the Notepadv2 class, you will notice several new constants have been defined along with a new mNotesCursor field used to hold the cursor we are using.
2、打开 Notepadv2类,看看顶部的代码,你会注意到定义了几个新常量,和新的mNotesCursor属性一起用于存储我们正在使用的游标。
* Note also that the fillData() method has a few more comments and now uses the new field to store the notes Cursor. The onCreate() method is unchanged from the first exercise. Also notice that the member field used to store the notes Cursor is now called mNotesCursor. The m denotes a member field and is part of the Android coding style standards.
3、fillData()方法有更多的注释,使用新的字段来保存便签游标。onCreate()方法没有改变。用于保存便签游标的成员变量现在称为mNotesCursor。m指示一个成员属性,是Android代码风格标准的一部分。
* There are also a couple of new overridden methods (onListItemClick() and onActivityResult()) which we will be filling in below.
4、还有一对新的重载方法:onListItemClick() 和onActivityResult(),下面我们会填充它们。
Step 2
第二步
Add an entry to the menu for deleting a note:
增加删除便签的菜单入口:
1. In the onCreateOptionsMenu() method, add a new line:
1、在方法onCreateOptionsMenu()中增加新行:
menu.add(0, DELETE_ID, 0, R.string.menu_delete);
2. The whole method should now look like this:
2、整个方法的代码如下:
@Override
public boolean onCreateOptionsMenu(Menu menu) {
super.onCreateOptionsMenu(menu);
menu.add(0, INSERT_ID, 0, R.string.menu_insert);
menu.add(0, DELETE_ID, 0, R.string.menu_delete);
return true;
}
Step 3
第三步
In the onMenuItemSelected() method, add a new case for DELETE_ID:
onMenuItemSelected()增加新代码,关联DELETE_ID:
mDbHelper.deleteNote(getListView().getSelectedItemId());
fillData();
return true;
1. Here, we use the deleteNote method to remove the note specified by ID. In order to get the ID, we call getListView().getSelectedItemId().
1、使用deleteNote方法,通过ID移除便签。为了获取ID,我们调用getListView().getSelectedItemId()
2. Then we fill the data to keep everything up to date.
2、我们填充数据以保证每件事情最新
The whole method should now look like this:
整个方法的代码:
@Override
public boolean onMenuItemSelected(int featureId, MenuItem item) {
switch(item.getItemId()) {
case INSERT_ID:
createNote();
return true;
case DELETE_ID:
mDbHelper.deleteNote(getListView().getSelectedItemId());
fillData();
return true;
}
return super.onMenuItemSelected(featureId, item);
}
Step 4
第四步
Starting Other Activities
启动其他的Activity
In this example our Intent uses a class name specifically. As well as starting intents in classes we already know about, be they in our own application or another application, we can also create Intents without knowing exactly which application will handle it.
这个例子中,Intent使用一个特定的类名。又在类中启动intent,正如我们知道的那样,是他们在我们自己的程序或者其他程序中,我们也能创建Intent而不知道哪个程序处理它。
For example, we might want to open a page in a browser, and for this we still use an Intent. But instead of specifying a class to handle it, we use a predefined Intent constant, and a content URI that describes what we want to do. See android.content.Intent for more information.
例如,我们可能想在浏览器中打开一个页面,为了这个目的,我们人就使用Intent。但不是指定一个类来处理它,我们使用一个预定义的Intent常量,一个内容URI(描述我们想做什么)。欲知更多信息,请看“android.content.Intent ”。
Fill in the body of the createNote() method:
填写createNode()方法的代码:
Create a new Intent to create a note (ACTIVITY_CREATE) using the NoteEdit class. Then fire the Intent using the startActivityForResult() method call:
创建新的Intent来创建一个便签(ACTIVITY_CREATE),使用NoteEdit类。然后,调用startActivityForResult(),触发Intent:
Intent i = new Intent(this, NoteEdit.class);
startActivityForResult(i, ACTIVITY_CREATE);
This form of the Intent call targets a specific class in our Activity, in this case NoteEdit. Since the Intent class will need to communicate with the Android operating system to route requests, we also have to provide a Context (this).
这种Intent调用的形式瞄准Activity中的特定类,这个例子中指NoteEdit。因为Intent类会需要与Android操作系统通讯,目的是路由请求消息,我们也不得不提供Context(this)。
The startActivityForResult() method fires the Intent in a way that causes a method in our Activity to be called when the new Activity is completed. The method in our Activity that receives the callback is called onActivityResult() and we will implement it in a later step. The other way to call an Activity is using startActivity() but this is a "fire-and-forget" way of calling it — in this manner, our Activity is not informed when the Activity is completed, and there is no way to return result information from the called Activity with startActivity().
startActivityForResult()方法触发Intent,这种方法促使Activity中的方法在新Activity完成时被调用。Activity中接受回调的方法是onActivityResult(),在稍后步骤中会实现它。调用Activity的其他方式是使用startActivity(),但是这种方法是"fire-and-forget"(发射后忘记)——使用这种方法,Activity完成时Activity消息并不灵通,startActivity()调用的Activity没有办法返回结果信息。
Don't worry about the fact that NoteEdit doesn't exist yet, we will fix that soon.
不要担心NoteEdit,我们会校正这个问题。
Step 5
第五步
Fill in the body of the onListItemClick() override.
填写onListItemClick()重载代码。
onListItemClick() is a callback method that we'll override. It is called when the user selects an item from the list. It is passed four parameters: the ListView object it was invoked from, the View inside the ListView that was clicked on, the position in the list that was clicked, and the mRowId of the item that was clicked. In this instance we can ignore the first two parameters (we only have one ListView it could be), and we ignore the mRowId as well. All we are interested in is the position that the user selected. We use this to get the data from the correct row, and bundle it up to send to the NoteEdit Activity.
onListItemClick()是一个回调函数,我们将会重载它。当用户从列表中选择一项时该函数自动被系统调用。四个参数:ListView对象、View(点击的ListView里面)、点击的列表位置、nRowId(点击的条目的记录ID)。这个例子中,我们忽略前面两个参数,也忽略mRowId。我们感兴趣的是用户选择的位置。我们使用这个方法获得正确行的数据,捆扎在一起送到NoteEdit活动。
In our implementation of the callback, the method creates an Intent to edit the note using the NoteEdit class. It then adds data into the extras Bundle of the Intent, which will be passed to the called Activity. We use it to pass in the title and body text, and the mRowId for the note we are editing. Finally, it will fire the Intent using the startActivityForResult() method call. Here's the code that belongs in onListItemClick():
在回调函数的实现中,创建Intent,以便使用NoteEdit类编辑便签。然后,增加数据进入Intent的附加Bundle中。新建的Intent对象传送给被调用的活动。我们使用它传入标题和内容文本,以及我们编辑的便签的mRowId。最后,使用startActivityForResult()方法发射Intent。下面的代码是onListItemClick()的实现:
super.onListItemClick(l, v, position, id);
Cursor c = mNotesCursor;
c.moveToPosition(position);
Intent i = new Intent(this, NoteEdit.class);
i.putExtra(NotesDbAdapter.KEY_ROWID, id);
i.putExtra(NotesDbAdapter.KEY_TITLE, c.getString(
c.getColumnIndexOrThrow(NotesDbAdapter.KEY_TITLE)));
i.putExtra(NotesDbAdapter.KEY_BODY, c.getString(
c.getColumnIndexOrThrow(NotesDbAdapter.KEY_BODY)));
startActivityForResult(i, ACTIVITY_EDIT);
* putExtra() is the method to add items into the extras Bundle to pass in to intent invocations. Here, we are using the Bundle to pass in the title, body and mRowId of the note we want to edit.
1、putExtra()方法增加条目到附加的Bundle中,传入参数intent的引用。此处,我们使用Bundle来传入标题、内容、我们想编辑的便签的记录ID
* The details of the note are pulled out from our query Cursor, which we move to the proper position for the element that was selected in the list, with the moveToPosition() method.
2、从查询游标中读出便签的详细信息,游标的位置移动到列表中选中的元素的位置,使用moveToPosition()方法移动。
* With the extras added to the Intent, we invoke the Intent on the NoteEdit class by passing startActivityForResult() the Intent and the request code. (The request code will be returned to onActivityResult as the requestCode parameter.)
3、附加信息加入Intent中,我们在NoteEdit类型中调用Intent,吧Intent和请求代码传递给startActivityForResult()。(请求代码作为requestCode参数在onActivityResult中返回。)
Note: We assign the mNotesCursor field to a local variable at the start of the method. This is done as an optimization of the Android code. Accessing a local variable is much more efficient than accessing a field in the Dalvik VM, so by doing this we make only one access to the field, and five accesses to the local variable, making the routine much more efficient. It is recommended that you use this optimization when possible.
注意:我们把mNotesCursor属性赋值到本地变量,这是对Android代码的优化。访问本地变量比访问DalvikVM的一个属性更有效率,因此,这样做了后我们访问了这个属性一次,访问本地变量5次,使程序有效得多。推荐尽可能的使用这种优化方法。
Step 6
第六步
The above createNote() and onListItemClick() methods use an asynchronous Intent invocation. We need a handler for the callback, so here we fill in the body of the onActivityResult().
上面提到的createNote()和onListItemClick()方法使用了异步Intent引用。我们需要处理回调行数,因此,此处我们填充onActivityResult()代码。
onActivityResult() is the overridden method which will be called when an Activity returns with a result. (Remember, an Activity will only return a result if launched with startActivityForResult.) The parameters provided to the callback are:
onActivityResult()是一个重载方法,当Activity返回结果的时候被调用。(记住,如果使用startActivityForResult触发,则Activity仅仅会返回一个结果。) 回调函数提供的参数如下:
* requestCode — the original request code specified in the Intent invocation (either ACTIVITY_CREATE or ACTIVITY_EDIT for us).
1、requestCode——在Intent引用中指定的原始请求代码(要么是ACTIVITY_CREATE,要么是ACTIVITY_EDIT)
* resultCode — the result (or error code) of the call, this should be zero if everything was OK, but may have a non-zero code indicating that something failed. There are standard result codes available, and you can also create your own constants to indicate specific problems.
2、resultCode——调用的结果或者错误代码,如果一切都没有问题,应该是0,但是可以是非0值,则表示有错误发生。有标准的结果代码获得,你也可以创建你自己的常量来指示特定错误。
* intent — this is an Intent created by the Activity returning results. It can be used to return data in the Intent "extras."
3、intent——通过有返回结果的Activty创建的Intent。他能用于返回数据,数据存放在Intent的“extras”中。
The combination of startActivityForResult() and onActivityResult() can be thought of as an asynchronous RPC (remote procedure call) and forms the recommended way for an Activity to invoke another and share services.
startActivityForResult()和onActivityResult()的结合能作为异步RPC,提供活动相互调用和共享服务的推荐方法。
Here's the code that belongs in your onActivityResult():
下面是onActivityResult()的代码:
super.onActivityResult(requestCode, resultCode, intent);
Bundle extras = intent.getExtras();
switch(requestCode) {
case ACTIVITY_CREATE:
String title = extras.getString(NotesDbAdapter.KEY_TITLE);
String body = extras.getString(NotesDbAdapter.KEY_BODY);
mDbHelper.createNote(title, body);
fillData();
break;
case ACTIVITY_EDIT:
Long mRowId = extras.getLong(NotesDbAdapter.KEY_ROWID);
if (mRowId != null) {
String editTitle = extras.getString(NotesDbAdapter.KEY_TITLE);
String editBody = extras.getString(NotesDbAdapter.KEY_BODY);
mDbHelper.updateNote(mRowId, editTitle, editBody);
}
fillData();
break;
}
* We are handling both the ACTIVITY_CREATE and ACTIVITY_EDIT activity results in this method.
1、在这个方法中,我们处理 ACTIVITY_CREATE 和 ACTIVITY_EDIT两种活动返回结果。
* In the case of a create, we pull the title and body from the extras (retrieved from the returned Intent) and use them to create a new note.
2、在创建的代码中,我们从extras获取标题和内容,然后用来创建新的便签。
* In the case of an edit, we pull the mRowId as well, and use that to update the note in the database.
3、在编辑的代码中,我们还是获取mRowId,然后用来修改数据库的便签数据。
* fillData() at the end ensures everything is up to date .
4、fillData()保证所有信息都是最新的。
Step 7
第七步
The Art of Layout
布局的艺术
The provided note_edit.xml layout file is the most sophisticated one in the application we will be building, but that doesn't mean it is even close to the kind of sophistication you will be likely to want in real Android applications.
note_edit.xml布局文件是我们建立的应用程序中最成熟的一个文件,但是那不并意味着接近你在真实Android程序中可能要求的那种成熟。
Creating a good UI is part art and part science, and the rest is work. Mastering Android layout is an essential part of creating a good looking Android application.
创建好的UI部分(参见“Android layout”)是艺术部分是科学,剩余的就是工作。掌握Android布局是创建好看的Android程序的基本部分。
Take a look at the View Gallery for some example layouts and how to use them. The ApiDemos sample project is also a great resource from which to learn how to create different layouts.
仔细看看一些例子的布局,研究怎么使用他们,参见“View Gallery”。ApiDemos例子项目也是好资源,可以学习了解怎么创建不懂的布局。
Open the file note_edit.xml that has been provided and take a look at it. This is the UI code for the Note Editor.
打开已经提供的note_edit.xml文件,仔细看看。这是Note Editor的UI代码。
This is the most sophisticated UI we have dealt with yet. The file is given to you to avoid problems that may sneak in when typing the code. (The XML is very strict about case sensitivity and structure, mistakes in these are the usual cause of problems with layout.)
这是我们已经处理的最成熟的UI。提供这个文件为了避免问题,这些问题可能由于键入代码而悄悄潜入。(XML是非常严格的,在大小写和结构等方面的错误是布局问题的最常见原因。)
There is a new parameter used here that we haven't seen before: android:layout_weight (in this case set to use the value 1 in each case).
这里有一个以前没有见过的新参数:android:layout_weight(本例中在所有情况下设置为值1)
layout_weight is used in LinearLayouts to assign "importance" to Views within the layout. All Views have a default layout_weight of zero, meaning they take up only as much room on the screen as they need to be displayed. Assigning a value higher than zero will split up the rest of the available space in the parent View, according to the value of each View's layout_weight and its ratio to the overall layout_weight specified in the current layout for this and other View elements.
layout_weight用在LinearLayouts中,在布局中赋值"importance"给View。所有View默认layout_weight值为0,意味着他们仅仅占据他们需要的显示空间。赋值大于0将会在父View中分割可获得的剩余空间,分割空间的依据是每个View的layout_weight的值,还有在View元素的现有布局中指定的全部layout_weight的比率。
To give an example: let's say we have a text label and two text edit elements in a horizontal row. The label has no layout_weight specified, so it takes up the minimum space required to render. If the layout_weight of each of the two text edit elements is set to 1, the remaining width in the parent layout will be split equally between them (because we claim they are equally important). If the first one has a layout_weight of 1 and the second has a layout_weight of 2, then one third of the remaining space will be given to the first, and two thirds to the second (because we claim the second one is more important).
举一个例子:假设我们在一行中有一个文本标签和两个文本编辑框。标签没有指定layout_weight,因此他占据必须的最小空间。如果两个文本编辑框的layout_weight都设置为1,可在父窗口中的剩余宽度被他们平分(因为我们声称他们同等重要)。如果第一个是1,第二个是2,那么剩余空间的三分之一分配给第一个,三分之二分配给第二个(因为我们要求第二个更重要)
This layout also demonstrates how to nest multiple layouts inside each other to achieve a more complex and pleasant layout. In this example, a horizontal linear layout is nested inside the vertical one to allow the title label and text field to be alongside each other, horizontally.
这个布局文件也证明怎么互相嵌套多个布局元素,以便获得更多复杂和合适的布局界面。本例中,水平线布局嵌套进入垂直线,以便允许标题标签和文本字段相互并排。
Step 8
第八步
Create a NoteEdit class that extends android.app.Activity.
创建NoteEdit类,继承android.app.Activity
This is the first time we will have created an Activity without the Android Eclipse plugin doing it for us. When you do so, the onCreate() method is not automatically overridden for you. It is hard to imagine an Activity that doesn't override the onCreate() method, so this should be the first thing you do.
我们第一次没有在ADT帮助下创建Activity。onCreate()方法没有自动重载。想像没有重载onCreate()方法的Activity是困难的,因此,首先要做的事情是重载onCreate()
1. Right click on the com.android.demo.notepad2 package in the Package Explorer, and select New > Class from the popup menu.
1、右击com.android.demo.notepad2包,从弹出菜单中选择 New > Class
2. Fill in NoteEdit for the Name: field in the dialog.
2、在对话框的“Name:”标签后填入“NoteEdit”
3. In the Superclass: field, enter android.app.Activity (you can also just type Activity and hit Ctrl-Space on Windows and Linux or Cmd-Space on the Mac, to invoke code assist and find the right package and class).
3、在“Superclass: ”标签中,键入“android.app.Activity”(你也能输入Activity,按 Ctrl-Space,启用代码助手找到正确的包和类)
4. Click Finish.
4、点击Finish
5. In the resulting NoteEdit class, right click in the editor window and select Source > Override/Implement Methods...
5、在NoteEdit类中,右击编辑窗口,选择菜单Source > Override/ImplementMethods...
6. Scroll down through the checklist in the dialog until you see onCreate(Bundle) — and check the box next to it.
6、滚动对话框中的清单,找到onCreate(Bundle)——选中前面的检查框
7. Click OK.
7、点击OK
The method should now appear in your class.
这个方法应该出现在你的类中。
Step 9
第九步
Fill in the body of the onCreate() method for NoteEdit.
填写onCreate()的代码
This will set the title of our new Activity to say "Edit Note" (one of the strings defined in strings.xml). It will also set the content view to use our note_edit.xml layout file. We can then grab handles to the title and body text edit views, and the confirm button, so that our class can use them to set and get the note title and body, and attach an event to the confirm button for when it is pressed by the user.
这里将会设置新活动的题目为“Edit Note”(在strings.xml文件中定义的一个字符串)。也会设置内容视图以便使用note_edit.xml布局文件。然后,处理标题和内容文本编辑框、确认按钮,因此,我们的类能使用他们来获取或者设置便签的标题和内容,附着事件到确认按钮,这个事件在用户按确认按钮时触发。
We can then unbundle the values that were passed in to the Activity with the extras Bundle attached to the calling Intent. We'll use them to pre-populate the title and body text edit views so that the user can edit them. Then we will grab and store the mRowId so we can keep track of what note the user is editing.
然后,我们对传入到Activity的值进行分类,传入值使用Activity的extras Bundle,extras Bundle附着到调用的Intent上。我们会使用他们来预先填充标题和内容文本编辑框,因此,用户能编辑他们。然后,我们会获取和存储mRowId,因此我们能留意用户编辑的什么便签。
1. Inside onCreate(), set up the layout:
1、在onCreate()内部,设置布局:
setContentView(R.layout.note_edit);
2. Find the edit and button components we need:
2、找到我们需要的编辑框和按钮组件:
These are found by the IDs associated to them in the R class, and need to be cast to the right type of View (EditText for the two text views, and Button for the confirm button):
在R类中,通过ID找到他们,需要强制转换到正确的视图类型(两个文本视图是EditText,确认按钮时Button)
mTitleText = (EditText) findViewById(R.id.title);
mBodyText = (EditText) findViewById(R.id.body);
Button confirmButton = (Button) findViewById(R.id.confirm);
Note that mTitleText and mBodyText are member fields (you need to declare them at the top of the class definition).
注意:mTitleText 和 mBodyText是成员变量(你需要在顶部声明他们)
3. At the top of the class, declare a Long mRowId private field to store the current mRowId being edited (if any).
3、在类的顶部,生命一个Long类型的mRowId私有变量,用来存储当前编辑的mRowId
4. Continuing inside onCreate(), add code to initialize the title, body and mRowId from the extras Bundle in the Intent (if it is present):
4、继续在onCreate()中增加代码来初始化标题、内容、mRowId,从Intent的extras Bundle(附加包)中获取初始化数据。
mRowId = null;
Bundle extras = getIntent().getExtras();
if (extras != null) {
String title = extras.getString(NotesDbAdapter.KEY_TITLE);
String body = extras.getString(NotesDbAdapter.KEY_BODY);
mRowId = extras.getLong(NotesDbAdapter.KEY_ROWID);
if (title != null) {
mTitleText.setText(title);
}
if (body != null) {
mBodyText.setText(body);
}
}
* We are pulling the title and body out of the extras Bundle that was set from the Intent invocation.
(1)从来自Intent引用的extras Bundle中获取标题和内容
* We also null-protect the text field setting (i.e., we don't want to set the text fields to null accidentally).
(2)零保护文本框设置(例如,我们不想意外地设置文本框为null)
5. Create an onClickListener() for the button:
5、针对按钮创建onClickListener()
Listeners can be one of the more confusing aspects of UI implementation, but what we are trying to achieve in this case is simple. We want an onClick() method to be called when the user presses the confirm button, and use that to do some work and return the values of the edited note to the Intent caller. We do this using something called an anonymous inner class. This is a bit confusing to look at unless you have seen them before, but all you really need to take away from this is that you can refer to this code in the future to see how to create a listener and attach it to a button. (Listeners are a common idiom in Java development, particularly for user interfaces.) Here's the empty listener:
Listener使UI实现更混乱的一个方面,但是,本例中试图完成的事情是简单的。当用户按确认按钮时,调用onClieck()方法,用来完成一些工作并且然会被编辑的便签的值给Intent调用者。我们利用称为匿名内嵌类的东西来做这些事情。如果你以前没有见到过,那么看起来这有点混乱,但是,你实际需要取走(完成)的所有事情是可以在未来引用这里的代码,注意怎样创建一个Listener,并附着到一个按钮(Listener是Java开发的一个习惯用语,尤其是在UI方面)。下面是空的Listener:
confirmButton.setOnClickListener(new View.OnClickListener() {
public void onClick(View view) {
}
});
Step 10
第十步
Fill in the body of the onClick() method in our listener.
填充侦听器里面的onClick()代码。
This is the code that will be run when the user clicks on the confirm button. We want this to grab the title and body text from the edit text fields, and put them into the return Bundle so that they can be passed back to the Activity that invoked this NoteEdit Activity. If the operation is an edit rather than a create, we also want to put the mRowId into the Bundle so that the Notepadv2 class can save the changes back to the correct note.
这些代码在用户点击确认按钮时运行。我们使用这些代码从编辑框中获取标题和内容文本串,然后把它们送给Bundle,以便他们能传回到Activity(NoteEdit Activity调用)。如果操作是编辑而不是创建,那么传送mRowId进入Bundle,以便Notepav2类能保存变化到正确的便签。
1. Create a Bundle and put the title and body text into it using the constants defined in Notepadv2 as keys:
1、创建Bundle,传递标题和内容文本到Bundle,使用在Notepadv2中定义的常量作为关键码:
Bundle bundle = new Bundle();
bundle.putString(NotesDbAdapter.KEY_TITLE, mTitleText.getText().toString());
bundle.putString(NotesDbAdapter.KEY_BODY, mBodyText.getText().toString());
if (mRowId != null) {
bundle.putLong(NotesDbAdapter.KEY_ROWID, mRowId);
}
2. Set the result information (the Bundle) in a new Intent and finish the Activity:
2、在新的Intent中设置结果信息(Bundle),完成Activity:
Intent mIntent = new Intent();
mIntent.putExtras(bundle);
setResult(RESULT_OK, mIntent);
finish();
* The Intent is simply our data carrier that carries our Bundle (with the title, body and mRowId).
(1)Intent是简单的数据运载工具,运载Bundle(携带标题、内容、mRowId)
* The setResult() method is used to set the result code and return Intent to be passed back to the Intent caller. In this case everything worked, so we return RESULT_OK for the result code.
(2)setResult()方法用于设置结果代码,返回Intent为了传回到Intent调用者。本例中,每件事情都正常工作,因此我们返回RESULT_OK作为结果代码
* The finish() call is used to signal that the Activity is done (like a return call). Anything set in the Result will then be returned to the caller, along with execution control.
(3)finish()调用用于发信号表明Activity被激发(像一个回调)。然后,在Result中设置的所有东西会返回给调用者,连同执行控制一起
The full onCreate() method (plus supporting class fields) should now look like this:
完整的onCreate()如下:
private EditText mTitleText;
private EditText mBodyText;
private Long mRowId;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.note_edit);
mTitleText = (EditText) findViewById(R.id.title);
mBodyText = (EditText) findViewById(R.id.body);
Button confirmButton = (Button) findViewById(R.id.confirm);
mRowId = null;
Bundle extras = getIntent().getExtras();
if (extras != null) {
String title = extras.getString(NotesDbAdapter.KEY_TITLE);
String body = extras.getString(NotesDbAdapter.KEY_BODY);
mRowId = extras.getLong(NotesDbAdapter.KEY_ROWID);
if (title != null) {
mTitleText.setText(title);
}
if (body != null) {
mBodyText.setText(body);
}
}
confirmButton.setOnClickListener(new View.OnClickListener() {
public void onClick(View view) {
Bundle bundle = new Bundle();
bundle.putString(NotesDbAdapter.KEY_TITLE, mTitleText.getText().toString());
bundle.putString(NotesDbAdapter.KEY_BODY, mBodyText.getText().toString());
if (mRowId != null) {
bundle.putLong(NotesDbAdapter.KEY_ROWID, mRowId);
}
Intent mIntent = new Intent();
mIntent.putExtras(bundle);
setResult(RESULT_OK, mIntent);
finish();
}
});
}
Step 11
第11步
The All-Important Android Manifest File
非常重要的Android Manifest文件
The AndroidManifest.xml file is the way in which Android sees your application. This file defines the category of the application, where it shows up (or even if it shows up) in the launcher or settings, what activities, services, and content providers it defines, what intents it can receive, and more.
AndroidManifest.xml文件是Android领会你的程序的方式。这个文件定义了程序的类型,在发射器和设置里面露面的地方,定义了活动、服务、内容提供器是什么,能接收的intent是什么,等等。
For more information, see the reference document AndroidManifest.xml
欲知更多信息参见文档“AndroidManifest.xml”
Finally, the new Activity has to be defined in the manifest file:
最后,新Activity必须定义在manifest文件中:
Before the new Activity can be seen by Android, it needs its own Activity entry in the AndroidManifest.xml file. This is to let the system know that it is there and can be called. We could also specify which IntentFilters the activity implements here, but we are going to skip this for now and just let Android know that the Activity is defined.
在新Activity能被Android了解之前,需要在AndroidManifest.xml中定义自己的Activity入口。这是让系统知道它在那儿且能被调用。我们也能指定Activity实现哪个IntentFilters,但是我们打算暂时跳过这个,只是让Android知道Activity被定义了。
There is a Manifest editor included in the Eclipse plugin that makes it much easier to edit the AndroidManifest file, and we will use this. If you prefer to edit the file directly or are not using the Eclipse plugin, see the box at the end for information on how to do this without using the new Manifest editor.
在Eclipse插件中包含了一个Manifest编辑器,使AndroidManifest文件编辑更加容易,我们将会使用这个编辑器。
1. Double click on the AndroidManifest.xml file in the package explorer to open it.
1、在AndroidManifest.xml文件上双击打开它
2. Click the Application tab at the bottom of the Manifest editor.
2、在Manifest编辑器底部点击Application便签
3. Click Add... in the Application Nodes section.
3、在Application Nodes段中点击 Add...
If you see a dialog with radiobuttons at the top, select the top radio button: "Create a new element at the top level, in Application".
如果你在顶部看到一个带有单选按钮的对话框,那么选择顶部的单选按钮"Create a new element at the top level, in Application"
4. Make sure "(A) Activity" is selected in the selection pane of the dialog, and click OK.
4、保证 "(A) Activity"在对话框的选择面板中被选中,然后点击OK
5. Click on the new "Activity" node, in the Application Nodes section, then type .NoteEdit into the Name* field to the right. Press Return/Enter.
5、在 Application Nodes段中,点击新的"Activity"节点,然后在“Name*”标签右边输入.NoteEdit,按Return/Enter
The Android Manifest editor helps you add more complex entries into the AndroidManifest.xml file, have a look around at some of the other options available (but be careful not to select them otherwise they will be added to your Manifest). This editor should help you understand and alter the AndroidManifest.xml file as you move on to more advanced Android applications.
Android Manifest编辑器帮助你增加更多复杂的条目到AndroidManifest.xml文件中,仔细看看可获得的其他选项(但是小心不要选择他们否则他们会被加入到你的Manifest文件中)。这个编辑器应该帮助你理解和修改AndroidManifest.xml文件,当你移到更多高级的Android程序时会很有用处。
If you prefer to edit this file directly, simply open the AndroidManifest.xml file and look at the source (use the AndroidManifest.xml tab in the eclipse editor to see the source code directly). Then edit the file as follows:
如果你宁愿直接编辑这个文件,简单打开 AndroidManifest.xml文件,然后了解资源(使用eclipse编辑器的AndroidManifest.xml标签直接领悟源代码)。然后,按照下面这样编辑文件:
This should be placed just below the line that reads:
这应该表示:
for the .Notepadv2 activity.
针对.Notepadv2活动
Step 12
第12步
Now Run it!
现在运行。
You should now be able to add real notes from the menu, as well as delete an existing one. Notice that in order to delete, you must first use the directional controls on the device to highlight the note. Furthermore, selecting a note title from the list should bring up the note editor to let you edit it. Press confirm when finished to save the changes back to the database.
现在,你应该能从菜单增加真实的便签,也能删除存在的便签。注意:为了删除,你必须首先使用设备方向控制来选中便签。此外,从列表中选择便签标题应该被提出到便签编辑器,以便你能编辑他。按确认按钮保证保存修改到数据库中。
教程:Notepad练习2
In this exercise, you will add a second Activity to your notepad application, to let the user create, edit, and delete notes. The new Activity assumes responsibility for creating new notes by collecting user input and packing it into a return Bundle provided by the intent. This exercise demonstrates:
在这个练习之中,你会增加第二个活动进入你的notepad程序,让用户创建、编辑、删除便签。新的Activity呈现的职责是创建新的便签来收集用户输入、打包进入一个返回的由intent提供的Bundle,这个练习证明了:
* Constructing a new Activity and adding it to the Android manifest
1、构造新Activity,增加进入Android manifext
* Invoking another Activity asynchronously using startActivityForResult()
2、使用startActivityForResult()异步调用另外一个Activity
* Passing data between Activity in Bundle objects
3、在Activity间使用Bundle对象传递数据
* How to use a more advanced screen layout
4、怎么使用更多的高级界面布局
Step 1
第一步
Create a new Android project using the sources from Notepadv2 under the NotepadCodeLab folder, just like you did for the first exercise. If you see an error about AndroidManifest.xml, or some problems related to an android.zip file, right click on the project and select Android Tools > Fix Project Properties.
创建新Android项目,使用NotepadCodeLab文件夹的Notepadv2源代码,和练习1的做法相同。如果你看到AndroidManifest.xml的错误,或者android.zip的问题,在project上右击,选择Android Tools > Fix Project Properties
Open the Notepadv2 project and take a look around:
打开Notepadv2项目,全部看一看:
* Open and look at the strings.xml file under res/values — there are several new strings which we will use for our new functionality
1、打开strings.xml 并查看——新功能会用到几个新的字符串
* Also, open and take a look at the top of the Notepadv2 class, you will notice several new constants have been defined along with a new mNotesCursor field used to hold the cursor we are using.
2、打开 Notepadv2类,看看顶部的代码,你会注意到定义了几个新常量,和新的mNotesCursor属性一起用于存储我们正在使用的游标。
* Note also that the fillData() method has a few more comments and now uses the new field to store the notes Cursor. The onCreate() method is unchanged from the first exercise. Also notice that the member field used to store the notes Cursor is now called mNotesCursor. The m denotes a member field and is part of the Android coding style standards.
3、fillData()方法有更多的注释,使用新的字段来保存便签游标。onCreate()方法没有改变。用于保存便签游标的成员变量现在称为mNotesCursor。m指示一个成员属性,是Android代码风格标准的一部分。
* There are also a couple of new overridden methods (onListItemClick() and onActivityResult()) which we will be filling in below.
4、还有一对新的重载方法:onListItemClick() 和onActivityResult(),下面我们会填充它们。
Step 2
第二步
Add an entry to the menu for deleting a note:
增加删除便签的菜单入口:
1. In the onCreateOptionsMenu() method, add a new line:
1、在方法onCreateOptionsMenu()中增加新行:
menu.add(0, DELETE_ID, 0, R.string.menu_delete);
2. The whole method should now look like this:
2、整个方法的代码如下:
@Override
public boolean onCreateOptionsMenu(Menu menu) {
super.onCreateOptionsMenu(menu);
menu.add(0, INSERT_ID, 0, R.string.menu_insert);
menu.add(0, DELETE_ID, 0, R.string.menu_delete);
return true;
}
Step 3
第三步
In the onMenuItemSelected() method, add a new case for DELETE_ID:
onMenuItemSelected()增加新代码,关联DELETE_ID:
mDbHelper.deleteNote(getListView().getSelectedItemId());
fillData();
return true;
1. Here, we use the deleteNote method to remove the note specified by ID. In order to get the ID, we call getListView().getSelectedItemId().
1、使用deleteNote方法,通过ID移除便签。为了获取ID,我们调用getListView().getSelectedItemId()
2. Then we fill the data to keep everything up to date.
2、我们填充数据以保证每件事情最新
The whole method should now look like this:
整个方法的代码:
@Override
public boolean onMenuItemSelected(int featureId, MenuItem item) {
switch(item.getItemId()) {
case INSERT_ID:
createNote();
return true;
case DELETE_ID:
mDbHelper.deleteNote(getListView().getSelectedItemId());
fillData();
return true;
}
return super.onMenuItemSelected(featureId, item);
}
Step 4
第四步
Starting Other Activities
启动其他的Activity
In this example our Intent uses a class name specifically. As well as starting intents in classes we already know about, be they in our own application or another application, we can also create Intents without knowing exactly which application will handle it.
这个例子中,Intent使用一个特定的类名。又在类中启动intent,正如我们知道的那样,是他们在我们自己的程序或者其他程序中,我们也能创建Intent而不知道哪个程序处理它。
For example, we might want to open a page in a browser, and for this we still use an Intent. But instead of specifying a class to handle it, we use a predefined Intent constant, and a content URI that describes what we want to do. See android.content.Intent for more information.
例如,我们可能想在浏览器中打开一个页面,为了这个目的,我们人就使用Intent。但不是指定一个类来处理它,我们使用一个预定义的Intent常量,一个内容URI(描述我们想做什么)。欲知更多信息,请看“android.content.Intent ”。
Fill in the body of the createNote() method:
填写createNode()方法的代码:
Create a new Intent to create a note (ACTIVITY_CREATE) using the NoteEdit class. Then fire the Intent using the startActivityForResult() method call:
创建新的Intent来创建一个便签(ACTIVITY_CREATE),使用NoteEdit类。然后,调用startActivityForResult(),触发Intent:
Intent i = new Intent(this, NoteEdit.class);
startActivityForResult(i, ACTIVITY_CREATE);
This form of the Intent call targets a specific class in our Activity, in this case NoteEdit. Since the Intent class will need to communicate with the Android operating system to route requests, we also have to provide a Context (this).
这种Intent调用的形式瞄准Activity中的特定类,这个例子中指NoteEdit。因为Intent类会需要与Android操作系统通讯,目的是路由请求消息,我们也不得不提供Context(this)。
The startActivityForResult() method fires the Intent in a way that causes a method in our Activity to be called when the new Activity is completed. The method in our Activity that receives the callback is called onActivityResult() and we will implement it in a later step. The other way to call an Activity is using startActivity() but this is a "fire-and-forget" way of calling it — in this manner, our Activity is not informed when the Activity is completed, and there is no way to return result information from the called Activity with startActivity().
startActivityForResult()方法触发Intent,这种方法促使Activity中的方法在新Activity完成时被调用。Activity中接受回调的方法是onActivityResult(),在稍后步骤中会实现它。调用Activity的其他方式是使用startActivity(),但是这种方法是"fire-and-forget"(发射后忘记)——使用这种方法,Activity完成时Activity消息并不灵通,startActivity()调用的Activity没有办法返回结果信息。
Don't worry about the fact that NoteEdit doesn't exist yet, we will fix that soon.
不要担心NoteEdit,我们会校正这个问题。
Step 5
第五步
Fill in the body of the onListItemClick() override.
填写onListItemClick()重载代码。
onListItemClick() is a callback method that we'll override. It is called when the user selects an item from the list. It is passed four parameters: the ListView object it was invoked from, the View inside the ListView that was clicked on, the position in the list that was clicked, and the mRowId of the item that was clicked. In this instance we can ignore the first two parameters (we only have one ListView it could be), and we ignore the mRowId as well. All we are interested in is the position that the user selected. We use this to get the data from the correct row, and bundle it up to send to the NoteEdit Activity.
onListItemClick()是一个回调函数,我们将会重载它。当用户从列表中选择一项时该函数自动被系统调用。四个参数:ListView对象、View(点击的ListView里面)、点击的列表位置、nRowId(点击的条目的记录ID)。这个例子中,我们忽略前面两个参数,也忽略mRowId。我们感兴趣的是用户选择的位置。我们使用这个方法获得正确行的数据,捆扎在一起送到NoteEdit活动。
In our implementation of the callback, the method creates an Intent to edit the note using the NoteEdit class. It then adds data into the extras Bundle of the Intent, which will be passed to the called Activity. We use it to pass in the title and body text, and the mRowId for the note we are editing. Finally, it will fire the Intent using the startActivityForResult() method call. Here's the code that belongs in onListItemClick():
在回调函数的实现中,创建Intent,以便使用NoteEdit类编辑便签。然后,增加数据进入Intent的附加Bundle中。新建的Intent对象传送给被调用的活动。我们使用它传入标题和内容文本,以及我们编辑的便签的mRowId。最后,使用startActivityForResult()方法发射Intent。下面的代码是onListItemClick()的实现:
super.onListItemClick(l, v, position, id);
Cursor c = mNotesCursor;
c.moveToPosition(position);
Intent i = new Intent(this, NoteEdit.class);
i.putExtra(NotesDbAdapter.KEY_ROWID, id);
i.putExtra(NotesDbAdapter.KEY_TITLE, c.getString(
c.getColumnIndexOrThrow(NotesDbAdapter.KEY_TITLE)));
i.putExtra(NotesDbAdapter.KEY_BODY, c.getString(
c.getColumnIndexOrThrow(NotesDbAdapter.KEY_BODY)));
startActivityForResult(i, ACTIVITY_EDIT);
* putExtra() is the method to add items into the extras Bundle to pass in to intent invocations. Here, we are using the Bundle to pass in the title, body and mRowId of the note we want to edit.
1、putExtra()方法增加条目到附加的Bundle中,传入参数intent的引用。此处,我们使用Bundle来传入标题、内容、我们想编辑的便签的记录ID
* The details of the note are pulled out from our query Cursor, which we move to the proper position for the element that was selected in the list, with the moveToPosition() method.
2、从查询游标中读出便签的详细信息,游标的位置移动到列表中选中的元素的位置,使用moveToPosition()方法移动。
* With the extras added to the Intent, we invoke the Intent on the NoteEdit class by passing startActivityForResult() the Intent and the request code. (The request code will be returned to onActivityResult as the requestCode parameter.)
3、附加信息加入Intent中,我们在NoteEdit类型中调用Intent,吧Intent和请求代码传递给startActivityForResult()。(请求代码作为requestCode参数在onActivityResult中返回。)
Note: We assign the mNotesCursor field to a local variable at the start of the method. This is done as an optimization of the Android code. Accessing a local variable is much more efficient than accessing a field in the Dalvik VM, so by doing this we make only one access to the field, and five accesses to the local variable, making the routine much more efficient. It is recommended that you use this optimization when possible.
注意:我们把mNotesCursor属性赋值到本地变量,这是对Android代码的优化。访问本地变量比访问DalvikVM的一个属性更有效率,因此,这样做了后我们访问了这个属性一次,访问本地变量5次,使程序有效得多。推荐尽可能的使用这种优化方法。
Step 6
第六步
The above createNote() and onListItemClick() methods use an asynchronous Intent invocation. We need a handler for the callback, so here we fill in the body of the onActivityResult().
上面提到的createNote()和onListItemClick()方法使用了异步Intent引用。我们需要处理回调行数,因此,此处我们填充onActivityResult()代码。
onActivityResult() is the overridden method which will be called when an Activity returns with a result. (Remember, an Activity will only return a result if launched with startActivityForResult.) The parameters provided to the callback are:
onActivityResult()是一个重载方法,当Activity返回结果的时候被调用。(记住,如果使用startActivityForResult触发,则Activity仅仅会返回一个结果。) 回调函数提供的参数如下:
* requestCode — the original request code specified in the Intent invocation (either ACTIVITY_CREATE or ACTIVITY_EDIT for us).
1、requestCode——在Intent引用中指定的原始请求代码(要么是ACTIVITY_CREATE,要么是ACTIVITY_EDIT)
* resultCode — the result (or error code) of the call, this should be zero if everything was OK, but may have a non-zero code indicating that something failed. There are standard result codes available, and you can also create your own constants to indicate specific problems.
2、resultCode——调用的结果或者错误代码,如果一切都没有问题,应该是0,但是可以是非0值,则表示有错误发生。有标准的结果代码获得,你也可以创建你自己的常量来指示特定错误。
* intent — this is an Intent created by the Activity returning results. It can be used to return data in the Intent "extras."
3、intent——通过有返回结果的Activty创建的Intent。他能用于返回数据,数据存放在Intent的“extras”中。
The combination of startActivityForResult() and onActivityResult() can be thought of as an asynchronous RPC (remote procedure call) and forms the recommended way for an Activity to invoke another and share services.
startActivityForResult()和onActivityResult()的结合能作为异步RPC,提供活动相互调用和共享服务的推荐方法。
Here's the code that belongs in your onActivityResult():
下面是onActivityResult()的代码:
super.onActivityResult(requestCode, resultCode, intent);
Bundle extras = intent.getExtras();
switch(requestCode) {
case ACTIVITY_CREATE:
String title = extras.getString(NotesDbAdapter.KEY_TITLE);
String body = extras.getString(NotesDbAdapter.KEY_BODY);
mDbHelper.createNote(title, body);
fillData();
break;
case ACTIVITY_EDIT:
Long mRowId = extras.getLong(NotesDbAdapter.KEY_ROWID);
if (mRowId != null) {
String editTitle = extras.getString(NotesDbAdapter.KEY_TITLE);
String editBody = extras.getString(NotesDbAdapter.KEY_BODY);
mDbHelper.updateNote(mRowId, editTitle, editBody);
}
fillData();
break;
}
* We are handling both the ACTIVITY_CREATE and ACTIVITY_EDIT activity results in this method.
1、在这个方法中,我们处理 ACTIVITY_CREATE 和 ACTIVITY_EDIT两种活动返回结果。
* In the case of a create, we pull the title and body from the extras (retrieved from the returned Intent) and use them to create a new note.
2、在创建的代码中,我们从extras获取标题和内容,然后用来创建新的便签。
* In the case of an edit, we pull the mRowId as well, and use that to update the note in the database.
3、在编辑的代码中,我们还是获取mRowId,然后用来修改数据库的便签数据。
* fillData() at the end ensures everything is up to date .
4、fillData()保证所有信息都是最新的。
Step 7
第七步
The Art of Layout
布局的艺术
The provided note_edit.xml layout file is the most sophisticated one in the application we will be building, but that doesn't mean it is even close to the kind of sophistication you will be likely to want in real Android applications.
note_edit.xml布局文件是我们建立的应用程序中最成熟的一个文件,但是那不并意味着接近你在真实Android程序中可能要求的那种成熟。
Creating a good UI is part art and part science, and the rest is work. Mastering Android layout is an essential part of creating a good looking Android application.
创建好的UI部分(参见“Android layout”)是艺术部分是科学,剩余的就是工作。掌握Android布局是创建好看的Android程序的基本部分。
Take a look at the View Gallery for some example layouts and how to use them. The ApiDemos sample project is also a great resource from which to learn how to create different layouts.
仔细看看一些例子的布局,研究怎么使用他们,参见“View Gallery”。ApiDemos例子项目也是好资源,可以学习了解怎么创建不懂的布局。
Open the file note_edit.xml that has been provided and take a look at it. This is the UI code for the Note Editor.
打开已经提供的note_edit.xml文件,仔细看看。这是Note Editor的UI代码。
This is the most sophisticated UI we have dealt with yet. The file is given to you to avoid problems that may sneak in when typing the code. (The XML is very strict about case sensitivity and structure, mistakes in these are the usual cause of problems with layout.)
这是我们已经处理的最成熟的UI。提供这个文件为了避免问题,这些问题可能由于键入代码而悄悄潜入。(XML是非常严格的,在大小写和结构等方面的错误是布局问题的最常见原因。)
There is a new parameter used here that we haven't seen before: android:layout_weight (in this case set to use the value 1 in each case).
这里有一个以前没有见过的新参数:android:layout_weight(本例中在所有情况下设置为值1)
layout_weight is used in LinearLayouts to assign "importance" to Views within the layout. All Views have a default layout_weight of zero, meaning they take up only as much room on the screen as they need to be displayed. Assigning a value higher than zero will split up the rest of the available space in the parent View, according to the value of each View's layout_weight and its ratio to the overall layout_weight specified in the current layout for this and other View elements.
layout_weight用在LinearLayouts中,在布局中赋值"importance"给View。所有View默认layout_weight值为0,意味着他们仅仅占据他们需要的显示空间。赋值大于0将会在父View中分割可获得的剩余空间,分割空间的依据是每个View的layout_weight的值,还有在View元素的现有布局中指定的全部layout_weight的比率。
To give an example: let's say we have a text label and two text edit elements in a horizontal row. The label has no layout_weight specified, so it takes up the minimum space required to render. If the layout_weight of each of the two text edit elements is set to 1, the remaining width in the parent layout will be split equally between them (because we claim they are equally important). If the first one has a layout_weight of 1 and the second has a layout_weight of 2, then one third of the remaining space will be given to the first, and two thirds to the second (because we claim the second one is more important).
举一个例子:假设我们在一行中有一个文本标签和两个文本编辑框。标签没有指定layout_weight,因此他占据必须的最小空间。如果两个文本编辑框的layout_weight都设置为1,可在父窗口中的剩余宽度被他们平分(因为我们声称他们同等重要)。如果第一个是1,第二个是2,那么剩余空间的三分之一分配给第一个,三分之二分配给第二个(因为我们要求第二个更重要)
This layout also demonstrates how to nest multiple layouts inside each other to achieve a more complex and pleasant layout. In this example, a horizontal linear layout is nested inside the vertical one to allow the title label and text field to be alongside each other, horizontally.
这个布局文件也证明怎么互相嵌套多个布局元素,以便获得更多复杂和合适的布局界面。本例中,水平线布局嵌套进入垂直线,以便允许标题标签和文本字段相互并排。
Step 8
第八步
Create a NoteEdit class that extends android.app.Activity.
创建NoteEdit类,继承android.app.Activity
This is the first time we will have created an Activity without the Android Eclipse plugin doing it for us. When you do so, the onCreate() method is not automatically overridden for you. It is hard to imagine an Activity that doesn't override the onCreate() method, so this should be the first thing you do.
我们第一次没有在ADT帮助下创建Activity。onCreate()方法没有自动重载。想像没有重载onCreate()方法的Activity是困难的,因此,首先要做的事情是重载onCreate()
1. Right click on the com.android.demo.notepad2 package in the Package Explorer, and select New > Class from the popup menu.
1、右击com.android.demo.notepad2包,从弹出菜单中选择 New > Class
2. Fill in NoteEdit for the Name: field in the dialog.
2、在对话框的“Name:”标签后填入“NoteEdit”
3. In the Superclass: field, enter android.app.Activity (you can also just type Activity and hit Ctrl-Space on Windows and Linux or Cmd-Space on the Mac, to invoke code assist and find the right package and class).
3、在“Superclass: ”标签中,键入“android.app.Activity”(你也能输入Activity,按 Ctrl-Space,启用代码助手找到正确的包和类)
4. Click Finish.
4、点击Finish
5. In the resulting NoteEdit class, right click in the editor window and select Source > Override/Implement Methods...
5、在NoteEdit类中,右击编辑窗口,选择菜单Source > Override/ImplementMethods...
6. Scroll down through the checklist in the dialog until you see onCreate(Bundle) — and check the box next to it.
6、滚动对话框中的清单,找到onCreate(Bundle)——选中前面的检查框
7. Click OK.
7、点击OK
The method should now appear in your class.
这个方法应该出现在你的类中。
Step 9
第九步
Fill in the body of the onCreate() method for NoteEdit.
填写onCreate()的代码
This will set the title of our new Activity to say "Edit Note" (one of the strings defined in strings.xml). It will also set the content view to use our note_edit.xml layout file. We can then grab handles to the title and body text edit views, and the confirm button, so that our class can use them to set and get the note title and body, and attach an event to the confirm button for when it is pressed by the user.
这里将会设置新活动的题目为“Edit Note”(在strings.xml文件中定义的一个字符串)。也会设置内容视图以便使用note_edit.xml布局文件。然后,处理标题和内容文本编辑框、确认按钮,因此,我们的类能使用他们来获取或者设置便签的标题和内容,附着事件到确认按钮,这个事件在用户按确认按钮时触发。
We can then unbundle the values that were passed in to the Activity with the extras Bundle attached to the calling Intent. We'll use them to pre-populate the title and body text edit views so that the user can edit them. Then we will grab and store the mRowId so we can keep track of what note the user is editing.
然后,我们对传入到Activity的值进行分类,传入值使用Activity的extras Bundle,extras Bundle附着到调用的Intent上。我们会使用他们来预先填充标题和内容文本编辑框,因此,用户能编辑他们。然后,我们会获取和存储mRowId,因此我们能留意用户编辑的什么便签。
1. Inside onCreate(), set up the layout:
1、在onCreate()内部,设置布局:
setContentView(R.layout.note_edit);
2. Find the edit and button components we need:
2、找到我们需要的编辑框和按钮组件:
These are found by the IDs associated to them in the R class, and need to be cast to the right type of View (EditText for the two text views, and Button for the confirm button):
在R类中,通过ID找到他们,需要强制转换到正确的视图类型(两个文本视图是EditText,确认按钮时Button)
mTitleText = (EditText) findViewById(R.id.title);
mBodyText = (EditText) findViewById(R.id.body);
Button confirmButton = (Button) findViewById(R.id.confirm);
Note that mTitleText and mBodyText are member fields (you need to declare them at the top of the class definition).
注意:mTitleText 和 mBodyText是成员变量(你需要在顶部声明他们)
3. At the top of the class, declare a Long mRowId private field to store the current mRowId being edited (if any).
3、在类的顶部,生命一个Long类型的mRowId私有变量,用来存储当前编辑的mRowId
4. Continuing inside onCreate(), add code to initialize the title, body and mRowId from the extras Bundle in the Intent (if it is present):
4、继续在onCreate()中增加代码来初始化标题、内容、mRowId,从Intent的extras Bundle(附加包)中获取初始化数据。
mRowId = null;
Bundle extras = getIntent().getExtras();
if (extras != null) {
String title = extras.getString(NotesDbAdapter.KEY_TITLE);
String body = extras.getString(NotesDbAdapter.KEY_BODY);
mRowId = extras.getLong(NotesDbAdapter.KEY_ROWID);
if (title != null) {
mTitleText.setText(title);
}
if (body != null) {
mBodyText.setText(body);
}
}
* We are pulling the title and body out of the extras Bundle that was set from the Intent invocation.
(1)从来自Intent引用的extras Bundle中获取标题和内容
* We also null-protect the text field setting (i.e., we don't want to set the text fields to null accidentally).
(2)零保护文本框设置(例如,我们不想意外地设置文本框为null)
5. Create an onClickListener() for the button:
5、针对按钮创建onClickListener()
Listeners can be one of the more confusing aspects of UI implementation, but what we are trying to achieve in this case is simple. We want an onClick() method to be called when the user presses the confirm button, and use that to do some work and return the values of the edited note to the Intent caller. We do this using something called an anonymous inner class. This is a bit confusing to look at unless you have seen them before, but all you really need to take away from this is that you can refer to this code in the future to see how to create a listener and attach it to a button. (Listeners are a common idiom in Java development, particularly for user interfaces.) Here's the empty listener:
Listener使UI实现更混乱的一个方面,但是,本例中试图完成的事情是简单的。当用户按确认按钮时,调用onClieck()方法,用来完成一些工作并且然会被编辑的便签的值给Intent调用者。我们利用称为匿名内嵌类的东西来做这些事情。如果你以前没有见到过,那么看起来这有点混乱,但是,你实际需要取走(完成)的所有事情是可以在未来引用这里的代码,注意怎样创建一个Listener,并附着到一个按钮(Listener是Java开发的一个习惯用语,尤其是在UI方面)。下面是空的Listener:
confirmButton.setOnClickListener(new View.OnClickListener() {
public void onClick(View view) {
}
});
Step 10
第十步
Fill in the body of the onClick() method in our listener.
填充侦听器里面的onClick()代码。
This is the code that will be run when the user clicks on the confirm button. We want this to grab the title and body text from the edit text fields, and put them into the return Bundle so that they can be passed back to the Activity that invoked this NoteEdit Activity. If the operation is an edit rather than a create, we also want to put the mRowId into the Bundle so that the Notepadv2 class can save the changes back to the correct note.
这些代码在用户点击确认按钮时运行。我们使用这些代码从编辑框中获取标题和内容文本串,然后把它们送给Bundle,以便他们能传回到Activity(NoteEdit Activity调用)。如果操作是编辑而不是创建,那么传送mRowId进入Bundle,以便Notepav2类能保存变化到正确的便签。
1. Create a Bundle and put the title and body text into it using the constants defined in Notepadv2 as keys:
1、创建Bundle,传递标题和内容文本到Bundle,使用在Notepadv2中定义的常量作为关键码:
Bundle bundle = new Bundle();
bundle.putString(NotesDbAdapter.KEY_TITLE, mTitleText.getText().toString());
bundle.putString(NotesDbAdapter.KEY_BODY, mBodyText.getText().toString());
if (mRowId != null) {
bundle.putLong(NotesDbAdapter.KEY_ROWID, mRowId);
}
2. Set the result information (the Bundle) in a new Intent and finish the Activity:
2、在新的Intent中设置结果信息(Bundle),完成Activity:
Intent mIntent = new Intent();
mIntent.putExtras(bundle);
setResult(RESULT_OK, mIntent);
finish();
* The Intent is simply our data carrier that carries our Bundle (with the title, body and mRowId).
(1)Intent是简单的数据运载工具,运载Bundle(携带标题、内容、mRowId)
* The setResult() method is used to set the result code and return Intent to be passed back to the Intent caller. In this case everything worked, so we return RESULT_OK for the result code.
(2)setResult()方法用于设置结果代码,返回Intent为了传回到Intent调用者。本例中,每件事情都正常工作,因此我们返回RESULT_OK作为结果代码
* The finish() call is used to signal that the Activity is done (like a return call). Anything set in the Result will then be returned to the caller, along with execution control.
(3)finish()调用用于发信号表明Activity被激发(像一个回调)。然后,在Result中设置的所有东西会返回给调用者,连同执行控制一起
The full onCreate() method (plus supporting class fields) should now look like this:
完整的onCreate()如下:
private EditText mTitleText;
private EditText mBodyText;
private Long mRowId;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.note_edit);
mTitleText = (EditText) findViewById(R.id.title);
mBodyText = (EditText) findViewById(R.id.body);
Button confirmButton = (Button) findViewById(R.id.confirm);
mRowId = null;
Bundle extras = getIntent().getExtras();
if (extras != null) {
String title = extras.getString(NotesDbAdapter.KEY_TITLE);
String body = extras.getString(NotesDbAdapter.KEY_BODY);
mRowId = extras.getLong(NotesDbAdapter.KEY_ROWID);
if (title != null) {
mTitleText.setText(title);
}
if (body != null) {
mBodyText.setText(body);
}
}
confirmButton.setOnClickListener(new View.OnClickListener() {
public void onClick(View view) {
Bundle bundle = new Bundle();
bundle.putString(NotesDbAdapter.KEY_TITLE, mTitleText.getText().toString());
bundle.putString(NotesDbAdapter.KEY_BODY, mBodyText.getText().toString());
if (mRowId != null) {
bundle.putLong(NotesDbAdapter.KEY_ROWID, mRowId);
}
Intent mIntent = new Intent();
mIntent.putExtras(bundle);
setResult(RESULT_OK, mIntent);
finish();
}
});
}
Step 11
第11步
The All-Important Android Manifest File
非常重要的Android Manifest文件
The AndroidManifest.xml file is the way in which Android sees your application. This file defines the category of the application, where it shows up (or even if it shows up) in the launcher or settings, what activities, services, and content providers it defines, what intents it can receive, and more.
AndroidManifest.xml文件是Android领会你的程序的方式。这个文件定义了程序的类型,在发射器和设置里面露面的地方,定义了活动、服务、内容提供器是什么,能接收的intent是什么,等等。
For more information, see the reference document AndroidManifest.xml
欲知更多信息参见文档“AndroidManifest.xml”
Finally, the new Activity has to be defined in the manifest file:
最后,新Activity必须定义在manifest文件中:
Before the new Activity can be seen by Android, it needs its own Activity entry in the AndroidManifest.xml file. This is to let the system know that it is there and can be called. We could also specify which IntentFilters the activity implements here, but we are going to skip this for now and just let Android know that the Activity is defined.
在新Activity能被Android了解之前,需要在AndroidManifest.xml中定义自己的Activity入口。这是让系统知道它在那儿且能被调用。我们也能指定Activity实现哪个IntentFilters,但是我们打算暂时跳过这个,只是让Android知道Activity被定义了。
There is a Manifest editor included in the Eclipse plugin that makes it much easier to edit the AndroidManifest file, and we will use this. If you prefer to edit the file directly or are not using the Eclipse plugin, see the box at the end for information on how to do this without using the new Manifest editor.
在Eclipse插件中包含了一个Manifest编辑器,使AndroidManifest文件编辑更加容易,我们将会使用这个编辑器。
1. Double click on the AndroidManifest.xml file in the package explorer to open it.
1、在AndroidManifest.xml文件上双击打开它
2. Click the Application tab at the bottom of the Manifest editor.
2、在Manifest编辑器底部点击Application便签
3. Click Add... in the Application Nodes section.
3、在Application Nodes段中点击 Add...
If you see a dialog with radiobuttons at the top, select the top radio button: "Create a new element at the top level, in Application".
如果你在顶部看到一个带有单选按钮的对话框,那么选择顶部的单选按钮"Create a new element at the top level, in Application"
4. Make sure "(A) Activity" is selected in the selection pane of the dialog, and click OK.
4、保证 "(A) Activity"在对话框的选择面板中被选中,然后点击OK
5. Click on the new "Activity" node, in the Application Nodes section, then type .NoteEdit into the Name* field to the right. Press Return/Enter.
5、在 Application Nodes段中,点击新的"Activity"节点,然后在“Name*”标签右边输入.NoteEdit,按Return/Enter
The Android Manifest editor helps you add more complex entries into the AndroidManifest.xml file, have a look around at some of the other options available (but be careful not to select them otherwise they will be added to your Manifest). This editor should help you understand and alter the AndroidManifest.xml file as you move on to more advanced Android applications.
Android Manifest编辑器帮助你增加更多复杂的条目到AndroidManifest.xml文件中,仔细看看可获得的其他选项(但是小心不要选择他们否则他们会被加入到你的Manifest文件中)。这个编辑器应该帮助你理解和修改AndroidManifest.xml文件,当你移到更多高级的Android程序时会很有用处。
If you prefer to edit this file directly, simply open the AndroidManifest.xml file and look at the source (use the AndroidManifest.xml tab in the eclipse editor to see the source code directly). Then edit the file as follows:
如果你宁愿直接编辑这个文件,简单打开 AndroidManifest.xml文件,然后了解资源(使用eclipse编辑器的AndroidManifest.xml标签直接领悟源代码)。然后,按照下面这样编辑文件:
This should be placed just below the line that reads:
这应该表示:
for the .Notepadv2 activity.
针对.Notepadv2活动
Step 12
第12步
Now Run it!
现在运行。
You should now be able to add real notes from the menu, as well as delete an existing one. Notice that in order to delete, you must first use the directional controls on the device to highlight the note. Furthermore, selecting a note title from the list should bring up the note editor to let you edit it. Press confirm when finished to save the changes back to the database.
现在,你应该能从菜单增加真实的便签,也能删除存在的便签。注意:为了删除,你必须首先使用设备方向控制来选中便签。此外,从列表中选择便签标题应该被提出到便签编辑器,以便你能编辑他。按确认按钮保证保存修改到数据库中。
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菜单,来增加新的便签
在这个练习中,你将会构建一个简单的便签列表,让用户增加新便签,而不能编辑。这个练习展示了:
* 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_height="wrap_content">
android:layout_height="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名称空间)
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:
然后保存文件并返回到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菜单,来增加新的便签
2008年12月12日星期五
教程:Notepad程序
教程:Notepad程序
本章的教程手把手介绍Android框架和工具,以便用之建立应用程序。从预先配置的项目文件开始,本教程引导你深入一下过程:简单的Notepad程序开发、提供设置项目的具体例子、开发程序逻辑和UI、编译运行程序。
教程作为一套练习介绍notepad程序的开发,每个联系由几个步骤组成。你可以按照每个联系中的步骤逐步建立和完善你的程序。这些联系详细解释每个步骤,并提供所有你需要的例子代码以完成程序。
当你完成教程时,你会成功创建正常运行的Android程序,并深入了解Android开发的更多重要概念。如果你想计入更多复杂功能到你的程序,你能通过notepad程序的可替换实现来检查代码,notepad程序在“ Sample Code”文档中提及。
谁应该使用这个教程
这个教程为有经验的开发人员设计,尤其是那些拥有JAVA编程知识的人。如果你没有编写过JAVA程序,你仍旧能使用这个教程,但是,你可能需要花费较多的时间,速度较慢。
这个教程假设你已经熟悉基本的Android程序概念和术语。如果你还不熟悉这些,你应该再继续以前阅读“Overview of an Android Application ”
也要注意:这个教程使用Eclipse开发环境,并安装有ADT插件。如果没有使用Eclipse,你能按照练习建立程序,但是你将会决定怎样在你的环境中完成和Eclipse相关的步骤。
准备练习
这个教程建立在“Installing the SDK ”、“Hello Android ”两篇文章的信息之上,这两篇文章详细解释了怎样设置你的开发环境。在你开始这个教程之前,你应该阅读这两篇文章,安装SDK,设置工作环境。
为了这个课程,准备如下:
1.下载项目练习数据包(zip)
2.解压到合适的位置
3.打开NotepadCodeLab文件夹
在NotepadCodeLab文件之内,你应该找到六个项目文件:Notepadv1, Notepadv2, Notepadv3, Notepadv1Solution, Notepadv2Solution 和Notepadv3Solution。Notepadv#项目是每个练习的起点,而Notepadv#Solutio项目是练习的解决方案。如果你在某个练习中有麻烦,你能通过比较你目前的工作和练习解决方案来解决。
练习
下表列举了教程练习,描述了每一条的发展领域。每个联系假定你已经所有前面的联系。
1、练习1
这里还是。构造简单的便签列表,让用户增加新的便签但是不能编辑。展示基本的ListActivity以及创建处理菜单选项。使用SQLite数据库存储便签。
2、练习2
增加第二个活动到程序中。展示构建新活动,增加活动到Android manifest文件,在两个活动之间传递数据,还有使用更高级的屏幕布局。还展示了怎么使用startActivityForResult()调用另外一个活动并返回一个结果.
3、练习3
增加生命周期事件处理到程序中,并让该事件维持生命周期内的程序状态。
4、练习4
展示怎样使用Eclipse调试器,怎样使用调试器来查看生命周期事件。本章是可选的但是强烈推荐。
其他资源以及进一步的学习
* 针对概念的更浅显更广泛的介绍并没有在教程中覆盖,请参考“Common Android Tasks”
* AndroidSDK包含各种各样具有完备功能的例子程序,有极好的机会进一步学习。你能在SDK的samples/目录下找到例子程序。
* 这个教程引入了完整Notepad应用程序,包含在SDK的samples/目录中,尽管它不能完全匹配。当你按照教程做的时候,强烈推荐你仔细看看Notepad程序的这个版本,它展示了多种有趣的补充,比如:
o 针对便签列表,设置一个自定义条纹列表
o 创建一个自定义文本视图,重载draw()方法以便使便签看起来像一个内忖的便签。
针对便签实现一个完整的ContentProvider。
o 还原、丢弃编辑,而不只是自动保存
2008年12月9日星期二
Android程序的解剖
Android程序的解剖
* Activity,即活动
* Broadcast Intent Receiver,即广播接收器
* Service,即服务
* Content Provider,即内容提供商
并不是每个程序都需要所有的这四个组件,但是你的程序将会使用这四个方面的某些部分组合而成。
一旦你已经决定你的应用程序需要的组件,你应该在AndroidManifest.xml文件中列举出来。AndroidManifest.xml是一个XML文件,你在其中声明你的程序的组件以及他们的能力和需求。请阅读“Android manifest file documentation”以了解更详细的资料。
Activity
活动(Activity)是Android生成块的最常用组件。一个活动通常是程序中的一个单一的屏幕。每个活动作为Activity基类的单个的继承类来实现。你的类将会显示一个UI,这个UI由Views(视图)以及对应的事件(event)组成。大多数程序由多个界面组成。例如,一个文本消息程序可能有一个展示发送消息目标的通讯录列表的界面,以及一个写消息到选中的通讯录的界面,还有查询旧消息或者修改设置的其他界面。这些界面的每一个都会被作为一个活动实现。移动到另外一个界面通过启动一个新的活动来完成。一些情况下,一个活动可能返回一个值给上一个活动——例如:一个让用户挑选图片的活动将会返回所选图片给呼叫者。
当一个新界面打开时,前一个界面暂停,放入一个历史堆栈中。用户能回退到历史堆栈中的前一个打开的界面。当不适合保留的时候,界面能从历史堆栈中选出来删除。Android为每个程序从主界面开始的所有界面保留历史堆栈。
Intent(计划)和计划过滤
Android使用一个特殊类调用Intent来从一个屏幕移动到另外一个界面。一个Intent描述了一个应用程序想做什么。intent数据结构的两个最重要部分是动作以及起作用的数据。动作的典型是MAIN(应用程序的入口)、VIEW、PICK、EDIT等。数据表达为一个URI。例如,为了浏览个人的通讯录信息,你会创建一个intent,该intent携带VIEW动作以及描述那个人的URI指向的数据。
有一个相关类称为IntentFilter。当意图(intent)是做什么事情的有效请求时,intent filter是说明活动(或者BroadcastReceiver)能处理的意图(intent),能显示个人通信录信息的活动将会发布一个IntentFilter,以说明活动知道当应用数据描述一个人时怎么处理动作VIEW。活动在AndroidManifest.xml文件中发布他们的IntentFilter。
从一个界面导航切换到另外一个界面是通过解析intent来完成的。为了向前导航,活动调用startActivity(myIntent)。然后系统搜索所有已安装程序的intent filter(意图过滤器),选择intent filter和myIntent最匹配的活动。新活动接到intent的通知,从而导致他启动。解析intent过程发生在运行时,此时startActivity被调用,提供了两个关键好处:
* 活动对于其他组件可以功能重用,只需简单的以Intent的形式发出请求。
* 活动能在任何时候被携带同等的IntentFilter的新活动替换。
广播接收器
当你想程序的代码用于外部事件的反馈时,你可以使用BroadcastReceiver,例如,电话铃声响时,或者,数据网络可获得时,或者午夜时分。BroadcstReceivers不显示UI,尽管他们可以使用NotificationManager来警告用户其感兴趣的事情是否已经发生。BroadcastReceivers在AndroidManifest.xml中注册,但是你也能使用Context.registerReceiver()从代码中注册。你的程序不一定在运行,因为BroadcastReceiver被调用;系统将会启动你的程序,如果必要的话,当BroadcastReceiver被触发的时候。程序也能通过Context.sendBroadcst()发送他们自己的intent广播到其他应用程序。
服务
服务是长期存活的没有UI运行的代码。好例子是从播放列表播放歌曲的媒体播放器。在媒体播放程序中,可能有一到多个活动供用户选择歌曲并开始播放。但是,音乐回放自己不应该被一个活动处理,因为用户希望音乐继续播放即使切换到一个新的界面。这种情况下,媒体播放器活动可使用Context.startService()启动一个后台运行的服务来保持音乐播放。然后,系统会继续运行音乐回放服务直到音乐播放完成。你可以通过阅读“Life Cycle of an Android Application”来了解系统服务的优先级。注意:你可以连接到服务(如果没有运行就启动它),使用方法Context.bindService()。当连接到服务时,你能通过服务暴露的接口和服务通信。对于音乐服务,这可能允许你暂停、重放等。
内容提供者
程序能保存数据到文件,一个SQLite数据库,或者,合理的其他任何机制。但是,内容提供者是有用的,如果你想你的程序的数据和其他程序共享。内容提供者是一个类,实现一个标准方法集,以便其他程序存取内容提供者能处理的的数据类型。
To get more details on content providers, see Accessing Content Providers.
为了了解内容提供者的详细情况,请参见“ Accessing Content Providers”
* Activity,即活动
* Broadcast Intent Receiver,即广播接收器
* Service,即服务
* Content Provider,即内容提供商
并不是每个程序都需要所有的这四个组件,但是你的程序将会使用这四个方面的某些部分组合而成。
一旦你已经决定你的应用程序需要的组件,你应该在AndroidManifest.xml文件中列举出来。AndroidManifest.xml是一个XML文件,你在其中声明你的程序的组件以及他们的能力和需求。请阅读“Android manifest file documentation”以了解更详细的资料。
Activity
活动(Activity)是Android生成块的最常用组件。一个活动通常是程序中的一个单一的屏幕。每个活动作为Activity基类的单个的继承类来实现。你的类将会显示一个UI,这个UI由Views(视图)以及对应的事件(event)组成。大多数程序由多个界面组成。例如,一个文本消息程序可能有一个展示发送消息目标的通讯录列表的界面,以及一个写消息到选中的通讯录的界面,还有查询旧消息或者修改设置的其他界面。这些界面的每一个都会被作为一个活动实现。移动到另外一个界面通过启动一个新的活动来完成。一些情况下,一个活动可能返回一个值给上一个活动——例如:一个让用户挑选图片的活动将会返回所选图片给呼叫者。
当一个新界面打开时,前一个界面暂停,放入一个历史堆栈中。用户能回退到历史堆栈中的前一个打开的界面。当不适合保留的时候,界面能从历史堆栈中选出来删除。Android为每个程序从主界面开始的所有界面保留历史堆栈。
Intent(计划)和计划过滤
Android使用一个特殊类调用Intent来从一个屏幕移动到另外一个界面。一个Intent描述了一个应用程序想做什么。intent数据结构的两个最重要部分是动作以及起作用的数据。动作的典型是MAIN(应用程序的入口)、VIEW、PICK、EDIT等。数据表达为一个URI。例如,为了浏览个人的通讯录信息,你会创建一个intent,该intent携带VIEW动作以及描述那个人的URI指向的数据。
有一个相关类称为IntentFilter。当意图(intent)是做什么事情的有效请求时,intent filter是说明活动(或者BroadcastReceiver)能处理的意图(intent),能显示个人通信录信息的活动将会发布一个IntentFilter,以说明活动知道当应用数据描述一个人时怎么处理动作VIEW。活动在AndroidManifest.xml文件中发布他们的IntentFilter。
从一个界面导航切换到另外一个界面是通过解析intent来完成的。为了向前导航,活动调用startActivity(myIntent)。然后系统搜索所有已安装程序的intent filter(意图过滤器),选择intent filter和myIntent最匹配的活动。新活动接到intent的通知,从而导致他启动。解析intent过程发生在运行时,此时startActivity被调用,提供了两个关键好处:
* 活动对于其他组件可以功能重用,只需简单的以Intent的形式发出请求。
* 活动能在任何时候被携带同等的IntentFilter的新活动替换。
广播接收器
当你想程序的代码用于外部事件的反馈时,你可以使用BroadcastReceiver,例如,电话铃声响时,或者,数据网络可获得时,或者午夜时分。BroadcstReceivers不显示UI,尽管他们可以使用NotificationManager来警告用户其感兴趣的事情是否已经发生。BroadcastReceivers在AndroidManifest.xml中注册,但是你也能使用Context.registerReceiver()从代码中注册。你的程序不一定在运行,因为BroadcastReceiver被调用;系统将会启动你的程序,如果必要的话,当BroadcastReceiver被触发的时候。程序也能通过Context.sendBroadcst()发送他们自己的intent广播到其他应用程序。
服务
服务是长期存活的没有UI运行的代码。好例子是从播放列表播放歌曲的媒体播放器。在媒体播放程序中,可能有一到多个活动供用户选择歌曲并开始播放。但是,音乐回放自己不应该被一个活动处理,因为用户希望音乐继续播放即使切换到一个新的界面。这种情况下,媒体播放器活动可使用Context.startService()启动一个后台运行的服务来保持音乐播放。然后,系统会继续运行音乐回放服务直到音乐播放完成。你可以通过阅读“Life Cycle of an Android Application”来了解系统服务的优先级。注意:你可以连接到服务(如果没有运行就启动它),使用方法Context.bindService()。当连接到服务时,你能通过服务暴露的接口和服务通信。对于音乐服务,这可能允许你暂停、重放等。
内容提供者
程序能保存数据到文件,一个SQLite数据库,或者,合理的其他任何机制。但是,内容提供者是有用的,如果你想你的程序的数据和其他程序共享。内容提供者是一个类,实现一个标准方法集,以便其他程序存取内容提供者能处理的的数据类型。
To get more details on content providers, see Accessing Content Providers.
为了了解内容提供者的详细情况,请参见“ Accessing Content Providers”
2008年12月3日星期三
升级UI到XML布局
升级UI到XML布局
你刚完成的“Hello,World”例子使用叫作“programmatic”(编码)UI布局,也就是你直接在源代码中构造和建立你的应用程序UI。如果你做过UI编程,你可能了解道路有时可能非常不容易的:布局小变动可能导致代码方面的大头痛。忘记恰当地融入VIew也是非常容易的,这将导致布局错误,并浪费时间来调试你的代码。
这就是Android提供另外一种UI构造模式:XML布局文件的原因。解释这种模式的最容易的方法是展示一个例子。下面是一个XML布局文件,其行为等同于你刚完成的基于编码构造的例子:
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:text="Hello, Android"/>
一个AndroidXML布局文件的普通结构是简单的。它是一棵标记树,每个标记是View类的名称。这个例子是包含一个元素,TextView,的非常简单的树。你可以使用在XML布局文件中View扩展类的名称,包括你在自己代码中定义的自定义View类。这种结构使快速建立UI变得非常容易,相比你在代码中使用的结构和语法,更简单。这种模式对web开发模式更有吸引力,你能隔离应用程序的表示层(UI)和逻辑层(存取数据)。
这个例子中,有四个XML属性。下面总结他们的意思:
(1)xmlns:android
这是一个XML名称空间声明,告诉Android工具:你打算引用在Android名称空间定义的通用属性。在每个Android布局文件中的外层标签必须有这个属性。
(2)android:layout_width
这个属性定义了View使用的屏幕的有效宽度是多少。这个例子中,他是唯一的VIew,所以我们想它占用整个屏幕,“fill_parent”就是这个意思。
(3)android:layout_height
这个属性很像android:layout_width,只是用于规定有效高度。
(4)android:text
这个属性设置TextView应该包含的文本内容。在这个例子中,他是我们司空见惯的"Hello, Android" 信息。
所以,那就是XML布局文件看起来的样子,但是你把它放在哪里呢?放到你的项目下/res/layout目录下。“res”是资源单词的缩写,这个目录存放你的程序需要的非代码资源,包括图片、本地化字符、XML布局文件。
ADT插件为你创建一个XML文件。在我们的上面的例子中,我们简直从来没有使用它。在Package浏览器总,展开文件夹/res/layout,编辑文件main.xml,使用上面的文字提供main.xml的内容然后保存。
马上打开文件r.java,这个文件在Package浏览器的源代码文件夹中。你将会发现它现在看起来像这样:
public final class R {
public static final class attr {
};
public static final class drawable {
public static final int icon=0x7f020000;
};
public static final class layout {
public static final int main=0x7f030000;
};
public static final class string {
public static final int app_name=0x7f040000;
};
};
项目的R.java文件是一个指向文件中定义的所有资源的索引。你在你的源代码中使用这个类,作为一种快捷引用你项目中的资源的方式。这是特别强大的代码完成功能,该功能存在于诸如Eclipse的集成开发环境中,之所以强大是因为他让你快速地、互动地定位你正在寻找的具体引用。
现在要注意的重要事情是名叫“layout”的内嵌类,以及他的成员“main”。ADT插件注意到你增加了一个新的XML布局文件,然后产生了这个r.java文件。当你加入其他资源进入你的项目时,你会发现r.java会跟着改变。
你需要做的最后的事情是使用你的UI的新XML版本而不是硬编码版本来修改HelloAndroid源代码。下面是你的新类看起来的样子,正如你看到的一样,源代码变得非常简单:
package com.android.hello;
import android.app.Activity;
import android.os.Bundle;
public class HelloAndroid extends Activity {
/** Called when the activity is first created. */
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
}
}
当你做这个修改的时候,不要仅仅拷贝粘贴进入。在那个R类上试试代码完成功能。你可能会发现它会帮助你许多。
既然你已经做了修改,进一步重新运行你的程序,你需要做的所有事情是点击绿色的Run箭头图标,或者选择菜单Run > Run History > Hello, Android。你应该看到...好,和你以前看到的是完全相同的界面!毕竟,这一点是要表明,两种不同的布局方式产生了相同的结果。
创建XML布局文件有更多的知识,但是我们在这里只介绍这么多了。想要知道这种强力方式的更多信息请参见“Implementing a User Interface”。
调试你的工程项目
ADT插件也和Eclipse调试器进行了完美集成。为了证明这点,让我们介绍我们代码的一个BUG。像这样修改你的代码:
package com.android.hello;
import android.app.Activity;
import android.os.Bundle;
public class HelloAndroid extends Activity {
/** Called when the activity is first created. */
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
Object o = null;
o.toString();
setContentView(R.layout.main);
}
}
这个修改引入了一个NullPointerException异常。如果你再次运行你的程序,你最后会在模拟器上看到:
按"Force Quit"来终止应用程序,并关闭模拟器窗口。
为了找出更多的错误,在Object o = null;行上设置断点,设置断点的操作是双击紧靠源代码行的标记栏。从主菜单中选择菜单Run > Debug History > Hello, Android进入调试模式。你的应用程序会在模拟器中重新启动,但是这次她会挂起在你设置的断点处。你能在Eclipse调试视图中单步调试你的代码,正如你为其他任何程序做的事情一样。
没有Eclipse时创建工程项目
如果你没有使用Eclipse,比如如果你喜欢其他IDE,或者简单地使用文本编辑器和命令行工具,那么Eclipse插件不能帮助你。但是,不要太担心,你不会因为不使用Eclipse而损失任何功能。
Android插件实际只是对包含在AndroidSDK内的一套工具的封装,这套工具包括模拟器、AAPT、ADB、DDMS,以及其他在别的地方提到的工具。因此,使用诸如ant建立文件之类的别的工具封装这些工具是可能的。
AndroidSDK包含一个称为“activitycreator.py”的Python描述脚本,这个脚本用于为你的工程项目创建所有的源代码和目录桩,又兼容ant的build.xml。这允许你从命令行建立你的工程项目,或者和你选择的IDE集成。
例如,为了创建一个HelloAndroid项目,和你在Eclipse中创建的一样,你使用如下的命令:
activitycreator.py --out HelloAndroid com.android.hello.HelloAndroid
为了建立项目工程,你运行命令“ant”。当命令成功完成时,你会放置一个文件helloAndroid.apk到bin目录下。.apk文件是一个Android包,能使用adb工具在模拟器中安装和运行。
欲知更多信息,请阅读上面引用的文档。
你刚完成的“Hello,World”例子使用叫作“programmatic”(编码)UI布局,也就是你直接在源代码中构造和建立你的应用程序UI。如果你做过UI编程,你可能了解道路有时可能非常不容易的:布局小变动可能导致代码方面的大头痛。忘记恰当地融入VIew也是非常容易的,这将导致布局错误,并浪费时间来调试你的代码。
这就是Android提供另外一种UI构造模式:XML布局文件的原因。解释这种模式的最容易的方法是展示一个例子。下面是一个XML布局文件,其行为等同于你刚完成的基于编码构造的例子:
android:layout_height="fill_parent"
android:text="Hello, Android"/>
一个AndroidXML布局文件的普通结构是简单的。它是一棵标记树,每个标记是View类的名称。这个例子是包含一个元素,TextView,的非常简单的树。你可以使用在XML布局文件中View扩展类的名称,包括你在自己代码中定义的自定义View类。这种结构使快速建立UI变得非常容易,相比你在代码中使用的结构和语法,更简单。这种模式对web开发模式更有吸引力,你能隔离应用程序的表示层(UI)和逻辑层(存取数据)。
这个例子中,有四个XML属性。下面总结他们的意思:
(1)xmlns:android
这是一个XML名称空间声明,告诉Android工具:你打算引用在Android名称空间定义的通用属性。在每个Android布局文件中的外层标签必须有这个属性。
(2)android:layout_width
这个属性定义了View使用的屏幕的有效宽度是多少。这个例子中,他是唯一的VIew,所以我们想它占用整个屏幕,“fill_parent”就是这个意思。
(3)android:layout_height
这个属性很像android:layout_width,只是用于规定有效高度。
(4)android:text
这个属性设置TextView应该包含的文本内容。在这个例子中,他是我们司空见惯的"Hello, Android" 信息。
所以,那就是XML布局文件看起来的样子,但是你把它放在哪里呢?放到你的项目下/res/layout目录下。“res”是资源单词的缩写,这个目录存放你的程序需要的非代码资源,包括图片、本地化字符、XML布局文件。
ADT插件为你创建一个XML文件。在我们的上面的例子中,我们简直从来没有使用它。在Package浏览器总,展开文件夹/res/layout,编辑文件main.xml,使用上面的文字提供main.xml的内容然后保存。
马上打开文件r.java,这个文件在Package浏览器的源代码文件夹中。你将会发现它现在看起来像这样:
public final class R {
public static final class attr {
};
public static final class drawable {
public static final int icon=0x7f020000;
};
public static final class layout {
public static final int main=0x7f030000;
};
public static final class string {
public static final int app_name=0x7f040000;
};
};
项目的R.java文件是一个指向文件中定义的所有资源的索引。你在你的源代码中使用这个类,作为一种快捷引用你项目中的资源的方式。这是特别强大的代码完成功能,该功能存在于诸如Eclipse的集成开发环境中,之所以强大是因为他让你快速地、互动地定位你正在寻找的具体引用。
现在要注意的重要事情是名叫“layout”的内嵌类,以及他的成员“main”。ADT插件注意到你增加了一个新的XML布局文件,然后产生了这个r.java文件。当你加入其他资源进入你的项目时,你会发现r.java会跟着改变。
你需要做的最后的事情是使用你的UI的新XML版本而不是硬编码版本来修改HelloAndroid源代码。下面是你的新类看起来的样子,正如你看到的一样,源代码变得非常简单:
package com.android.hello;
import android.app.Activity;
import android.os.Bundle;
public class HelloAndroid extends Activity {
/** Called when the activity is first created. */
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
}
}
当你做这个修改的时候,不要仅仅拷贝粘贴进入。在那个R类上试试代码完成功能。你可能会发现它会帮助你许多。
既然你已经做了修改,进一步重新运行你的程序,你需要做的所有事情是点击绿色的Run箭头图标,或者选择菜单Run > Run History > Hello, Android。你应该看到...好,和你以前看到的是完全相同的界面!毕竟,这一点是要表明,两种不同的布局方式产生了相同的结果。
创建XML布局文件有更多的知识,但是我们在这里只介绍这么多了。想要知道这种强力方式的更多信息请参见“Implementing a User Interface”。
调试你的工程项目
ADT插件也和Eclipse调试器进行了完美集成。为了证明这点,让我们介绍我们代码的一个BUG。像这样修改你的代码:
package com.android.hello;
import android.app.Activity;
import android.os.Bundle;
public class HelloAndroid extends Activity {
/** Called when the activity is first created. */
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
Object o = null;
o.toString();
setContentView(R.layout.main);
}
}
这个修改引入了一个NullPointerException异常。如果你再次运行你的程序,你最后会在模拟器上看到:
按"Force Quit"来终止应用程序,并关闭模拟器窗口。
为了找出更多的错误,在Object o = null;行上设置断点,设置断点的操作是双击紧靠源代码行的标记栏。从主菜单中选择菜单Run > Debug History > Hello, Android进入调试模式。你的应用程序会在模拟器中重新启动,但是这次她会挂起在你设置的断点处。你能在Eclipse调试视图中单步调试你的代码,正如你为其他任何程序做的事情一样。
没有Eclipse时创建工程项目
如果你没有使用Eclipse,比如如果你喜欢其他IDE,或者简单地使用文本编辑器和命令行工具,那么Eclipse插件不能帮助你。但是,不要太担心,你不会因为不使用Eclipse而损失任何功能。
Android插件实际只是对包含在AndroidSDK内的一套工具的封装,这套工具包括模拟器、AAPT、ADB、DDMS,以及其他在别的地方提到的工具。因此,使用诸如ant建立文件之类的别的工具封装这些工具是可能的。
AndroidSDK包含一个称为“activitycreator.py”的Python描述脚本,这个脚本用于为你的工程项目创建所有的源代码和目录桩,又兼容ant的build.xml。这允许你从命令行建立你的工程项目,或者和你选择的IDE集成。
例如,为了创建一个HelloAndroid项目,和你在Eclipse中创建的一样,你使用如下的命令:
activitycreator.py --out HelloAndroid com.android.hello.HelloAndroid
为了建立项目工程,你运行命令“ant”。当命令成功完成时,你会放置一个文件helloAndroid.apk到bin目录下。.apk文件是一个Android包,能使用adb工具在模拟器中安装和运行。
欲知更多信息,请阅读上面引用的文档。
订阅:
博文 (Atom)