Intro/tutorial-ex2

From Android中文网

Android中文网(androidcn.net) 版权申明 : creativecommons licenses
Jump to: navigation, search

目录

[编辑] 记事本练习 2

在这一练习中,你将添加第二个活动到你的notepad应用中,从而允许用户创建、编辑和删除便签。新的活动将用户的输入打包到一个由相应的intent提供的Bundle的方式来创建新的便签。这一节将演示:

  • 创建一个新的活动并且将它添加到Android的manifest 文件中
  • 通过使用startSubActivity() 方法来异步调用另一个活动.
  • 通过Bundle在活动之间传递数据
  • 如何使用更多的屏幕布局高级特性
[练习(1)] [练习(2)] [练习(3)] [额外练习]

[编辑] 步骤 1

从NotepadCodeLab 文件夹中导入已经存在的Notepadv2 项目。如果你发现有和AndroidManifest.xml或android.zip相关的错误,右键点击项目,选择Tools->Fix Project Properties菜单来修复它。

我们先浏览一下 Notepadv2 项目

  • 打开res/values目录下strings.xml 文件 — 有一些我们将会用到的字符串资源
  • 看看Notepadv2 类文件的头部,你会发现这里增加了一些内容 -- 一些新的常量定义,一些用来定义"记录行(Rows)"的变量(为了在列表中保存记录)
  • 为了适应新增加的"记录行",fillData()也作了轻微的改动,以记录行变量代替了本地变量
  • 这里有两个重载的方法(onListItemClick() 和 onActivityResult()) ,我们将在下面的步骤中补充内容。

[编辑] 步骤 2

检查 onCreate()方法,你会发现它与我们的第一个例子一样。

[编辑] 步骤 3

我们需要增加一个删除(delete)按钮: 在onCreateOptionsMenu() 新加一行

menu.add(0, DELETE_ID, R.string.menu_delete); 

全部代码如下:

   @Override
   public boolean onCreateOptionsMenu(Menu menu) {
       super.onCreateOptionsMenu(menu);
       menu.add(0, INSERT_ID, R.string.menu_insert);
       menu.add(0, DELETE_ID, R.string.menu_delete);
       return true;
   }

[编辑] 步骤 4

在一个活动(Activity)中启动其他的活动

我们已经知道了如何通过Intent(意向)启动一个指定活动的方法,这个活动可以在我们自己的应用中,也可以在其他的应用中。我们也可以通过Intent启动一个并不确定的活动,而且也不必关心这个活动在哪个应用中。 举个例子,我们可能想在浏览器中打开一页,为此我们仍然使用一个intent,但是我们将使用一个预定义的intent常量而不是通过定义一个类的方式去处理,并且添加一个内容uri来描述我们想做什么.


在onMenuItemSelected()方法中, 为DELETE_ID添加一个新的case处理

       dbHelper.deleteRow(rows.get(getSelection()).rowId);
       fillData();
       break;
  • 用getSelection()方法从ListAdapter中查看当前列表中的哪条便签被选择
  • 然后从我们持有的rows字段找到那一行并且获取到该行的rorwId值, 然后用DBHelper 类,根据该rorwId值删除相应的行
  • 重新填充数据,保证所有的东西都是最新的
  • 现在,整个方法看起来应该是这样


   @Override
   public boolean onMenuItemSelected(int featureId, Item item) {
       super.onMenuItemSelected(featureId, item);
       switch(item.getId()) {
       case INSERT_ID:
           createNote();
           fillData();
           break;
       case DELETE_ID:
           dbHelper.deleteRow(rows.get(getSelection()).rowId);
           fillData();
           break;
       }
      
       return true;
   }

[编辑] 步骤 5

完成createNote()方法:

我们将创建一个Intent,使用NoteEdit类来添加一个新的note(ACTIVITY_CREATE)。我们通过startSubActivity()方法来调用它。

       Intent i = new Intent(this, NoteEdit.class);
      startSubActivity(i, ACTIVITY_CREATE);

事实上NoteEdit 是不存在的,不过别担心,我们马上添加它。

:在这个例子中,我们的Intent使用了一个特定命名的类,有些时候,这样做效果更好。尽管我们一般都是使用action和contentURI来调用Intent。参考[android.content.intent]可以获取更多信息。

[编辑] 步骤 6

填充重载方法onListItemClick()的方法体

onListItemClick()是一个重载方法,当用户从列表中选择一个条目时该方法被调用。它传递四个参数: 触发事件的ListView 对象, ListView 对象中被点击的View项, 被点击的列表位置,被点击条目的rowId. 在这个实例中,我们可以忽略前两个参数(我们只有一个ListView), 我们也忽略掉rowId参数(xing:其实他的值与rows.get(position).rowId以及后面例子用到的getSelectionRowID()是相同的)。所有我们感兴趣的是用户选择的位置, 我们用这个位置值去从正确的行获取数据, 并将这些数据打包发送到NoteEdit活动。

该方法创建一个Intent通过NoteEdit类去编辑便签。然后它添加数据到Intent的临时包(Bundle)中,这个包能用来向Intent中传递信息。我们用它来传递标题和便签体文本以及我们正在编辑的便签所在的行号信息. 最后,将通过startSubActivity()方法调用来触发该Intent.

       super.onListItemClick(l, v, position, id);
       Intent i = new Intent(this, NoteEdit.class);
       i.putExtra(KEY_ROW_ID, rows.get(position).rowId);
       i.putExtra(KEY_BODY, rows.get(position).body);
       i.putExtra(KEY_TITLE, rows.get(position).title);
       startSubActivity(i, ACTIVITY_EDIT);
  • 方法使用了Intent的一个临时包,我们用这个临时包去传递我们想编辑的便签的标题,便签体和行号,然后我们以ACTIVITY_EDIT方式启动NoteEdit
  • putExtra() 是用来将条目添加到传递给intent的临时包的方法

[编辑] 步骤 7

上面的createNote()和onListItemClick()用到了异步的intent的调用,因此我们需要一个机制来处理调用结果,重载onActivityResult()方法可以完成这个任务。

onActivityResult()是一个重载方法,它将在一个子活动返回的时候被调用。提供的参数有

  • requestCode — 开始在Intent调用时的请求代码 (对我们来说或者是 ACTIVITY_CREATE 或者ACTIVITY_EDIT)
  • resultCode — 调用的结果码(或错误码), 如果一切顺利,应该是0, 但是也可以是一个非0的代买来表明某种错误. 有标准的结果代码可以获取, 你也可以创建你自己的常量去表示特定的结果
  • data — 这是一个简单的字符串用来存储各种返回的文本信息 (举个例子, 可以是一个用户在一个对话框里键入的结果). 如果你只有一个信息需要返回,而这个信息恰好是一个字符串时,这个变量将很有用。如果你需要返回更多值,请用Bundle替代它
  • extras — 这是返回的被调Intent的Bundle(如果有)

startSubActivity() 和onActivityResult()的联合可以被看作类似于异步的RPC(远程过程调用),并作为在Activities之间互相调用和共享服务的推荐方式.

       super.onActivityResult(requestCode, resultCode, data, extras);
      
       switch(requestCode) {
       case ACTIVITY_CREATE:
           String title = extras.getString(KEY_TITLE);
           String body = extras.getString(KEY_BODY);
           dbHelper.createRow(title, body);
           fillData();
           break;
       case ACTIVITY_EDIT:
           Long rowId = extras.getLong(KEY_ROW_ID);
           if (rowId != null) {
               String editTitle = extras.getString(KEY_TITLE);
               String editBody = extras.getString(KEY_BODY);
               dbHelper.updateRow(rowId, editTitle, editBody);
           }
           fillData();
           break;
       }
  • 在这个方法中我们同时处理ACTIVITY_CREATE和ACTIVITY_EDIT调用的结果
  • 在创建便签时,我们可以获取放入Bundle中的标题和内容。
  • 在编辑便签时,Bundle中增加了rowid信息,并用它来更新数据库中的便签
  • 最后fillData()方法确保所有数据是最新的

[编辑] 步骤 8

布局的艺术

提供的note_edit.xml 布局文件是我们即将构建的应用的最复杂的部分,但并不意味着它就是在你的真实的Android应用中想要的接近完美的布局文件。 创建一个好的 UI部分是艺术部分是科学,剩下的是工作。创建一个精美的Android应用的要点部分是掌握Android layout。 查阅 View Gallery 获取一些布局的例子和如果使用它们. ApiDemos 也提供了大量的资源,从那里可以学习到如何创建不同的布局.

打开并且看一看例子提供的note_edit.xml文件. 这是NoteEditor的UI代码. 这是我们处理过的最复杂的UI. 提供这个文件可以避免在你键入代码时产生问题 (XML对大小写和结构要求是非常严格的,这些错误通常导致布局XML文件问题)

这里有一个新的我们以前没有见过的参数: android:layout_weight (在这里值被设为1).

layout_weight 被用在LinearLayouts 中来为布局中的视图赋予“重要”值. 所有的视图的layout_weight缺省值为0,意味着他们只在屏幕上占据它们需要显示的大小的空间。赋一个比0大的值将会根据这个View的ayout_weight 值以这个值在该布局中所有为这个View和其它Views定义的layout_weight的比例将父容器的可用空间进行划分。

来给个例子: 假设我们在水平行上有一个文本标签和两个文本编辑框试图. 文本标签没有定义layout_weight 值,所以它将占据最小的需要提供的空间. 如果每个文本框试图的layout_weight 都被设置为1, 在父布局中的剩余的宽度将被它们平分.如果一个文本视图的layout_weight值为1,另外一个是2, 那么剩余空间的三分之一将给第一个文本框,三分之二将给第二个文本框

这个布局文件也演示了如何在其他布局内部嵌套布局从而生成更复杂和漂亮的布局. 在这个例子中,一个水平的线性布局嵌套在另外一个垂直的布局中,从而允许标题标签和文本字段在水平方向上边界对齐

[编辑] Step 9

Create a NoteEdit class that extends android.app.Activity.

创建一个继承自 android.app.Activity.的NoteEdit类

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 (there is an override/implement methods option available from the right click popup menu in the eclipse editor that we will use)

这是第一次我们将创建一个活动而不是由Android Eclipse插件帮助我们完成它.当你这么做的时候,onCreate()方法并没有自动被重载. 一个活动没有重载onCreate() 方法是难以想象的, 因此你要做的第一件事情就是重载该方法 (从eclipse编辑器的右键菜单中,有一个重载/实现菜单项可以被我们使用)

Right click on the com.google.android.demo.notepad2 package in the Package Explorer, and select New->Class from the popup menu

右键包浏览中的com.google.android.demo.notepad2包, 从弹出菜单中选择 New->Class

Fill in NoteEdit for the Name: field in the dialog

为对话话的Name字段填写NoteEdit

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)

在Superclass字段输入android.app.Activity(你可以只输入Activity, 然后在Windows和Linux系统中敲Ctrl-Space键,或者在Mac系统中敲Cmd-Space键, 从而激活代码辅助功能找出正确的包和类)

Hit the Finish button

点完成按钮

In the reslting NoteEdit class, right click in the editor window and select Source->Override/Implement Methods...

在生成的NoteEdit 类, 右键编辑窗口,选择Source->Override/Implement Methods...菜单

Scroll down through the checklist in the dialog until you see onCreate(Bundle) — and check the box next to it

下拉对话框中的复选列表,直到你看到onCreate(Bundle) — 选择复选框

Hit the OK button.

点OK 按钮

[编辑] Step 10

Fill in the body of the onCreate() method.

填充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 from the extras bundle in the calling Intent, and use those to pre-populate the title and body text edit views with data so that the user can edit them. We will grab and store the rowId as well so that we can keep track of what note the user is editing.

然后,我们可以解开从调用Intent传到活动里的临时包以获取参数值,并用这些值预填充标题和便签体文本试图,这样用户就可以编辑它们。我们可以获取和存储rowId值,这样我们就可以知道用户正在编辑哪一条便签

Set up the layout:

设置布局

       setContentView(R.layout.note_edit);

Find the edit and button components we need:

查找我们需要的编辑框和按钮组

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类)

       titleText = (EditText) findViewById(R.id.title);
       bodyText = (EditText) findViewById(R.id.body);
      
       Button confirmButton = (Button) findViewById(R.id.confirm);

Note that titleText and bodyText are member fields (you will need to add them at the top of the class definition)

注意titleText 和bodyText 都是成员字段 (你需要在类的定义的顶部添加它们)


Create a Long rowId private field which will be used to store the current rowId being edited (if any)

创建一个Long类型的rowId私有字段,这个字段将被用来存储当前的正在被编辑的行的rowId (如果有)


Add code to initialize the title, body and rowId from the extras bundle in the intent, if it is present:

添加代码,如果临时包提供了值,用intent临时包的数据初始化变量title, body 和 rowId


       rowId = null;
       Bundle extras = getIntent().getExtras();
       if (extras != null) {
           String title = extras.getString(Notepadv2.KEY_TITLE);
           String body = extras.getString(Notepadv2.KEY_BODY);
           rowId = extras.getLong(Notepadv2.KEY_ROW_ID);
          
           if (title != null) {
               titleText.setText(title);
           }
           if (body != null) {
               bodyText.setText(body);
           }
       }

We are pulling the title and body out of the extras bundle that was set from the Intent invocation

我们从调用者Intent传递过来的临时包抽取title、 body 值

Have to null-protect the text field setting (i.e. we don't want to set the text fields to null accidentally)

在对文本字段赋值时必须做NULL保护(举个例子,我们不想意外地将文本域设置为NULL)


Create an onClickListener() for the button:

为按钮创建一个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 simply want an onClick() method to be called when the user presses the confirm button, and we can 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 iiom in Java development, particularly for user interfaces).

监听器可能是在UI实现中比较让人迷惑的特性之一,但在这个例子中,我们将设法简单实现它。当用户按下确认按钮时,我们只需要一个onClick()方法被调用,我们可以用这个方法去做一些事,并将被编辑的便签的值返回给调用者Intent.我们通过一个叫匿名内部类的方式实现它。除非你以前见过,否则这看上去会有点迷糊人,但是你将来可以参考这一段代码去看如何创建一个监听器并且将它绑定到一个按钮上,这将真正使你受益。 (监听器在java开发,特别是用户界面的开发中是一种普遍习惯用法).


       confirmButton.setOnClickListener(new View.OnClickListener() {
           public void onClick(View view) {
              
           }
          
       });

[编辑] Step 11

Fill in the body of the onClick() method.

填充方法 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 Intent in the first place. If the operation is an edit rather than a create, we also want to put the rowId into the bundle as well so that the Notepadv2 class can save the changes back to the correct note.

当用户按下确认按钮时,这段代码将被执行。我们需要这个方法去获取标题和便签体文本框的文本,并且将这些文本放到一个返回包中,这样他们就可以被传递回最初调用这个Intent的活动中,如果是一个编辑操作而不是创建,我们也需要将rowId放到返回包中,这样Notepadv2 类能够将相应的便签的修改保存

Create a bundle and put the title and body text into it using the constants defined in Notepadv2 as keys

创建一个包,并且使用在Notepadv2 类中定义的常量作为主键,将标题和笔集体文本放置到包中

      Bundle bundle = new Bundle();
     
      bundle.putString(Notepadv2.KEY_TITLE, titleText.getText().toString());
      bundle.putString(Notepadv2.KEY_BODY, bodyText.getText().toString());
      if (rowId != null) {
          bundle.putLong(Notepadv2.KEY_ROW_ID, rowId);
      }

Set the result information, including the bundle, and finish the activity:

设置返回信息,包括包,然后完成活动

The setResult() method is used to set the result code, return data string, and extras bundle to be passed back to the Intent caller. In this case everything worked, so we return RESULT_OK for the result code. We are not using the string data field in this case, so we pass a null back for that and pass the bundle we have just created with the title, body and rowId information in it.

setResult()方法用来设置返回码、返回的数据串、以及将要传递给Intent调用者的返回包。在这里,所有的事情都完成了,因此我们设置返回码为RESULT_OK ,在这我们没用到字符串数据,因此传递一个null值,并将我们刚创建的带有标题、便签体和rowid信息的包传递回去。

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.

finish()调用用来通知活动已经完成 (就像return 调用). 在Result中设置的任何值都会由调用控制返回给调用者

      setResult(RESULT_OK, null, bundle);
      finish();


The full onCreate() method (plus supporting class fields) should now look like this:

完整的 onCreate()方法现在看起来应该是这样 (包括类成员)

   private EditText titleText;
   private EditText bodyText;
   private Long rowId;
   @Override
   protected void onCreate(Bundle icicle) {
       super.onCreate(icicle);
       setContentView(R.layout.note_edit);
      
       titleText = (EditText) findViewById(R.id.title);
       bodyText = (EditText) findViewById(R.id.body);
     
       Button confirmButton = (Button) findViewById(R.id.confirm);
      
       rowId = null;
       Bundle extras = getIntent().getExtras();
       if (extras != null) {
           String title = extras.getString(Notepadv2.KEY_TITLE);
           String body = extras.getString(Notepadv2.KEY_BODY);
           rowId = extras.getLong(Notepadv2.KEY_ROW_ID);
         
           if (title != null) {
               titleText.setText(title);
           }
           if (body != null) {
               bodyText.setText(body);
           }
       }
      
       confirmButton.setOnClickListener(new View.OnClickListener() {
           public void onClick(View view) {
               Bundle bundle = new Bundle();
              
               bundle.putString(Notepadv2.KEY_TITLE, titleText.getText().toString());
               bundle.putString(Notepadv2.KEY_BODY, bodyText.getText().toString());
               if (rowId != null) {
                   bundle.putLong(Notepadv2.KEY_ROW_ID, rowId);
               }
              
               setResult(RESULT_OK, null, bundle);
               finish();
           }
         
       });
   }

[编辑] Step 12

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:

最后,新的活动必须被定义到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.

在新的活动能被Android系统识别前,它需要在AndroidManifest.xml 文件中有它自己的活动条目。这让系统知道它的存在并且能被调用。我们也可以在这里定义活动实现了哪些IntentFilters ,但现在我们将跳过这个设置而只是让 Android系统指导活动被定义了

<activity class=".NoteEdit"/>

This should be placed just below the line that reads </activity> for the .Notepadv2 activity.

这一行应该放在Notepadv2活动的</Activity>行下

[编辑] Step 13

Now Run it! (use Run As -> Android Application on the project right click menu again)

现在运行它! (再次使用项目右键菜单 Run As -> Android Application )


You should now be able to add real notes from the menu, as well as deleting an existing one using the menu. Furthermore, selecting a note title from the list should bring up the note editor to let you edit it. Hit confirm when finished to save the changes back to the database.

现在你可以从菜单添加真正的便签,也可以用菜单删除已经存在的便签。另外,可以选择列表中的一条便签的标题从而调出便签编辑窗口可以让你编辑它。编辑完成点确认按钮可以将改动保存到数据库

[编辑] Solution and Next Steps

You can see the solution to this exercise in Notepadv2Solution from the zip file to compare with your own.

你可以从zip文件的Notepadv2Solution目录中找到这个练习的解决方案,并且将它和你自己的比较一下


Now try editing a note, and then hitting the back button on the emulator instead of the confirm button (the back button is below the menu button). You will see an error come up. Clearly our application still has some problems. Worse still, if you did make some changes and hit the back button, when you go back into the notepad to look at the note you change, you will find that all your changes have been lost. In the next exercise we will fix these problems.

现在试着编辑一条便签,然后点模拟器的后退按钮而不是确认按钮(后退按钮在菜单按钮下边)。你可以看到产生了一个错误。明显我们的应用有些问题。更糟的是,如果你真的做了某些改动然后点了后退按钮,当你回到记事本里去看你改过的便签,你会发现所有你的改动都丢失了。在下一练习中,我们将修正这一问题。

Once you are ready, move on to Tutorial Exercise 3 where you will fix the problems with the back button and lost edits by introducing a proper life cycle into the NoteEdit Activity.

一旦你准备好了,转到指南 练习 3 ,在那里,通过介绍NoteEdit 活动的生命期你将解决由于使用后退按钮导致丢失编辑信息的问题。.

Back to the Tutorial main page....

回到指南主页....

Personal tools