第十七章 双版面主从用户界面
本章是为了适应平板设备。双版面主从用户界面,也就是平板上的列表和详情界面同时展示的情况。
一、增加布局灵活性
双版面布局里面,一个 Activity 托管两个 Fragment。
1、方法上使用 @LayoutRes 注解,这告诉Android Studio,任何时候该注解的方法都应该返回有效的布局资源ID。
2、创建包含两个Fragment容器的布局
3、使用别名资源
别名资源是一种指向其他资源的特殊资源。它存放在 res/values/ 目录下,并按照约定定义在 refs.xml 文件中。
<resources>
<item name="activity_masterdetail" type="layout">@layout/activity_fragment</item>
</resources>
别名资源首先指向了单版面布局资源文件。别名资源自身也具有资源ID:R.layout.activity_masterdetail。注意,别名的 type 属性决定资源ID是属于什么内部类,即使别名资源自身在 res/values/ 目录中,它的资源ID依然属于 R.layout 内部类。
4、创建平板设备专用可选资源
res 文件夹下创建一个 values-sw600dp 文件夹,之后在其下创建一个 refs 文件,同上面一样,只不过这次别名资源指向双版面布局资源。
<resources>
<item name="activity_masterdetail" type="layout">@layout/activity_twopane</item>
</resources>
对于上述两个别名资源,我们的目标是:
- 对于小于指定尺寸的设备,使用 activity_fragment.xml 资源文件
- 对于大于指定尺寸的设备,使用 activity_twopane.xml 资源文件
Android只提供一部分的资源适配机制。配置修饰符 -sw600dp 的作用是:如果设备尺寸大于某个指定值,就使用对应的资源文件。sw 是smallest width(最小高度)的缩写。虽然字面上是宽度的含义,但它实际指的是屏幕的最小尺寸(dimension),因而 sw 与设备的当前方向无关。
在确定可选资源时,-sw600dp 配置修饰符表明:对任何最小尺寸为600dp或更高dp的设备,都使用该资源。
对于希望使用activity_fragment.xml 的小尺寸设备要怎么做呢?Android是这样判断的:既然设备尺寸小于 -sw600dp 配置修饰符的指定值,那就使用默认的 activity_fragment.xml 资源文件。
二、activity:fragment 的托管者
为了让Fragment独立,我们可以在 fragment 中定义回调接口,委托托管activity来完成那些不应由fragment处理的任务。托管activity将实现回调接口,履行托管fragment的任务。
1、fragment 回调接口
要委托工作任务给托管activity,通常的做法是由 fragment 定义名为 Callbacks 的回调接口。回调接口定义了fragment委托给托管activity处理的工作任务。任何打算托管目标fragment的activity都必须实现它。
有了回调接口,就不用关心谁是托管者,fragment 可以直接调用托管 activity 的方法。
(1)实现 CrimeListFragment.Callbacks 回调接口
为了实现Callbacks接口,首先要定义一个成员变量,可以存放实现Callbacks接口的对象。然后将托管activity强制类型转换为Callbacks对象并赋值给Callbacks类型变量。
activity赋值是在fragment的生命周期方法中处理的:public void onAttach(Context context),该方法是在fragment附加给activity时调用的,当然fragment是否保留并不重要。Activity 是 Context的子类,所以,onAttach可以传入Context参数,这确实很灵活,请确保使用onAttach(Context) 方法,而不是已废弃的 onAttach(Activity) 方法(将来可能会被移除)。
类似的,在相应的生命周期销毁方法中,将Callbacks变量设置为null。public void onDetach( ) 方法,在这里将变量清空的原因是:随后再也无法访问该activity或指望它继续存在了。
private Callbacks mCallbacks;
public interface Callbacks{
void onCrimeSelected(Crime crime);
}
@Override
public void onAttach(Context context) {
super.onAttach(context);
mCallbacks = (Callbacks) context;
}
@Override
public void onDetach() {
super.onDetach();
mCallbacks = null;
}
现在就有办法调用托管activity的方法了。另外,它也不关心托管activity是谁,只要托管activity实现了Callbacks接口,fragment中的一切代码行为都可以保持不变。
注意,未经类安全性检查,就将托管activity强制转换为Callbacks对象,这意味着,托管activity必须实现Callbacks接口,这并非是不良的依赖关系,但记录下它非常重要。
之后,根据业务逻辑,需要考虑如何在托管activity中实现 onCrimeSelected() 方法,此方法被调用时,托管activity需要判断到底是使用手机设备界面布局还是平板设备界面布局。最后,在fragment里需要的地方调用 onCrimeSelected() 方法即可。
到现在,应用代码在平板设备上已经能显示新布局,但是,当前如果修改crime内容,列表项并不会更新。需要添加另一个回调接口来解决这个问题。
(2)实现 CrimeFragment.Callbacks 回调接口
定义接口如下:public interface Callbacks{ void onCrimeUpdated(Crime crime); }
其代码写法跟上面差不多,主要是清楚项目逻辑,列出来该接口和实现方法需要完成的工作,在合适的地方调用即可。
三、深入学习:设备屏幕尺寸的确定
Android 3.2之前,屏幕大小修饰符是基于设备的屏幕大小来提供可选资源的。屏幕大小修饰符将不同的设备分为四大类别:small、normal、large及xlarge。
下表展示了每个类别修饰符的最低屏幕大小:
顺应允许开发者测试设备尺寸的新修饰符的推出,屏幕大小修饰符已在Android 3.2中弃用。下表列出了新的修饰符:
假设想指定某个布局仅适用于屏幕宽度至少300dp的设备,可以使用宽度修饰符,并将布局文件放入 res/layout-w300dp 目录下(w 代表屏幕宽度)。类似的,我们也可以使用“hXXXdp”修饰符(h代表屏幕高度)。
设备方向变换的话,设备的宽和高也会交换。为了确实某个具体的屏幕尺寸,我们可以使用 sw(最小宽度)。sw 指定了屏幕的最小规格尺寸。设备的方向会变,因此 sw 可以是最小宽度,也可以是最小高度。例如,如果屏幕尺寸为 1024*800,那么 sw 值就是800;而如果屏幕尺寸为 800*1024,那么 sw 值仍然是800。
第十八章 应用本地化
本地化是一个基于设备语言设置,为应用提供合适资源的过程。在应用中提供各个版本语言资源,Android会根据设备语言的设置在应用中自动找到并使用相应的语言资源。
一、资源本地化
语言设置是设备配置的一部分,和处理屏幕方向、屏幕尺寸以及其他配置因素改变一样,Android也提供了用于不同语言的配置修饰符。本地化处理因而变得简单:创建带目标语言配置修饰符的资源子目录,并放入备选资源。其余就交给Android资源系统自动处理。
在项目工具窗口中,右键点击res/values目录,选择 New---->Values resource file 菜单项。文件名输入 strings.xml ,Source set 选择 main,Directory name 设置为values。然后,在 Available qualifiers 列表窗口,选择 Locale,使用>>按钮把它移入 Chosen qualifiers 窗口,在 Language 列表窗口中选 zh:Chinese,此时,右边的 Specific Region Only 窗口会自动选中 Any Region,这样就是对了,设置完成了。注意,Android Studio会自动设置 Directory name 为 values-zh。点击OK按钮完成。
1、默认资源
英文语言的配置修饰符为 -en。若是想把原来的 values 目录重命名为 values-en,当设备语言是中文或英文的时候,运行应用一切正常,但是如果把语言改为法语呢?问题来了,Android无法找到匹配当前语言设置的资源!若是这些字符串资源是在布局文件里引用的,那么应用不会崩溃,但是应用会显示资源 ID 数值;若是这些字符串资源的引用是在Java代码中,那么应用就会崩溃。
所以,提供默认资源非常重要。没有配置修饰符的资源就是默认资源,当无法找到匹配当前配置的资源,Android就会使用默认资源,默认资源至少能保证应用能正常运行。
Android默认资源使用规则并不适用于屏幕显示密度。项目的 drawable 目录通常按屏幕显示密度要求,带有 -mdpi、-xxhdpi这样的修饰符。不过,Android决定使用哪一类drawable资源并不是简单地匹配设备的屏幕显示密度,也不是在没有匹配的资源时直接使用默认资源。最终的选择取决于对屏幕尺寸和显示密度的综合考虑。Android甚至可能会选择低于或高于当前设备屏幕密度的drawable资源,然后通过缩放去适配设备。无论如何,请记住一点:不要在 res/drawable/ 目录下放置默认的 drawable资源。
2、检查资源本地化完成情况
是否已为某种语言提供全部本地化资源?应用支持的语言越来越多,资源文件里的字符串也越来越多,想快速确认也越来越难。所以Android Studio提供了 资源翻译编辑器 这个工具,能集中查看资源翻译完成情况。
在项目工具窗口,右键点击某个语言版本的 strings.xml ,选择 Open Translations Editor 菜单项打开资源翻译编辑器。
3、区域修饰符
修饰资源目录也可以使用语言加区域修饰符,这样可以让资源使用更有针对性。例如,西班牙语可以使用 -es-rES 修饰符。其中,r 代表区域,ES是西班牙语的ISO 3166-1-alpha-2标准码。配置修饰符对大小写不敏感,但最好遵循Android命名约定:语言代码小写,区域代码大写,但前面加个小写的 r 。
注意,语言区域修饰符,如 -es-rES,看上去像两个不同的修饰符的合体,实际并不是这样,这是因为,区域本身不能单独用作修饰符。
下图是Android不同系统版本的区域资源匹配策略。
由上图可得结论:资源应尽可能通用,最好是使用仅限语言的修饰目录,尽量少用区域修饰。这样,不仅方便开发维护,也方便适配不同版本的系统。
二、配置修饰符
Android中的配置修饰符有不少,比如语言(values-zh/)、屏幕方位(layout-land/)、屏幕显示密度(drawable-mdpi/)以及屏幕尺寸(layout-sw600dp/)等等。
1、可用资源优先级排定
考虑到有那么多匹配资源的配置修饰符,有时,会出现设备配置与好几个可选资源都匹配的情况。遇到这种情况,Android会基于表18-1的顺序确定修饰符的使用优先级。
比如,我们项目中既有中文备选版本的配置,也有屏幕尺寸的配置,因为中文备选版本的资源优先级最高,所以我们看到的是来自于 values-zh/strings.xml 文件的字符串。
2、多重配置修饰符
可以在同一资源目录上使用多个配置修饰符。这需要各配置修饰符按照优先级别顺序排列。因此,values-zh-land 是一个有效的资源目录名,而 values-land-zh 目录名则无效。在Android Studio中新建资源文件时,工具会自动配置正确的目录名。
3、寻找最匹配的资源
我们来看Android是如何确定使用什么资源文件的。首先,比如当前设备有以下四个版本的字符串备选资源:
- values/strings.xml
- values-zh/strings.xml
- values-w600dp/strings.xml
- values-zh-w600dp/strings.xml
其次,在设备配置方面,有台Nexus 5X,语言设为简体中文,屏宽600dp以上(可用宽度731dp,可用高度411dp)。
(1)排除不兼容的目录
要找到最匹配的资源,Android首先排除不兼容当前设备配置的资源目录。
结合备选资源和设备配置来看,四个版本的备选资源均兼容设备的当前配置。(如果设备旋转至竖直模式,设备配置会改变。此时,values-w600dp/ 与 values-zh-w600dp/ 资源目录不兼容当前配置,因此排除。)
(2)按优先级表排除不兼容的目录
筛掉不兼容的资源目录后,自优先级最高的MCC(移动国家码)开始,Android逐项查看并按优先级表继续筛查不兼容目录(表18-1)。如果有任何以MCC为修饰符的资源目录,那么所有不带MCC修饰符的都会被排除。如果仍有多个目录匹配,Android就继续按次高优先级筛选,如此反复,直至找到唯一满足兼容性的目录。
本例中没有目录包含MCC修饰符,因此无法筛选掉任何目录。接着,Android查看到次高优先级的设备语言修饰符。values-zh/ 和 values-zh-w600dp/ 目录包含语言修饰符。所以,不包含语言修饰符的 values-w600dp/ 可排除。
由于仍有多个目录匹配,因此继续看优先级表,接下来是屏幕宽度。此时,Android会找到一个带屏宽修饰符的目录以及两个不带屏宽修饰符的目录,由此,values/ 和 values-zh/ 目录也被排除。就这样,values-zh-w600dp/ 成了唯一满足兼容需求的目录。因而,Android最终确定使用 values-zh-w600dp/ 目录下的资源。
三、测试备选资源
开发应用时,为了查看布局及其他资源的使用效果,一定要针对不同设备配置做好测试。在虚拟设备或实体设备上测试都行,还可以使用图形布局工具测试。
图形布局工具有很多选项,用于预览布局在不同配置下的显示效果。这些选项有屏幕尺寸、设备类型、API级别以及设备语言等。
要查看这些选项,可在图形布局工具中打开布局文件,使用下图所示的工具栏上的一些选项设置。
如果想确认项目是否包括所有必需的默认资源,可设置设备使用未提供本地化资源的语言。运行应用,查看所有视图界面并旋转设备。如果应用崩溃,请查看LogCat中的 “Resource not found...” 错误信息,排查缺少哪些默认资源。也请关注是否存在非崩溃问题,如用户界面显示资源ID那样的问题。
第十九章 Android辅助功能
一个易用的应用适合所有人使用,即便是视力、行动、听力有障碍的人也能使用。这些障碍有永久性的,也有暂时性的或特定场景下的:暂时原因导致眼睛看不清楚、手沾水沾油难以触碰屏幕、噪音盖过了手机一切声音等等。
开发适合所有人的易用应用非常困难,慢慢来。本章使用一个TalkBack的辅助工具,让应用更加易用。
一、TalkBack
TalkBack 是Google提供的Android屏幕阅读器。基于用户的操作,它能读出屏幕的内容。
TalkBack实际是一个辅助服务,这个特别的部件能读取应用屏幕上的信息(无论哪种应用都可以)。只要不嫌麻烦,谁都可以设计开发这样的辅助服务。但TalkBack已经非常好用,其应用相当广泛。
要使用TalkBack,需要Android设备(虚拟设备不支持TalkBack)。
要启用TalkBack,请打开设置,点按辅助功能。在服务类别下,点按TalkBack打开它,然后,点按右上角开关启用TalkBack服务。
现在,TalkBack已处于启用状态(如果是首次使用,还会看到使用演示教程)。点按工具栏向上按钮退出。
注意,屏幕上是不是有了变化?一个绿框出现在向上按钮上,而且设备开始说话。在移动设备屏幕上操作时,“点按”是常见的说法,但TalkBack却使用“点”以及“连点两下”。
绿框表示当前UI元素获得了辅助焦点,一次只能有一个UI元素得到辅助焦点。UI元素得到辅助焦点后,TalkBack会提供该UI元素的信息。
设备启用TalkBack后,点操作会给予UI元素焦点,连点两下会激活。所以,当向上按钮获得焦点后,连点两下就回到上一屏了。如果是CheckBox获得焦点,连点两下就是切换勾选状态。(同样,如果设备锁屏了,点锁屏按钮,然后连点两次屏幕任何地方就会解锁)。
1、点击浏览
只要启用了TalkBack,点击浏览(Explore by Touch)功能也会开启。这就意味着,点按某UI元素,设备就会读出相关信息(当然,被点按的UI元素要有可读信息才行)。
让向上按钮仍处于聚焦状态,连点两次屏幕任何地方,TalkBack就会读出:“Accessibility。”
Android框架里的组件,如ToolBar、RecyclerView、ListView以及Button等,默认都支持TalkBack。想要用好TalkBack辅助功能,应尽可能多用框架内置组件。当然,也可以让定制组件支持TalkBack辅助功能,不过这个比较复杂,这里不讲。
需要两根手指按住屏幕上下滚动才能滚动列表。滚动时,设备会发出声响。这实际是对滚动的一种声音反馈。
2、线性浏览
想象一下,你第一次点击浏览一个应用会是什么情况?很可能你不知道点击哪个按钮才对。你明白,要想知道按了什么按钮,只能等TalkBack读出聚焦按钮说明才行。这会是一种什么体验?结果很可能是多次按同一个按钮。
好在TalkBack还有线性浏览功能。事实上,使用TalkBack最常见的方式就是使用线性浏览:右滑屏幕,辅助焦点移动到下一个UI元素;左滑,移动到上一个UI元素。这样用户就可以线性浏览应用。
比如,启动CriminalIntent应用,进入crime明细界面。点击工具栏上的标题栏,让其聚焦。设备开始朗读“CriminalIntent”。现在,向右滑屏,辅助焦点随机移动到添加新crime记录的按钮上。对于菜单项和按钮这样的框架组件,TalkBack会默认读出组件上显示的文字。添加新crime的按钮上没有文字,TalkBack就会去找其他地方,在菜单项XML文件中指定过标题信息,于是TalkBack就找出该信息读出。有时,TalkBack也能告诉用户某个组件接受什么操作,或这是什么组件。继续右滑,TalkBack给出SHOW SUBTITLE 按钮的信息。再右滑,辅助焦点移动到第一条crime记录上;现在向左滑,辅助焦点又移回SHOW SUBTITLE 按钮。
总之,Android会智能有序的移动辅助焦点。这就是TalkBack的线性浏览。
二、实现非文字性元素可读
现在,点击工具栏上添加crime的按钮,让其聚焦,等TalkBack读完按钮相关信息后,连点屏幕任何地方,进入crime明细页面。
1、添加内容描述
在Crime明细页面,点击聚焦拍照按钮,拍照按钮无文字描述,除了告诉用户连点两下激活,TalkBack没什么好说的。这种情况,我们可以给 ImageButton 添加内容描述,这样TalkBack就有内容可读了。内容描述是一段针对组件的文字说明,供TalkBack朗读。
要添加组件内容描述,可以在组件的布局XML文件里,添加 android:contentDescription 属性。当然,也可以在布局实例化代码里,使用 someView.setContentDescription(someString) 方法。
添加内容描述时,文字表述要简洁明了。像组件是什么类型这种信息,TalkBack会自动提供,没有必要写在内容描述里。
2、实现组件可聚焦
接下来,点按照片预览处(当前显示的是灰色占位图)。你可能以为辅助聚焦框会上移,但绿色聚焦框包围了整个fragment视图区域,而并非移到ImageView组件上,TalkBack读的也是整个fragment视图的相关信息。问题在哪?
因为,ImageView组件没有做可聚焦登记。有些框架组件,如 Button、CheckBox等,默认是可聚焦的;而像 ImageView 和 TextView 这样的框架组件需要手动登记。设置 android:focusable 属性值为 true 或使用监听器都可以让组件可聚焦。
三、提升辅助体验
有些UI组件,如ImageView,虽然会给用户提供一些信息,但没有文字性内容,你也应该给这些组件添加内容描述,如果某个组件提供不了任何有意义的说明,应该把它的内容描述设置为null,让TalkBack忽略它。
好的辅助易用设计不是一字不漏的读屏幕。相反,应注重用户体验的一致性,重要的信息和上下文一定要传达。
现在,crime预览图就给了用户不好的体验,即使有照片,TalkBack也总说当前未拍照,为了解决这个问题,让用户能听到正确信息,需要在代码里动态设置ImageView的内容描述。
1、使用 label 提供上下文
点按聚焦EditText框,TalkBack提示“编辑框,请给crime加标题”。TalkBack默认读出EditText框里的内容。没输入标题前,TalkBack会读出 android:hint 指定的内容,所以不需要也不应该给EditText设置内容描述。
但是,这实际是有问题的。在标题栏里输入文字:Sticker vandalism,然后点按聚焦EditText,TalkBack提示:“编辑框,Sticker vandalism”。
这个问题就是,如果输入了文字,TalkBack使用者就失去了上下文,不知道EditText框是做什么的。这对于视力好的人来说没有影响,因为上面有标题文字标签;但是对视力障碍的人就不太友好了,如果就输入了简单标题,有视力障碍的人可能要费力猜测EditText框是做什么的。
可以标明EditText和TextView的关系,让TalkBack掌握同样的上下文关系。只要给TextView添加 android:labelFor 属性就可以了。
android:labelFor="@+id/crime_title"
android:labelFor 属性告诉TalkBack,TextView是以某个ID值指定的视图(EditText)的标签。labelFor定义在View类里,所以可以任意两个View互打标签。注意,这里必须使用 @+id 这样的语法,因为我们正引用了一个当前还没定义的ID值。
四、深入学习:使用辅助功能扫描器
理论上,测试应用的辅助功能得靠真正每天在用辅助服务的用户。但即使现实不允许,也应竭尽所能。
为此,Google提供了一个辅助功能扫描器。它能评估应用在辅助功能方面做的如何并给出改进意见。