Android RLT Support
Android RTL 适配
适配入门
如何切换到阿拉伯语?
- 语言→添加语言→搜索: 阿拉伯(选第一个即可)
- 选择第一语言为阿拉伯语
快速切换到阿语,阿语切换到英语,https://www.pgyer.com/qiubaitools,密码:111111>
方向判断
- 从 Locale 判断是否是 RTL 布局
1
2
3
4
5
fun Any.isRTL() = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
TextUtilsCompat.getLayoutDirectionFromLocale(getCurrentLocale()) == LayoutDirection.RTL
} else {
TextUtilsCompat.getLayoutDirectionFromLocale(getCurrentLocale()) == 1
}
- 某个布局判断
1
View.layoutDirection == ViewCompat.LAYOUT_DIRECTION_RTL
AS 一键适配
AS 支持一键适配 RTL,主要是在原来 Layout 中设置 Left 和 Right 属性的补充添加 Start 和 End 属性。
Start 属性在 LTR 中对应 Left,在 RTL 中对应 Right,在 API 17 开始支持,为了兼容低版本,需要同时有 Left 和 Start。从市场来看,Android 4.2 系统以下的手机用户已经不多了,我的建议是可以不兼容,具体还得你们看自家产品在 4.2 系统以下用户数。
Refactor > Add RTL Support Where Possible…
开启 RTL 支持
在 AndroidManifest.xml
文件中 application
节点添加支持从右到左布局方式代码:
1
2
3
4
5
<application
...
android:supportsRtl="true" >
...
</application>
Drawable 适配 (一张图,drawable 翻转)
某些图标在 RTL 布局中,需要做反转,比如类似 <--
的图标在 RTL 布局中应该显示成 -->
。
Android API 大于等于 19 autoMirrored=true
1
2
3
<ImageView
src="@drawable/arrow_right"
autoMirrored="true" />
需要 API 等级在 19 以后才可以。
Android API 小于 19
1、提供多套图
用 drawable-ldrtl
提供多套图
1
2
<ImageView
src="@drawable/arrow_right" />
多个 drawable
目录:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
├── drawable-xxxhdpi
└── arrow_right.png
├── drawable-xxhdpi
└── arrow_right.png
├── drawable-xhdpi
└── arrow_right.png
├── drawable-hdpi
└── arrow_right.png
├── drawable-mdpi
└── arrow_right.png
├── drawable-ldrtl-xxxhdpi
└── arrow_right.png
├── drawable-ldrtl-xxhdpi
└── arrow_right.png
├── drawable-ldrtl-xhdpi
└── arrow_right.png
├── drawable-ldrtl-hdpi
└── arrow_right.png
├── drawable-ldrtl-mdpi
└── arrow_right.png
会造成图片资源冗余,增大 apk 体积
2、翻转
图片镜像翻转 180°
,对于 ImageView
1
android:rotationY="@integer/rotation"
示例:
1
2
3
<ImageView
src="@drawable/arrow_right"
android:rotationY="@integer/rotation" />
在 values/integer.xml
声明如下
1
2
3
<resources>
<integer name="rotation">0</integer>
</resources>
最后在 value-ldrtl/integer.xml
声明如下:
1
2
3
<resources>
<integer name="rotation">180</integer>
</resources>
还有其他的 anim-ldrtl/
、drawable-ldrtl/
也是一样
字符、数字、时间等的适配
String.format
传递 Locale.getDefault()
,如果是数字的话不需要翻译成阿语的数字用 Locale.US
如时间、数字等阿拉伯数字,无需适配阿拉伯语的话,指定 Locale.US 即可
Android 阿拉伯语下占位符问题
values-ar/strings(阿拉伯文) 中若有占位符,若只有一个占位符,%s 需要写成 s%,若有多个占位符,则仍旧是%s,如果不确定是 s% 还是%s,看看大神们是如何操作的:
This is the crash:
1
2
3
4
5
6
7
8
E/AndroidRuntime( 5739): Caused by: java.util.UnknownFormatConversionException: Conversion: أ
E/AndroidRuntime( 5739): at java.util.Formatter$FormatToken.unknownFormatConversionException(Formatter.java:1399)
E/AndroidRuntime( 5739): at java.util.Formatter$FormatToken.checkFlags(Formatter.java:1336)
E/AndroidRuntime( 5739): at java.util.Formatter.transform(Formatter.java:1442)
E/AndroidRuntime( 5739): at java.util.Formatter.doFormat(Formatter.java:1081)
E/AndroidRuntime( 5739): at java.util.Formatter.format(Formatter.java:1042)
E/AndroidRuntime( 5739): at java.util.Formatter.format(Formatter.java:1011)
E/AndroidRuntime( 5739): at java.lang.String.format(String.java:1999)
Next, I highlighted the format argument (d$ 1%), deleted it, and retyped it (by pressing % 1 $ d). My string now looks like this:
阿语是从右到左,输入后感觉是反向输入的
String.format 保留小数位数
String.format(“%.nf”,d);—- 表示保留 N 位
1
2
String result1 = String.format("%.2f", d);
String result = String.format("%.3f", d);
java.util.UnknownFormatConversionException: Conversion = 'د'
String.format
阿语报错,报错代码:
1
2
3
4
5
错误
<string name="store_goods_room_vehicle_welcome">مرحبا s% دخواه للغرفة بالسيارة</string>
代码:
StringUtils.format(R.string.store_goods_room_vehicle_welcome, commonInfo?.user?.name?: "")
这是由于阿语下 %s
写反了。
解决:
1
<string name="store_goods_room_vehicle_welcome">مرحبا %s دخواه للغرفة بالسيارة</string>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
2020-04-26 21:08:51.828 E/CrashReport: java.util.UnknownFormatConversionException: Conversion = 'د'
at java.util.Formatter$FormatSpecifier.conversion(Formatter.java:2782)
at java.util.Formatter$FormatSpecifier.<init>(Formatter.java:2812)
at java.util.Formatter$FormatSpecifierParser.<init>(Formatter.java:2625)
at java.util.Formatter.parse(Formatter.java:2558)
at java.util.Formatter.format(Formatter.java:2505)
at java.util.Formatter.format(Formatter.java:2459)
at java.lang.String.format(String.java:2870)
at qsbk.app.chat.common.utils.StringUtils.format(StringUtils.java:37)
at qsbk.app.chat.common.utils.StringUtils.format(StringUtils.java:28)
at club.jinmei.mgvoice.m_room.model.message.RoomCommonInfoMessage.getVehicleNameCorrectText(RoomCommonInfoMessage.kt:71)
at club.jinmei.mgvoice.m_room.model.message.RoomCommonInfoMessage.getMessageShowContent(RoomCommonInfoMessage.kt:97)
at club.jinmei.mgvoice.m_room.room.message_list.MessageAdapter.bindMessage(MessageAdapter.kt:594)
at club.jinmei.mgvoice.m_room.room.message_list.MessageAdapter.convert(MessageAdapter.kt:163)
at club.jinmei.mgvoice.m_room.room.message_list.MessageAdapter.convert(MessageAdapter.kt:83)
at com.chad.library.adapter.base.BaseQuickAdapter.onBindViewHolder(BaseQuickAdapter.java:983)
......
DecimalFormat
通过 DecimalFormatSymbols
1
DecimalFormat("###.##", DecimalFormatSymbols.getInstance(Locale.ENGLISH))
阿语下数字 0 是一个点
.
双方向字符 (bidi 字符) 方向适配
见 bidi算法-xxx.md
辅助查看加的 bidi 控制符对不对:
1
2
3
4
5
6
7
8
9
10
11
12
13
/**
* 字符串转换unicode
*/
fun String.string2Unicode(): String {
val unicode = StringBuffer()
for (i in 0 until length) {
// 取出每一个字符
val c = this[i]
// 转换为unicode
unicode.append("\\u" + Integer.toHexString(c.toInt()))
}
return unicode.toString()
}
X6 金豆反过来
- LTR 中显示
x 6
- RTL 显示
6 X
时间适配
如时间、数字等阿拉伯数字,无需适配阿拉伯语的话,指定 Locale.US 即可
时间格式化代码:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
private const val DEFAULT_DATE_FORMAT = "yyyy-MM-dd HH:mm:ss"
private const val DEFAULT_DATE_FORMAT_DAY = "yyyy-MM-dd"
fun Long.format(): CharSequence {
return _format()
}
fun Long.formatUS(): CharSequence {
return this._format(locale = Locale.US)
}
fun Long.formatCN(): CharSequence {
return this._format(locale = Locale.CHINA)
}
fun Long.formatDay(): CharSequence {
return this._format(DEFAULT_DATE_FORMAT_DAY)
}
fun Long.formatDayUS(): CharSequence {
return this._format(DEFAULT_DATE_FORMAT_DAY, Locale.US)
}
fun Long.formatDayCN(): CharSequence {
return this._format(DEFAULT_DATE_FORMAT_DAY, Locale.CHINA)
}
private fun Long._format(
pattern: String = DEFAULT_DATE_FORMAT,
locale: Locale = Locale.getDefault()
): CharSequence {
return SimpleDateFormat(pattern, locale).format(this)
}
控件适配
布局和控件适配
- padding 和 margin 的适配:start 和 end 替代 left 和 right
- TextView 的适配:layoutDirection(设置组件的布局方向)、textDirection(设置组件文字的方向) 、textAlignment(设置组件文字的对齐)
- ViewPager 的适配,建议用 ViewPager2
使用全局样式
EditText
在 style.xml 样式中全部 EditText 都设置
1
2
3
4
5
6
7
8
9
10
11
<style name="AppTheme" parent="Theme.AppCompat.Light.NoActionBar">
...
<item name="editTextStyle">@style/EditTextStyle.Alignment</item>
...
</style>
<style name="EditTextStyle.Alignment" parent="@android:style/Widget.EditText">
<item name="android:textAlignment">viewStart</item>
<item name="android:gravity">start</item>
<item name="android:textDirection">locale</item>
</style>
TextView
全局给所有 TextView 添加一个 RTL 属性
1
2
3
4
5
6
7
8
9
<style name="AppTheme" parent="Theme.AppCompat.Light.NoActionBar">
...
<item name="android:textViewStyle">@style/TextViewStyle.TextDirection</item>
...
</style>
<style name="TextViewStyle.TextDirection" parent="android:Widget.TextView">
<item name="android:textDirection">locale</item>
</style>
TextView 适配
- 用 setCompoundDrawablesRelative 替代 setCompoundDrawables
- android:layoutDirection 设置组件的布局方向
- INHERIT
layout direction 继承 parent - LOCALE
layout direction 由 locale 中决定 - LTR
layout direction left to right - RTL
layout direction right to left
- INHERIT
- android:textDirection 设置组件文字的方向
Defines the direction of the text. 必须是一个 int 值.
- anyRtl 段落的文本方向是 RTL 如果包含任意一个 RTL 强字符,如果没有一个 RTL 字符看是否包括任意一个 LTR 字符有的话就是 LRT;如果两者都不是段落文本的方向由这个 view 的 layout direction 决定
- firstStrong 根 view 默认是这个。段落文本方向取第一个强字符的方向作为文本方向,如果没有强字符,由这个 view 的 layout direction 决定
- firstStrongLtr 段落文本方向取第一个强字符的方向作为文本方向,如果没有强字符,该段落的文本方向是 LTR
- firstStrongRtl 段落文本方向取第一个强字符的方向作为文本方向,如果没有强字符,该段落的文本方向是 RTL
- inherit 默认
- locale 段落文本方向跟随系统 Locale
- ltr 段落文本方向是 ltr
- rtl 段落文本方向是 rtl
- android:textAlignment 设置组件文字的对齐
Defines the alignment of the text.
- inherit 默认
- center 居中对齐段落
- gravity root view 默认,和每行文字的方向相关
- textEnd 对齐段落 end,例如:
ALIGN_OPPOSITE
- textStart 对齐段落 start,例如:
ALIGN_NORMAL
- viewEnd 对齐 view 的 end,如果 layoutDirection 是 LTR 是 ALIGN_RIGHT,如果是 RLT 那么是 ALIGN_LEFT
- viewStart 对齐 view 的 start,如果 layoutDirection 是 LTR 是 ALIGN_LEFT,如果是 RLT 那么是 ALIGN_RIGHT
ViewPager 适配
Android 官方控件大多支持 RTL,ViewPager 除
https://github.com/diego-gomez-olvera/RtlViewPager
https://github.com/yotadevices/RtlViewPager
貌似在阿语会出现 StackOverflowError,可采用 ViewPager2 替代
切换语言
见 [[多语言切换]]
多语言切换包
- 服务器编辑好多语言包
- 生成对应的语言包 res/values/strings.xml,其他语言的 strings.xml
- 客户端定义一个 task,下载多语言包
- 解压覆盖多语言
其他
对集合进行倒序处理
1
Collections.reverse(List<?> list);
代码动态设置控件 setMargins
1
2
3
FrameLayout.LayoutParams params = new FrameLayout.LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT);
params.setMargins(10, 0, 10, 0);
params.setMarginEnd(10);
其他
- 横向布局 LinearLayout ,可以使用 FrameLayout,控件需要靠左或靠右可以使用 layout_gravity 设置对应属性
切换阿拉伯语时,网格布局 item 之间的距离会出现增大问题,处理方法是:网格分割线 ItemDecoration 需要加入语言来判断,调换原来设置左右的边距即可 - 禁止掉之前的侧滑返回,以免出现冲突
- 一些方向图标,重新做一个相对方向的放到 mipmap-ldrtl-xxxhdpi 包下;也可以一张图设置翻转
- 动画翻转, 放在 anim-ldrtl 将对应的动画进行反向处理
- 布局里如果设置了 paddingLeft、drawableLeft 等等这些属性更改为一个支持 RTL 的属性 paddingStart、drawableStart;但是有些地方可以不加的,例如:购物车上的数量徽章,加了之后感觉怪怪的,所以还是不加了
- 利用在 AS 右边的预览布局工具中的语言切换工具,切换成阿拉伯语,能实时看到布局的效果图
- EditText 添加 android:layoutDirection=”locale” ,如果外面有 TextInputLayout 的需给它设置 android:textDirection=”locale” ,如果输入类型时密码时还需添加一个属性 android:textAlignment=”viewStart”
- TextView 需要加上 android:textAlignment=”viewStart 或 viewEnd” 以及 android:textDirection=”locale”
- RecyclerView 网络布局的可以考虑使用 StaggeredGridLayoutManager ,如果数量太多的网格布局,不太建议使用,可能会出现滑动混乱
- 阿拉伯语目录下的 String.xml 文件, 出现占位符 d% 需要注意改为 %d, 但又并不是所有都改成这样, 目前我发现当代码中使用了 Toast 和 SpannableString 属性的就需要更改为 %d
Ref
- Android 中东阿拉伯语适配,看这一篇够了
https://www.jianshu.com/p/d8cd294a5c31 - 安卓自动化脚本,辅助开发者处理国际化问题