概述
阿拉伯作为一个土豪帝国,android在sdk17(Android4.2)也开始支持RTL(right to left)布局了。镜像view等一些设计原则,都可以从官网找到。给出链接传送门
https://material.io/guidelines/usability/bidirectionality.html#bidirectionality-rtl-mirroring-guidelines
RTL布局
rtl布局的判断来自两个部分,一个是系统语言,还有一个就是AndroidManifest.xml中application结点中的supportRtl属性,只有当为true的时候,app才会支持镜像适配。
<application
...
android:supportsRtl="true"
/>
</application>
布局的判断可以使用LayoutDirection.
总结一个函数就是这样的:
/**
* 判断是否进行阿拉伯镜像,判断取决于两个条件:<br />
* 一丶Androidmanifest.xml中的supportsRtl<br />
* 二、当前系统为阿拉伯语
*
* @return 是否为rtl
*/
public static boolean isRTL() {
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.JELLY_BEAN_MR1) {
return false;
}
ApplicationInfo applicationInfo = BaseApplication.getInstance().getApplicationInfo();
boolean hasRtlSupport = (applicationInfo.flags & FLAG_SUPPORTS_RTL) == FLAG_SUPPORTS_RTL;
boolean isRtl = TextUtils.getLayoutDirectionFromLocale(Locale.getDefault()) == View.LAYOUT_DIRECTION_RTL;
return hasRtlSupport && isRtl;
}
如何适配
在如何适配上,可以分几个方面:图标、xml属性、代码set方法、部分控件、自定义控件。
图标
图标大概是最直白的一个。back按钮,喇叭、进度条等等,都得反一下。新建一个drawable-ldrtl-xxhdpi目录统统扔进去。
ps:具体的可以参考之前的官网链接,里面讲的很清楚,只提一个重点,那么就是进度条,并不是所有的进度条都需要镜像,如果是多媒体播放的进度条,那么还是遵循从左往右的,例如视频等等,也可以参考youtube、facebook这样的app来看看。
xml属性
对于UI上的适配,大部分处理都是由布局xml来决定的,这里推荐使用Android Studio自带的“RTL Support”来优化。
鼠标右键编辑区域,Refactor->Add RTL Support Where Possible...
点击run后,会适配所有left和right相关的属性,一律换成start和end,值得注意一点的是,如果你的app要支持到17以下,那么写属性的时候,需要把两个方法都写上,这样才不会报错。
这里涉及到的相关属性包括:
LTR | RTL |
---|---|
layout_marginLeft /layout_marginRight | layout_marginStart/layout_marginEnd |
paddingLeft/paddingRight | paddingStart/paddingEnd |
layout_alignLeft/layout_alignRight | layout_alignStart/layout_alignEnd |
layout_alignParentLeft/layout_alignParentRight | layout_alignParentStart/layout_alignParentEnd |
layout_toLeftOf/layout_toRightOf | layout_toStartOf/layout_toEndOf |
drawableLeft/drawableRight | drawableStart/drawableEnd |
ps:可能还有某些比较偏的属性,就不列出来了。这里还有一个比较特殊的gravity和layout_gravity,当app支持17以下的时候,可以直接使用start/end代替left/right,官方解释是start/end的字节码要大于left/right,所以你可以直接写成
android:gravity="start"
而不用写成
android:gravity="left|start"
还有一个就是textview,阿拉伯文字都是从右往左的,所以在阿拉伯语言下,如果出现英文中文或者数字,有时候还是会显示在左边。这里介绍一个属性“textDirection”。可以自由定义文字从哪一边开始显示。一般可以设置android:textDirection="locale"让它跟着系统语言走。控制view也有相同属性,叫作“layoutDirection”,可以控制view是否镜像(比如不想某些view发生镜像,可以通过设置这个属性来控制)。
======2018.01.17补充======
今天发现,<include..>标签里的属性,不会被Refactor->Add RTL Support Where Possible...适配~
代码set方法
这个就很好理解了,xml属性里用到的,代码设置里也需要适配一下。同样,支持17以下的话,需要适配版本,话不多说,直接上代码
RelativeLayout.LayoutParams params = (RelativeLayout.LayoutParams) view.getLayoutParams();
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1) {
params.setMarginEnd(0);
} else {
params.rightMargin = 0;
}
这里涉及到的相关方法包括:
LTR | RTL |
---|---|
.leftMargin /.rightMargin | setMarginStart/setMarginEnd |
setPadding | setPaddingRelative |
setCompoundDrawablesWithIntrinsicBounds | setCompoundDrawablesRelativeWithIntrinsicBounds |
setCompoundDrawables | setCompoundDrawablesRelative |
ps:可能有些比较偏的函数,这里也不列出来了。还有就是给recyclerview设置ItemDecoration的时候,left/right要区分RTL。
部分控件
虽然Android说是支持了RTL,但是还是有一些常用的控件,没能做到适配,这里列举我遇到的两个。viewpager和tablayout(tablayout继承HorizontalScrollView)。
viewpager主要就是手势和view child的交互问题,很简单,网上已经有大神给出了解决方案,大致就是对数据源进行反转,上github链接
RtlViewPager。
HorizontalScrollView的问题主要就是,有时候会发生初始状态不在0位置。这里的解决方案是,计算所有child的宽度总和,然后scroll一下就行了。当然,没那么笨,因为HorizontalScrollView里面只能放一个layout,只要计算这个一个layout的宽度就行了。这个问题的原因暂时还没看源码,但是估计是因为,当HorizontalScrollView add完所有的child的时候,会停留在最后,然后执行一波scrollTo(0),让队列回到最前面,但是对于RTL的布局来讲,0位置恰恰就是最后,因此就会出现初始状态不在0位置的问题。
/**
* @author tong.xu
* fix rtl下,tablayout初始化后,不是停留在初始位置的bug
*/
final int scrollX = mTabLayout.getChildAt(0).getMeasuredWidth();
if (CommonUtil.isRTL() && scrollX != 0) {
mTabLayout.post(new Runnable() {
@Override public void run() {
mTabLayout.scrollTo(scrollX, 0);
}
});
}
自定义控件
大概这一部分是最难改的了,为什么这么说呢,因为它大概集成了上述所有的问题,并且还要结合自定义控件的逻辑去修改。如果是当初自己写的还稍微好点儿,要是别人写的,还要去读懂实现的逻辑,那是非常耗时费劲的,还容易出bug,所以建议还是谁挖的坑,谁来补(哈哈_,不厚道地笑了)。
简单说说怎么改的入手点吧。首先弄明白rtl和正常的布局有什么不同,比如getLeft(),getX(),X坐标上的变动基本都是要反的,就像之前的HorizontalScrollView,什么滚动啊之类的,都要注意。我来举个最简单的例子,画一个刻度尺。
一格的刻度大小为
int cell = (Screen.width - 2*x) / 2;
ltr:1的x坐标是x,2是x + cell,3是x + 2 * cell
rtl:(这个时候就要从最右算过来)1的x坐标是Screen.width - x,2是Screen.width - x - cell,3是Screen.width - x - 2 * cell
各个值 | LTR下的x坐标 | RTL的x坐标 |
---|---|---|
1 | x | Screen.width - x |
2 | x + cell | Screen.width - x - cell |
3 | x + 2 * cell | Screen.width - x - 2 * cell |
看起来是不是一点儿也不麻烦,当然我这个只是一个最简单的例子,当各种逻辑混起来的时候,肯定还会复杂,我这里只给一个思路,具体的还是要靠自己去适配了。
补充(重点,很容易出现的问题)
如果在wrap_content的布局里,放置一个centerInParent的子view,或者是相对应垂直/水平方向的高宽(例如父布局的宽为warp_content,子view使用了centerInHorizontal),在RTL环境下,会导致子view宽高放大,进而影响相邻的view,可能会挤出去,最后看不到。看了一下源码,warp_content在rtl情况下并没有做适配,查看layout的时候,发现宽高变成了65536。。。具体可以看源码对wrap的计算。
总结
总结一下吧,阿拉伯适配对于一个国际化的公司其实还是很重要的,尤其是阿拉伯石油大亨,用户价值还是很高的。适配的操作步骤,按照上述方法一步步下来,先适配简单的,再适配困难的,个别问题再个别处理,一套下来适配个90%基本都是没有问题的。好了,就讲到这里吧!