Flutter如何适配RTL
阿拉伯语和希伯来语等是使用的从右到左书写的文字系统。世界上估计有4.22亿人以阿拉伯语做为母语。使用从右至左的人口可以说是更多了。所以对于出海项目来说,是不能忽视的一部分。
RTL可以说是本地化适配中比较麻烦的一项,并没有多语言适配来的简单。RTL简单说就像视频开了“镜像”,但实际并没有这么简单粗暴,比如商品图片我们不可能镜像,但是返回按钮的箭头却需要翻转过来。
因为这方面适配国内不常做,所以相关资料比较少。我自己在采坑后,做了下面的整理,希望可以给到你帮助。
概览
这里我们可以先建立实现目标的效果,所以可以参考Material的双向设计指南
我列出一些效果图片,简单说明一下:
-
代表方向元素的需要“镜像”,某些图标可能看起来有方向性,但它们实际上代表用右手握住一个物体,比如搜索图标的手柄通常位于右下角。所以不用处理。但如果是返回按钮,则需要。
-
阿拉伯文字大部分但不完全是从右到左书写的;数学表达式、数字日期和数字单位仍使用从左到右的书写方式。例如图中的输入框,我们将文字排列方向改为了从右至左,此时用户输出英文等其他从左至右的语言时,需要遵循自己的规则。
需要注意的一些情况:
- 不要镜像媒体播放按钮和进度条,因为它们指的是磁带播放的方向,而不是时间的方向。
- 时钟仍然顺时针旋转。时钟图标或带有顺时针箭头的圆形刷新或进度指示器不应镜像。
适配
首先需要项目中接入intl
,这部分我不过多介绍,可以参考官方文档。这里想说明的是如果当前语言设置为了从右到左的语言,例如阿拉伯语(Locale('ar')
),那么Flutter就会自己翻转过来,我们不需要额外的进行设置。
因为GlobalWidgetsLocalizations
帮我们做了适配。
所以对于自带的Widget,例如PageView
、BottomNavigationBar
,我们不需要做特殊处理,减轻了很多适配工作。
我们上面也提到了,实际适配中情况比较多,并不是简单的“镜像”。所以Flutter帮我们做的全局处理一定造成“误伤”。
如果你需要固定方向,可以使用Directionality
Widget包裹,指定textDirection
属性。如果是Row
、Stack
、Text
等可以设置textDirection
。
Widget替换
适配之所以这么简单,主要是因为我们常用的Row
等Widget里面的crossAxisAlignment
方向并不是有明确方向性的left、right,而是start、end。
如果反过来想,适配可能有问题的地方就是我们代码中指定left、right的地方。比如我们常用的EdgeInsets.only(left: 12)
、Align
的Alignment.centerRight
、Positioned
。
我下面列出一些对应的替代方法,我们可以在平时的开发中就养成习惯,使用这些Widget。做到更好的兼容性。
Positioned
->PositionedDirectional
Positioned(
left: 50,
child: RedBox(),
),
PositionedDirectional(
start: 50,
child: RedBox(),
),
Alignment
->AlignmentDirectional
Align(
alignment: Alignment.centerRight,
child: RedBox(),
),
Align(
alignment: AlignmentDirectional.centerEnd,
child: RedBox(),
),
EdgeInsets
->EdgeInsetsDirectional
Padding(
padding: EdgeInsets.only(right: 10),
child: RedBox(),
),
Padding(
padding: EdgeInsetsDirectional.only(end: 10),
child: RedBox(),
),
Border
->BorderDirectional
BoxDecoration(
border: Border(left: BorderSide(width: 4)),
color: Colors.red,
),
BoxDecoration(
border: BorderDirectional(start: BorderSide(width: 4)),
color: Colors.red,
),
BorderRadius
->BorderRadiusDirectional
BoxDecoration(
borderRadius: BorderRadius.only(topLeft: Radius.circular(10)),
color: Colors.red,
),
BoxDecoration(
borderRadius: BorderRadiusDirectional.only(topStart: Radius.circular(10)),
color: Colors.red,
),
方向判断
比如我们有一个返回按钮,此时需要翻转过来。可以使用Transform.scale
widget和Directionality.of(context)
方法结合处理。
Transform.scale(
scaleX: Directionality.of(context) == TextDirection.rtl ? -1 : 1,
child: Image.asset("xxx"),
);
PS:使用Icon
Widget可以使用textDirection
属性处理。
混合文本
混合文本就是说,一段文字里有多种语言,例如他说‘Hello’,我说30%
。这里面有中文,英文,数字。那么我们翻译后,得到的结果是قال Hello، قلت 30%
,实际显示如下:
可以看到英文,数字的部分是保留原有的方向。这就是上面我们说到的如果是输入框,需要根据输入的语言,遵循各自的显示规则。
- 输出或显示英文,数字时,从左到右显示。
- 包含阿拉伯语时,从右到左显示。
例如Text
、TextField
Widget一般不用指定textDirection
属性,自动就会展示正确顺序。不过标点符合是个例外,例如What is your gender?
会显示成?What is your gender
。因为对于这个符号,无法确定你的语言,所以就使用了从右到左,放到了”末尾“。
再例如输入内容的时候,我们希望输入和书写的习惯是一致的,输入英文数字时从左到右,输入阿拉伯语是从右到左。
这里我们就可以使用intl
中的Bidi.detectRtlDirectionality(text)
方法,返回true就是TextDirection.rtl
,false就是TextDirection.ltr
。然后将方向结果设置到TextField
中。
方便起见,可以使用auto_direction库处理。它的内部就是使用Bidi.detectRtlDirectionality
方法实现的。
CustomPainter
CustomPainter
绘制的位置都是固定的,所以可以参考图片翻转的方法处理。这样处理比较简单。
其他
- 字体选择、行高、字间距也是需要考虑的。
- 日历也是从右至左,需要考虑使用标准阿拉伯文数字还是东阿拉伯数字。
适配这项工作要想简单,就是要在项目初期规划好,代码也要规范。例如暗黑模式适配,如果颜色都是各处写死,没有统一封装管理,等到适配的时候就是处处修改。RTL也是一样,如果初期就使用Directional相关的widget去实现,适配时也能轻松不少。
最后就是在实际适配中,可以多搜集一些规则,多看一些国外网站,App的效果作为参考。当然最好是有一个懂阿拉伯语的人,哈哈。
参考
-
Right to Left (RTL) in Flutter App - Developer’s Guide
-
阿拉伯语本地国际化