惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

F
Fortinet All Blogs
S
Secure Thoughts
月光博客
月光博客
美团技术团队
雷峰网
雷峰网
Exploit-DB.com RSS Feed
Exploit-DB.com RSS Feed
奇客Solidot–传递最新科技情报
奇客Solidot–传递最新科技情报
N
News and Events Feed by Topic
freeCodeCamp Programming Tutorials: Python, JavaScript, Git & More
Forbes - Security
Forbes - Security
W
WeLiveSecurity
P
Proofpoint News Feed
阮一峰的网络日志
阮一峰的网络日志
爱范儿
爱范儿
G
GRAHAM CLULEY
cs.AI updates on arXiv.org
cs.AI updates on arXiv.org
AI
AI
Last Week in AI
Last Week in AI
Google Online Security Blog
Google Online Security Blog
Schneier on Security
Schneier on Security
云风的 BLOG
云风的 BLOG
Threat Intelligence Blog | Flashpoint
Threat Intelligence Blog | Flashpoint
Recent Announcements
Recent Announcements
Webroot Blog
Webroot Blog
T
Tor Project blog
Cisco Talos Blog
Cisco Talos Blog
N
News and Events Feed by Topic
罗磊的独立博客
The Register - Security
The Register - Security
Blog — PlanetScale
Blog — PlanetScale
T
Threat Research - Cisco Blogs
博客园 - 【当耐特】
Apple Machine Learning Research
Apple Machine Learning Research
人人都是产品经理
人人都是产品经理
T
The Exploit Database - CXSecurity.com
www.infosecurity-magazine.com
www.infosecurity-magazine.com
B
Blog
腾讯CDC
Microsoft Azure Blog
Microsoft Azure Blog
酷 壳 – CoolShell
酷 壳 – CoolShell
H
Hacker News: Front Page
Application and Cybersecurity Blog
Application and Cybersecurity Blog
Engineering at Meta
Engineering at Meta
Latest news
Latest news
IT之家
IT之家
D
DataBreaches.Net
博客园 - 司徒正美
N
Netflix TechBlog - Medium
V
V2EX
钛媒体:引领未来商业与生活新知
钛媒体:引领未来商业与生活新知

博客园 - jlxuqiang

左手信仰,右手长剑 Cheat Sheet 《高级语言程序设计》课程指导 第 23 章 深入学习 intent 和任务 (选讲) 第 21 章 隐式 intent 12 信息2班 《基于Android的软件开发》课程成绩 test 13 信息2班 《安卓程序设计基础》课程成绩 第 20 章 相机 II:拍摄并处理照片 第 19 章 相机 I:取景器 第 18 章 上下文菜单与上下文操作模式 第 17 章 存储与加载本地文件 第 16 章 工具栏 第 15 章 应用本地化 第 14 章 fragment 的保留 第 13 章 使用 MediaPlayer 播放音频 第 12 章 对话框 第 11 章 使用 ViewPager 第 10 章 使用 fragment argument
第 22 章 Master-Detail 用户界面
jlxuqiang · 2015-10-24 · via 博客园 - jlxuqiang

请参考教材,全面理解和完成本章节内容... ...

复制工程ch21,将工程目录改名为ch22.

本章将为「陋习手记」应用打造适应平板设备的用户界面,让用户能同时查看到列表和明细界面并与它们进行交互。图22-1展示了这样的列表明细界面。通常我们也称为主从用户界面(master-detail interface)。

image

图22-1 同时显示列表和明细的用户界面

本章的测试需要一台平板设备(我用大手机也行)或AVD。要创建平板AVD,首先启动Android Virtual Device Manager,然后在分类Category里单击Tablet,选择一个平板AVD设备。最后设置AVD的系统目标版本为API级别第17级。如图22-2所示:

image

图22-2 AVD平板设备选择

22.1 增加布局灵活性

在手机设备上,CrimeListActivity生成的是单版面(single-pane)布局。而在平板设备上,为同时显示主从视图,我们需要它生成双版面(two-pane)布局。

在双版面布局中,CrimeListActivity将同时托管CrimeListFragmentCrimeFragment(如图22-3所示)。

image

图22-3 不同类型的布局

要实现双版面布局,需执行如下操作步骤:

  • 修改SingleFragmentActivity,不再硬编码实例化布局;
  • 创建包含两个fragment容器的布局;
  • 修改CrimeListActivity,实现在手机设备上实例化单版面布局,而在平板设备上实例化双版面布局。
22.1.1 修改SingleFragmentActivity

CrimeListActivitySingleFragmentActivity的子类。当前,SingleFragmentActivity只能实例化activity_fragment.xml布局。为使SingleFragmentActivity类更加抽象、灵活,我们让它的子类自己提供布局资源ID。

在SingleFragmentActivity.java中,添加一个protected方法,返回activity需要的布局资源ID,如代码清单22-1所示。

代码清单22-1 增加SingleFragmentActivity类的灵活性(SingleFragmentActivity.java)

image

现在,虽然SingleFragmentActivity抽象类的功能和以前一样,但是它的子类可以选择覆盖getLayoutResId()方法返回所需布局,而不用再使用固定不变的activity_fragment.xml布局。

22.1.2 创建包含两个fragment容器的布局

在包浏览器中,右键单击res/layout/目录,选择创建一个新的布局文件,并将文件命名为activity_twopane.xml,然后选择LinearLayout作为根元素。参照图22-4,完成双版面布局的XML内容定义。

image

图22-4 包含两个fragment容器的布局(layout/activity_twopane.xml)

注意,布局定义的第一个FrameLayout也有一个fragmentContainer布局资源ID,因此SingleFragmentActivity.onCreate(...)方法的相关代码能够像以前一样工作。activity创建后,createFragment()方法返回的fragment将会出现在屏幕左侧的版面中。

要测试新建布局,在CrimeListActivity类中覆盖getLayoutResId()方法,返回R.layout.activity_twopane资源ID,如代码清单22-2所示。

代码清单22-2 使用双版面布局(CrimeListActivity.java)

image

在平板设备或AVD上运行「陋习手记」应用,确认可以看到如图22-5所示的用户界面。注意,右边的明细版面什么也没显示,点击任意列表项,也无法显示对应的陋习明细信息。本章稍后将完成crime明细fragment容器的编码及设置工作。

image

图22-5 平板设备上的双版面布局

当前,无论是在手机还是在平板设备上,CrimeListActivity都会生成双版面的用户界面。下一节将使用别名资源来解决这个问题。

22.1.3 使用别名资源

别名资源是一种指向其他资源的特殊资源。它存放在res\values\目录下,并按照约定定义在refs.xml文件中。

本小节将分别创建用于手机指向activity_fragment.xml布局的别名资源,以及用于平板指向activity_twopane.xml布局的别名资源。

在项目中,右键单击res\values\目录,创建一个新的XML文件, New->Values resource file,并将文件命名为refs.xml,参照代码清单22-3,在新建的refs.xml中添加item节点定义。

代码清单22-3 创建默认的别名资源值(res/values/refs.xml)

image

别名资源指向了单版面布局资源文件。别名资源自身也具有资源ID:R.layout.activity_masterdetail。注意,别名的type属性决定了资源ID属于什么内部类。即使别名资源自身存放在res/values/目录中,它的资源ID依然归属于R.layout内部类。

修改CrimeListActivity类的相应代码,以R.layout.activity_masterdetail资源ID替换R.layout.activity_fragment,如代码清单22-4所示。

代码清单22-4 再次切换布局(CrimeListActivity.java)

image

运行「陋习手记」应用,验证别名资源是否可以正常工作。一切正常的话,CrimeListActivity应该再次生成了单版面布局。

image

图22-5-1 再次生成了单版面布局

创建平板设备专用可选资源

存放在values目录下的别名资源是系统默认的别名资源,所以,CrimeListActivity生成了默认的单版面布局。

现在,创建一个可选别名资源,以实现在平板等大屏幕设备上,activity_masterdetail别名资源可以指向activity_twopane.xml双版面布局资源。

在包浏览器中,右键单击res目录,新建一个名为values-sw600dp的目录。将res\values\refs.xml文件复制到 res\values-sw600dp目录下,然后参照代码清单22-5,修改别名资源指向双版面布局。

注:如果在工程里看不见新建的values-sw600dp的目录,在工程外部将refs.xml文件复制到 res\values-sw600dp目录下。

代码清单22-5 用于大屏幕设备的可选资源(res/values-sw600dp/refs.xml)

image

配置修饰符-sw600dp是什么意思?SW是smallest width(最小宽度)的缩写,虽然字面上是宽度的含义,但它实际指的是屏幕的最小尺寸(dimension),因而SW与设备的当前方向无关。

在确定可选资源时,-sw600dp配置修饰符表明:对任何最小尺寸为600dp或更高dp的设备,都使用该资源。对于指定平板的屏幕尺寸规格来说,这是一种非常好的做法。

需要说明的是, Android 3.2中才引入了最小宽度配置修饰符。这意味着,运行Android 3.0或Android 3.1系统的平板设备无法识别它。

为解决该问题,可以增加另一种使用-xlarge(仅适用于Android 3.2以前的版本)屏幕尺寸修饰符的可选资源。

单击res目录,新建一个名为values-xlarge的目录。然后将values-sw600dp\refs.xml资源文件复制到新建的values-xlarge目录中。现在我们有了另一个如代码清单22-6所示的资源文件。

代码清单22-6 用于Android 3.2之前版本的可选资源(res/values-xlarge/refs.xml)

<resources>

<item name="activity_masterdetail" type="layout">@layout/activity_twopane</item>

</resources>

配置修饰符-xlarge包含的资源适用于最低尺寸为720×960dp的设备。该修饰符仅适用于运行Android 3.2之前版本的设备。Android 3.2及之后的系统版本会自动找到并使用-sw600dp修饰符目录下的资源。

分别在手机和平板上运行CriminalIntent应用。确认单双版面的布局达到预期效果。

22.2 Activity:fragment 的托管者

既然单双版面的布局显示已处理完成,我们来着手添加CrimeFragment给crime明细fragment容器,让CrimeListActivity可以展示一个完整的双版面用户界面。

为保持fragment的独立性,我们可以在fragment中定义回调接口,委托托管activity来完成那些不应由fragment处理的任务。托管activity将实现回调接口,履行托管fragment的任务。

fragment回调接口

为委托工作任务给托管activity,通常的做法是由fragment定义名为Callbacks的回调接口。回调接口定义了fragment委托给托管activity处理的工作任务。任何打算托管目标fragment的activity必须实现这些定义的接口。

有了回调接口,无需知道自己的托管者是谁,fragment可以直接调用托管activity的方法。

实现CrimeListFragment.Callbacks回调接口

要实现一个Callbacks接口,首先定义一个成员变量存放实现Callbacks接口的对象。然后将托管activity强制类型转换为Callbacks对象并赋值给Callbacks类型变量。

在CrimeListFragment.java中,添加一个Callbacks接口。另外再添加一个mCallbacks变量并覆盖onAttach(Activity)onDetach()方法,完成变量的赋值与清空,如代码清单22-7所示。

代码清单22-7 添加回调接口(CrimeListFragment.java)

image

现在,CrimeListFragment有了调用托管activity方法的途径。另外,它也不关心托管activity是谁。只要托管activity实现了CrimeListFragment.Callbacks接口,而CrimeListFragment中一切代码行为都保持不变。

注意,未经类安全性检查,CrimeListFragment就将托管activity强制转换为CrimeListFragment.Callbacks对象。这意味着,托管activity必须实现CrimeListFragment.Callbacks接口。这并非是不良的依赖关系,但记录下它非常重要。

接下来,在CrimeListActivity类中,实现CrimeListFragment.Callbacks接口,如代码清单22-8所示。暂时不用理会onCrimeSelected(Crime)空方法,稍后,我们再来处理。

代码清单22-8 实现回调接口(CrimeListActivity.java)

image

最终,在onListItemClick(...)方法里以及在用户创建新crime时,CrimeListFragment将调用onCrimeSelected(Crime)方法。现在,我们先来思考如何实现CrimeListActivity.onCrimeSelected(Crime)方法。

onCrimeSelected(Crime)方法被调用时,CrimeListActivity需要完成以下二选一的任务:

  • 如使用手机用户界面布局,启动新的CrimePagerActivity
  • 如使用平板用户界面布局,将CrimeFragment放入detailFragmentContainer中。

为确定需实例化手机还是平板界面布局,可以检查布局ID。但最好最准确的检查方式是检查布局是否包含detailFragmentContainer。因为,布局文件名随时可能更改,并且我们也不关心布局是从哪个文件实例化产生。我们只需知道,布局文件是否包含可以放入CrimeFragmentdetailFragmentContainer

如果证实布局包含detailFragmentContainer,那么我们就会创建一个fragment事务,将我们需要的CrimeFragment添加到detailFragmentContainer中。如果之前就有CrimeFragment存在,首先应从detailFragmentContainer中移除它。

在CrimeListActivity.java中,实现onCrimeSelected(Crime)方法,按照布局界面的不同,响应crime的选择,如代码清单22-9所示。

代码清单22-9 有条件的CrimeFragment启动(CrimeListActivity.java)

image

最后,在CrimeListFragment类中,在启动新的CrimePagerActivity的地方,调用onCrimeSelected(Crime)方法。

在CrimeListFragment.java中,修改onListItemClick(...)onOptionsItemSelected(MenuItem)方法实现对Callbacks.onCrimeSelected(Crime)方法的调用,如代码清单22-10所示。

代码清单22-10 调用全部回调方法(CrimeListFragment.java)

image

onOptionsItemSelected(...)方法中调用回调方法时,只要新增一项crime记录,就会立即重新加载crime列表。这很有必要,因为在平板设备上,新增crime记录后,crime列表依然可见。而在手机设备上,crime明细界面会在列表界面之前出现,列表项的刷新可以很灵活地处理。

在平板设备上运行CriminalIntent应用。新添加一项crime记录,可以看到,一个CrimeFragment视图立即被添加并显示在detailFragmentContainer容器中。然后,尝试查看其他旧记录以观察CrimeFragment视图的切换,如图22-6所示。

image

图22-6 已关联的主界面和明细界面

然而,如果修改crime明细内容,列表项并不会以最新数据刷新显示。当前,在CrimeListFragment.onResume()方法中,只有新添加一项crime记录,我们才能立即重新刷新显示列表项界面。但是,在平板设备上,CrimeListFragmentCrimeFragment将会同时可见。因此,当CrimeFragment出现时,CrimeListFragment不会暂停,自然也就永远不会从暂停状态恢复了。这就是crime列表项不能重新加载刷新的根本原因。

下面,我们将在CrimeFragment中添加另一个回调接口来修正该问题。

CrimeFragment.Callbacks回调接口的实现

CrimeFragment类中定义的接口如下:

public interface Callbacks {
    void onCrimeUpdated(Crime crime);
}

保存crime记录的修改时,CrimeFragment类都将调用托管activity的onCrimeUpdated(Crime)方法。CrimeListActivity类将会实现onCrimeUpdated(Crime)方法,从而重新加载CrimeListFragment的列表。

实现CrimeFragment的接口之前,首先在CrimeListFragment类中新增一个方法,用来重新加载刷新CrimeListFragment列表,如代码清单22-11所示。

代码清单22-11 新增updateUI()方法(CrimeListFragment.java)

image

然后,在CrimeFragment.java中,添加回调方法接口以及mCallbacks成员变量并实现onAttach(...)onDetach()方法,如代码清单22-12所示。

代码清单22-12 新增CrimeFragment回调接口(CrimeFragment.java)

image

然后在CrimeListActivity类中实现CrimeFragment.Callbacks接口,在onCrimeUpdated(Crime)方法中重新加载crime列表项,如代码清单22-13。

代码清单22-13 刷新显示crime列表(CrimeListActivity.java)

image
在CrimeFragment.java中,如果Crime对象的标题或问题处理状态发生改变,触发调用onCrimeUpdated(Crime)方法,如代码清单22-14所示。

代码清单22-14 调用onCrimeUpdated(Crime)方法(CrimeFragment.java)

image

onActivityResult(...)方法中,Crime对象的记录日期、现场照片以及嫌疑人都有可能修改,因此,还需在该方法中调用onCrimeUpdated(Crime)方法。当前,crime现场照片以及嫌疑人并没有出现在列表项视图中,但并排的CrimeFragment视图应该显示了这些更新,如代码清单22-15所示。

代码清单22-15 再次调用onCrimeUpdated(Crime)方法(CrimeFragment.java)

image

CrimeListActivity现在有了CrimeFragment.Callbacks接口的一个良好实现。然而,如果在手机设备上运行CriminalIntent应用,它将会崩溃。记住,任何托管CrimeFragment的activity都必须实现CrimeFragment.Callbacks接口。因此,我们还需要在CrimePagerActivity类中实现CrimeFragment.Callbacks接口。

对于CrimePagerActivity类,onCrimeUpdated(Crime)方法什么都不用做,因此直接实现一个空方法即可(如代码清单22-16所示)。CrimePagerActivity类托管CrimeFragment时,必需的列表加载刷新已经在OnResume()方法中完成了。

代码清单22-16 CrimeFragment.Callbacks接口的空实现(CrimePagerActivity.java

image

在平板设备上运行CriminalIntent应用。确认CrimeFragment视图中发生的任何修改,ListView视图都能够更新显示,如图22-7所示。

image

图22-7 列表刷新显示了明细界面的修改

至此,CriminalIntent应用的开发全部完成了。