概述:
Android平台可以运行在各种屏幕尺寸的设备上, 系统会合适地调整APP的UI来适应各种屏幕. 通常情况下, 作为开发者我们要做的就是灵活的设计UI并使用替代资源来对不同的屏幕尺寸进行优化. 但是, 有时候可能我们会想要更进一步地提升不同屏幕尺寸的用户体验. 比如, 平板电脑提供了更大的屏幕空间, 让我们可以一次展示更多的信息给用户. 手机设备通常屏幕比较小, 我们需要独立地显示这些信息, 这就对信息的分布提出了更高的要求. 而不仅仅是将它们变大这么简单. 单纯的变大会让设计看起来很傻, 用户体验也不好.
在Android 3.0中, Android引入了一组新的framework API让我们可以更加有效地设计activity来支持大屏幕: 就是Fragment API. Fragment让我们可以独立地处理组件的行为, 这就让我们可以在平板设备上创建多面板的layout或者在手机设备上使用独立的activity. Android 3.0还引入了ActionBar, 它在屏幕的顶端提供了一个专用UI来为用户识别APP以及提供操作和导航.
本文提供指导可以帮助大家创建一个使用fragment和action bar来优化平板和手机上的用户体验的APP.
基本原则:
这里是一些可以让我们在平板和手机上提供良好用户体验的基本原则:
l 基于fragment来设计activity, 这让我们可以很好的重用组件.特别是在平板上的复合面板layout和手机上的单面板layout之间重用. 一个Fragment代表activity中的一个特性或者一部分用户接口. 我们可以将fragment作为一个activity的模块化部分, 它拥有自己的生命周期, 并且在运行的时候我们可以灵活的添加或者删除它们.
l 使用action bar,但要确定设计的灵活性, 以便在不同屏幕尺寸下可以调整action bar的layout. ActionBar是一个为activity提供的UI组件, 它用于代替传统的屏幕顶端的标题栏. 默认情况下, action bar在左边会包含一个APP的logo, 然后是activity标题, 右边则是访问options menu的条目.
我们可以从options menu直接启用菜单条目使其出现在action bar中. 还可以在action bar中添加导航功能, 比如tabs和下拉列表, 可以使用APP图标来补充系统的返回键行为, 来导航APP的home activity或者”up”键.
l 实现灵活的layout, 并像一个web开发者那样思考. 一个灵活的layout设计让我们的APP适应屏幕尺寸的变化. 并非所有的平板都是同样尺寸的, 也不是所有的手机都是同样的尺寸. 即便当我们为平板和手机提供了不同的fragment, 依然很有必要设计适合的layout来调整尺寸以适应屏幕.
注意: 除了action bar, 所有其它的功能都可以在Android 3.0中实现, 此外, 甚至可以通过支持库在Android 1.6中实现fragment的设计.
创建单面板和复合面板的layout:
最有效的在平板和手机下创建不同用户体验的方法是使用不同的fragment组合, 我们可以为平板设计复合面板layout, 并为手机设计单面板的layout. 比如, 一个在平板上运行的APP可以在左边展示一个标题列表, 并在右边展示完整的内容– 在左边选择一个标题可以让右边的内容更新. 但是在手机上两种组件应该独立的显示在屏幕上– 选择一个标题会使得整个屏幕开始显示内容. 有两种技术可以通过fragment实现这种设计:
l 多fragment, 一个activity: 不管设备尺寸如何, 只使用一个activity,但是会在运行时决定是否在一个layout中组合使用fragment(来创建一个复合面板设计)或者替换fragment(来创建一个单面板设计).
l 多fragment, 多activity: 平板上, 在一个activity中放置多个fragment;在手机上, 则使用独立的activity来显示每个fragment. 栗如, 当设计平板的时候使用两个fragment在一个activity中, 为手机也使用相同的activity, 却替换其layout只包含第一个fragment. 当运行在手机上的时候, 我们需要转换fragment(比如用户选择了一个条目), 启动另一个activity来显示第二个fragment.
选择哪种方案取决于我们的设计和个人偏好. 第一种选择要求我们在运行时确定屏幕尺寸并动态的添加每个fragment – 而不是在xml文件中声明每个fragment – 因为我们不能从activity中移除一个在XML layout中声明的fragment. 当使用第一种方法的时候, 我们可能还需要在每次fragment改变的时候根据操作和导航模式更新action bar. 在某些情况下, 这些因素可能不会影响我们的设计, 所以使用一个activity并切换fragment可以工作的很好(特别是如果平板设计需要动态添加fragment). 然而其它时候, 在手机上动态切换fragment会让代码变得更加复杂, 因为我们必须在activity的代码中管理所有的fragment动作(而不是使用layout资源文件来定义fragment)并且要自己管理fragment的后退栈(而不是让普通的activity栈来处理后退导航).
本文主要关注第二种选择, 小屏幕时, 它会在独立的activity中显示每个fragment. 使用这种方法意味着我们可以使用替代layout文件为不同的屏幕尺寸定义不同的fragment, 保持fragment代码模块化, 管理action bar简单, 并让系统处理手机上所有的返回栈工作. 下图展示了这一行为是如何在平板和手机上工作的:
在上图中显示的APP里, Activity A是”主Activity”, 它根据屏幕尺寸使用不同的layout来同时显示一个或者两个fragment:
l 在一个大屏幕上, ActivityA layout包含Fragment A和Fragment B.
l 在小屏幕上, ActivityA layout只包含Fragment A. 为了在Fragment B中显示详情, 必须打开Activity B. 在这里Activity B永远不会在一个大屏幕中显示. 它只是用来展示Fragment B的, 所以只有在小屏幕设备上才会出现两个Fragment分别显示的情况.
根据屏幕尺寸, 系统使用了不同的main.xml layout文件:
手机使用res/layout/main.xml:
<?xml version="1.0" encoding="utf-8"?>
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent">
<!--"Fragment A" -->
<fragment class="com.example.android.TitlesFragment"
android:id="@+id/list_frag"
android:layout_width="match_parent"
android:layout_height="match_parent"/>
</FrameLayout>
平板上则使用res/layout-large/main.xml:
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="horizontal"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:id="@+id/frags">
<!-- "Fragment A" -->
<fragment class="com.example.android.TitlesFragment"
android:id="@+id/list_frag"
android:layout_width="@dimen/titles_size"
android:layout_height="match_parent"/>
<!-- "Fragment B" -->
<fragment class="com.example.android.DetailsFragment"
android:id="@+id/details_frag"
android:layout_width="match_parent"
android:layout_height="match_parent" />
</LinearLayout>
当用户选择了一个条目的时候APP的反应取决于Fragment B在layout中是否可用:
l 如果Fragment B在layout中, 那么Activity A提醒Fragment B更新它自己.
l 如果Fragment B不在layout中, 那么Activity A会启动ActivityB.
想要实现该功能的话, 模块化fragment十分重要. 特别是应该遵循这两条原则:
l 不要从一个fragment直接操作另一个.
l 确保所有fragment关注的内容都在fragment内部而不是由它所在的Activity来处理.
为了避免直接从一个fragment调用另一个fragment, 可以在每个fragment中定义一个回调interface, 它可以用来传递事件给它的Activity. 当Activity收到一个回调的时候(比如用户选择了一个条目), 它会根据当前的fragment配置做出合适的相应. 栗如, Activity A可以这样处理条目选择:
public class MainActivity extends Activity implements TitlesFragment.OnItemSelectedListener {
...
/** This is a callback that the list fragment (Fragment A)
calls when a list item is selected */
public void onItemSelected(int position) {
DisplayFragment displayFrag = (DisplayFragment) getFragmentManager()
.findFragmentById(R.id.display_frag);
if (displayFrag == null) {
// DisplayFragment (Fragment B) is not in the layout (handset layout),
// so start DisplayActivity (Activity B)
// and pass it the info about the selected item
Intent intent = new Intent(this, DisplayActivity.class);
intent.putExtra("position", position);
startActivity(intent);
} else {
// DisplayFragment (Fragment B) is in the layout (tablet layout),
// so tell the fragment to update
displayFrag.updateContent(position);
}
}
}
当DisplayActivity(也就是Activity B)启动的时候, 它会读取intent中的数据并传递给DisplayFragment(Fragment B). 如果Fragment B需要传回一个结果给FragmentA(因为Activity B是由startActivityForResult()启动的), 那么操作流程类似于FragmentB和Activity B之间的接口. 也就是Activity B实现一个不同的由Fragment B定义的接口. 当Activity B从fragment收到一个回调的时候, 它可以为activity设置该结果(使用setResult())并结束自己. Activity A就可以接收结果并传递给Fragment A.
使用Action Bar:
Action Bar不管在平板还是手机上都是Android重要的UI组件. 为了确保actionbar的行为在所有尺寸的屏幕上都可以合理工作, 在使用ActionBar API的时候不应该使用复杂的自定义功能. 在使用标准的ActionBar API来设计action bar的时候, Android会在不同尺寸的屏幕下完美地处理所有的工作. 这是一些在使用action bar的时候应该遵守的重要原则:
l 当设置一个菜单条目到action条目的时候, 避免使用”always”值. 在菜单资源中, 如果希望菜单条目出现在actionbar中, 应为android:showAsAction属性指定”ifRoom”值. 但是我们可能会在actionview不为溢出菜单提供默认action的时候(就是必须作为一个action view出现的时候)使用”always”. 但是不应该多于一次或者两次的使用”always”. 大多数其它情况下如果我们希望条目作为action item出现的时候应该使用”ifRoom”作为android:showAsAction的值. 强行堆积太多的action item在action bar中, 会使得UI显得杂乱无章, 也会使得action item跟action bar中的其它组件堆叠在一起, 比如action title.
l 当向action bar添加一个带有texttitle的action item的时候, 同样也应该提供一个图标, 并声明showAsAction=”ifRoom|withText”.这样, 如果没有足够的空间给title, 但是会有足够的空间给图标, 这时只有图标会被使用.
l 总是为actionitem提供一个title, 甚至不启用”withText”的时候, 因为用户可以通过一个长按操作看到title – title中的文字会以一个toast的形式出现.
l 如果可以的话避免使用自定义导航模式. 应该使用内置的tab和和下拉导航模式 – 它们被设计可以适应不同的屏幕尺寸. 栗如, 当宽度对tabs和其他action item来说太窄的时候(比如手机竖屏的时候), tabs将会出现在action bar下面. 如果我们必须在action bar中创建一个自定义导航模式或者其它自定义view, 需要仔细的在小屏幕上测试它们.
比如, 下面演示了系统如何基于屏幕尺寸来适配action bar. 在手机上, 只有两个action item可以显示, 这样剩下的menu item就得在溢出菜单上才能看到(因为android:showAsAction被设置为了”ifRoom”), tabs会出现在一个独立的行中. 在平板上, 可以容纳更多的action item, tabs也是.
使用splitaction bar:
当我们的APP运行在Android 4.0及更高版本中, 有一个额外的模式可以用在action bar上, 叫做”split action bar”. 当我们启用split action bar的时候, 一个独立的bar将会出现在屏幕底部, 当activity运行在一个较窄的屏幕上的时候, 它可以用来显示所有的action选项. Split action bar确保了在较窄的屏幕上有合理的空间来显示action item, 并且还为导航和title留下了空间. 要使用split action bar, 只需要简单的添加uiOptions=”splitActionBarWhenNarrow”到<activitiy>或者<application>中就可以了.
左边是启用了导航tabs的split action bar. 右边是禁用了app icon和title的split action bar.
如果我们想要隐藏顶上的main action bar, 因为正在使用split action bar, 所以可以调用setDisplayShowHomeEnabled(false)来禁用app图标. 这种情况下, 在主action bar中已经没剩下什么东西了, 所以它会消失, 剩下的只有在上面的导航tabs和下面的actionitem(上面右图).
使用”up”导航:
我们可以在action bar中使用APP图标, 在合适的时候来方便用户导航– 可以作为一个方法返回到home activity(就像在网站中点击了logo那样)活着作为一种方法来导航到前一个页面. 尽管它看起来跟返回键有些相似, up导航选项为那些直接从外部进入APP的情况提供了一个更加可预测的方法, 比如从一个notification, app widget或者其它的APP.
当在不同的设备上用不同的组合使用fragment的时候, 考虑到每种情况下的up行为是很重要的. 比如, 在一个手机上, 我们的APP同一时刻只显示了一个fragment, 有一个可以导航到父屏幕的up导航是很合理的, 而在大屏幕的复合面板上则不需要. 更多关于up导航的信息可以参考ActionBar.
其它设计建议:
当使用一个ListView的时候, 要考虑到如何利用有限的空间来显示更多或者更少的信息. 也就是说, 我们可以在list adapter中为item创建替代layout, 这样可以在更大的屏幕中显示更多的信息.
为values创建替代资源文件, 比如integers, dimensions, Boolean. 为这些资源使用sizequalifiers, 这样我们就可以根据当前的屏幕尺寸来简单的应用不同的layout size, font size或者启用/禁用功能.
总结:
这里介绍的内容是经常可以在书本上看到的知识, 主要是如何利用fragment和action bar来兼容不同尺寸的屏幕.
参考: https://developer.android.com/guide/practices/tablets-and-handsets.html