做 Android 国际化,遇到阿拉伯语言,会注意到,他们的本地阅读习惯是从右往左的,也就是 RTL。

Android 自带了 RTL 的方案,文字和布局方向会自动镜像排列,但是图片不会自动镜像。图片的镜像有两种方案,都需要额外的工作:

  1. 在 drawable 对应的 ldtr 文件夹放入一张对应图片的同名的镜像图片,例如 drawable-ldrtl-xhdpi 文件夹
  2. 不直接使用图片,创建 drawable,例如:
    <?xml version="1.0" encoding="utf-8"?>
    <bitmap xmlns:android="http://schemas.android.com/apk/res/android"
        android:src="@drawable/icon"
        android:autoMirrored="true"/>

上面第 2 种方案,可以注意到 android:autoMirrored="true" 这个属性,他能把 drawable 自动镜像,很遗憾,这个属性不能直接在布局的 XML 文件中使用。

不过,我们可以使用 Android 的 layout infalter 机制,Hook View 树的生成过程,修改 ImageView 的 src 或者 View 的 background,利用 android:autoMirrored="true" 对应的 drawable.setAutoMirrored(true) 代码。

Android 提供了这样的 Hook 接口:LayoutInflater.FactoryLayoutInflater.Factory2,后者比前者多一个重载方法,直接使用后者即可。在 Activity 创建的时候,注入我们自己的 rtlFactory

val inflater = activity.layoutInflater  
inflater.factory2 = rtlFactory

XML 生成 View 的时候,会触发 rtlFactory 中的 onCreateView() 方法,这个方法里,我们可以将生成的系统 View 对象替换成我们指定的自定义的 View 对象,比如下面的 RtlImageVIew

override fun onCreateView(
    parent: View?, name: String, context: Context, attrs: AttributeSet
): View? {
    if (name == "ImageView) {
        return RtlImageVIew(context, attrs)
    }
}

RtlImageVIew 中,可以读出 XML 文件中设置好的图片资源,设置自动镜像:

val drawable = AppCompatResources.getDrawable(view.context, srcResId) //读取 drawable
drawable?.isAutoMirrored = true //设置自动镜像
view.setImageDrawable(drawable)

当然也可以给 RtlImageView 添加自定义属性 app:mirrorSrc="true",用来标记是否需要自动镜像。View 的 background 也同理。

综上,在布局的 XML 文件中,如果需要图片自动镜像,直接写即可:

<ImageView  
    android:layout_width="26dp"  
    android:layout_height="26dp"
    app:mirrorSrc="true"  
    android:src="@drawable/icon" />

<TextView  
    android:layout_width="wrap_content"  
    android:layout_height="wrap_content"  
    app:mirrorBackground="true"
    android:background="@drawable/bg_chat" />

更多详细的代码在 GitHub 上,我封装了个库,可以直接使用。