[Learn Android Studio 汉化教程]第五章:Reminders实验:第一部分

By now you are familiar with the basics of creating a new project, programming, and refactoring.It is time to create an Android application, otherwise known as an app. This chapter introduces the first of four lab projects. These labs are intended to familiarize you with using Android Studio in the context of developing an app. In this project, you will develop an app to manage a list of items you want to remember. The core functionality will allow you to create and delete
reminders and flag certain reminders as important. An important item will be emphasized by an orange tab to the left of the reminder’s text. The app will incorporate an action bar menu, context menus, a local database for persistence, and multiple selection on devices that support multiple selection.
到现在为止你已经熟悉了创建一个新项目,编程,和重构的基本操作了。是时候创建一个Android 应用了,或者说成我们所谓的APP。这章将介绍四个实例项目的第一个。这些实例目的是让你熟悉使用Android Studio开发APP的上下文方面。核心功能是允许你创建和删除一个提醒以及标志那些重要的提醒。重要的条目文体左边将被强调黄色标签。这个APP将合同一个动作条菜单,上下文菜单,一个本地的数据库作存储,如果设备支持的话还有多重选择。

Figure 5-1 illustrates the completed app running on the emulator. This example introduces you to Android fundamentals and you will also learn how to persist data by using the built-in SQLite database. Don’t worry if some of the topics are unfamiliar; later chapters cover those topics in greater detail.

Note We invite you to clone this project using Git in order to follow along, though you will be recreating this project with its own Git repository from scratch. If you do not have Git installed on your computer, see Chapter 7. Open a Git-bash session in Windows (or a terminal in Mac or Linux) and navigate to C:\androidBook\reference\ (If you do not have a reference directory, create one. On Mac navigate to /your-labs-parent-dir/reference/) and issue the following git command: git clone https://bitbucket.org/csgerber/reminders.git Reminders.
注意:为了一致性,我们建议你用Git克隆这个项目,虽然你将从草稿里用它自身的Git存储库重建这个项目。如果你还没有安装Git,请看第7章。在窗口里打开一个Git-bash会话(在Mac或Linux里的终端)并导航到C:\androidBook\reference\<译者注:在Git-bash控制台里可以使用Dos命令cd c:改当前盘为C;dir查看当前目录文件;cd androidBook进入androidBook子目录;cd reference进入reference子目录>(如果没有这个目录创建它)并提交下面的Git命令:git clone https://bitbucket.org/csgerber/reminders.git Reminders。

图5-1 完成的app界面

To operate the Reminders app, you can use the overflow menu of the Action Bar. Tapping the overflow button, which looks like three vertical dots on the right side of the menu bar, opens a menu with two options as shown in Figure 5-2: New Reminder, and Exit. Tapping New Reminder opens a dialog box as shown in Figure 5-3. In the dialog box, you can add text for your new reminder and then tap Commit to add it to the list. Tapping Exit simply
quits the app.

图5-2 激活溢出菜单的app界面

图5-3 新提醒对话框

Tapping any reminder in the list opens a context menu with two options, shown in Figure 5-4:Edit Reminder and Delete Reminder. Tapping Edit Reminder from the context menu opens the Edit Reminder pop-up dialog box shown in Figure 5-5, where you can change the text of the reminder. Tapping Delete Reminder from the context menu deletes the reminder from the list.

图5-4 上下文菜单

图5-5 编辑提醒对话框

Starting a New Project

Start a new project in Android Studio by using the New Project Wizard as explained in Chapter 1. Enter Reminders as the application name, set the company domain to gerber.apress.com, and choose the Blank Activity template. Save this project under the path C:\androidBook\Reminders. It’s a good idea to keep all of your lab projects in a common folder such as C:\androidBook (or use ~/androidBook for Mac/Linux) for consistency with our examples. On the next page of the wizard, select Phone and Tablet and set the Minimum SDK to API 8: Android 2.2 (Froyo). By setting your min API level to 8, you are making your app available to more than 99% of the Android market. Click the Next button, choose the Blank Activity from the available templates, and click Next again. Set the activity name to RemindersActivity and then click Finish, as shown in Figure 5-6.
在Android Studio开始一个新的项目,用新项目向导如同在第1章里所介绍的。输入Reminders作为项目名,设置公司域名为gerber.apress.com,并选Blank Activity模板。保存到C:\androidBook\Reminders。为了我们例子的一致性,这是一个好主意保存你所有的实验项目到同一个目录,比如C:\androidBook (or use ~/androidBook 对于 Mac/Linux来说)。在向导的下一页,选择电话和掌上设备并设置最低支持SDK到API 8:Android 2.2(冻酸奶)。通过设定最低支持SDK到API 8,你让这个app支持目前市场上99%的设备。点击下一步按钮,选择Blank Activity,再点下一步。设置activity名称为RemindersActivity并点完成,如图5-6所示。

图5-6 输入activity名称

Android Studio displays activity_reminders.xml in Design mode. The activity_reminders.xml file is the layout for your main activity, as shown in Figure 5-7. As discussed in Chapter 1, the project should run on either an emulator or a device at this point. Feel free to connect your device or launch your emulator and the run the project to try it out.
Android Studio在设计模式下显示activity_reminder.xml。这是你的主activity的布局,如图5-7示。如同第1章所讨论的,在这刻项目可以运行在仿真器上或设备上。只要你乐意随便用哪个。

图5-7 activity_reminders的设计模式

Initializing the Git Repository

Your first step after creating a new project should be to manage the source code with version control. All the labs is this book use Git, a popular version-control system that works seamlessly with Android Studio and is available online for free. Chapter 7 explores Git and version control more thoroughly.
建立新项目后的第一步必须是用版本控制来管理源代码。所有这本书的例子都用Git,一个流行的版本控制系统,无缝地协同Android Studio工作并且一直是在线免费的。第7章更彻底地探索了Git和版本控制。

If you do not already have Git installed on your computer, please refer to the section entitled Installing Git in Chapter 7. Choose VCS ➤ Import into Version Control ➤ Create Git Repository from the main menu. (In the Apple OS, choose VCS ➤ VCS Operations ➤ Create Git Repository.) Figures 5-8 and 5-9 demonstrate this flow.
如果你还没安装Git,请参考第7章。从主菜单选择VCS>Import into Version Control>Create Git
Repository。(在IOS上,选择VCS >VCS Operations>Create
Git Repository)图5-8和5-9展示了这个流程。

图5-8 创建一个Git存储库

图5-9 为Git存储库选择根目录

When prompted to select the directory for Git init, make sure that the Git project will be initialized in the root project directory (again, called Reminders in this example). Click OK.

You will notice that most of the files located in the Project tool window have turned brown, which means that they are being tracked by Git but have not yet been added to the Git repository nor are they scheduled to be added. Once your project is under Git’s control, Android Studio uses a coloring scheme to indicate the status of files as they are created, modified, or deleted. This coloring scheme will be explained in more detail as we progress though you can research this topic in more detail here: jetbrains.com/idea/help/file-status-highlights.html.
你将注意到项目工具窗里大部分文件变成棕色的,意味着它们被Git跟踪但还没加入到Git的存储库而且时刻表也没加载。一但你的项目被Gitr控制,Android Studio使用一个色彩策略,随着我们项目的进行将会解释更多的细节。当然你也可能在这:jetbrains.com/idea/help/file-status-highlights.html得更多的细节,如果你想研究的话。

Click the Changes tool button located along the bottom margin to toggle open the Changes tool window and expand the leaf labeled Unversioned Files. This will show all files that are being tracked. To add them, select the Unversioned Files leaf and press Ctrl+Alt+A | Cmd+Alt+A or right-click the Unversioned Files leaf and choose Git ➤ Add. The brown files should have turned green, which means that they have been staged in Git and are now ready to be committed.
点击位于窗口底部边缘的Changes工具按钮切换打开Changes工具窗口并展开叶子标签的未受版本控制文件。这将显示所有被跟踪的文件。为加载它们,选择未受版本控制文件叶子并按Ctrl+Alt+A | Cmd+Alt+A或右击未受版本控制文件叶子并选择Git>Add。棕色文件将会变成绿色,意味着它们在Git中已阶段化而且现在准备被提交了。

Press Ctrl+K | Cmd+K to invoke the Commit Changes dialog box. Committing files is the process of recording project changes to the Git version control system. As shown in Figure 5-10, the Author drop-down menu is used to override the current default committer. You should leave the Author field blank, and Android Studio will simply use the defaults you initially set during your Git installation. Deselect all check-box options in the Before Commit section. Put the following message in the Commit Message field: Initial commit using new project wizard. Click the Commit button and select Commit again from the drop-down items.
按Ctrl+K | Cmd+K来调用提交更改对话框。提交文件是Git版本控制系统记录项目更改的一个过程。如图5-10所示,授权者下拉菜单用于重写当前缺省提交者。让这栏空着吧,这样Android Studio将简单地用你安装Git时设的缺省值。去选Before Commit多选框里所有的选项。把下面的信息放入到Commit Message区里:Initial commit using new project wizard。点击提交按钮并在下拉条目里再次选择提交。

图5-10 提交更改到Git

By default, the Project tool window should be open. The Project tool window organizes your project in different ways, depending on which view is selected in the mode drop-down menu at the top of the window. By default, the drop-down menu is set to Android view, which organizes the files according to their purpose and has nothing to do with the way the files are organized on your computer’s operating system. As you explore the Projects tool window, you will notice three folders under the app folder: manifests, java, and res. The manifests folder is where your Android manifest files can be found. The java folder is where your Java source files may be found. The res folder holds all of your Android resource files. The resources located under the res directory may be XML files, images, sounds, and other assets that help define the appearance and UI experience of your app. Once you’ve had the opportunity to explore Android view, we recommend switching to Project view which is more intuitive because it maps directly to the file structure on your computer.
默认情况下,项目工具窗将会打开。项目工具窗以不同的方式组织你的项目,取决于顶部窗口的下拉菜单所选择的示图。缺省地,下拉菜单是Android示图,它按文件目的组织文件而不是按你电脑操作系统组织文件的方式。当你展开项目工具窗,将注意到三个文件夹在app文件夹下:manifests, java, 和 res。Manifests文件夹里有你的Android manifest文件。Java文件夹是存放java 源文件的地方。Res文件夹保存所有你的Android资源文件。在res目录下的资源可能是XML文件,图象,声音,和其他资源用于帮助定义你的app外观和用户体验。一但你有机会展开Android示图,我们推荐切换到Project示图,它更直观因为它直接反映了你电脑上的目录结构。

Building the User Interface

By default, Android Studio opens the XML layout file associated with the main activity in a new tab of the Editor and sets its mode to Design, so the Visual Designer is typically the first thing you see in your new project. The Visual Designer lets you edit the visual layout of your app. In the middle of the screen is the Preview Pane. The Preview Pane displays a visual representation of an Android device while rendering the results of the layout you are currently editing. This representation can be controlled by using the preview layout controls across the top of the screen. These controls adjust the preview and can be used to select different (or multiple) flavors of Android devices, from smartphones to tablets or wearables. You can also change the theme associated with your layout description. On the left side of the screen, you’ll find the Control palette. It contains various controls and widgets that can be dragged and placed onto the stage, which is a visual representation of the device. The right side of the IDE contains a component tree that shows the hierarchy of components described in your layout. The layout uses XML. As you make changes in the Visual Designer, these changes are updated in XML. You can click the Design and Text tabs to toggle between visual- and text-editing modes. Figure 5-11 identifies several key areas of the Visual Designer.
默认情况下,Android Studio打开与主activity相关联的XML布局文件在一个新的编辑选项卡里并设置为设计模式,因而通常你在新项目里先看到的是可视化开发器。可视化开发器让你编辑app的可视化布局。在屏幕的中央是预览面板。预览面板是Android设备渲染你当前编辑的布局结果的虚拟展示。这个展示可控于屏幕上方横跨的布局预览控制。这些控制可调整预览并选择不同风格的Android设备,从智能电话到穿戴设备。你也可以改变布局里所描述的相关主题。屏幕的左边,你会发现控件板。包含了众多的控件和widget,它们可被拖放到正虚拟展示的设备平台上。IDE的右侧包含了一个组件树展示了布局里组件的层次关系。布局使用XML文件。当你在这个可视化开发器作出修改时,这些修改将更新于XML中。你可选择Design或Text选项卡来切换可视化或是文本编辑模式。图5-11标识几个可视化开发器关键区域。

图5-11 可视化开发器界面

Working with the Visual Designer

Let’s start by creating a list of reminders. Click the Hello World TextView control on the stage and then press Delete to remove it. Find the ListView control in the palette and drag it onto the stage. As you drag, the IDE will display various measurement and alignment guidelines to help you position the control which will tend to snap to the edges as you drag close to them. Drop the ListView so that it aligns with the top of the screen. You can position it either at the top-left or the top-center. After it is positioned, find the Properties view on the lower-right side of the Editor. Set the id property to reminders_list_view. The id property is a name you can give to controls that allows you to reference them programmatically in Java code; and this is how we will refer to the ListView later when we modify the Java source code. Change the layout:width property in the Properties window and set it to match_parent. This will expand the control so that it occupies as much space as the parent control it lives within. You will learn more about the details of designing layouts in Chapter 8. For now, your layout should resemble Figure 5-12.
让我们开始创建reminders的列表项吧。在平台上点击Hello World文本框并删除它。找到ListView控件并拖放到平台里。当你拖动时,IDE将显示变化着的尺寸度量以及排列参考来帮助你定们控件,那些当你拖动靠近它们时会企图抓住边缘。拖放listView它会在屏幕顶部排列。你可以定位在顶部的左边或中间。在定位后,找到在编辑器右下侧的属性示图。设置id属性为reminders_list_view。Id属性是你可以在JAVA代码里控件编程参考;且这个是我们将如何参考的ListView在之后修改代码时。修改layout:width属性设置为match_parent。这将扩展控件动态地占有尽可能多的父控件宽度。关于布局在第8章里你会学到更多细节。现在,你的布局将装配成图5-12那样。

图5-12 有个ListView的activity_reminders的布局

In Android, an activity defines the logic that controls user interaction with your app. When learning Android for the first time, it helps to think of an activity as a screen within your app, though activities can be more complicated than that. These activities typically inflate a layout which define where things appear on-screen. The layout files are defined as XML but can be edited visually using the Visual Designer as described earlier.

Editing the Layout’s Raw XML

Click the Text tab along the bottom to switch from visual editing to text editing. This brings up a view of the raw XML for the layout, along with a live preview to the right. Changes you make to the XML are immediately reflected in the preview pane. Change the background color of the RelativeLayout to a dark grey by inserting android:background="#181818" underneath the line which reads android:layout_height="match_parent". Colors are expressed in hexadecimal values. See Chapter 9 for more information on hexadecimal color values. Notice that there is now a dark-grey swatch that appears in gutter next to the line you inserted which set the background color of the root ViewGroup.If you toggle back to Design mode, you will observe that the entire layout is now dark-grey.
点击编辑器左下方的文本选项卡,从图形化编辑切换到文本编辑模式。这带来了布局的原生XML示图,右边伴随着一个预览面板。将RelativeLayout的背景色改为黑色,插入这行:android:background="#181818" 到android:layout_height="match_parent"下面。颜色用十六进制来表达。可以看下第9章关于十六进制颜色的更多信息。注意到你插入那条设置根示图背景色那行后夹缝里的黑灰色的样本。如果你回到设计模式,会观察到整个布局变成黑灰色了。

Hard-coding a color value directly in your XML layout file is not the best approach. A better option is to define a colors.xml file under the values resource folder and define your colors there. The reason we externalize values to XML files such as colors.xml is that these resources are kept and edited in one place and they can be referenced easily throughout your project.

Select the hex value #181818 and cut it to your clipboard by using Ctrl+X | Cmd+X or by choosing Edit ➤ Cut. Type @color/dark_grey in its place. This value uses special syntax to refer to an Android color value named dark_grey. This value should be defined in an Android resource file called colors.xml, but because this file does not yet exist in your project, Android Studio highlights this error in red. Press Alt+Enter and you will be prompted with options to correct the error. Select the second option, Create Color Value Resource dark_grey, and then paste the value in the Resource value: field of the next dialog box that appears and click Ok.
用Ctrl+X | Cmd+X 或通过主菜单的 Edit ➤ Cut选择十六进制的颜色值#181818并剪切它到粘贴板。输入@color/dark_grey到这个位置。这是一个特殊的语法参考到Android资源文件colors.xml,但这个文件还不存在于你的项目里,Android Studio会高亮红色指出这个错误。按Alt+Enter会提示纠正这个错误的选项。选择第二个选项,创建颜色资源dark_grey,接着把刚才的颜色值粘贴到资源值里:下个出现的对话框值域并点击OK。

The New Color Value Resource dialog box will create the Android resource file colors.xml and fill it with the hexadecimal value. Click OK and then click OK in the Add Files to Git dialog box to have this new file added to version control and be sure to select the Remember, Don’t Ask Again checkbox so that you’re not bothered with this message again. Figure 5-13 demonstrates this flow.
新颜色值资源对话框将创建Android资源文件colors.xml并填充十六进制的值。点击OK接着在加入文件到Git对话框里还是点OK,这个新文件加入到版本控制,并确保选择Remember,Don’t Ask Again这个复选框因而这个信息下次不会再打扰你了。图5-13演示了这个流程。

图5-13 析出硬代码颜色值到一个资源文件
The ListView in preview mode contains row layouts that do not provide enough contrast with our chosen background color. To change the way these items appear, you will define a layout for the row in separate layout file. Right-click the layout folder under the res folder and choose New ➤ Layout Resource File. Enter reminders_row in the New Resource File dialog box. Use LinearLayout as the root ViewGroup and keep the rest of the defaults as seen in Figure 5-14.
在预览模式下这个ListView控件包含的行布局在我们所选的背景色时没有足够的对比度。要改变这项表象,你将在另外一个布局文件里定义该布局。右击资源文件夹里的布局文件夹并选择New ➤ Layout Resource File。在新资源文件对话框里输入reminders_row。在根示图组里用线性布局并让其他的为默认值,如图5-14所示。

图5-14 新资源文件对话框

You will now create the layout for an individual list item row. The LinearLayout root ViewGroup is the outermost element in the layout. Set its orientation to vertical by using the controls in the toolbar at the top of the preview pane. Be careful when you use this control because horizontal lines indicate a vertical orientation and vice versa. Figure 5-15 highlights the Change Orientation button.

图5-15 修改走向按钮

Find the properties view along the bottom right of the preview pane. Find the layout:height property and set it to 50dp. This property controls the height of a control, and the dp suffix refers to the density-independent pixels measurement. This is a metric that Android uses to allow layouts to scale properly regardless of the screen density on which they are rendered. You can click any property in this view and start typing to incrementally search for properties, and then press the up- or down-arrows to continue searching.

Drag and drop a horizontal LinearLayout inside the vertical LinearLayout. Drag and drop a CustomView control inside the horizontal LinearLayout and set its class property to android. view.View to create a generic empty view and give it an id property of row_tab. As of this writing, there is a limitation in Android Studio that does not allow you to drag a generic View from the palette. Once you click CustomView, you will get a dialog box with different choices, none of which include the generic View class. Select any class from the dialog and place it in
your layout. Find the class property of the view you just placed using the properties window in the properties pane to the right and change it to android.view.View to work around this limitation. Refer to Listing 5-1 to see how this is done.
拖放一个水平线性布局到垂直线性布局里。拖放一个自定义控件到水平线性控件里并设置它的类属性为android.view.View,来创建一个通用的空示图并付予id为row_tab。在写这篇文章时,Android Studio还是有点局限:它不允许你从控件板里拖放通用示图。当你点击CustomView时,将得到一个有不同选项的对话框,没有一个包括了通用的View类。选择任意一个类并放到你的布局里。为这个限制的情况找到类属性并修改为android.view.View。对照清单5-1看看如何完成它

You will use this generic View tab to flag certain reminders as important. With the edit mode still set to Text, change your custom View’s layout:width property to 10dp and set its layout:height property to match_parent. Using the match_parent value here will make this View control as tall as its parent container. Switch to Design mode and drag and drop a Large Text control inside the horizontal LinearLayout of the Component Tree and set its width and height properties to match_parent. Verify that your Large Text component is positioned to the right of the custom view control. In the Component Tree, the component labelled textView should be nested inside the LinearLayout (horizontal) component and underneath the view component. If textView appears above view, drag it down with your mouse so that it snaps to the second (and last) position. Give your TextView control an id value of row_text and set its textSize property to 18sp. The sp suffix refers to the caleindependent
pixels measurement which performs like dp, but also respects the user’s text size settings so that, for example, if the user were hard of sight and wanted text on her phone to display large, sp would respect this setting, whereas dp would not. Therefore, it’s always a good idea to use sp for textSize. You will learn more about screen measurements in Chapter 8.

Finally, set the TextView control’s text property to Reminder Text. Switch to Text mode and make additional changes to the XML so that your code resembles Listing 5-1.
最后,设置文本控件的文本属性为Reminder Text"。切换到文本编辑模式做更多的修改如清单5-1所示。
清单5-1 reminders_row 布局XML代码
Listing 5-1. The reminders_row Layout XML Code
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/row_tab" />
android:text="Reminder Text"
android:textSize="18sp" />

You will now create some custom colors. Switch to Design mode. Select the root LinearLayout (vertical) in the Component Tree. Set its android:background attribute to @color/dark_grey to reuse the color we defined earlier. Select the row_tab component in the Component Tree and set its android:background attribute to @color/green. Select the
row_text component and set its android:textColor attribute to @color/white. As before, these colors are not defined in the colors.xml file, and you will need to use the same process as before to define them. Switch to Text mode. Press the F2 key repeatedly to jump back and forth between these two additional errors and press Alt+Enter to bring up the IntelliSense suggestion. Choose the second suggestion in both cases and fill in the pop-up dialog box with the values #ffffff for fixing the white color and #003300 to fix the green color. After using the suggestion dialog box to fix these errors, you can hold the Ctrl key and left-click any of these colors which will bring you to the colors.xml file and should look like Listing 5-2.
Listing 5-2. The colors.xml File
<color name="dark_grey">#181818</color>
<color name="white">#ffffff</color>
<color name="green">#003300</color>

Return to the activity_reminders.xml layout file. You will now connect the new reminders_row layout to the ListView in this layout. Switch to Text mode and add the following attribute to the ListView element: tools:listitem="@layout/reminders_row" as shown in Figure 5-16.

Adding this attribute doesn’t change the way the layout renders when it runs; it merely changes what the preview pane uses for each item in the list view. To make use of the new layout, you must inflate it using Java code and we will show you how do that in a subsequent step.
加上这个属性在运行时并没改变布局的表象;它只不过改变这个list view的每个条目的预览面板。要让这个新布局有用,你必须用JAVA代码充实它,这个我们将在后续的章节里展示给你。

图5-16 预览面板现正展示一个用户自定义的ListView布局

Adding Visual Enhancements

You have just completed a custom layout for your ListView rows, but you shouldn’t stop there. Adding a few visual enhancements will make your app stand-out from the others. Take a look at how the text renders on-screen. A careful eye will catch how it is slightly off-center and runs up against the green tab on the left. Open the reminders_row layout to make some minor adjustments. You want the text to gravitate toward the vertical center of the row and give a bit of padding so as to provide some visual separation from the side edges. Replace your TextView element with the code in Listing 5-3.
Listing 5-3. TextView Additional Attributes
android:text="Reminder Text"

The additional ellipsize attribute will truncate text that is too long to fit in the row with an ellipsis on the end, whereas the maxLines attribute restricts the number of lines in each row to 1. Finally, add two more generic view objects from Listing 5-4 after the inner LinearLayout but before the closing tag of the outer LinearLayout to create a horizontal rule beneath the row. The outer LinearLayout is set to a height of 50dp, and the inner LinearLayout is set to a height of 48dp. The two generic view objects will occupy the remaining vertical 2dp inside the layout creating a beveled edge. This is shown in Listing 5-4.
Listing 5-4. Extra Generic Views for beveled edge

Adding Items to ListView

You will now make changes to the activity that uses the layout you just modified. Open the Project tool window and find the RemindersActivity file under your java source folder. It will be located under the com.apress.gerber.reminders package. Find the onCreate() method in this file. It should be the first method defined in your class. Declare a ListView member called mListView and change the onCreate() method to look like the code in Listing 5-5. You will need to resolve the imports ListView and ArrayAdapter.
Listing 5-5. Add List Items to the ListView
public class RemindersActivity extends ActionBarActivity {
private ListView mListView;
protected void onCreate(Bundle savedInstanceState) {
mListView = (ListView) findViewById(R.id.reminders_list_view);
//The arrayAdatper is the controller in our
//model-view-controller relationship. (controller)
ArrayAdapter<String> arrayAdapter = new ArrayAdapter<String>(
//layout (view)
//row (view)
//data (model) with bogus data to test our listview
new String[]{"first record", "second record", "third record"});
//Remainder of the class listing omitted for brevity

This code looks up the ListView by using the id property you defined earlier and removes the the default list divider so that the custom beveled divider we created earlier will render properly. The code also creates an adapter with a few example list items. Adapter is a special Java class defined as part of the Android SDK that functions as the Controller in the Model-View-Controller relationship among the SQLite database (Model), the ListView (View), and the Adapter (Controller). The Adapter binds the Model to the View and handles updates and refreshes. Adapter is the superclass of ArrayAdapter, which binds elements of an Array to a View. In our case, this View is a ListView. ArrayAdapter takes three parameters in its three-argument constructor. The first parameter is a Context object represented by the current activity. The Adapter also needs to know which layout and which field, or fields, in the layout should be used to display row data. To satisfy this requirement, you pass the ids of both the layout and the TextView item in the layout. The last parameter is an array of Strings used for each item in the list. If you run the project at this point, you will see the values
given in the ArrayAdapter constructor displayed in the list view as seen in Figure 5-17.
代码里用你早先定义id属性查找ListView控件,并且去除了默认的条目分隔器,这样我们早先自定义的带斜边分隔器将会很好地渲染。这些代码也创建了一个带有几个例子条目的适配器。适配器是一种特别的JAVA类定义于Android SDK里,它的功能作为在SQLite数据库(模型)里模型-示图-控制器关系的控制器,ListView(示图),还有适配器(控制器)。适配器绑定模型到示图上并且处理更新和刷新。Adapter是ArrayAdapter的超类,用来绑定数组到示图里。在我们的例子里,这个示图是ListView。ArrayAdapter的构造函数有三个参数。第一个是上下文对象用来表达当前activity。适配器也需要知道那个布局和布局里的区域或多个区域用于显示行数据。为满足这个要求,你要传送布局和文本示图条目的id到布局里。最后一个参数是一个字符串数组用于表里的每个条目。如果你这时运行项目,将看到给到ArrayAdapter构造器的那些值显示如图5-17。

图5-17 ListView例子

Press Ctrl+K | Cmd+K to commit your changes to Git and use Adds ListView with custom colors as the commit message. As you work through a project, it is good practice to perform incremental commits to Git while using commit messages that describe the features each commit adds/removes/changes. Keeping this habit makes it easy to identify individual commits and later build release notes for future collaborators and users.
按Ctrl+K | Cmd+K提交更改到Git并填入Adds ListView with custom colors到提交信息里。当你工作于这个项目里,用提交信息描述每次提交的追加/移除/更改来执行增加提交到Git是一个不错的体验。为将来的合作者及用户保持这个习惯来会让事情变得容易点,来鉴别各次单独的提交以及以后的构建记录。

Setting the Action Bar Overflow Menu

Android uses a common visual element called Action Bar. The Action Bar is where many apps locate navigation and other options that allow the user to perform important tasks. When you run the app at this point, you may notice a menu icon that looks like three vertical dots. These dots are known as the overflow menu. Clicking the overflow menu
icon produces a menu with a single menu item entry called settings. This menu item is placed there as part of the new project wizard template and is essentially a placeholder that performs no action. The RemindersActivity loads the menu_reminders.xml file, which is found under the res/menu folder. Make changes to this file to add new menu items to the activity as seen in Listing 5-6.
Listing 5-6. New Menu Items
<menu xmlns:android="http://schemas.android.com/apk/res/android"
tools:context="com.apress.gerber.reminders.app.RemindersActivity" >
<item android:id="@+id/action_new"
android:title="new Reminder"
app:showAsAction="never" />
<item android:id="@+id/action_exit"
app:showAsAction="never" />

In the preceding code listing, the title attribute corresponds to the text displayed in the menu item. Since we’ve hard-coded these attributes, Android Studio will flag these values as warnings. Press F2 to jump between these warnings and press Alt+Enter to pull up the IntelliSense suggestions. You simply need to press Enter to accept the first suggestion, type a name for the new String resource, and as soon as the dialog box pops-up, press Enter again to accept the named resource. Use new_reminder for the name of the first item and exit for the second.
在前述的代码清单里,title属性关联于菜单项的显示文本。因为我们用硬代码设置这些属性值,Andorid Studio将会标记警告。按F2在这些警告间跳跃并按Alt+Enter拉出智能建议。你只需简单地按回车接受第一个建议,输入新字符串资源的名称,接着马上弹出对话框,再按回车接受命名的资源。用new_reminder作为第一个条目的名称,第二个叫exit。

Open RemindersActivity and replace the onOptionsItemSelected() method with the text in Listing 5-7. You will need to resolve the import for the Log class. When you tap a menu item in the app, the runtime invokes this method, passing in a reference to whichever MenuItem was tapped. The switch statement takes the itemId of the MenuItem and either performs a log statement or terminates the activity, depending on which item was tapped. This example uses the Log.d() method that writes text to the Android debug logs. If your app contained multiple activities and those activities were viewed prior to the current activity, then calling finish() would simply pop the current activity off the backstack and control would pass to the next underlying activity. Because the RemindersActivity is the only activity in this app, the finish() method pops the one and only activity off the backstack and results in the termination of your app.
Listing 5-7. onOptionsItemSelected( ) Method Definition
public boolean onOptionsItemSelected(MenuItem item) {
switch (item.getItemId()) {
case R.id.action_new:
//create new Reminder
Log.d(getLocalClassName(),"create new Reminder");
return true;
case R.id.action_exit:
return true;
return false;

Run the app and test the new menu options. Tap the new Reminder menu option and watch the Android log to see the message appear. The Android DDMS (Dalvik Debug Monitor Service) window will open as you run your app on your emulator or device, and you will need to select the Debug option under Log Level to see debug logs. Run your app and interact with the menu items. Pay attention to the logs in the Android DDMS window as you tap the New Reminder menu item. Finally, press Ctrl+K | Cmd+K and commit your code to Git using Adds new reminder and exit menu options as your commit message.
运行这个app并测试新的菜单选项。点击new Reminder菜单项并观察Android日志里出现的信息。Android DDMS(Dalvik调试监控服务)窗将与仿真器或设备运行app的同时打开,并且你将需要选择调试选项低于Log级别才可看到调试日志。运行你的app并与这些菜单项互动。注意在你点击New Reminder菜单项时Android DDMS出来的日志。最后,按Ctrl+K | Cmd+K并提交你的代码到Git,用Adds new reminder and exit menu options作为提交信息。

Persisting Reminders

Because the Reminders app will need to maintain a list of reminders, you will need a persistence strategy. The Android SDK and runtime provide an embedded database engine called SQLite, which is designed to operate in constrained memory environments and is well suited for mobile devices. This section covers the SQLite database and explores how to maintain a list of reminders. Our strategy will include a data model, a database proxy class, and a CursorAdapter. The model will hold the data that is read from and written to the database. The proxy will be an adapter class that will translate simple calls from the app into API calls to the SQLite database. Finally, the CursorAdapter will extend a standard Android class that deals with data access in an abstract way.
因为Reminders这个app需要保存提醒清单,你将需要一个保存策略。Android SDK和运行时系统提供了一个内建的数据库引擎叫SQLite,它设计为有限记忆体环境并很适合于移动设备。这节涵括了SQLite数据库并浏览怎么保存提醒清单。我们的策略将包括一个数据模型,一个数据库代理类,还有一个游标适配器(CursorAdapter)。这个模型将维持数据读写到数据库。代理将是一个适配器类将把从app简单调用转换为到数据库API调用。最后,游标适配器将扩展为一个标准的Android类用于以抽象方式处理数据访问。

Data Model

Let’s start by creating the data model. Right click the com.apress.gerber.reminders package and select New ➤ Java Class. Name your class Reminder and press Enter. Decorate your class with the code in Listing 5-8. This class is a simple POJO (Plain Old Java Object) that defines a few instance variables and corresponding getter and setter
methods. The Reminder class includes an integer ID, a String value, and a numeric importance value. The ID is a unique number used to identify each reminder. The String value holds the text for the reminder. The importance value is a numeric indicator that flags an individual reminder as important (1 = important, 0 = not important). We used int
rather than boolean here because the SQLite database does not have a boolean datatype.
让我们开始创建数据模型。右击com.apress.gerber.reminders包并选择New ➤ Java Class。命名这个类为Reminder并按回车。如清单5-8那样装裱这个类。这是一个简单的POJO(简单的老JAVA对象)定义了几个实例变量和相应的getter及setter方法。Remider类包含了和个整型的ID,字符串变量,和数值化的重要值。ID是用于标记每个提醒的唯一数字。字符串则保存了提醒的文本。重要值是一个数值化的指示器用来标志一个独立的提醒是否重要(1=重要,0=不重要)。我们更愿意用整型而不是布尔值是因为SQLite数据库没有布尔数据类型。
Listing 5-8. Reminder Class Definition
public class Reminder {
private int mId;
private String mContent;
private int mImportant;
public Reminder(int id, String content, int important) {
mId = id;
mImportant = important;
mContent = content;
public int getId() {
return mId;
public void setId(int id) {
mId = id;
public int getImportant() {
return mImportant;
public void setImportant(int important) {
mImportant = important;
public String getContent() {
return mContent;
public void setContent(String content) {
mContent = content;

Now you will create a proxy to the database. Again, this proxy will translate simple application calls into lower-level SQLite API calls. Create a new class in your com.apress. gerber.reminders package called RemindersDbAdapter. Place the code in Listing 5-9 directly inside of your newly created RemindersDbAdapter class. As you resolve imports,
you will notice that DatabaseHelper is not found in the Android SDK. We will define the DatabaseHelper class in a subsequent step. This code defines the column names and indices; a TAG for logging; two database API objects; some constants for the database name, version, and the main table name; the context object; and a SQL statement used to create the database.
现在你将创建一个数据代理。再次,这个代理将转换简单的应用调用为低级别的SQLite API调用。在com.apress.gerber.reminders包里新建一个类叫RemindersDbAdapter。把清单5-9的代码直接加入到这个新类里。当你解析导入时,发现DatabaseHelper并没在Android SDK里。我们将在后面的步骤里定义DatabaseHelper。这些代码定义了列名称和索引;一个TAG作日志;两个上下文对外对象;和一个SQL语句用于创建数据库。
Listing 5-9. Code to be placed inside the RemindersDbAdapter class
//these are the column names
public static final String COL_ID = "_id";
public static final String COL_CONTENT = "content";
public static final String COL_IMPORTANT = "important";
//these are the corresponding indices
public static final int INDEX_ID = 0;
public static final int INDEX_CONTENT = INDEX_ID + 1;
public static final int INDEX_IMPORTANT = INDEX_ID + 2;
//used for logging
private static final String TAG = "RemindersDbAdapter";
private DatabaseHelper mDbHelper;
private SQLiteDatabase mDb;
private static final String DATABASE_NAME = "dba_remdrs";
private static final String TABLE_NAME = "tbl_remdrs";
private static final int DATABASE_VERSION = 1;
private final Context mCtx;
//SQL statement used to create the database
private static final String DATABASE_CREATE =
"CREATE TABLE if not exists " + TABLE_NAME + " ( " +
COL_ID + " INTEGER PRIMARY KEY autoincrement, " +


DatabaseHelper is a SQLite API class used to open and close the database. It uses Context, which is an abstract Android class that provides access to the Android operating system. DatabaseHelper is a custom class, and must be defined by you. Use the code in Listing 5-10 to implement DatabaseHelper as an inner class of RemindersDbAdapter.
Place this proceeding code towards the end of the RemindersDbAdatper but still inside RemindersDbAdapters enclosing braces.
DatabaseHelper是一个SQLite API类,用于打开和关闭数据库。它用到了上下文Context,这是一个抽象的Android类以提供到Android操作系统的访问。DatabaseHelper是一个用户自定义类,且必须由你来定义。用清单5-10实施DatabaseHelper类,作为RemindersDbAdapter的内部类。把这些代码放到RemindersDbAdapter后段,但在类结束的花括号的前面。
Listing 5-10. RemindersDbAdapter
private static class DatabaseHelper extends SQLiteOpenHelper {
DatabaseHelper(Context context) {
super(context, DATABASE_NAME, null, DATABASE_VERSION);
public void onCreate(SQLiteDatabase db) {
public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
Log.w(TAG, "Upgrading database from version " + oldVersion + " to "
+ newVersion + ", which will destroy all old data");

DatabaseHelper extends SQLiteOpenHelper, which helps maintain the database with special callback methods. Callback methods are methods that the runtime environment will call throughout the life-cycle of the application, and they use the SQLiteDatabase db variable supplied to execute SQL commands. The constructor is where the database is initialized. The constructor passes the database name and version to its superclass; and then the superclass does the hard work of setting up the database. The onCreate() method is called automatically by the runtime when it needs to create the database. This operation runs only once, when the app first launches and the database has not yet been created. The onUpgrade() method is called whenever the database needs to be upgraded, for example if the developer changes the schema. If you do change the database schema, be sure to increment the DATABASE_VERSION by one, and onUpgrade() will manage the rest. If you forget to increment the DATABASE_VERSION, your app will crash even in debug build mode. In the preceding code, we run a SQL command to drop the one and only table in the database before running the onCreate() method to re-create the table.
DatabaseHelper 继承于QLiteOpenHelper,用于以特殊的回调方法来维护数据库。回调方法是运行时环境在应用的整个生命周期内都调用的方法,并且它们用到SQLiteDatabase db变量提供SQL命令的执行。在构造器里数据库就被初始化了。构造器传递数据库名和版本到它的父类;然后父类做数据库的设置繁杂工作。当需要建立数据库时onCreate()方法被运行时自动调用。这个操作只运行一次,当app首次启动并且数据库还未创建时。onUpgrade()方法是在数据库更新时被调用,例如假如开发者改变纲要。如果你改变数据库的纲要,确保给DATABASE_VERSION增加1,然后onUpgrade()会处理剩下的事情。如果你忘了给DATABASE_VERSION加1,你的app将崩溃即便在调试构建模式下。在前述的代码里,在运行onCreate()方法之前我们运行一个SQL命令来移除数据库里唯一的表单,以方便重建表单。

The code in Listing 5-11 demonstrates using DatabaseHelper to open and close the database. The constructor saves an instance of Context, which is passed to DatabaseHelper. The open() method initializes the helper and uses it to get an instance of the database, while the close() method uses the helper to close the database. Add this code after all the member variable definitions and before the DatabaseHelper inner class definition inside the RemindersDbAdapter class. When you resolve imports, use the android.database.SQLException class.
Listing 5-11. Database Open and Close Methods
public RemindersDbAdapter(Context ctx) {
this.mCtx = ctx;
public void open() throws SQLException {
mDbHelper = new DatabaseHelper(mCtx);
mDb = mDbHelper.getWritableDatabase();
public void close() {
if (mDbHelper != null) {

Listing 5-12 contains all the logic that handles the creating, reading, updating, and deleting of Reminder objects in the tbl_remdrs table. These are usually referred to as CRUD operations; CRUD stands for create, read, update, delete. Add the proceeding code after the close() method inside the RemindersDbAdapter class.
Listing 5-12. Database CRUD Operations
//note that the id will be created for you automatically
public void createReminder(String name, boolean important) {
ContentValues values = new ContentValues();
values.put(COL_CONTENT, name);
values.put(COL_IMPORTANT, important ? 1 : 0);
mDb.insert(TABLE_NAME, null, values);
//overloaded to take a reminder
public long createReminder(Reminder reminder) {
ContentValues values = new ContentValues();
values.put(COL_CONTENT, reminder.getContent()); // Contact Name
values.put(COL_IMPORTANT, reminder.getImportant()); // Contact Phone Number
// Inserting Row
return mDb.insert(TABLE_NAME, null, values);
public Reminder fetchReminderById(int id) {
Cursor cursor = mDb.query(TABLE_NAME, new String[]{COL_ID,
new String[]{String.valueOf(id)}, null, null, null, null
if (cursor != null)
return new Reminder(
public Cursor fetchAllReminders() {
Cursor mCursor = mDb.query(TABLE_NAME, new String[]{COL_ID,
null, null, null, null, null
if (mCursor != null) {
return mCursor;
public void updateReminder(Reminder reminder) {
ContentValues values = new ContentValues();
values.put(COL_CONTENT, reminder.getContent());
values.put(COL_IMPORTANT, reminder.getImportant());
mDb.update(TABLE_NAME, values,
COL_ID + "=?", new String[]{String.valueOf(reminder.getId())});
public void deleteReminderById(int nId) {
mDb.delete(TABLE_NAME, COL_ID + "=?", new String[]{String.valueOf(nId)});
public void deleteAllReminders() {
mDb.delete(TABLE_NAME, null, null);

Each of these methods uses the SQLiteDatabase mDb variable to generate and execute SQL statements. If you are familiar with SQL, you may guess that these SQL statements will be in the form of an INSERT, SELECT, UPDATE, or DELETE.
每个方法都用到了SQLiteDatabase mDb变量来生成和执行SQL语句。如果你熟悉SQL,你会猜到这些SQL语句将由INSERT, SELECT, UPDATE, 或 DELETE组成。

The two create methods use a special ContentValues object, which is a data shuttle used to pass data values to the database object’s insert method. The database will eventually convert these objects into SQL insert statements and execute them. There are two read methods, one for fetching a single reminder and another for fetching a cursor to iterate all reminders. You will use Cursor later in a special Adapter class.

The update method is similar to the second create method. However, this method calls an update method on the lower-level database object, which will generate and execute an update SQL statement rather than an insert.

Last, there are two delete methods. The first takes an id parameter and uses the database object to generate and execute a delete statement for a particular reminder. The second method requests that the database generate and execute a delete statement to remove all the reminders from the table.
最后,有两个删除方法。第一个针对特定的提醒用 id参数和数据库对象来生成和执行一条删除语句。第二个方法需要数据库生成并执行一条删除语句来移除所有表单里的提醒。

At this point, you need a means of getting reminders out of the database and into the ListView. Listing 5-13 demonstrates the logic necessary to bind database values to individual row objects by extending the special Adapter Android class you saw earlier. Create a new class c
2016-03-13 12:05 添加评论 分享



qiangxcn - 热衷而无建树

赞同来自: studio

看到这篇文章时,Android Studio已到了1.5版本了。你会发现用这个教程建立的空Activity,溢出菜单(暂时这么叫,哪位高人帮忙更正下)根本出不来,在配置文件里将activity的风格属性去除似乎就好了。

studio - i Bug



bjjoy - 初出茅庐java程序员


楼主能稍微解释一下overflow menu那部分就好了,给个运行截图也行。看到这个教程已经是android studio 2.0了,里边没有menu。查看给出的git代码部分,这个menu_reminders.xml感觉和哪里都没有关联。

契卡 - 00年的J2EE程序员





Deselect all check-box options in the Before Commit section.


退出全屏模式 全屏模式 回复