Reminders Lab: Part 2
This chapter covers capturing user input through the use of custom dialog boxes. We alsocontinue to demonstrate the use of adapters and an SQLite database. In this chapter, we complete the lab we began in Chapter 5.<翻译>
第六章 Reminders 实例:第二部分
这章涵括了通过对话框捕获用户输入。也继续演示适配器及SQLite数据库的使用。这章里,我们将完成从第五章开始的例子。
Adding/Removing Reminders
The example in Chapter 5 left the screen empty without any reminders. To see what the app layout would look like with a list of reminders, it’s useful to add some example reminders when the app launches. If you tried to come up with a solution to the challenge from the preceding chapter, compare your code with the changes in Listing 6-1. The code in Listing 6-1 checks whether there is any saved state for the instance, and if there isn’t, it proceeds to set up the example data. To do so, the code invokes some methods on DatabaseAdapter;one to clear out all reminders, and another to insert some reminders.
<翻译>
增加/删除提醒
第五章里这个例子的屏幕还没有任何提醒。为了让布局看到提醒清单,当app启动时加载些提醒的例子上去,这是很有用的。如果你想挑战这章处理过程,比较下清单6-1和你的代码。清单6-1检查是否有保存的实例,如果有,处理将设置例子数据。为此,代码调用了些DatabaseAdapter的方法;一个是清除所有的提醒,另一个则是加入些提醒。
Listing 6-1. Add Some Example Reminders
public class RemindersActivity extends ActionBarActivity {
private ListView mListView;
private RemindersDbAdapter mDbAdapter;
private RemindersSimpleCursorAdapter mCursorAdapter;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_reminders);
mListView = (ListView) findViewById(R.id.reminders_list_view);
mListView.setDivider(null);
mDbAdapter = new RemindersDbAdapter(this);
mDbAdapter.open();
if (savedInstanceState == null) {
//Clear all data
mDbAdapter.deleteAllReminders();
//Add some data
mDbAdapter.createReminder("Buy Learn Android Studio", true);
mDbAdapter.createReminder("Send Dad birthday gift", false);
mDbAdapter.createReminder("Dinner at the Gage on Friday", false);
mDbAdapter.createReminder("String squash racket", false);
mDbAdapter.createReminder("Shovel and salt walkways", false);
mDbAdapter.createReminder("Prepare Advanced Android syllabus", true);
mDbAdapter.createReminder("Buy new office chair", false);
mDbAdapter.createReminder("Call Auto-body shop for quote", false);
mDbAdapter.createReminder("Renew membership to club", false);
mDbAdapter.createReminder("Buy new Galaxy Android phone", true);
mDbAdapter.createReminder("Sell old Android phone - auction", false);
mDbAdapter.createReminder("Buy new paddles for kayaks", false);
mDbAdapter.createReminder("Call accountant about tax returns", false);
mDbAdapter.createReminder("Buy 300,000 shares of Google", false);
mDbAdapter.createReminder("Call the Dalai Lama back", true);
}
//Removed remaining method code for brevity...
}
//Removed remaining method code for brevity...
}
There are several calls to the createReminder() method, each taking a String value with the reminder text and a boolean value flagging the reminder as important. We set a few values to true to provide a good visual effect. Click and drag a selection around all of the createReminder() calls and then press Ctrl+Alt+M | Cmd+Alt+M to bring up the Extract Method dialog box, as shown in Figure 6-1. This is one of many refactoring operations available both via the Refactor menu and via a shortcut key combination. Enter insertSomeReminders as the name for the new method and press Enter. The code in RemindersActivity will be replaced by a call to the new method you named in the Extract Method dialog box, and the code will be moved into the body of this method.
<翻译>
有几个createReminder()方法的调用,每个都是用一个字符串作提醒文本,以及一个布尔值标记提醒是否重要。我们设置一些实体值让显示好看点。选中createReminder()方法调用的代码块按以Ctrl+Alt+M | Cmd+Alt+M汲取方法,如图6-1示。这是一个通过重构菜单和快捷键结合的重构操作。输入insertSomeReminders作为方法名并确认。这些代码块将以析出的方法代替,这些代码块将在方法体里了。
图6-1 析出方法对话框,创建一个insertSomeReminders()方法
Run the app to see how the interface looks and behaves with the example reminders. Your app should look something like the screenshot in Figure 6-2. Some of the reminders should be displayed with a green row tab, while the ones marked important will be displayed with an orange tab. Commit your changes with the message Adds Example reminders.
<翻译>
运行app看到的界面,拥有提醒例子了。你的app应该看起来象图6-2的截屏那样。有些提醒显示绿色的行选项卡,而那些重要的提醒则显示橙色行选项卡。提交你的更改到Git,备注Adds Example reminders。
图6-2 插入了提醒例子的实时运行
Responding to User Interaction
No app is of much use unless it responds to input. In this section, you will add logic to respond to touch events and eventually allow the user to edit the individual reminders. The main component in the app is ListView, a subclass of the Android View object. Up to this point, you haven’t done much with View objects other than place them in layouts. The android.view.View object is a superclass of all components that draw to the screen.<翻译>
响应用户的互动
没有app不响应输入。在这节,你将加入响应点击事件的逻辑并且最终允许用户编辑独立的提醒。在app里的主要元素是ListView,一个Android View的子类。直致此刻,除了把它放到布局里,你还没做过其它的什么。android.view.View是所有你看到的所有屏幕元素的超类。
Add the code from Listing 6-2 to the bottom of the onCreate() method in RemindersActivity, just before the closing curly brace, and then resolve imports. This is an anonymous inner class implementation of OnItemClickListener that has a single method, onItemClicked(). This object will be used by the runtime as you interact with the ListView component to which it is attached. The onCreate() method of the anonymous inner class will be called whenever you tap the ListView. The method we define uses Toast, a class in the Android SDK. The call to Toast.makeText() causes a small pop-up to display on-screen with whatever text is passed to
the method. You can use Toast as a quick indicator that a method is being called properly, as shown in Listing 6-2.
<翻译>
把清单6-2的代码加到RemindersActivity的onCreate()方法后面,即在方法结束花括号之前,并解决导入类的问题。这是一个匿名内部类实现OnItemClickListener接口,它只有一个方法,onItemClicked()。这个对象将用于你互动与它所跟踪的ListView元素的实时运行。当用户点击ListView时,匿名内部类的onCreate()方法将被调用。我们定义一个吐司,一个Android SDK的类。调用Toast.makeText()将导致在屏幕上弹出一个小菜单,显示出你传送给方法的文本。你可以看到清单6-2的代码,作为快速正确使用吐司的指引。
Note Toast messages may be hidden on certain devices. An alternate approach would be to log a message by using the Android logger, which is covered in detail in Chapter 12.
<翻译>
注意:吐司信息可能在一些设备上被隐藏。另一个替代途径是记录一个日志信息,用Android的日志记录器,那个在第十二章会有祥述。
Listing 6-2. Set an OnItemClickListener with a Toast
//when we click an individual item in the listview
mListView.setOnItemClickListener(new AdapterView.OnItemClickListener() {
@Override
public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
Toast.makeText(RemindersActivity.this, "clicked " + position,
Toast.LENGTH_SHORT).show();
}
});
Clicking the first item in the list invokes the onItemClick() method with a position that has the value 0 as elements in the list are indexed starting at zero. The logic then pops a toast with the text clicked and the position, as shown in Figure 6-3.
<翻译>
点击屏幕第一个提醒将调用onItemClick()方法,在清单里的位置是0,所以索引是0。这个逻辑将弹出位置信息的文本,如图6-3所示。
图6-3 点击第一条提醒弹出的吐司信息
User Dialog Boxes
With some familiarity of touch events, you can now enhance the click listener to show a dialog box. Replace the entire onItemClick() method with the code in Listing 6-3. When you resolve imports, please use the android.support.v7.app.AlertDialog class.<翻译>
用户对话框
大家所熟悉的一些点击事件,现在你可增强点击监听为显示一个对话框。用清单6-3的代码替换所有的onItemClick()方法。解决导入时,请用android.support.v7.app.AlertDialog类。
Listing 6-3. onItemClick( ) Modifications to Allow Edit/Delete
public void onItemClick(AdapterView<?> parent, View view, final int masterListPosition, long id) {
AlertDialog.Builder builder = new AlertDialog.Builder(RemindersActivity.this);
ListView modeListView = new ListView(RemindersActivity.this);
String[] modes = new String[] { "Edit Reminder", "Delete Reminder" };
ArrayAdapter<String> modeAdapter = new ArrayAdapter<>(RemindersActivity.this,
android.R.layout.simple_list_item_1, android.R.id.text1, modes);
modeListView.setAdapter(modeAdapter);
builder.setView(modeListView);
final Dialog dialog = builder.create();
dialog.show();
modeListView.setOnItemClickListener(new AdapterView.OnItemClickListener() {
@Override
public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
//edit reminder
if (position == 0) {
Toast.makeText(RemindersActivity.this, "edit "
+ masterListPosition, Toast.LENGTH_SHORT).show();
//delete reminder
} else {
Toast.makeText(RemindersActivity.this, "delete "
+ masterListPosition, Toast.LENGTH_SHORT).show();
}
dialog.dismiss();
}
});
}
In the preceding code you see another Android class at work, AlertDialog.Builder. The class Builder is a nested static class inside the AlertDialog class, and it is used to build AlertDialog.
<翻译>
在处理代码里你看到了另Android类在工作,AlertDialog.Builder。这个类创建器嵌入在AlertDialog类里,是个静态类,它用于创建AlertDialog。
The code in this lab so far creates a ListView and an ArrayAdapter to feed items to a ListView. You may recall this pattern from Chapter 5. The adapter is created with an array of two potential choices, Edit Reminder and Delete Reminder, before being passed to ListView, which is, in turn, passed to AlertDialog.Builder. The builder is then used to create and show a dialog box with the list of choices.
<翻译>
在这个实例里的代码远超于创建一个ListView以及用ArrayAdapter喂入条目到ListVite。你可能回想起第五章的这种模型。这个章节建立一个有两个潜在元素(选项)的数组,编辑提醒和删除提醒,在传送到ListView之前,这个是,轮流地,传送给AlertDialog.Builder。这个创建器然后用这些选项清单创建并显示一个对话框。
Pay careful attention to the last section of code in the Listing 6-3. It is similar to the OnItemClickListener() code added earlier; however, we are attaching a click listener to the modeListView that was created inside the current OnItemClickListener. What you see is a ListView with an OnItemClickListener that creates another modeListView and another nested OnItemClickListener to respond to tap events for the modeListView.
<翻译>
注意下代码清单6-3最后的选择部分。有点象之前的OnItemClickListener();不管怎样,我们附加了一个modeListView的监听器,那个在当前OnItemClickListener里创建的。你看到的是带有OnItemClickListener的ListView(指的是activity_reminders.xml里的reminders_list_view),<译者:在它的onItemClick()方法里>创建了另一个modeListView还有另一个嵌入的OnItemClickListener来响应来自modeListView的选项点击事件。
The nested click listener pops a toast message indicating whether the edit or delete item was tapped. It also renames the position parameter from the outer OnItemClickListener as masterListPosition to distinguish it from the position parameter in the nested OnItemClickListener. This master position is used in the toast to indicate which reminder is being potentially edited or deleted. Finally, the dialog.dismiss() method is invoked from the
click listener, which removes the dialog box completely.
<翻译>
内嵌的点击监听器弹出一个吐司信息来指示是编辑还是删除所点击的条目。它也重命名来自外部调用者OnItemClickListener的位置参数叫做masterListPosition来区分内置的OnItemClickListener的位置参数。这个masterListPosition用于吐司指明哪条提醒被用于可能的编辑或删除。最后,dialog.dismiss()方法调用于点击监听器,用来完全移除对话框。
Test the new feature shown in Figure 6-4 by running it on your device or emulator. Tap a reminder and then tap either Edit Reminder or Delete Reminder from the new pop-up dialog box. If the position of the reminder reported in the toast does not match the reminder you tapped, double-check that you are appending the masterListPosition value to the text in your toast and not using position. Press Ctrl+K | Cmd+K to commit this logic and use the message Adds a ListView dialog for individual list items.
<翻译>
运行一下来测试新特性如图6-4所示。点击一条提醒接着在弹出对话框再点编辑或删除。如果吐司里报告的提醒位置和你点击的对不上,再确认一下你吐司里追加masterListPosition值而不是postion。按Ctrl+K | Cmd+K提交更改到Git,并附上信息Adds a ListView dialog for individual list items。
图6-4 模拟删除一条提醒
Providing Multichoice Context Menus
With the app beginning to take shape, you will now attack a feature that allows multiple reminders to be edited in one operation. This feature is available only on devices running API 11 and higher. You will make this feature conditionally available in the app by using the resource-loading conventions. This process is explained later in this chapter and in detail in Chapter 8. You will also need to include a check at runtime to decide whether to enable the feature.<翻译>
提供多选上下文菜单
随着这个app逐渐成形,现在将达成这种特性:在一次操作里允许多选提醒条目用于编辑。这种特性只能在API 11或更高版本上才有效。你将通过使用资源载入协定来有条件地达成这种特性。这个处理将在这章稍后解释并且第8章有所有的细节。你也将需要包含一个运行时检查看看是否支持这种特性。
Start by creating an alternate layout for the reminder row items. Open the Project tool window and right-click the res folder to bring up a context menu. Choose New Android Resource File from the menu and enter reminders_row as the name in the dialog box, as shown in Figure 6-5.
<翻译>
为提醒行条目创建另一个备用的布局。打开项目工具窗口在资源夹上右击带出来的上下文。选择新的Android资源文件命名为reminders_row,如图6-5示。
图6-5 新资源文件reminders_row
Select Layout as the Resource Type, which automatically changes the directory name to layout. Select Version under the Available Qualifiers section and then click the double chevron (>>) button to add Version to the list of chosen qualifiers. Enter 11 as the Platform API Level and note that the directory name has been updated to reflect the chosen qualifier. These are called resource qualifiers and they are interrogated during runtime to allow you to customize your user interface for particular devices and platform versions. Press Enter
(or click OK) to accept this new resource-qualified directory and continue. If you open the Project tool window and set its view to Android as in Figure 6-6, you will see both reminders_row layout files grouped together under the layout folder. Again, the Android view of the project window groups related files together to allow you to efficiently manage them.
<翻译>
选择资源类型为布局,这样就自动改变目录名为layout。在有效限定词部分选择相应的版本然后双击(>>)双V纹章按钮增加到选择的限定词里。输入11作为平台API级别并注意到目录名更新了,并反映了选择的限定词版本。那叫资源限定并且它们整合于运行时里,让你可以为特别的设备和平台版本定制你的用户界面。按回车(或点OK)接受这个新资源限定目录并继续。如果你打开项目工具窗并设为Android示图,如图6-6,你将看到layout文件夹下所有的reminders_row布局文件在一起。还有,Android示图的项目工具窗让相关联的文件聚集在一起使你有效地管理它们。
图6-6 聚集的布局
Copy the entirety of the original reminders_row layout and paste it into the newly created layout for version 11. Now change the background attribute of the inner horizontal LinearLayout by using the following:
android:background="?android:attr/activatedBackgroundIndicator"
<翻译>
复制整个原始的reminders_row布局并粘贴到刚新建版本11的布局里。现在修改内层的水平线性布局的背景色属性如下:
android:background="?android:attr/activatedBackgroundIndicator"
This value assigned to the background attribute is prefixed with ?android:attr/, which refers to a style defined in the Android SDK. The Android SDK provides many such predefined attributres, and you may use them in your app. The activatedBackgroundIndicator attribute uses the system-defined color for the background of items activated during multiselect mode.
<翻译>
这个值分配背景色属性带前缀?android:attr/,这是参考定义于Android SDK里的一种风格。Android SDK提供了很多这样的预定义属性,并且你可以使用它们到你的app上。在多选模式时activatedBackgroundIndicator属性使用了系统定义的有效背景色。
Targeting Earlier SDKs
Now you will learn how to introduce a platform-dependent feature. Open the Project tool window and open the build.gradle file for the app module under the Gradle Scripts section (It will be the second entry). These Gradle files hold the build logic for compiling and packaging the app. All the configuration regarding which platforms your app supports is located in these special files (Chapter 13 explores the Gradle build system in depth).Notice that the minSdkVersion is set to 8 which allows your app to run on 99%+ of all Android devices. The feature we are about to create requires a minimum SDK (aka API) version of 11. The code and features we cover in this section will allow users running version SDK 11 or higher to take advantage of a feature called contextual action mode. Furthermore, those running an SDK version less than 11 will not see this feature, but more importantly, their app will not crash.
<翻译>
目标于早期的SDK
现在你将学习如何引入一个平台依赖的特性。打开项目工具窗并打开在Gradle剧本区下面的app模块 build.gradle文件(它会在第二个入口里)。Gradle文件含有编译的构建逻辑和app的包装。参照你的app所支持的平台所有配置存在于这些特别的文件里(第十三章深度探索了Gradle构建系统)。注意到最低SDK版本设置为8,这让你的app可以运行在99%的Android设备上。现在我们将创建的这个特性需要最低SDK版本是11。涵括这节的代码和特性将允许用户运行在SDK11或更高版本带来的更先进的特性,叫上下文动作模式。而且,低于SDK11的将不会有这个特性,但更重要的是,他们的app不会因此而崩溃。
Adding Contextual Action Mode
This next feature introduces a context action menu during multiselect mode, which is a list of actions that can be applied to the context of all of the selected items. Add a new menu resource by right-clicking the res/menu directory and selecting New ➤ Menu resource file and name it cam_menu. Decorate it with the following code:<?xml version="1.0" encoding="utf-8"?>
<menu xmlns:android="http://schemas.android.com/apk/res/android";>
<item android:id="@+id/menu_item_delete_reminder"
android:icon="@android:drawable/ic_menu_delete"
android:title="delete" />
</menu>
<翻译>
加入上下文动作模式
接下来介绍的多选模式的上下文动作模式菜单,是一个动作清单可用于所有选择项的上下文。加载一个新的菜单资源,在res/menu目录上右击选择New ➤ Menu资源文件并命名为cam_menu。以下列代码清单完成它:
<?xml version="1.0" encoding="utf-8"?>
<menu xmlns:android="http://schemas.android.com/apk/res/android";>
<item android:id="@+id/menu_item_delete_reminder"
android:icon="@android:drawable/ic_menu_delete"
android:title="delete" />
</menu>
This resource file defines a single delete action item for the context menu. Here you are also using slightly different attribute values. These special values are similar to what you used in the background attribute earlier in that they give you access to built-in Android defaults. However, the ?android:attr/ prefix is used only when referencing a style attribute. The syntax used here in these attributes follows a slightly different form. Using the at symbol (@) triggers a namespace lookup for resource values. You can access various namespaces in
this way. The android namespace is where all of the built-in Android values are located. Within this namespace are various resource locations such as drawable, string, and layout. When you use the special @+id prefix, it creates a new ID in your project’s R.java file, and when you use the @id prefix, it looks for an existing ID in the R.java file of the Android SDK. This example defines a new ID name, menu_item_delete_reminder, which is associated with the menuoption. It also pulls an icon out of the android:drawable namespace, which is used as its icon.
<翻译>
这个资源文件为上下文菜单定义了唯一的删除动作条目。这里你也正用了一点不同的属性值。这个特别的属性象之前的背景色属性一样也是访问Android内建的缺省值。不管怎样,在那前缀?android:attr/ prefix只用于参考风格属性。在这用在属性上的语法参照一个稍有不同的格式。用at符号@触发一个到资源值查询的命名空间。你可以用这种方式访问变量命名空间。Android命名空间是所有内建Android值所在的地方。用这个命名空间是多种变量资源所存在的如drawable,string以及布局。当你用了@+id为前缀,它会创建一个新的ID在你的项目的R.java文件里,且当你用到@id前缀,它会查找Andriod SDK的R.java文件存在的ID。这个例子里定义了一个新的ID名,menu_item_delete_reminder,它结合于菜单选项。它也从android:drawable命名空间里拉出一个图标,作为它的图标。
With the new context menu and an alternate layout for devices running API 11 or higher, you can add a check to conditionally enable multiselect mode with the context action menu. Open RemindersActivity and add the following if block at the end of the onCreate method:
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB) {
}
<翻译>
用新的上下文菜单和一个备用的布局运行于API 11或更高版本,你可以加载一个有条件的检查以允许带有上下文动作菜单的多选模式。打开RemindersActivity并加上下面的代码到onCreate方法的后面。
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB) {
}
The Build class is imported from the android.os package and gives you access to a set of constant values that can be used to match a device with a specific API level. In this case, you are expecting the API level to be at or above HONEYCOMB which contains an integer value of 11. Insert the code in Listing 6-4 inside the if block you just defined. The if block protects devices that are running an OS less than Honeycomb without which the app would crash.
<翻译>
构建类从android.os包里导入并且给你访问一系列的常量值,那些可用于以指定的API级别来匹配设备。在这个例子里,你期望API级别等于或高于HONEYCOMB而它实际包含整数11。把清单6-4的代码插入到刚定义的块里。IF块保护了运行OS低于HONEYCOMB的系统不让这个app崩溃。
Listing 6-4. MultiChoiceModeListener Example
mListView.setChoiceMode(ListView.CHOICE_MODE_MULTIPLE_MODAL);
mListView.setMultiChoiceModeListener(new AbsListView.MultiChoiceModeListener() {
@Override
public void onItemCheckedStateChanged(ActionMode mode, int position, long id, boolean
checked) { }
@Override
public boolean onCreateActionMode(ActionMode mode, Menu menu) {
MenuInflater inflater = mode.getMenuInflater();
inflater.inflate(R.menu.cam_menu, menu);
return true;
}
@Override
public boolean onPrepareActionMode(ActionMode mode, Menu menu) {
return false;
}
@Override
public boolean onActionItemClicked(ActionMode mode, MenuItem item) {
switch (item.getItemId()) {
case R.id.menu_item_delete_reminder:
for (int nC = mCursorAdapter.getCount() - 1; nC >= 0; nC--) {
if (mListView.isItemChecked(nC)) {
mDbAdapter.deleteReminderById(getIdFromPosition(nC));
}
}
mode.finish();
mCursorAdapter.changeCursor(mDbAdapter.fetchAllReminders());
return true;
}
return false;
}
@Override
public void onDestroyActionMode(ActionMode mode) { }
});
Resolve any imports. You will notice that getIdFromPositon() is not defined and is flagged red. Place your cursor on the method and press Alt+Enter to invoke IntelliSense and select Create Method. Select RemindersActivity as the target class. Select int as the return value.Decorate the method as seen in Listing 6-5.
<翻译>
解决所有导入的问题。你将注意到getIdFromPosition()没有定义并标为红色(错误)。把光标放在这个方法上并按劳取酬Alt_Enter调用IntelliSense并选择创建方法。选择RemindersActivity作为目标类。选择整型作为返回类型。用清单6-5的代码完成这个方法。
Listing 6-5. getIdFromPosition() method
private int getIdFromPosition(int nC) {
return (int)mCursorAdapter.getItemId(nC);
}
The preceding logic defines a MultiChoiceModeListener and attaches it to the ListView. Whenever you long-press an item in the ListView, the runtime invokes the onCreateActionMode() method on the MultiChoiceModeListener. If the method returns with the boolean true value, multichoice action mode is entered. The logic in the overridden method here inflates a context menu that is displayed in the action bar when in this mode. The benefit of using multichoice action mode is that you can select multiple rows. One tap selects the item, and a subsequent tap deselects the item. As you tap each of the items from the context menu, the runtime will invoke the onActionItemClicked() method with the menu item that was tapped.
<翻译>
此处理逻辑定义了一个MultiChoiceModeListener并把它附到ListView。无论何时你长按ListView的一个条目时,运行时调用onCreateActionMode()方法在MultiChoiceModeListener上。如果方法返回真值,就进入多选动作模式。这里重写方法的逻辑填充了一个上下文菜单用于显示动作条。使用多选动作模式的好处是你可以选择多行。一次点击选中了某个条目,接下来再点击则会去选这个条目。当你点击上下文菜单里的每个条目,运行时将带着被点的条目调用onActionItemClicked()方法。
In this method, you check to see whether the delete item was tapped by comparing the itemId with the id of the delete element you added to the menu item. (See the XML listing at the start of this section for a description of the delete item’s ID.) If the item is selected, you loop over each of the list items and request that mDbAdapter delete them. After deleting the selected items, the logic invokes finish() on the ActionMode object, which will disable multiselect action mode and return the ListView to its normal state. Next you invoke fetchAllReminders() to reload all the reminders from the database and pass the cursor returned from that call to the changeCursor method on the mCursorAdapter object. Finally, the method returns true to indicate that the action has been properly handled. In every other case where the logic is not handled, the method returns false, indicating that some other event listener can handle the tap event.
<翻译>
在这个方法里,通过比较itemId和你加到菜单项里的删除元素的id,检查下删除条目是否被点击了。(看看在本节开始时所描述有关删除条目ID的XML清单)。如果这个条目被选,轮询所有的清单条目并要求mDbAdapter来删除它们。在删除所选条目后,逻辑调用动作模式对象finish()方法,这将禁止多选动作模式并返回ListView到普通模式。接下来你调用fetchAllReminders()方法众数据库重新装载所有的提醒条并传送在mCursorAdapter对象changeCursor方法所返回的游标。最后,方法返回真值来表明动作被正确地处理了。所有其它的没被正确处理的逻辑,方法返回假,表明一些其它的事件监听器可能处理了这个点击事件。
Android Studio will highlight a couple of statements in error because you are using APIs that are not available on platforms older than Honeycomb. This error is generated from Lint, a static analysis tool built into the Android SDK and fully integerated into Android Studio. You need to add the following annotation to the RemindersActivity.onCreate() method either above or below the @Override annotation and resolve the import for TargetApi:
@TargetApi(Build.VERSION_CODES.HONEYCOMB)
<翻译>
Android Studio将高亮一堆错误语句,因为你正使用的API无效或低于Honeycomb。这外错误生成于Lint,一个状态分析工具内建于Android SDK并完全整合在Android Studio里。你需要加上下面的声明在RemindersActivity.onCreate()方法里,在@Override声明之上或之下都行,而且要目标API解决导入问题:
@TargetApi(Build.VERSION_CODES.HONEYCOMB)
This special annotation tells Lint to treat the method call as targeting the supplied API level regardless of what the build configuration specifies. Commit your changes to Git with the message Adds Contextual Action Mode with context action menu. Figure 6-7 depicts what you might see when you build and run the app to test the new feature.
<翻译>
这个特别的声明告诉Lint欺骗方法调用是提供的API级别的目标,不管构建配置所指定的。提交更改到Git用Adds Contextual Action Mode with context action menu作注释。图6-7描写了你可能看到的新特性。
图6-7 允许多选模式
Implementing Add, Edit, and Delete
So far, you have added logic to delete reminders from the list. This logic is available exclusively in contextual action mode. You currently have no way to either insert new reminders or modify existing reminders. However, you will now create a custom dialog box to add reminders, and another to edit existing reminders. Eventually, you will bind these dialog boxes to RemindersDbAdapter.<翻译>
迄今为止,你已经添加了从清单里删除提醒的逻辑。这个逻辑在上下文动作模式里也可以有效执行。目前还无法插入新提醒或编辑现有的提醒。无论如何,你将创建一个用户自定义对话框或添加提醒,另一个用来编辑现有的提醒。最终,你将绑定这些对话框到RemindersDbAdapter。
Before proceeding, you need to define a few additional colors. Add the following color definitions to your colors.xml file:
<color name="light_grey">#bababa</color>
<color name="black">#000000</color>
<color name="blue">#ff1118ff</color>
<翻译>
在处理这个之前,先添加一些新的颜色。把这它们加到colors.xml文件里:
<color name="light_grey">#bababa</color>
<color name="black">#000000</color>
<color name="blue">#ff1118ff</color>
Note Typically, you would have an overall color theme for your app, which would ensure consistency between all screens and dialog boxes. However, color theme is beyond the scope of this simple lab.
<翻译>
注意:通常,你的app可能有一个全面的颜色风格,这将保证所有屏幕和对话框的一致性。无论如何,颜色风格超出了这个简单例子的范围了。
Planning a Custom Dialog Box
A good habit to develop is to sketch your UI by using simple tools prior to implementing it. Doing so allows you to visualize how elements will fit on the screen prior to investing any code. You can use an editor such as Inkscape, which works across platforms, or you can use something as simple as notebook paper and a pencil. In the mobile business, these sketches are called wireframes.<翻译>
策划一个用户对话框
开发的一个好习惯是:以简单工具优于执行它来描绘你的用户界面。这样在引入任何代码之胶,让你图形化元素如何适合屏幕。你可用一个编辑器如inkscape,它是跨平台的,或者你可以用笔记本的纸张和铅笔。在移动商务里,这些描绘称作线框图(wireframe)。
Figure 6-8 is an illustration of our custom dialog box done with Inkscape. The wireframe is intentionally informal, to emphasize the placement of components rather than a specific look and feel.
<翻译>
图6-8是我们的用户对话框的插图,完成于inkscape。这个线框图有意不正式,来强调元素的摆放好过一个精准的外观和感觉。
图6-8 线框图描绘用户自定义对话框
Note Some of the custom artwork and wireframes in this book were created using Inkscape, a multiplatform vector graphics editor. It is freely available at www.inkscape.org.
<翻译>
注意:这本书里的一些用户自定义的绘图和线框图正是用lnkspace创建的,一个多平台向量图形编辑器。在www.inkscape.org上,它是免费的。
With the wireframe in place, you can start planning how to line-up the components on-screen. Since most components flow from top to bottom, using a vertical LinearLayout for the outermost container is an obvious choice. However, the two buttons at the bottom are side by side. For these you could use a horizontal LinearLayout and nest it inside the containing vertical LinearLayout. Figure 6-9 adds annotations to the drawing and highlights this nested component.
<翻译>
把线框图就在那了,你可以开始计划如何排列屏幕的元素。因为大多数元素从上排到下,在最外层用一个竖直线性布局,这是最显然的选择。不管怎样,底下的两个按钮并排在一起。这样你可以用一个水平线性布局放在前面的竖直线性布局里。图6-9加上一些声明来描绘和高亮这些内嵌的元件。
图6-9 线框图描绘widget标签
Moving from Plans to Code
With these wireframes in place, try to design the layout by using the Visual Designer. Begin by right-clicking the res directory in the Project tool window and selecting the Create a New Android Resource File option and give your resource file a name of dialog_custom and then choose Layout as the Resource type. Complete the dialog box by using LinearLayout as your Root element. To reproduce our wireframe, drag and drop Views from the palette onto the stage. Listing 6-6 contains the completed layout XML definition with the ID values youwill use in the Java code.
<翻译>
从策划到代码
随着线框图在那,尽量用图形化设计器来开发布局。开始在res目录上右击并选择创建一个新的Android资源文件然后命名为dialog_custom,资源类型为布局资源。以线性布局作为根元素,完成对话框。接着我们的线框图,从调色板拖放视图控件到这个平台上。清单6-6包含了这个完成的XML布局文件,并带有将在代码中用到的ID值。
Listing 6-6. Completed dialog_custom.xml
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/custom_root_layout"
android:layout_width="300dp"
android:layout_height="fill_parent"
android:background="@color/green"
android:orientation="vertical"
>
<TextView
android:id="@+id/custom_title"
android:layout_width="fill_parent"
android:layout_height="60dp"
android:gravity="center_vertical"
android:padding="10dp"
android:text="New Reminder:"
android:textColor="@color/white"
android:textSize="24sp" />
<EditText
android:id="@+id/custom_edit_reminder"
android:layout_width="fill_parent"
android:layout_height="100dp"
android:layout_margin="4dp"
android:background="@color/light_grey"
android:gravity="start"
android:textColor="@color/black">
<requestFocus />
</EditText>
<CheckBox
android:id="@+id/custom_check_box"
android:layout_width="fill_parent"
android:layout_height="30dp"
android:layout_margin="4dp"
android:background="@color/black"
android:paddingLeft="32dp"
android:text="Important"
android:textColor="@color/white" />
<LinearLayout
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:orientation="horizontal">
<Button
android:id="@+id/custom_button_cancel"
android:layout_width="0dp"
android:layout_height="60dp"
android:layout_weight="50"
android:text="Cancel"
android:textColor="@color/white"
/>
<Button
android:id="@+id/custom_button_commit"
android:layout_width="0dp"
android:layout_height="60dp"
android:layout_weight="50"
android:text="Commit"
android:textColor="@color/white"
/>
</LinearLayout>
</LinearLayout>
Creating a Custom Dialog Box
You will now use the completed dialog layout in RemindersActivity. Listing 6-7 is an implementation of a new fireCustomDialog() method. Place this code in the RemindersActivity.java file, just above the onCreateOptionsMenu() method and resolve imports.<翻译>
生成一个用户对话框
现在RemindersActivity里用完成的对话框布局了。清单6-7实现了一个新的fireCcustomDialog()方法。把这些代码放到RemindersActivit文件里去,仅在onCreateOptonsMenu()方法上面,并解决好导入的问题。
Listing 6-7. The fireCustomDialog( ) Method
private void fireCustomDialog(final Reminder reminder){
// custom dialog
final Dialog dialog = new Dialog(this);
dialog.requestWindowFeature(Window.FEATURE_NO_TITLE);
dialog.setContentView(R.layout.dialog_custom);
TextView titleView = (TextView) dialog.findViewById(R.id.custom_title);
final EditText editCustom = (EditText) dialog.findViewById(R.id.custom_edit_reminder);
Button commitButton = (Button) dialog.findViewById(R.id.custom_button_commit);
final CheckBox checkBox = (CheckBox) dialog.findViewById(R.id.custom_check_box);
LinearLayout rootLayout = (LinearLayout) dialog.findViewById(R.id.custom_root_layout);
final boolean isEditOperation = (reminder != null);
//this is for an edit
if (isEditOperation){
titleView.setText("Edit Reminder");
checkBox.setChecked(reminder.getImportant() == 1);
editCustom.setText(reminder.getContent());
rootLayout.setBackgroundColor(getResources().getColor(R.color.blue));
}
commitButton.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
String reminderText = editCustom.getText().toString();
if (isEditOperation) {
Reminder reminderEdited = new Reminder(reminder.getId(),
reminderText, checkBox.isChecked() ? 1 : 0);
mDbAdapter.updateReminder(reminderEdited);
//this is for new reminder
} else {
mDbAdapter.createReminder(reminderText, checkBox.isChecked());
}
mCursorAdapter.changeCursor(mDbAdapter.fetchAllReminders());
dialog.dismiss();
}
});
Button buttonCancel = (Button) dialog.findViewById(R.id.custom_button_cancel);
buttonCancel.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
dialog.dismiss();
}
});
dialog.show();
}
The fireCustomDialog( ) method will be used for both inserts and edits, since there is little difference between both operations. The first three lines of the method create an Android dialog box with no title and inflate the layout from Listing 6-6. The fireCustomDialog( ) method then finds all of the important elements from this layout and stores them in local variables. Then the method sets an isEditOperation boolean variable by checking whether the reminder parameter is null. If there is a reminder passed in (or if the value is not null), the method assumes that this is not an edit operation and the variable is set to false; otherwise, it is set to true. If the call to fireCustomDialog( ) is an edit operation, the title is set to Edit Reminder while the CheckBox and EditText are set using values from the reminder parameter. The method also sets the background of the outermost container layout to blue, in order to visually distinguish an edit dialog box from in insert dialog box.
<翻译>
fireCustomDialog( )方法将用于插入和编辑,就算它们间有少许不同。方法的开始三行创建了一个Android对话框,没有标题并填充清单6-6的布局。fireCustomDialog( )方法接着从这个布局里查找所有重要的元素并储存到本地变量里。然后方法查看reminder参数是否为空,设置一个isEditOperation布尔变量。如果说有一个提醒被传入(或者值为不空),方法会假设这不是一个编辑操作并把这个变量设为假;否则,设为真。如果调用fireCustomDialog( )方法的是一个编辑操作,标题设为Edit Reminder而且CheckBox和EditText用提醒参数的值来设定。这个方法也设置了最外层容器布局的背景色为蓝色,为了从视觉上区别插入对话框和编辑对话框。
The next several lines compose a block of code that sets and defines an OnClickListener for the Commit button. This listener responds to click events on the Commit button by updating the database. Again, the isEditOperation() is checked, and if an edit operation is underway, then a new reminder is created by using the ID from the reminder parameter and the values from the EditText and on-screen check-box value. This reminder is passed to mDbAdapter by using the updateReminder() method.
<翻译>
接下来的几行由一个代码块组成,为提交按钮设置及定义一个OnClickListener。这个监听器响应点击事件,更新数据库。再次,isEditOperation()被检查了,如果编辑操作进行中,那么一个新提醒创建了,用来自提醒参数的ID以及EditText的值还有在屏的复选框值。这个提醒传送给mDbAdapter,通过updateReminder()方法。
If an edit is not underway, the logic asks mDbAdapter to create a new reminder in the database by using the values from the EditText and on-screen check-box value. After either the update or create call is invoked, the reminders are reloaded by using the mCursorAdapter. changeCursor() method. This is logic similar to that which you added earlier in Listing 6-5. The click listener dismisses the dialog box after the reminders are reloaded.
<翻译>
如果编辑没有进行,这个逻辑查询mDbAdapter来创建一个新的提醒到数据库,用EditText的值还有在屏的复选框值。在无论是更新或创建调用后,所有提醒通过mCursorAdapter.changeCursor()方法会重新装载.这个逻辑有点象之前添加的清单6-5代码。点击监听器在提醒重新装载后解散对话框。
After configuring the click behavior of the Commit button, the example sets another click listener for the Cancel button. This listener simply dismisses the dialog box. With the behavior for both of these buttons specified, the example concludes by showing the custom dialog box.
<翻译>
在配置提交按钮的点击行为后,这个例子设置了另一个取消按钮的监听器。这个监听器只是简单地解散对话框。所有按钮的行为定义后,这个例子结束于显示用户对话框。
Now you can use this new method in the OnItemClickListener for the modeListView in the onCreate() method. Find the onItemClick() method for this listener and replace the entire method with the following code:
public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
//edit reminder
if (position == 0) {
int nId = getIdFromPosition(masterListPosition);
Reminder reminder = mDbAdapter.fetchReminderById(nId);
fireCustomDialog(reminder);
//delete reminder
} else {
mDbAdapter.deleteReminderById(getIdFromPosition(masterListPosition));
mCursorAdapter.changeCursor(mDbAdapter.fetchAllReminders());
}
dialog.dismiss();
}
<翻译>
现在你可以为在onCreate()方法里的modeListView用这个新方法在OnItemClickListener中。为这个监听器找到onItemClick()方法用下面的代码替换原来所有代码。
ublic void onItemClick(AdapterView<?> parent, View view, int position, long id) {
//edit reminder
if (position == 0) {
int nId = getIdFromPosition(masterListPosition);
Reminder reminder = mDbAdapter.fetchReminderById(nId);
fireCustomDialog(reminder);
//delete reminder
} else {
mDbAdapter.deleteReminderById(getIdFromPosition(masterListPosition));
mCursorAdapter.changeCursor(mDbAdapter.fetchAllReminders());
}
dialog.dismiss();
}
To edit a reminder, you replace the Toast.makeText() call with a call to find the reminder by using the ListView position. This reminder is then passed to the fireCustomDialog() method to trigger the edit behavior. To delete a reminder, you use logic identical to that you added in Listing 6-5 during multichoice mode. Again, mDbAdapter.deleteReminderById() is used to delete the reminder, and the changeCursor() method is used with the cursor returned from the mDbAdapter.fetchAllReminders() call.
<翻译>
为了编辑提醒,去掉Toast.makeText()调用,替换为用ListView位置来找到提醒的调用。这价目提醒然后被传送到fireCustomDialog()方法来触发编辑行为。为删除提醒,用和清单6-5一样的代码逻辑。再次,mDbAdapter.deleteReminderById()用于删除提醒,并且changeCursor()用于从mDbAdapter.fetchAllReminders()调用中返回游标。
Find the onOptionsItemSelected() method at the very bottom of the RemindersActivity.java file and modify it to look like Listing 6-8.
<翻译>
找到onOptionsItemSelected()方法,在RemindersActivity.java文件很底部的地方,编辑它如清单6-8。
Listing 6-8. onOptionsItemSelected Definition
public boolean onOptionsItemSelected(MenuItem item) {
switch (item.getItemId()) {
case R.id.action_new:
//create new Reminder
fireCustomDialog(null);
return true;
case R.id.action_exit:
finish();
return true;
default:
return false;
}
}
Here you simply add a call to fireCustomDialog() when the selected menu item is action_new item. You pass null to the method, as the logic covered earlier checks for a null value and sets the isEditOperation to false and thus invoking a New Reminder dialog box. Run the app and test the new feature. You should be able to see the new custom dialog boxes. You will see a green dialog box when you create a reminder, and a blue dialog box when you edit a reminder, as shown in Figure 6-10 and Figure 6-11 respectively. Test the menu items to make sure that the creating and deleting operations function as they should. Commit your changes to Git with a commit message of Adds database Create, Read, Update, and Delete support with custom dialogs.
<翻译>
这里,当你选择了菜单项是action_new,简单地调用了fireCustomDialog()。传送一个空值给它,象之前提及的空值检查并设置isEditOperation为假,然后征用一个新提醒对话框。运行app检查下新特性。你会看到新的用户对话框。当创建新提醒时,你将看到一个绿色对话框,而编辑提醒时是一个蓝色的对话框,如图6-10和6-11所分别对应的。测试菜单项确保创建和删除操作功能象它们应该的那样。提交更改到Git,附上提交信息:Adds database Create, Read, Update, and Delete support with custom dialogs。
图6-10 新提醒对话框
图6-11 编辑提醒对话框
Adding a Custom Icon
With all of the features in place, you can add a custom icon as the finishing touch. You can use any image editor to create an icon or, if you are not graphically inclined, find some royalty-free clip art on the Web. Our example replaces the ic_launcher icon with custom artwork created in Inkscape. Open the Project tool window and right-click the res/mipmap directory. Now select New ➤ Image Asset. You will see a dialog box like Figure 6-12. Click the elipses button located on the far right of the Image file: field and navigate to the location of the image asset you crated. Leave the rest of the settings as they appear inFigure 6-13. Now click Next, and in the subsequent dialog box click Finish.<翻译>
加上用户图标
所有的特性都放置好了,你可以加上一个用户图标作为完成点缀。可以用任何图形编辑器来创建它,或者如果你不喜欢图形编辑,从网上下点免费的。我们的例子放置ic_launcher图标,它创建于Inkscape。找开项目工具窗并右击res/mipmap目录。选New ➤ Image Asset。将看到图6-12那样的对话框。点击Image File:选择框最右边的省略号图标,导航到你要的图片位置并插入进来。让其他的设置如图6-13所示。点下一步,接着点完成。
图6-12 新图片资源对话框
There are a number of folders with the name mipmap. These folders each have suffixes that are designated screen-size qualifiers. The Android runtime will pull resources out of a particular folder, depending on the screen resolution of the device on which the app is running. Resource folders and their suffixes are covered in more detail in Chapter 8.
<翻译>
有不少目录使用了mipmap这个名字。这些目录每个都有着后缀,那个指派屏幕尺寸限定。Android运行时将从特别的目录里拉出资源,依照app所运行设备屏幕的解析度。资源夹和它们的后缀在第8章将有祥解。
Insert the following lines of code into the onCreate() method of RemindersActivity, after the line of code which inflates the layout, setContentView(R.layout.activity_reminders);. This code displays a custom icon in your Action Bar:
ActionBar actionBar = getSupportActionBar();
actionBar.setHomeButtonEnabled(true);
actionBar.setDisplayShowHomeEnabled(true);
actionBar.setIcon(R.mipmap.ic_launcher);
<翻译>
插入下面的几行代码到onCreate()方法里,在填充布局之后,即setContentView(R.layout.activity_reminders);。这些代码将显示用户图标在你的动作条里:
ActionBar actionBar = getSupportActionBar();
actionBar.setHomeButtonEnabled(true);
actionBar.setDisplayShowHomeEnabled(true);
actionBar.setIcon(R.mipmap.ic_launcher);
When you run the code, you will see your custom icon in the Action Bar. Figure 6-13 shows an example of the app running with the custom icon.
<翻译>
运行代码,将看到用户图标在动作条里。图6-13展示了以用户图标运行的app。
图6-13 动作条的用户图标
Press Ctrl+K | Cmd+K and commit your changes with the message Adds a custom icon.
<翻译>
按Ctrl+K | Cmd+K提交更改到Git,以Adds a custom icon作为提交信息。
Summary
Congatulations! You have implemented your very first Android app using Android Studio. In the process, you learned how to edit XML layouts using the Visual Designer. You also learned how to edit raw XML using the Text mode. The chapter showed you how to conditionally implement Contextual Action Mode on platforms that support the feature. Finally, you saw how to add a custom icon for various screen densities.<翻译>
小结
恭喜!你已实现了使用Android Studio完成的第一个Android app。在这个过程里,你学会了用图形化设计器编辑XML布局文件。你也学会了如何用文本模式编辑一个行XML布局。这章展示了在支持的平台上如何有条件地实现上下文动作模式。最后,你看到了如何加载一个用户图标到不同的分辨率的屏幕上。