Android国际化,阿语RTL适配总结

Android国际化,阿语RTL适配总结,第1张

语言切换 语言种类

例如:

/**
     * 英国(英语)
     */
    EN("en"),

    /**
     * 西班牙(西班牙语)
     */
    ES("es"),

    /**
     * 西班牙(葡萄牙语)
     */
    PT("pt"),

    /**
     * 法国(法语)
     */
    FR("fr"),

    /**
     * 俄罗斯(俄语)
     */
    RU("ru"),

    /**
     * 意大利(意大利语)
     */
    IT("it"),

    /**
     * 德国(德语)
     */
    DE("de"),

    /**
     * 荷兰(荷兰语)
     */
    NL("nl"),

    /**
     * 阿拉伯(阿拉伯语)
     */
    AR("ar"),

    /**
     * 韩国、朝鲜(韩语)
     */
    KO("ko"),

    /**
     * 日本(日语)
     */
    JA("ja");
代码切换App语言

更改context语言配置

@JvmStatic
    fun wrap(context: Context, newLocale: Locale): Context {
        return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
            createConfigurationResources(context, newLocale)
        } else {
            applyLanguage(context, newLocale)
            context
        }
    }

    private fun applyLanguage(context: Context, newLocale: Locale) {
        val resources = context.resources
        val configuration = resources.configuration
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
            // apply locale
            configuration.setLocale(newLocale)
        } else {
            // updateConfiguration
            configuration.locale = newLocale
            val dm = resources.displayMetrics
            resources.updateConfiguration(configuration, dm)
        }
    }

    @TargetApi(Build.VERSION_CODES.N)
    private fun createConfigurationResources(context: Context, newLocale: Locale): Context {
        val resources = context.resources
        val configuration = resources.configuration
        configuration.setLocale(newLocale)
        val localeList = LocaleList(newLocale)
        LocaleList.setDefault(localeList)
        configuration.setLocales(localeList)
        return context.createConfigurationContext(configuration)
    }

基类activity应用语言配置

open class BaseActivity : AppCompatActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
    }
    
    override fun attachBaseContext(newBase: Context) {
        super.attachBaseContext(LanguageUtil.wrap(newBase, Language.getCurrentLanguage()))
    }
}

LTR和RTL,适配阿语

RTL 语言由来
RTL 是 Right-to-left(从右向左) 的缩写。其意为人们书写阅读习惯是从右向左,朝左继续的,常见的 RTL 语言有阿拉伯语,希伯来语等。

全局API设置

Android 4.2 即 SDK 17 开始,提供了全面的本地布局支持,允许镜像布局,可以同时支持 RTL 和 LTR。

Android Studio全局设置RTL

AndroidManifest.xml

<application
        ...
        android:theme="@style/AppTheme"
        android:supportsRtl="true">
        ...
application>        
图片配置
将RTL切图放在下图对应的文件夹内,可以实现阿语下图片的转换
styles里的AppTheme对于TextView、EditText等控件的文本方向、对齐方式设置
android:layoutDirection 设置组件的布局排列方向
android:textDirection 设置组件的文字排列方向
android:textAlignment 设置文字的对齐方式
 <style name="AppTheme" parent="Theme.AppCompat.Light.NoActionBar">
        ...
        "editTextStyle">@style/AppTheme.EditTextStyle
        "android:textViewStyle">@style/AppTheme.TextViewStyle
        "android:autoCompleteTextViewStyle">@style/AppTheme.AutoCompleteTextView
        "textInputStyle">@style/AppTheme.TextInputLayout
        ...
    style>

    <style name="AppTheme.EditTextStyle" parent="Widget.AppCompat.EditText">
        "android:textAlignment">viewStart
        "android:textDirection">locale
    style>

    <style name="AppTheme.TextViewStyle" parent="Widget.AppCompat.TextView">
        "android:textDirection">locale
    style>

    <style name="AppTheme.AutoCompleteTextView" parent="Widget.AppCompat.AutoCompleteTextView">
        "android:textAlignment">viewStart
        "android:textDirection">locale
    style>

    <style name="AppTheme.TextInputLayout" parent="Widget.Design.TextInputLayout">
        "android:textAlignment">viewStart
        "android:textDirection">locale
        "hintEnabled">false
    style>

特别注意:在AppTheme中设置控件的文本方向和对齐方式,按道理是全局生效的。但是在实际使用中,并没有全局生效,这个时候需要在具体控件使用时进行单独设置

<TextView
        ...
        style="@style/AppTheme.TextViewStyle"
        .../>

    <com.google.android.material.textfield.TextInputLayout
        ...
        style="@style/AppTheme.TextInputLayout"
        ...>

        <MultiAutoCompleteTextView
            ...
            style="@style/AppTheme.AutoCompleteTextView"
            ... />

    com.google.android.material.textfield.TextInputLayout>
关于Padding、Margin、setCompoundDrawables等带有方向属性的设置

通过Android Studio的RTL工具全局替换,XML里面发生的变化有:
paddingLeft -> paddingStart
paddingRight -> paddingEnd
marginLeft -> marginStart
marginRight -> marginEnd
layout_alignParentLeft -> layout_alignParentStart
layout_alignParentRight -> layout_alignParentEnd
drawableLeft -> drawableStart
drawableRight -> drawableEnd
layout_constraintLeft_toLeftOf -> layout_constraintRight_toRightOf
layout_constraintRight_toRightOf -> layout_constraintEnd_toEndOf

所以,在代码和styles中关于上述带有方向的属性需要手动修改。
例如:

<resources>
     ...
    <style name="XXX">
        "android:layout_width">30dp
        "android:layout_height">20dp
        "android:layout_alignParentRight">true
        "android:layout_alignParentEnd">true
        "android:clickable">false
        "android:layout_marginEnd">15dp
        "android:layout_marginStart">15dp
        "android:focusable">false
    style>
    ...
resources>
...
enterBtn.setPaddingRelative(12F.dp2Px, 10F.dp2Px, 8F.dp2Px, 10F.dp2Px)
enterBtn.setCompoundDrawablesRelative(null, null, drawable, null)
...
    /**
     * 设置相对位置的margin
     */
    fun <T : ViewGroup.MarginLayoutParams> T.setRelativeMargin(margin: MICMargin) {
        marginStart = margin.left
        topMargin = margin.top
        marginEnd = margin.right
        bottomMargin = margin.bottom
    }
(parent.layoutParams as ViewGroup.MarginLayoutParams).setRelativeMargin(
                            24f.dp2Px
                            , 7f.dp2Px
                            , 7f.dp2Px
                            , 7f.dp2Px)
部分重要控件适配

ViewPager2
相比于ViewPager,ViewPager2基于RecyclerView实现,支持垂直方向滚动,支持RTL。
使用 ViewPager效果

ViewPager实现的图片查看


使用 ViewPager2效果

ViewPager2实现的图片查看

流式布局(FlexBoxLayout)
使用之前的自定义控件FlowLayout

使用谷歌官方的FlexBoxLayout


3. PopWindow

mPopupWin.showAsDropDown(targetView, 0, 0);

targetView应该为显示位置相对应的目标view

Banner
public class BannerLocaleCircleIndicator extends BaseIndicator {
    private int mNormalRadius;
    private int mSelectedRadius;
    private int maxRadius;

    public BannerLocaleCircleIndicator(Context context) {
        this(context, null);
    }

    public BannerLocaleCircleIndicator(Context context, AttributeSet attrs) {
        this(context, attrs, 0);
    }

    public BannerLocaleCircleIndicator(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        mNormalRadius = config.getNormalWidth() / 2;
        mSelectedRadius = config.getSelectedWidth() / 2;
    }

   ...
    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);
        int count = config.getIndicatorSize();
        if (count <= 1) {
            return;
        }
        float left = 0;
        int currentPosition = LanguageUtil.isAR() ? count - config.getCurrentPosition() - 1 : config.getCurrentPosition();
        for (int i = 0; i < count; i++) {
            mPaint.setColor(currentPosition == i ? config.getSelectedColor() : config.getNormalColor());
            int indicatorWidth = currentPosition == i ? config.getSelectedWidth() : config.getNormalWidth();
            int radius = currentPosition == i ? mSelectedRadius : mNormalRadius;
            canvas.drawCircle(left + radius, maxRadius, radius, mPaint);
            left += indicatorWidth + config.getIndicatorSpace();
        }
    }
}

banner指示器currentPosition在阿语下需要count - config.getCurrentPosition() - 1,为镜像位置

适配阿语的banner

针对性兼容 ImageView,字体图标TextView镜像
    /**
     * 将image、字体图标等控件镜像翻转
     */
    fun <T : View> T.imageToRTL() {
        if (LanguageUtil.isAR()) {
            scaleX = -1f
        }
    }
和Drawable镜像
     /**
     * 设置镜像drawable
     */
    fun <T : View?> T.setRtlBackgroundDrawable(drawable: Drawable?) {
        drawable?.isAutoMirrored = LanguageUtil.isAR()
        this?.backgroundDrawable = drawable
    }
动画方向
在阿语下,X坐标轴方向会改变
     /**
     * 购物车动画
     *
     * @param startView 动画的起点位置
     * @param endView   动画的终点位置
     * @param context
     * @param root      父窗体 用于添加的动画的View
     * @param time      动画持续时间单位s
     */
    public static void AddToCart(final Context context, final View startView, final View endView, final RelativeLayout root, final Drawable bgDrawable, final float time) {

        if (context == null || (context instanceof Activity && ((Activity) context).isFinishing()) || startView == null || endView == null || root == null || bgDrawable == null)
            return;
        endView.postDelayed(new Runnable() {
            @Override
            public void run() {
                //新建一个ImageView 用于动画显示
                final ImageView view = new ImageView(context);
                //确定ImageView大小与传进来的ImageView相同
                RelativeLayout.LayoutParams params = new RelativeLayout.LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT);
                //获取ImageView的图片 并设置在新的ImageView上
                view.setImageDrawable(bgDrawable);

                //计算父控件的位置
                int[] parent = new int[2];
                root.getLocationInWindow(parent);

                //计算起点控件位置
                int[] startLocation = new int[2];
                startView.getLocationInWindow(startLocation);
                if (LanguageUtil.isAR()) {
                    startLocation[0] = FMFScreenUtil.getWindowWidth() - startLocation[0];
                }

                //计算终点控件位置
                int[] endLocation = new int[2];
                endView.getLocationInWindow(endLocation);
                if (LanguageUtil.isAR()) {
                    endLocation[0] = FMFScreenUtil.getWindowWidth() - endLocation[0];
                }
                //确定ImageView的位置与startView相同
                params.setMarginStart(startLocation[0] - parent[0] - root.getPaddingStart());
                params.topMargin = startLocation[1] - parent[1] - root.getPaddingTop();
                root.addView(view, params);

                //计算两者的横向X轴的距离差
                int XtoX = endLocation[0] - startLocation[0]/* + endView.getWidth() / 2 - startView.getWidth() / 2*/ + endView.getWidth() / 2;
                //根据距离 时间 获取到对应的X轴的初速度
                final float xv = XtoX / time;

                //计算两者的横向X轴的距离差
                int YtoY = endLocation[1] - startLocation[1] + endView.getHeight() / 2;
                //根据距离 时间 初始设置的Y轴初速度与X轴初速度相同 获取到竖直方向上的加速度
                final float g;
                if (xv > 0) {
                    g = (YtoY + xv * time) / time / time * 2;
                } else {
                    g = (YtoY - xv * time) / time / time * 2;
                }
                final ValueAnimator va = new ValueAnimator();
                va.setDuration((long) (time * 1000));
                va.setObjectValues(new PointF(0, 0));
                //计算位置
                va.setEvaluator(new TypeEvaluator<PointF>() {
                    @Override
                    public PointF evaluate(float v, PointF pointF, PointF t1) {
                        PointF point = new PointF();
                        point.x = v * xv * time;
                        if (xv > 0) {
                            point.y = g * (v * time) * (v * time) / 2 - xv * v * time;
                        } else {
                            point.y = g * (v * time) * (v * time) / 2 + xv * v * time;

                        }
                        return point;
                    }
                });

                va.start();

                //设置动画
                va.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
                    @Override
                    public void onAnimationUpdate(ValueAnimator valueAnimator) {
                        PointF point = (PointF) valueAnimator.getAnimatedValue();
                        view.setTranslationX(point.x);
                        view.setTranslationY(point.y);
                    }
                });

                //在动画结束时去掉动画添加的ImageView
                va.addListener(new Animator.AnimatorListener() {
                    @Override
                    public void onAnimationStart(Animator animator) {

                    }

                    @Override
                    public void onAnimationEnd(Animator animator) {
                        root.removeView(view);
                    }

                    @Override
                    public void onAnimationCancel(Animator animator) {

                    }

                    @Override
                    public void onAnimationRepeat(Animator animator) {

                    }
                });
            }
        }, 100);
    }
React-Native RTL适配 图片RTL适配
import {isSA} from '@utils/common';

const imagePath = {
  XXX: () => {
    return isSA()
      ? require('@assets/img/XXX@rtl.png')
      : require('@assets/img/XXX.png');
  },
  XXXX: () => {
    return isSA()
      ? require('@assets/img/XXXX@rtl.png')
      : require('@assets/img/XXXX.png');
  },
};

export default imagePath;
                <ImageBackground
                  ...
                  source={imagePath.member_center_footer_image()}>
                  ...
                </ImageBackground>
Android和iOS适配细节
问题类型结论
排版总体整齐,个别文案显示错乱或者不规范个别文案或者控件显示错乱不对齐时,按照整体页面的位置和方向进行对齐,可以考虑单独适配的技术实现方式。
阿语 英文混合出现,按照什么样式展示?1.整体按照语言环境去显示位置,比如英语环境下,整体居左;在阿语环境下,整体居右。 2.英文单词本身还是按照英文顺序从左往右展示。
纯英文在阿语下按照什么样式展示?1.整体按照语言环境去显示位置,比如英语环境下,整体居左;在阿语环境下,整体居右。2.按照英文单词顺序显示。
输入框输入光标,英文对齐方式Android 1.光标英语环境下,居左;阿语环境下,居右。 2.阿语环境下,输入的文案,如果是英文,显示在左边;如果是阿语,显示在右边 。iOS 1.ios15以下,光标和输入内容都是居右显示。 2.ios15以上,光标和输入内容都是根据输入语言,英文居左,阿语居右。

欢迎分享,转载请注明来源:内存溢出

原文地址: http://outofmemory.cn/web/992562.html

(0)
打赏 微信扫一扫 微信扫一扫 支付宝扫一扫 支付宝扫一扫
上一篇 2022-05-21
下一篇 2022-05-21

发表评论

登录后才能评论

评论列表(0条)

保存