第六章:Reminders实验:第二部分

最后更新于:2022-04-01 22:05:17

# 第六章:Reminders实验:第二部分 > 译者:[qiangxcn](http://ask.android-studio.org/?/people/qiangxcn) > 来源:[Learn Android Studio 汉化教程 第六章:Reminders实验:第二部分](http://ask.android-studio.org/?/article/979) 这章涵括了通过对话框捕获用户输入。也继续演示适配器及SQLite数据库的使用。这章里,我们将完成从第五章开始的例子。 ## 增加/删除提醒 第五章里这个例子的屏幕还没有任何提醒。为了让布局看到提醒清单,当app启动时加载些提醒的例子上去,这是很有用的。如果你想挑战这章处理过程,比较下清单6-1和你的代码。清单6-1检查是否有保存的实例,如果有,处理将设置例子数据。为此,代码调用了些DatabaseAdapter的方法;一个是清除所有的提醒,另一个则是加入些提醒。 Listing 6-1. Add Some Example Reminders ```java 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... } ``` 有几个`createReminder()`方法的调用,每个都是用一个字符串作提醒文本,以及一个布尔值标记提醒是否重要。我们设置一些实体值让显示好看点。选中`createReminder()`方法调用的代码块按以`Ctrl+Alt+M | Cmd+Alt+M`汲取方法,如图6-1示。这是一个通过重构菜单和快捷键结合的重构操作。输入`insertSomeReminders`作为方法名并确认。这些代码块将以析出的方法代替,这些代码块将在方法体里了。 ![](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-08-06_57a5d3c5ae5c8.png) 图6-1 析出方法对话框,创建一个`insertSomeReminders()`方法 运行app看到的界面,拥有提醒例子了。你的app应该看起来象图6-2的截屏那样。有些提醒显示绿色的行选项卡,而那些重要的提醒则显示橙色行选项卡。提交你的更改到Git,备注Adds Example reminders。 ![](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-08-06_57a5d3c5cf111.png) 图6-2 插入了提醒例子的实时运行 ## 响应用户的互动 没有app不响应输入。在这节,你将加入响应点击事件的逻辑并且最终允许用户编辑独立的提醒。在app里的主要元素是`ListView`,一个Android `View`的子类。直致此刻,除了把它放到布局里,你还没做过其它的什么。`android.view.View`是所有你看到的所有屏幕元素的超类。 把清单6-2的代码加到`RemindersActivity`的`onCreate()`方法后面,即在方法结束花括号之前,并解决导入类的问题。这是一个匿名内部类实现`OnItemClickListener`接口,它只有一个方法,`onItemClicked()`。这个对象将用于你互动与它所跟踪的`ListView`元素的实时运行。当用户点击`ListView`时,匿名内部类的`onCreate()`方法将被调用。我们定义一个吐司,一个Android SDK的类。调用`Toast.makeText()`将导致在屏幕上弹出一个小菜单,显示出你传送给方法的文本。你可以看到清单6-2的代码,作为快速正确使用吐司的指引。 注意:吐司信息可能在一些设备上被隐藏。另一个替代途径是记录一个日志信息,用Android的日志记录器,那个在第十二章会有祥述。 Listing 6-2. Set an `OnItemClickListener` with a `Toast` ```java //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(); } }); ``` 点击屏幕第一个提醒将调用`onItemClick()`方法,在清单里的位置是0,所以索引是0。这个逻辑将弹出位置信息的文本,如图6-3所示。 ![](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-08-06_57a5d3c5f360a.png) 图6-3 点击第一条提醒弹出的吐司信息 ## 用户对话框 大家所熟悉的一些点击事件,现在你可增强点击监听为显示一个对话框。用清单6-3的代码替换所有的`onItemClick()`方法。解决导入时,请用`android.support.v7.app.AlertDialog`类。 Listing 6-3. `onItemClick( )` Modifications to Allow Edit/Delete ```java 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 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(); } }); } ``` 在处理代码里你看到了另Android类在工作,`AlertDialog.Builder`。这个类创建器嵌入在`AlertDialog`类里,是个静态类,它用于创建`AlertDialog`。 在这个实例里的代码远超于创建一个`ListView`以及用`ArrayAdapter`喂入条目到`ListVite`。你可能回想起第五章的这种模型。这个章节建立一个有两个潜在元素(选项)的数组,编辑提醒和删除提醒,在传送到`ListView`之前,这个是,轮流地,传送给`AlertDialog.Builder`。这个创建器然后用这些选项清单创建并显示一个对话框。 注意下代码清单6-3最后的选择部分。有点象之前的`OnItemClickListener()`;不管怎样,我们附加了一个`modeListView`的监听器,那个在当前`OnItemClickListener`里创建的。你看到的是带有`OnItemClickListener`的`ListView`(指的是`activity_reminders.xml`里的`reminders_list_view`),(译者:在它的`onItemClick`方法里)创建了另一个`modeListView`还有另一个嵌入的`OnItemClickListener`来响应来自`modeListView`的选项点击事件。 内嵌的点击监听器弹出一个吐司信息来指示是编辑还是删除所点击的条目。它也重命名来自外部调用者`OnItemClickListener`的位置参数叫做`masterListPosition`来区分内置的`OnItemClickListener`的位置参数。这个`masterListPosition`用于吐司指明哪条提醒被用于可能的编辑或删除。最后,`dialog.dismiss()`方法调用于点击监听器,用来完全移除对话框。 运行一下来测试新特性如图6-4所示。点击一条提醒接着在弹出对话框再点编辑或删除。如果吐司里报告的提醒位置和你点击的对不上,再确认一下你吐司里追加`masterListPosition`值而不是`postion`。按`Ctrl+K | Cmd+K`提交更改到Git,并附上信息Adds a ListView dialog for individual list items。 ![](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-08-06_57a5d3c618031.png) 图6-4 模拟删除一条提醒 ## 提供多选上下文菜单 随着这个app逐渐成形,现在将达成这种特性:在一次操作里允许多选提醒条目用于编辑。这种特性只能在API 11或更高版本上才有效。你将通过使用资源载入协定来有条件地达成这种特性。这个处理将在这章稍后解释并且第8章有所有的细节。你也将需要包含一个运行时检查看看是否支持这种特性。 为提醒行条目创建另一个备用的布局。打开项目工具窗口在资源夹上右击带出来的上下文。选择新的Android资源文件命名为r`eminders_row`,如图6-5示。 ![](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-08-06_57a5d3c647296.png) ![](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-08-06_57a5d3c670dbe.png) 图6-5 新资源文件`reminders_row` 选择资源类型为布局,这样就自动改变目录名为`layout`。在有效限定词部分选择相应的版本然后双击(`>>`)双V纹章按钮增加到选择的限定词里。输入11作为平台API级别并注意到目录名更新了,并反映了选择的限定词版本。那叫资源限定并且它们整合于运行时里,让你可以为特别的设备和平台版本定制你的用户界面。按回车(或点OK)接受这个新资源限定目录并继续。如果你打开项目工具窗并设为Android示图,如图6-6,你将看到`layout`文件夹下所有的`reminders_row`布局文件在一起。还有,Android示图的项目工具窗让相关联的文件聚集在一起使你有效地管理它们。 ![](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-08-06_57a5d3c69e2cb.png) 图6-6 聚集的布局 复制整个原始的`reminders_row`布局并粘贴到刚新建版本11的布局里。现在修改内层的水平线性布局的背景色属性如下: ```xml android:background="?android:attr/activatedBackgroundIndicator" ``` 这个值分配背景色属性带前缀`?android:attr/`,这是参考定义于Android SDK里的一种风格。Android SDK提供了很多这样的预定义属性,并且你可以使用它们到你的app上。在多选模式时`activatedBackgroundIndicator`属性使用了系统定义的有效背景色。 ## 目标于早期的SDK 现在你将学习如何引入一个平台依赖的特性。打开项目工具窗并打开在Gradle剧本区下面的app模块 `build.gradle`文件(它会在第二个入口里)。Gradle文件含有编译的构建逻辑和app的包装。参照你的app所支持的平台所有配置存在于这些特别的文件里(第十三章深度探索了Gradle构建系统)。注意到最低SDK版本设置为8,这让你的app可以运行在99%的Android设备上。现在我们将创建的这个特性需要最低SDK版本是11。涵括这节的代码和特性将允许用户运行在SDK11或更高版本带来的更先进的特性,叫上下文动作模式。而且,低于SDK11的将不会有这个特性,但更重要的是,他们的app不会因此而崩溃。 ## 加入上下文动作模式 接下来介绍的多选模式的上下文动作模式菜单,是一个动作清单可用于所有选择项的上下文。加载一个新的菜单资源,在`res/menu`目录上右击选择`New ➤ Menu`资源文件并命名为`cam_menu`。以下列代码清单完成它: ```xml ``` 这个资源文件为上下文菜单定义了唯一的删除动作条目。这里你也正用了一点不同的属性值。这个特别的属性象之前的背景色属性一样也是访问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`命名空间里拉出一个图标,作为它的图标。 用新的上下文菜单和一个备用的布局运行于API 11或更高版本,你可以加载一个有条件的检查以允许带有上下文动作菜单的多选模式。打开`RemindersActivity`并加上下面的代码到`onCreate`方法的后面。 ```java if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB) { } ``` 构建类从`android.os`包里导入并且给你访问一系列的常量值,那些可用于以指定的API级别来匹配设备。在这个例子里,你期望API级别等于或高于HONEYCOMB而它实际包含整数11。把清单6-4的代码插入到刚定义的块里。IF块保护了运行OS低于HONEYCOMB的系统不让这个app崩溃。 Listing 6-4. `MultiChoiceModeListener` Example ```java 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) { } }); ``` 解决所有导入的问题。你将注意到`getIdFromPosition()`没有定义并标为红色(错误)。把光标放在这个方法上并按劳取酬`Alt_Enter`调用`IntelliSense`并选择创建方法。选择`RemindersActivity`作为目标类。选择整型作为返回类型。用清单6-5的代码完成这个方法。 Listing 6-5. `getIdFromPosition()` method ```java private int getIdFromPosition(int nC) { return (int)mCursorAdapter.getItemId(nC); } ``` 此处理逻辑定义了一个`MultiChoiceModeListener`并把它附到L`istView`。无论何时你长按ListView的一个条目时,运行时调用`onCreateActionMode()`方法在`MultiChoiceModeListener`上。如果方法返回真值,就进入多选动作模式。这里重写方法的逻辑填充了一个上下文菜单用于显示动作条。使用多选动作模式的好处是你可以选择多行。一次点击选中了某个条目,接下来再点击则会去选这个条目。当你点击上下文菜单里的每个条目,运行时将带着被点的条目调用`onActionItemClicked()`方法。 在这个方法里,通过比较`itemId`和你加到菜单项里的删除元素的`id`,检查下删除条目是否被点击了。(看看在本节开始时所描述有关删除条目`ID`的XML清单)。如果这个条目被选,轮询所有的清单条目并要求`mDbAdapter`来删除它们。在删除所选条目后,逻辑调用动作模式对象`finish()`方法,这将禁止多选动作模式并返回`ListView`到普通模式。接下来你调用`fetchAllReminders()`方法众数据库重新装载所有的提醒条并传送在`mCursorAdapter`对象`changeCursor`方法所返回的游标。最后,方法返回真值来表明动作被正确地处理了。所有其它的没被正确处理的逻辑,方法返回假,表明一些其它的事件监听器可能处理了这个点击事件。 Android Studio将高亮一堆错误语句,因为你正使用的API无效或低于Honeycomb。这外错误生成于Lint,一个状态分析工具内建于Android SDK并完全整合在Android Studio里。你需要加上下面的声明在`RemindersActivity.onCreate()`方法里,在`@Override`声明之上或之下都行,而且要目标API解决导入问题: ```java @TargetApi(Build.VERSION_CODES.HONEYCOMB) ``` 这个特别的声明告诉Lint欺骗方法调用是提供的API级别的目标,不管构建配置所指定的。提交更改到Git用Adds Contextual Action Mode with context action menu作注释。图6-7描写了你可能看到的新特性。 ![](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-08-06_57a5d3c6c4055.png) 图6-7 允许多选模式 ## Implementing Add, Edit, and Delete 迄今为止,你已经添加了从清单里删除提醒的逻辑。这个逻辑在上下文动作模式里也可以有效执行。目前还无法插入新提醒或编辑现有的提醒。无论如何,你将创建一个用户自定义对话框或添加提醒,另一个用来编辑现有的提醒。最终,你将绑定这些对话框到`RemindersDbAdapter`。 在处理这个之前,先添加一些新的颜色。把这它们加到`colors.xml`文件里: ```xml #bababa #000000 #ff1118ff ``` 注意:通常,你的app可能有一个全面的颜色风格,这将保证所有屏幕和对话框的一致性。无论如何,颜色风格超出了这个简单例子的范围了。 ## 策划一个用户对话框 开发的一个好习惯是:以简单工具优于执行它来描绘你的用户界面。这样在引入任何代码之胶,让你图形化元素如何适合屏幕。你可用一个编辑器如inkscape,它是跨平台的,或者你可以用笔记本的纸张和铅笔。在移动商务里,这些描绘称作线框图(wireframe)。 图6-8是我们的用户对话框的插图,完成于inkscape。这个线框图有意不正式,来强调元素的摆放好过一个精准的外观和感觉。 ![](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-08-06_57a5d3c6e4476.png) 图6-8 线框图描绘用户自定义对话框 注意:这本书里的一些用户自定义的绘图和线框图正是用lnkspace创建的,一个多平台向量图形编辑器。在`www.inkscape.org`上,它是免费的。 把线框图就在那了,你可以开始计划如何排列屏幕的元素。因为大多数元素从上排到下,在最外层用一个竖直线性布局,这是最显然的选择。不管怎样,底下的两个按钮并排在一起。这样你可以用一个水平线性布局放在前面的竖直线性布局里。图6-9加上一些声明来描绘和高亮这些内嵌的元件。 ![](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-08-06_57a5d3c707183.png) 图6-9 线框图描绘`widget`标签 ## 从策划到代码 随着线框图在那,尽量用图形化设计器来开发布局。开始在res目录上右击并选择创建一个新的Android资源文件然后命名为`dialog_custom`,资源类型为布局资源。以线性布局作为根元素,完成对话框。接着我们的线框图,从调色板拖放视图控件到这个平台上。清单6-6包含了这个完成的XML布局文件,并带有将在代码中用到的`ID`值。 Listing 6-6. Completed `dialog_custom.xml` ```xml
';