Android RTL(right to left) 适配采坑

5,315 阅读3分钟

背景

最近参与了一个中东项目,给阿拉伯土豪们做 app。其中最重要的点就是 RTL(right to left)适配,因为他们的语言是从右向左读(如下图),所以布局也要做镜像。

20190817220045.png

开始适配

网上关于 rtl 适配有很多资料,一些常规做法本文不再赘述,只记录一些比较特别的点以及解决方案。

1、AndroidStudio Refactor 一键适配布局文件

这个技巧很多文章都提到了,本文不再解释细节了,只说一下步骤。

  1. res 文件夹右键 -> refactor -> add Right-to-left Support

    image.png

  2. 勾选你要适配的版本

我这次只选了第一项(Replace left/right with Start/End)。第二项是用来适配 android 4.2 以下版本的,没有亲身实践是否好用,如果你有需求可以自己试试

3.Run

然后你的布局文件中所有使用 MarginLeft/PaddingLeft 等等关于左右布局的地方都会被替换为 XXXStart/End。

这时运行程序,会发现大部分的 rtl case 都会覆盖了。

2、ViewPager

关于 ViewPager,网上资料也说的比较详细。本文只记录两个重点

  1. 官方 ViewPager2 默认支持 RTL,推荐直接用

  2. 使用 RtlViewPager

本次RTL适配原计划是直接将项目重构成 ViewPager2,但无奈的是,项目中有一些基础 UI 库强耦合了 ViewPager1,导致适配工作量很大,于是不得不使用 RtlViewPager。

关于 RtlViewPager,项目地址,作者也表示到,此库不再维护,推荐大家使用 ViewPager2

image.png

3、图标的翻转

比如项目中页面返回键,是自己设计的箭头,也需要翻转。这里不需要再找设计重新切图了,Android xml 有属性 android:autoMirrored="true",可以在 rtl 时帮你翻转 drawable

需要注意android:autoMirrored="true"需要加在最外层,如果加在了里层是不生效的,不如下面这段代码示例


<?xml version="1.0" encoding="utf-8"?>
<selector xmlns:android="http://schemas.android.com/apk/res/android" android:autoMirrored="true"> <!-- 加这里,生效 -->

  <item android:state_enabled="false">
    <bitmap android:alpha="@integer/xxx" android:dither="true"
      android:src="@drawable/xxx" /> <!-- 加这里,不生效 -->
  </item>

  <item android:state_pressed="true">
    <bitmap android:alpha="@integer/xxx" android:dither="true"
      android:src="@drawable/xxxl" />
  </item>
  <item android:drawable="@drawable/xxx"/>

</selector>

4、RecyclerView#ItemDecoration

RecyclerView 中使用 ItemDecoration 时,通常我们设置 item 之间的 marginLeft/Right,看到 left right 就知道要出问题了。

比如项目中有下面这样的代码,如果是第一个 item,marginLeft 15,如果不是则为 5,这种效果在项目中很常见。但是在 rtl 之后,第一个 item 会到最右,导致 margin 错乱


  @Override
  public void getItemOffsets(Rect outRect, View view, RecyclerView parent,
          RecyclerView.State state) {
    final int childPosition = parent.getChildAdapterPosition(view);
    if (mOrientation == OrientationHelper.HORIZONTAL) {
      outRect.left = childPosition == 0 ? 15 : 5;
    }
  }

这种情况下,需要我们手动判断去适配,改后代码如下


  @Override
  public void getItemOffsets(Rect outRect, View view, RecyclerView parent,
          RecyclerView.State state) {
    final int childPosition = parent.getChildAdapterPosition(view);
    if (mOrientation == OrientationHelper.HORIZONTAL) {
      if (RTLHelper.isRtl(parent) {
          outRect.right = childPosition == 0 ? 15 : 5;
      } else {
          outRect.left = childPosition == 0 ? 15 : 5;
      }
    }
  }

5、View 动画适配

  1. View#getX 和动画坐标系翻转问题 比如项目中需要用到 View 在屏幕中的位置信息以及 View#getX 信息做一些过度动画,比如电商 app 里,选择了一件商品加入购物车时,会将有商品掉落进购物车的过度动画,如下图

Blank Diagram (2).png

如果你的过度动画方案是

  • new 一个过度的 movingView
  • movingView setX setY
  • 做 translation 和 scale 动画

就会发现rtl 后,getX 获取的位置时屏幕位置,这没问题,但动画坐标系其实是翻转过来,如图中右半部分(这里是为什么没有细研究,实践过确实如此)

比如翻转后再使用 setX(500),实际上 view 已经跑到屏幕外的右边了,所以需要用屏幕宽度减去当前 x,让 x 为负数,使 view 回到坐标系内。另外可能用到 View#getLeft/Right 等 api 时都会涉及类似问题。

结尾

本文只是记录了我在适配过程中遇到的一些特殊的点,需要从零开始适配 rtl 的同学可以参考这篇 Android 本地化适配:RTL(right-to-left) 适配清单,其中讲清了大部分适配的做法,很详细。

另外,当心切换阿拉伯语后,切不回来默认语言~

转文声明

如有文章转载需求,请注明本文作者以及链接,感谢各位理解支持