应用包含可能特定于某种文化的资源。例如,应用可以包含特定于文化的字符串,这些字符串被翻译成当前区域设置的语言。
最好将特定于文化的资源与应用的其余部分分开。Android 根据系统区域设置设置解析特定于语言和文化的资源。您可以通过在 Android 项目中使用资源目录来提供对不同区域设置的支持。
您可以指定适合使用您应用的人员文化的资源。您可以提供适合用户语言和文化的任何资源类型。例如,以下屏幕截图显示一个应用在设备的默认en_US
区域设置和西班牙语es_ES
区域设置中显示字符串和可绘制资源。
使用 Android SDK 工具创建项目时,工具会在项目的顶层生成一个res/
目录。在这个res/
目录中是各种资源类型的子目录。还有一些默认文件,例如res/values/strings.xml
文件,其中包含您的字符串值。
支持不同的语言不仅仅是使用特定于区域设置的资源。一些用户为其 UI 区域设置选择使用从右到左 (RTL) 脚本的语言,例如阿拉伯语或希伯来语。其他将 UI 区域设置设置为使用从左到右 (LTR) 脚本的语言(例如英语)的用户可能会查看或生成使用 RTL 脚本的语言的内容。为了支持这两种类型的用户,您的应用需要执行以下操作
- 为 RTL 区域设置采用 RTL UI 布局。
- 检测并声明在格式化消息内显示的文本数据的方向。通常,您可以调用方法(如本指南中所述),该方法会为您确定文本数据的方向。
创建区域设置目录和资源文件
要添加对更多区域设置的支持,请在res/
内部创建其他目录。每个目录的名称必须符合以下格式
<resource type>-b+<language code>[+<country code>]
例如,values-b+es/
包含具有语言代码es
的区域设置的字符串资源。同样,mipmap-b+es+ES/
包含具有es
语言代码和ES
国家/地区代码的区域设置的图标。
Android 在运行时根据设备的区域设置加载相应的资源。有关更多信息,请参阅提供替代资源。
确定要支持的区域设置后,创建资源子目录和文件。例如
MyProject/ res/ values/ strings.xml values-b+es/ strings.xml mipmap/ country_flag.png mipmap-b+es+ES/ country_flag.png
使用本地化资源填充资源文件。以下是本地化字符串和图像资源文件的示例
位于/values/strings.xml
中的英语字符串(默认区域设置)
<resources> <string name="hello_world">Hello World!</string> </resources>
位于/values-b+es/strings.xml
中的西班牙语字符串(es
区域设置)
<resources> <string name="hello_world">¡Hola Mundo!</string> </resources>
位于/mipmap/country_flag.png
中的美国国旗图标(默认区域设置)
位于/mipmap-b+es+ES/country_flag.png
中的西班牙国旗图标(es_ES
区域设置)
注意:您可以对任何资源类型使用配置限定符,例如区域设置限定符。例如,您可能希望提供位图可绘制对象的本地化版本。有关更多信息,请参阅本地化您的应用。
在您的应用中使用资源
使用每个资源的name
属性引用源代码和其他 XML 文件中的资源:R.<resource type>.<resource name>
。有多种方法可以以此方式接受资源,如下例所示
Kotlin
// Get a string resource val hello = resources.getString(R.string.hello_world) // Or supply a string resource to a method that requires a string TextView(this).apply { setText(R.string.hello_world) }
Java
// Get a string resource String hello = getResources().getString(R.string.hello_world); // Or supply a string resource to a method that requires a string TextView textView = new TextView(this); textView.setText(R.string.hello_world);
在 XML 文件中,您可以使用语法@<resource type>/<resource name>
引用资源,只要 XML 属性接受兼容的值,如下例所示
<ImageView android:layout_width="wrap_content" android:layout_height="wrap_content" android:src="@mipmap/country_flag" />
注意:为了确保正确优先考虑用户语言设置,请使用resConfigs
属性指定您的应用支持的语言。有关更多信息,请参阅指定您的应用支持的语言。
格式化消息中的文本
应用中最常见的任务之一是格式化文本。通过将文本和数字数据插入到适当的位置来格式化本地化消息。不幸的是,当处理 RTL UI 或 RTL 数据时,简单的格式化可能会显示不正确甚至无法读取的文本输出。
阿拉伯语、希伯来语、波斯语和乌尔都语等语言是从右到左(RTL)书写的。但是,某些元素,例如数字和嵌入的从左到右(LTR)文本,在其他RTL文本中是从左到右书写的。使用LTR文字的语言(包括英语)也是双向的,因为它们可以包含需要从右到左显示的嵌入式RTL文字。
应用程序经常会生成这种嵌入式相反方向文本的实例,例如通过将任意语言和任意文本方向的文本数据插入本地化消息中。这种方向的混合通常不包含关于相反方向文本开始和结束位置的明确指示,因此应用程序生成的文本可能会导致糟糕的用户体验。
虽然系统默认处理双向文本通常会按预期呈现文本,但当您的应用程序将其插入本地化消息时,文本可能无法正确呈现。以下是文本可能显示不正确的示例情况
-
插入到消息开头的文本
PERSON_NAME 正在呼叫您
-
以数字开头的文本,例如地址或电话号码
987 654-3210
-
以标点符号开头的文本,例如电话号码
+19876543210
-
以标点符号结尾的文本
您确定吗?
-
包含两个方向的文本
单词 בננה 是希伯来语中的香蕉。
示例
假设一个应用程序有时需要显示消息“您是说 %s 吗?”,并在运行时将地址插入 %s 的位置。该应用程序支持不同的 UI 本地化设置,因此消息来自特定于本地化的资源,并在设备设置为 RTL 本地化设置时使用 RTL 方向。例如,对于希伯来语 UI,消息显示如下:
האם התכוונת ל %s?
但是,建议的地址可能来自不包含本地化语言文本的数据库。例如,如果地址位于加利福尼亚州,则它在数据库中使用英语文本显示。如果您将地址“15 Bay Street, Laurel, CA”插入 RTL 消息而不提供任何关于文本方向的提示,则结果将出乎意料或不正确。
האם התכוונת ל 15 Bay Street, Laurel, CA?
门牌号码出现在地址的右侧,而不是预期的左侧。这使得门牌号码看起来更像是一个奇怪的邮政编码。如果您在使用 LTR 文本方向的消息中包含 RTL 文本,也会出现同样的问题。
说明和解决方案
此示例中的问题是由于文本格式化程序未指定“15”是地址的一部分,因此系统无法确定“15”是其之前的 RTL 文本的一部分还是其之后的 LTR 文本的一部分。
要解决此问题,请使用 unicodeWrap()
方法(来自 BidiFormatter
类)。此方法检测字符串的方向,并将其包装在声明该方向的 Unicode 格式字符中。
以下代码片段演示了如何使用 unicodeWrap()
Kotlin
val mySuggestion = "15 Bay Street, Laurel, CA" val myBidiFormatter: BidiFormatter = BidiFormatter.getInstance() // The "did_you_mean" localized string resource includes // a "%s" placeholder for the suggestion. String.format(getString(R.string.did_you_mean), myBidiFormatter.unicodeWrap(mySuggestion))
Java
String mySuggestion = "15 Bay Street, Laurel, CA"; BidiFormatter myBidiFormatter = BidiFormatter.getInstance(); // The "did_you_mean" localized string resource includes // a "%s" placeholder for the suggestion. String.format(getString(R.string.did_you_mean), myBidiFormatter.unicodeWrap(mySuggestion));
由于“15”现在出现在声明为 LTR 的文本中,因此它显示在正确的位置。
האם התכוונת ל 15 Bay Street, Laurel, CA?
对您插入本地化消息的每一部分文本使用 unicodeWrap()
方法,除非以下情况之一适用:
- 文本被插入到机器可读字符串中,例如 URI 或 SQL 查询。
- 您知道该文本片段已正确包装。
注意:如果您的应用程序面向 Android 4.3(API 级别 18)或更高版本,请使用 Android 框架中找到的 BidiFormatter
版本。否则,请使用支持库中找到的 BidiFormatter
版本。
格式化数字
在您的应用程序逻辑中,使用 格式化字符串,而不是方法调用,将数字转换为字符串。
Kotlin
var myIntAsString = "$myInt"
Java
String myIntAsString = String.format("%d", myInt);
这会根据您的区域设置适当地格式化数字,这可能包括使用不同的数字集。
当您使用 String.format()
在设置为使用自己数字集的区域设置(例如波斯语和大多数阿拉伯语区域设置)的设备上创建 SQL 查询时,如果查询的任何参数都是数字,则会出现问题。这是因为数字以区域设置的数字格式化,而这些数字在 SQL 中无效。
为了保留 ASCII 格式的数字并保持 SQL 查询的有效性,您需要使用包含区域设置作为第一个参数的 String.format()
的重载版本。使用区域设置参数 Locale.US
。
支持布局镜像
使用 RTL 文字的用户更喜欢 RTL 用户界面,其中包括右对齐的菜单、右对齐的文本和指向左侧的前进箭头。
图 4 显示了设置应用程序中屏幕的 LTR 版本与其 RTL 版本之间的对比。
在向您的应用程序添加 RTL 支持时,请记住以下几点:
- 仅当在运行 Android 4.2(API 级别 17)或更高版本的设备上使用时,RTL 文本镜像才受应用程序支持。要了解如何在较旧的设备上支持文本镜像,请参阅本指南中的 为旧版应用程序提供支持。
- 要测试您的应用程序是否支持 RTL 文本方向,请按照本指南中的说明 使用开发者选项进行测试,并邀请使用 RTL 文字的用户使用您的应用程序。
注意:要查看与布局镜像相关的其他设计指南,包括适合镜像和不适合镜像的元素列表,请参阅 双向性 材料设计指南。
要在您的应用程序中镜像 UI 布局,使其在 RTL 区域设置中显示为 RTL,请完成以下部分中的步骤。
修改构建和清单文件
修改您的应用程序模块的 build.gradle
文件和应用程序清单文件,如下所示:
build.gradle (模块:app)
Groovy
android { ... defaultConfig { targetSdkVersion 17 // Or higher ... } }
Kotlin
android { ... defaultConfig { targetSdkVersion(17) // Or higher ... } }
AndroidManifest.xml
<manifest ... > ... <application ... android:supportsRtl="true"> </application> </manifest>
注意:如果您的应用程序面向 Android 4.1.1(API 级别 16)或更低版本,则 android:supportsRtl
属性将被忽略,以及出现在您的应用程序布局文件中的任何 start
和 end
属性值。在这种情况下,RTL 布局镜像不会在您的应用程序中自动发生。
更新现有资源
将现有布局资源文件中的 left
和 right
分别转换为 start
和 end
。这允许框架根据用户的语言设置对齐应用程序的 UI 元素。
注意:在更新资源之前,请了解如何 为旧版应用程序提供支持,或面向 Android 4.1.1(API 级别 16)和更低版本的应用程序。
要使用框架的 RTL 对齐功能,请更改表 1 中显示的布局文件中的属性。
表 2 显示了系统如何根据目标 SDK 版本、是否定义了 left
和 right
属性以及是否定义了 start
和 end
属性来处理 UI 对齐属性。
|
已定义 left 和 right? | 已定义 start 和 end? | 结果 |
---|---|---|---|
是 | 是 | 是 |
start 和 end 将被使用,覆盖 left 和 right |
是 | 是 | 否 | left 和 right 将被使用 |
是 | 否 | 是 | start 和 end 将被使用 |
否 | 是 | 是 |
left 和 right 将被使用(start 和 end 将被忽略) |
否 | 是 | 否 | left 和 right 将被使用 |
否 | 否 | 是 |
start 和 end 将解析为 left 和 right |
添加特定于方向和语言的资源
此步骤涉及添加布局、可绘制对象和值资源文件的特定版本,这些文件包含针对不同语言和文本方向的自定义值。
在 Android 4.2(API 级别 17)及更高版本中,您可以使用 -ldrtl
(layout-direction-right-to-left)和 -ldltr
(layout-direction-left-to-right)资源限定符。为了保持与现有资源的向后兼容性,旧版本的 Android 使用资源的语言限定符来推断正确的文本方向。
假设您要添加一个特定的布局文件来支持 RTL 文字,例如希伯来语、阿拉伯语和波斯语。为此,请在您的 res/
目录中添加一个 layout-ldrtl/
目录,如下例所示:
res/ layout/ main.xml This layout file is loaded by default. layout-ldrtl/ main.xml This layout file is loaded for languages using an RTL text direction, including Arabic, Persian, and Hebrew.
如果您想添加一个仅针对阿拉伯语文本设计的特定版本的布局,则您的目录结构如下所示:
res/ layout/ main.xml This layout file is loaded by default. layout-ar/ main.xml This layout file is loaded for Arabic text. layout-ldrtl/ main.xml This layout file is loaded only for non-Arabic languages that use an RTL text direction.
注意:特定于语言的资源优先于特定于布局方向的资源,而后者优先于默认资源。
使用受支持的小部件
从 Android 4.2(API 级别 17)开始,大多数框架 UI 元素都自动支持 RTL 文本方向。但是,一些框架元素(例如 ViewPager
)不支持 RTL 文本方向。
只要它们相应的清单文件包含属性赋值 android:supportsRtl="true"
,主屏幕小部件就支持 RTL 文本方向。
为旧版应用程序提供支持
如果您的应用面向 Android 4.1.1(API 级别 16)或更低版本,请除了 start
和 end
属性外,还要包含 left
和 right
属性。
要检查您的布局是否需要使用 RTL 文本方向,请使用以下逻辑
Kotlin
private fun shouldUseLayoutRtl(): Boolean { return if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.JELLY_BEAN_MR1) { View.LAYOUT_DIRECTION_RTL == layoutDirection } else { false } }
Java
private boolean shouldUseLayoutRtl() { if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.JELLY_BEAN_MR1) { return View.LAYOUT_DIRECTION_RTL == getLayoutDirection(); } else { return false; } }
注意:为避免兼容性问题,请使用 23.0.1 或更高版本的 Android SDK Build Tools。
使用开发者选项测试
在运行 Android 4.4(API 级别 19)或更高版本的设备上,您可以在 设备上的开发者选项 中启用强制使用 RTL 布局方向。此设置可让您在 RTL 模式下查看使用 LTR 脚本(例如英文文本)的文本。
更新应用逻辑
本节介绍在调整应用以处理多个文本方向时需要更新的应用逻辑的具体方面。
属性更改
要处理任何与 RTL 相关的属性(例如布局方向、布局参数、填充、文本方向、文本对齐或可绘制对象位置)的更改,请使用 onRtlPropertiesChanged()
回调。此回调可让您获取当前布局方向并相应地更新活动中的 View
对象。
视图
如果您正在创建不直接属于活动视图层次结构的 UI 小部件(例如对话框或类似吐司的 UI 元素),请根据上下文设置正确的布局方向。以下代码片段演示了如何完成此过程
Kotlin
val config: Configuration = context.resources.configuration view.layoutDirection = config.layoutDirection
Java
final Configuration config = getContext().getResources().getConfiguration(); view.setLayoutDirection(config.getLayoutDirection());
View
类的几种方法需要额外考虑
onMeasure()
- 视图测量可能会因文本方向而异。
onLayout()
- 如果您创建自己的布局实现,则需要在您的
onLayout()
版本中调用super()
并调整您的自定义逻辑以支持 RTL 脚本。 onDraw()
- 如果您正在实现自定义视图或向绘图添加高级功能,则需要更新代码以支持 RTL 脚本。使用以下代码确定您的窗口小部件是否处于 RTL 模式
Kotlin
// On devices running Android 4.1.1 (API level 16) and lower, // you can call the isLayoutRtl() system method directly. fun isLayoutRtl(): Boolean = layoutDirection == LAYOUT_DIRECTION_RTL
Java
// On devices running Android 4.1.1 (API level 16) and lower, // you can call the isLayoutRtl() system method directly. public boolean isLayoutRtl() { return (getLayoutDirection() == LAYOUT_DIRECTION_RTL); }
可绘制对象
如果您有一个需要为 RTL 布局镜像的可绘制对象,请根据设备上运行的 Android 版本完成以下步骤之一
- 在运行 Android 4.3(API 级别 18)及更低版本的设备上,添加并定义
-ldrtl
资源文件。 -
在 Android 4.4(API 级别 19)及更高版本上,在定义可绘制对象时使用
android:autoMirrored="true"
,这允许系统为您处理 RTL 布局镜像。注意:
android:autoMirrored
属性仅适用于简单的可绘制对象,其双向镜像只是整个可绘制对象的图形镜像。如果您的可绘制对象包含多个元素,或者反映您的可绘制对象会改变其解释,您可以自己执行镜像。尽可能地,请咨询双向专家以确定镜像的可绘制对象对用户是否有意义。
重力
如果您的应用布局代码使用 Gravity.LEFT
或 Gravity.RIGHT
,请将这些值分别更改为 Gravity.START
和 Gravity.END
。
如果您有依赖于 Gravity.LEFT
或 Gravity.RIGHT
属性的 Kotlin 或 Java 代码,您可以通过设置 absoluteGravity
与 layoutDirection
相匹配来使其适应此更改。
例如,如果您正在使用以下代码
Kotlin
when (gravity and Gravity.HORIZONTAL_GRAVITY_MASK) { Gravity.LEFT -> { // Handle objects that are left-aligned. } Gravity.RIGHT -> { // Handle objects that are right-aligned. } }
Java
switch (gravity & Gravity.HORIZONTAL_GRAVITY_MASK) { case Gravity.LEFT: // Handle objects that are left-aligned. break; case Gravity.RIGHT: // Handle objects that are right-aligned. break; }
将其更改为以下内容
Kotlin
val absoluteGravity: Int = Gravity.getAbsoluteGravity(gravity, layoutDirection) when (absoluteGravity and Gravity.HORIZONTAL_GRAVITY_MASK) { Gravity.LEFT -> { // Handle objects that are left-aligned. } Gravity.RIGHT -> { // Handle objects that are right-aligned. } }
Java
final int layoutDirection = getLayoutDirection(); final int absoluteGravity = Gravity.getAbsoluteGravity(gravity, layoutDirection); switch (absoluteGravity & Gravity.HORIZONTAL_GRAVITY_MASK) { case Gravity.LEFT: // Handle objects that are left-aligned. break; case Gravity.RIGHT: // Handle objects that are right-aligned. break; }
这意味着即使您使用 start
和 end
作为重力值,您也可以保留处理左对齐和右对齐值的现有代码。
注意:应用重力设置时,请使用包含 layoutDirection
参数的 Gravity.apply()
的重载版本。
边距和填充
要在您的应用中支持 RTL 脚本,请遵循与边距和填充值相关的以下最佳实践
- 使用
getMarginStart()
和getMarginEnd()
代替特定方向的属性等效项leftMargin
和rightMargin
。 - 使用
setMargins()
时,如果您的应用检测到 RTL 脚本,请交换left
和right
参数的值。 - 如果您的应用包含自定义填充逻辑,请覆盖
setPadding()
和setPaddingRelative()
。
支持每个应用的语言偏好设置
在许多情况下,多语言用户将其系统语言设置为一种语言(例如英语),但他们希望为特定应用(例如荷兰语、中文或印地语)选择其他语言。为了帮助应用为这些用户提供更好的体验,Android 13 为支持多种语言的应用引入了以下功能
-
系统设置:用户可以在其中为每个应用选择首选语言的集中位置。
您的应用必须在其清单中声明
android:localeConfig
属性,以告诉系统它支持多种语言。要了解更多信息,请参阅有关 创建资源文件并在应用的清单文件中声明它 的说明。 -
其他 API:这些公共 API(例如
setApplicationLocales()
和getApplicationLocales()
方法)位于LocaleManager
中,允许应用在运行时设置与系统语言不同的语言。使用自定义应用内语言选择器的应用可以使用这些 API 为用户提供一致的用户体验,无论他们在何处选择其语言偏好设置。公共 API 还可以帮助您减少样板代码的数量,并且它们支持拆分 APK。它们还支持 应用自动备份 以存储应用级别的用户语言设置。
为了与以前的 Android 版本向后兼容,AndroidX 中也提供了等效的 API。我们建议使用 Appcompat 1.6.0-beta01 或更高版本。
要了解更多信息,请参阅有关 实现新 API 的说明。
另请参阅
其他资源
要了解有关支持旧版设备的更多信息,请查看以下资源