Android 3.0引入的新的片断(Fragment)API,让我们更容易地创建动态用户界面。在这个教程中,我们学习如何将一个两屏的ListView转换成WebView流,以适应大屏幕的单屏流设计,比如在平板设备中。
这篇文章的节奏将比我们的入门教程更快一些。如果你对基本的Android控件或概念不熟悉你可能需要复习这个网站上我们其它的一些教程,甚至是Android API参考。最终的开源代码可以在Google code上下载到。
片段简介
在我们开始之间,让我们在更高的层次上定义一下什么是片段。通常来说,片段是一大块用户界面,它具有自己的生存周期。如果它听起来像一个Activity,那是因为它确实很像一个Activity。然而,片段与Activity不同,片段必须存在于Activity之内。片段不须要在它每次初始化的时候与同一个Activity配对,这使它具有一些灵活性。与Activity一样,片段也无需包含任何用户界面。
步骤0:开始
这个教程假设你读过我们的列表视图教程,你可以下载那个教程的代码,并完成一些任务,然后开始,也可以直接下载这个教程的代码直接开始。
步骤1:重新设计界面
下图示意了我上在列表视图教程中所提到的文章阅读应用,我们还没有考虑并使用片段:
这个流程在相对小屏幕上运行得很不错。然而,在大屏幕上,比如Motorola Xoom平板的10寸屏幕上,在列表视图上却浪费了很多空间。WebView看起来正常,但是有点枯燥。
这就是要引入片段的地方:在大屏幕上,我们可以提供更有效的用户界面,如果我们可以在同一屏上显示ListView和WebView。当用户点击左边“面板”的列表视图中的某一项时,右边的WebView更新显示相应的内容。这种工作流程经常用于email或文档或RSS阅读器。下图就是重新设计之后的界面示意图:
步骤2:转换为基于片段的设计
现在我们知道了新的流程应该如何设计,我们也知道当前的两个活动必须转换成片段。我们将分几步来完成这个转换。第一步保持界面样子不变,只是使用片段修改每个界面内容。一个片段将包含当前的ListView,另一个包含WebView。然后我们再转到单个屏幕的实现,修改ListView和WebView之间的消息传递。
首先,将你的程序的项目构建目标改变Android 3.0。在Eclipse中,右键点击项目并选择“属性”。点击Android部分并选中Android 3.0。我们不使用任何Google API,所以Android开源项目版本足够了。然后点击“确定”按钮。
现在你就可以访问新的API了,包括片段API。
注意:在将来的教程中,我们将讨论如何使用新的兼容层来使得像片段API这样的技术在更早版本的Android设备上也能工作。但是现在它只能运行在Android 3.0设备上。
步骤3:创建片段类
创建两个Java类来代表两个片段:ListView界面和WebView界面。将它们命名为TutListFragment和TutViewerFragment。TutListFragment将继承ListFragment类,TutViewerFragment只是继承Fragment类。
在TutListFragment类中,我们需要重写两个方法: onListItemClick()和onCreate()。这些方法的内容看起来应该很熟悉,它与之前我们讲过的TutListActivity类的代码一致。这个代码很快就要修改,但是现在暂时不需要,下面是当前TutListFragment类的代码:
- @Override
- public void onListItemClick(ListView l, View v, int position, long id) {
- String[] links = getResources().getStringArray(R.array.tut_links);
- String content = links[position];
- Intent showContent = new Intent(getActivity().getApplicationContext(),
- TutViewerActivity.class);
- showContent.setData(Uri.parse(content));
- startActivity(showContent);
- }
- @Override
- public void onCreate(Bundle savedInstanceState) {
- super.onCreate(savedInstanceState);
- setListAdapter(ArrayAdapter.createFromResource(getActivity()
- .getApplicationContext(), R.array.tut_titles,
- R.layout.list_item));
- }
TutViewerFragment类更简单一些。我们基于当前片段运行在同一个活动下并且直接从Fragment类内问部获取目标数据的事实。添加一个重写onCreateView()方法。这个方法的代码应该看起来像这样:
- @Override
- public View onCreateView(LayoutInflater inflater, ViewGroup container,
- Bundle savedInstanceState) {
- Intent launchingIntent = getActivity().getIntent();
- String content = launchingIntent.getData().toString();
- WebView viewer = (WebView) inflater.inflate(R.layout.tut_view, container, false);
- viewer.loadUrl(content);
- return viewer;
- }
直接访问活动实例的能力非常有用,但是在后面会引起一个问题。如果这个片段存在于带有列表片段的界面上会怎么样呢?在那样的情况下,就会没有启动目标来获取URL。类似的在TutListFragment中,只要当用户点击一个列表项时我们都直接启动一个新的Activity。如果TutViewFragment在同一个活动中存在什么怎么样呢?如果这样的话,启动一个新的活动就没有意义了。我们将在这个教程的后面回过头来解决这个问题。
步骤4:添加片段布局资源
现在创建一个新的名为“tutlist_fragment.xml”的布局文件来表示包含文章列表的片段。片段布局资源使用你创建的Fragment类的标签和引用。
- xmlns:android="http://schemas.android.com/apk/res/android"
- android:name="com.mamlambo.tutorial.tutlist.TutListFragment"
- android:layout_width="match_parent"
- android:layout_height="match_parent"
- android:id="@+id/tutlist_fragment">
接下来,创建一个类似的布局文件,叫做tutview_fragment.xml:
- xmlns:android="http://schemas.android.com/apk/res/android"
- android:name="com.mamlambo.tutorial.tutlist.TutViewerFragment"
- android:layout_width="match_parent"
- android:layout_height="match_parent"
- android:id="@+id/tutview_fragment">
步骤5:更新Activity类
TutListActivity和TutViewerActivity类必须修改。TutListActivity类只有一个方法,onCreate(),现在需要修改它来加载你在前一步创建的合适的片段布局资源,如下:
- @Override
- public void onCreate(Bundle savedInstanceState) {
- super.onCreate(savedInstanceState);
- setContentView(R.layout.tutlist_fragment);
- }
TutListActivity应该继承Activity类,而不是ListActivity类。
TutViewerActivity类也需要类似的修改,它的onCreate()方法现在看起来像这样:
- @Override
- public void onCreate(Bundle savedInstanceState) {
- super.onCreate(savedInstanceState);
- setContentView(R.layout.tutview_fragment);
- }
步骤6:检查你的进度
尝试现在运行程序。你会发现它和以前一样。没什么值得兴奋的,不是么?然而,整个用户界面现在使用片段来运行了。这使你需要做的下一步修改更加平滑,我们添加一个新的布局来组合两个片段以在一个界面上显示。然而可能你也注意到了,片段之间的通信的处理和我们文章之间的通信一样。事实上,我们每个片段对应的活动保持不变。当一个活动包含并管理两个片段时,这将不符合需求。首先让我们来修复它。
步骤7:改变TutListFragment通信
像你在步骤3中学到的一样,从TutListFragment对象直接启动一个活动不再有效了。WebView UI可能与列表是同一个活动的一部分——总之那就是我们对于大屏幕的计划。在那种情况下,我们只想在第二个片段中更新WebView的URL。
做这些修改,我们需要做几件事情。首先,我们让片段不依赖于它们所在的活动。要做到这一点,在TutListFragment类中添加一个侦听器,如下:
- public interface OnTutSelectedListener {
- public void onTutSelected(Uri tutUri);
- }
然后通过更新onListItemClickListener()方法来触发它,如下:
- @Override
- public void onListItemClick(ListView l, View v, int position, long id) {
- String[] links = getResources().getStringArray(R.array.tut_links);
- String content = links[position];
- tutSelectedListener.onTutSelected(Uri.parse(content));
- }
接下来让TutListActivity类实现OnTutSelectedListener接口,如下:
- public class TutListActivity extends Activity implements
- TutListFragment.OnTutSelectedListener {
- ...
- @Override
- public void onTutSelected(Uri tutUri) {
- Intent showContent = new Intent(getApplicationContext(),
- TutViewerActivity.class);
- showContent.setData(tutUri);
- startActivity(showContent);
- }
现在我们分离了片段的功能,这些功能用于处理用户界面,作为控制器的活动,向下一个活动传递数据。我们后面要修改onTutSelected()方法来决定是否启动一个新的活动实例或者更新现有的片段实例。
步骤8:改变TutViewerFragment通信
现在让我们把注意力转到TutViewerFragment类上,它的代码也需要修改。片段不再查询启动目标来找出加载哪个URL,而是等待被通知要加载哪个URL。在样,我们可以直接修改WebView而不需要每次加载都重新创建片段。
首先,修改TutViewerFragment类,让它包含一个叫做updateUrl()的方法:
- public void updateUrl(String newUrl) {
- if (viewer != null) {
- viewer.loadUrl(newUrl);
- }
- }
其次,删除所有onCreateView()方法下的功能,除了inflate()的调用。在TutViewerActivity类中,添加这些功能检索Intent然后调用updateUrl()方法,如下:
- @Override
- public void onCreate(Bundle savedInstanceState) {
- super.onCreate(savedInstanceState);
- setContentView(R.layout.tutview_fragment);
- Intent launchingIntent = getIntent();
- String content = launchingIntent.getData().toString();
- TutViewerFragment viewer = (TutViewerFragment) getFragmentManager()
- .findFragmentById(R.id.tutview_fragment);
- viewer.updateUrl(content);
- }
此时此刻,程序的行为还是没有变化。然而通过进一步的代码,片段现在可以共存在同一个活动中或者分开。
步骤9:添加双片段布局
现在让我们来创建带有两个片段的布局,以供特定情况使用。在layout-land目录(你可能需要自己创建),粘贴一份tutlist_fragment.xml。它将对横屏和竖屏提供不同的布局。竖屏模式将保持不变。编辑这个文件如下:
- xmlns:android="http://schemas.android.com/apk/res/android"
- android:layout_width="match_parent"
- android:layout_height="match_parent"
- android:orientation="horizontal">
- android:name="com.mamlambo.tutorial.tutlist.TutListFragment"
- android:layout_width="0dp"
- android:layout_height="match_parent"
- android:id="@+id/tutlist_fragment"
- android:layout_weight="45">
- android:name="com.mamlambo.tutorial.tutlist.TutViewerFragment"
- android:layout_width="0dp"
- android:layout_height="match_parent"
- android:id="@+id/tutview_fragment"
- android:layout_weight="55">
这将界面分隔成水平地两个片段(左右结构)。
步骤10:添加动态选项
现在我们可以为程序添加一些简单的逻辑,可以在启动一个新的活动(双屏模式)和更新存在的片段(单屏模式)之间切换。
为了达到这个目的,更新TutListActivity类的onTutSelected()方法如下:
- @Override
- public void onTutSelected(String tutUrl) {
- TutViewerFragment viewer = (TutViewerFragment) getFragmentManager()
- .findFragmentById(R.id.tutview_fragment);
- if (viewer == null || !viewer.isInLayout()) {
- Intent showContent = new Intent(getApplicationContext(),
- TutViewerActivity.class);
- showContent.setData(Uri.parse(tutUrl));
- startActivity(showContent);
- } else {
- viewer.updateUrl(tutUrl);
- }
- }
我们所做的就是获取片段并检查它是否是现存的布局的一部分。如果不是,查看器活动启动,否则更新已存在的片段。
步骤11:运行最新的使用片段的程序
到此,程序将有两种模式:竖屏保持不变,横屏显示列表位于WebView的左侧。现在可以做几个改进,但是只是做微调,优化。比如,如果你在竖屏WebView模式下并旋转屏幕,结果还是只有WebView界面。你必须点击返回以获得双面视图。程序修正不在这个教程讲述的范围,但是你可以发现,如果使用适当的布局并且加上一些活动逻辑,你可以对于不同的屏幕和设备做到非常强大和灵活。
总结
片段API帮助组织用户界面组件,以使它们可以实现跨活动重用。这样,程序可以在相对少的代码量下,动态地适应它的流程和用户界面。你也能看到基于片段构建的代码更容易重新组织。更值得高兴的是,通过Google提供的兼容库,现在任何程序都可以使用片段了,它甚至兼容到Android 1.6。现在就使用片段来为每一个屏幕大小和形状创建你的程序用户界面吧!
【编辑推荐】