Android用户界面设计:使用片段
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
- publicvoidonListItemClick(ListViewl,Viewv,intposition,longid){
- String[]links=getResources().getStringArray(R.array.tut_links);
- Stringcontent=links[position];
- IntentshowContent=newIntent(getActivity().getApplicationContext(),
- TutViewerActivity.class);
- showContent.setData(Uri.parse(content));
- startActivity(showContent);
- }
- @Override
- publicvoidonCreate(BundlesavedInstanceState){
- super.onCreate(savedInstanceState);
- setListAdapter(ArrayAdapter.createFromResource(getActivity()
- .getApplicationContext(),R.array.tut_titles,
- R.layout.list_item));
- }
TutViewerFragment类更简单一些。我们基于当前片段运行在同一个活动下并且直接从Fragment类内问部获取目标数据的事实。添加一个重写onCreateView()方法。这个方法的代码应该看起来像这样:
- @Override
- publicViewonCreateView(LayoutInflaterinflater,ViewGroupcontainer,
- BundlesavedInstanceState){
- IntentlaunchingIntent=getActivity().getIntent();
- Stringcontent=launchingIntent.getData().toString();
- WebViewviewer=(WebView)inflater.inflate(R.layout.tut_view,container,false);
- viewer.loadUrl(content);
- returnviewer;
- }
直接访问活动实例的能力非常有用,但是在后面会引起一个问题。如果这个片段存在于带有列表片段的界面上会怎么样呢?在那样的情况下,就会没有启动目标来获取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
- publicvoidonCreate(BundlesavedInstanceState){
- super.onCreate(savedInstanceState);
- setContentView(R.layout.tutlist_fragment);
- }
TutListActivity应该继承Activity类,而不是ListActivity类。
TutViewerActivity类也需要类似的修改,它的onCreate()方法现在看起来像这样:
- @Override
- publicvoidonCreate(BundlesavedInstanceState){
- super.onCreate(savedInstanceState);
- setContentView(R.layout.tutview_fragment);
- }
步骤6:检查你的进度
尝试现在运行程序。你会发现它和以前一样。没什么值得兴奋的,不是么?然而,整个用户界面现在使用片段来运行了。这使你需要做的下一步修改更加平滑,我们添加一个新的布局来组合两个片段以在一个界面上显示。然而可能你也注意到了,片段之间的通信的处理和我们文章之间的通信一样。事实上,我们每个片段对应的活动保持不变。当一个活动包含并管理两个片段时,这将不符合需求。首先让我们来修复它。
步骤7:改变TutListFragment通信
像你在步骤3中学到的一样,从TutListFragment对象直接启动一个活动不再有效了。WebView UI可能与列表是同一个活动的一部分——总之那就是我们对于大屏幕的计划。在那种情况下,我们只想在第二个片段中更新WebView的URL。
做这些修改,我们需要做几件事情。首先,我们让片段不依赖于它们所在的活动。要做到这一点,在TutListFragment类中添加一个侦听器,如下:
- publicinterfaceOnTutSelectedListener{
- publicvoidonTutSelected(UritutUri);
- }
然后通过更新onListItemClickListener()方法来触发它,如下:
- @Override
- publicvoidonListItemClick(ListViewl,Viewv,intposition,longid){
- String[]links=getResources().getStringArray(R.array.tut_links);
- Stringcontent=links[position];
- tutSelectedListener.onTutSelected(Uri.parse(content));
- }
接下来让TutListActivity类实现OnTutSelectedListener接口,如下:
- publicclassTutListActivityextendsActivityimplements
- TutListFragment.OnTutSelectedListener{
- ...
- @Override
- publicvoidonTutSelected(UritutUri){
- IntentshowContent=newIntent(getApplicationContext(),
- TutViewerActivity.class);
- showContent.setData(tutUri);
- startActivity(showContent);
- }
现在我们分离了片段的功能,这些功能用于处理用户界面,作为控制器的活动,向下一个活动传递数据。我们后面要修改onTutSelected()方法来决定是否启动一个新的活动实例或者更新现有的片段实例。
步骤8:改变TutViewerFragment通信
现在让我们把注意力转到TutViewerFragment类上,它的代码也需要修改。片段不再查询启动目标来找出加载哪个URL,而是等待被通知要加载哪个URL。在样,我们可以直接修改WebView而不需要每次加载都重新创建片段。
首先,修改TutViewerFragment类,让它包含一个叫做updateUrl()的方法:
- publicvoidupdateUrl(StringnewUrl){
- if(viewer!=null){
- viewer.loadUrl(newUrl);
- }
- }
其次,删除所有onCreateView()方法下的功能,除了inflate()的调用。在TutViewerActivity类中,添加这些功能检索Intent然后调用updateUrl()方法,如下:
- @Override
- publicvoidonCreate(BundlesavedInstanceState){
- super.onCreate(savedInstanceState);
- setContentView(R.layout.tutview_fragment);
- IntentlaunchingIntent=getIntent();
- Stringcontent=launchingIntent.getData().toString();
- TutViewerFragmentviewer=(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
- publicvoidonTutSelected(StringtutUrl){
- TutViewerFragmentviewer=(TutViewerFragment)getFragmentManager()
- .findFragmentById(R.id.tutview_fragment);
- if(viewer==null||!viewer.isInLayout()){
- IntentshowContent=newIntent(getApplicationContext(),
- TutViewerActivity.class);
- showContent.setData(Uri.parse(tutUrl));
- startActivity(showContent);
- }else{
- viewer.updateUrl(tutUrl);
- }
- }
我们所做的就是获取片段并检查它是否是现存的布局的一部分。如果不是,查看器活动启动,否则更新已存在的片段。
步骤11:运行最新的使用片段的程序
到此,程序将有两种模式:竖屏保持不变,横屏显示列表位于WebView的左侧。现在可以做几个改进,但是只是做微调,优化。比如,如果你在竖屏WebView模式下并旋转屏幕,结果还是只有WebView界面。你必须点击返回以获得双面视图。程序修正不在这个教程讲述的范围,但是你可以发现,如果使用适当的布局并且加上一些活动逻辑,你可以对于不同的屏幕和设备做到非常强大和灵活。
总结
片段API帮助组织用户界面组件,以使它们可以实现跨活动重用。这样,程序可以在相对少的代码量下,动态地适应它的流程和用户界面。你也能看到基于片段构建的代码更容易重新组织。更值得高兴的是,通过Google提供的兼容库,现在任何程序都可以使用片段了,它甚至兼容到Android 1.6。