首页 » Android程序设计:第2版 » Android程序设计:第2版全文在线阅读

《Android程序设计:第2版》Fragment和布局

关灯直达底部

在前面几节中,我们描述了如何开发使用Fragment项目,并且在蜂巢版本之前的Android系统上编译它。但是,对于大多数应用而言,这仅仅只是个开始。应用可以在Froyo设备上编译并不代表它能够很好地在Froyo设备上运行。实际上,要想达到在任何情况下UI效果都很理想是非常困难的。本节将会描述其中一些问题,并提出一些解决方案。

坦白而言,这不是什么好事。应用在其生命周期中,可能会运行在1080i大屏幕电视上,全家人在后座观看;也可能是在12寸的平板上运行,通过横向模式查看;或者在手机上运行,通过纵向模式查看;因此UI展现不可能是唯一的。要达到良好的UI效果,唯一的途径是在设计这些UI时,考虑到不同屏幕大小这些问题。把UI导入到另一台设备需要的定制工作可能仅仅是调整一下图片和字体,也可能需要对UI的整个工作流程做很大修改。

使UI适用于不同的设备的最简单的方式是使用配置限定符(configuration qualifier)。配置限定符只不过是在应用的资源路径树的路径名称后面添加特定的后缀。它支持应用包含多个不同的资源版本,每个版本都和特定内容相关。

举个例子,一个活动的屏幕是在单个布局文件中:main.xml。该布局是通过常量R.layout.main和代码关联,在…/res/layout/main.xml文件中定义项目资源。

通过配置限定符,同一个常量R.layout.main可能有不同的定义,其中一个在…/res/layout/main.xml文件中定义,另一个在…/res/layout-port/main.xml文件中定义。Android系统在运行时会根据当前设备的显示方式,把该常量解析成横向或纵向模式。

配置限定符不仅包含屏幕方向、像素密度、屏幕纵横比以及绝对尺寸这些因素,还涵盖一些其他方面,如语言、区域、停靠模式(docking mode)以及白天/黑夜显示模式。一个资源常量可能包含成千上万个变量,每个变量表示一组限定符组合,虽然这种情况极少存在,但也是有可能的。

配置限定符并不会使得处理各种不同的场景变简单;而只是使之变成可能。Eclipse开发者工具对限定符提供了最小支持。Wizard的第二个页面创建新的资源文件(File→New→Other→Android XML Values File;当命名新的资源文件后,按下Next按钮)列出了配置限定符,支持添加任意多少的配置限定符。

使用Fragment功能的UI开发人员对配置限定符情有独钟,因为它们支持横向和纵向模式的自然切换。对于以横向模式查看的西方读者,Fragment UI自然就会把上下文信息放到屏幕的左侧边栏,把保存详细信息的fragment放到右侧边栏,如图7-3所示。

图7-3:经典的横向Fragment布局

在纵向模式下,显然以上布局是不合理的。然而,通过配置限定符创建布局的横向和纵向变量是很简单的,只需要改变LinearLayout变量,把横向fragment改成纵向fragment。图7-4显示了经过小小的修改后,在纵向模式下,UI效果看起来也很好。

图7-4:纵向Fragment布局

以上展现方式的假设是屏幕有很多空间,可以把单个屏幕划分成多个区域。需要记住的是,fragment对于大屏幕是很有效的展现方式,但是在UI设计中使用它来改进显示效果往往并不可行。虽然当今有些手机的屏幕尺寸很大,可以支持fragment,但是大部分设备还不支持。在这些设备中,你需要把新的、基于fragment的UI平滑地切换为Android原始的、扑克牌大小的用户界面。

可以通过几种方式来选择UI风格。一种方式是维护多种不同的应用版本,在Android市场中通过manifest文件的market filters来区分。举个例子,为小尺寸屏幕定制的manifest文件可能包含如下内容:


<supports-screens                android:largeScreens=/"false/"                android:xlargeScreens=/"false/" />  

为大尺寸屏幕定制的manifest文件可能包含如下内容:


<supports-screens                android:smallScreens=/"false/"                android:normalScreens=/"false/"                android:largeScreens=/"true/"                android:xlargeScreens=/"true/" />  

不同应用版本如果除了屏幕尺寸大小不同以外,还有其他因素,以上这种定制的展示方式就很有意义了。举个例子,某个应用版本希望在特定的环境中使用(可能是对汽车中某个版本的目标进行扩展,监测汽油消耗),该场景包括特定尺寸的屏幕,使用Market过滤器来选择UI风格可能是很有效的。

在个别情况下,通过market filter使得UI风格匹配不同的设备可以正常工作,但是这种方式通常会带来很多麻烦。我们简单看一下market filter属性的文档,就会发现区分不同屏幕尺寸这个功能正处于变更过渡期。对于版本13,在前面实例中使用的...Screens属性已经被android:requiresSmallestWidthDp属性替代了。遗憾的是,Market当前过滤功能尚未包含该属性。

在运行时选择基于fragment的UI还是card-stack-based的UI的标准是:以更明智的方式使用配置限定符。例如,以图7-3所描述的基于fragment的UI布局为例,该应用是个简单的联系人视图:点击左侧边栏的联系人姓名就会在右侧栏的fragment中显示其详细信息。其布局看起来如下:


<LinearLayout            xmlns:android=/"http://schemas.android.com/apk/res/android/"            android:orientation=/"horizontal/"            android:layout_            android:layout_            >            <ListView                android:id=/"@+id/contacts/"                android:layout_                android:layout_                android:layout_weight=/"1/"            />            <FrameLayout                android:id=/"@+id/contact_detail/"                android:layout_                android:layout_                android:layout_weight=/"2/"                android:background=/"@color/blue/"            /></LinearLayout>  

我们前面已经介绍了如何使用配置限定符在单个应用中创建不同的布局版本。在前面的例子中,我们又增加了一种类似的布局,它对纵向模式显示进行了优化,并使用配置限定符告诉Android运行时使用哪一种布局。

切换回statck-of-cardsUI风格的技巧在于创建新的布局,该布局不包含任何Fragment组件,代码如下:


<LinearLayout            xmlns:android=/"http://schemas.android.com/apk/res/android/"            android:orientation=/"horizontal/"            android:layout_            android:layout_            >            <ListView                android:id=/"@+id/contacts/"                android:layout_                android:layout_                android:layout_weight=/"1/"            /></LinearLayout>  

剩下要做的就是修改代码,使得它在创建fragment之前可以进行检查,确保有足够的地方可以放置该fragment。如果没有地方放置该fragment,就切换回基于stack的UI风格。

stackFragment方法和stackActivity方法分别实现了fragment UI风格和statck-of-cardsUI风格。如例7-1所示,stackFragment方法使用事务管理器来替换当前的fragment;而stackActivity方法是通过Intent,以通用方式启动新的活动。把联系方式的详细信息抽象成一个通用类,UI类ContactDetailsActivity和ContactDetailsFrament都使用该类,这样看起来相对更简洁明了。

在运行时,还有一种方式可以对基于Fragment的UI和card-stack-based的UI进行选择:使用startActivityFromFrament类的Activity方法。该方法会拦截fragment启动intent。

要在示例程序中使用该技术,我们需要采取有些不同的策略。不论是哪一种UI风格,左侧边栏面板的列表视图是个fragment。该fragment总是启动intent,显示详细信息。如果应用是在足够小的屏幕上,需要stack-of-cards的UI风格,intent会启动详细信息活动。相反地,如果应用可以使用fragment UI风格,根活动会拦截所有启动操作,而是显示ContactDetailsFragment。这段代码看起来和之前的很相似,都包含布局、card-stack和fragment。第一种定义只是在目录res/layout-small(内容限定符“small”),而第二种定义是在布局路径中,包含其他限定符(比如“large”)。

以下是根活动代码段。R.layout.main类会自动调用ContactsFragment类,StartActivityFromFragment方法基于布局项决定是否启动DetailFragment实例。如果没有布局项,会启动新的activity实例。

最后要说明的是,这段代码是ContactFragment类的实现。如果点击列表视图,该类一般会作为intent实例转发。根活动可以选择转发该intent实例,或者把请求作为新的fragment实例。