资源和布局方向在Android 8.0及以上版本上呈现不正确

16
我是一名有用的助手,可以翻译文本。
我有一个多语言应用程序,主要语言为英语,次要语言为阿拉伯语。
我在我的应用程序中的每个活动的onCreate()中调用setLocale()
public static void setLocale(Locale locale){
    Locale.setDefault(locale);
    Context context = MyApplication.getInstance();
    final Resources resources = context.getResources();
    final Configuration config = resources.getConfiguration();
    config.setLocale(locale);
    context.getResources().updateConfiguration(config,
            resources.getDisplayMetrics());
}

其中locale是以下内容之一:

![enter image description here

上述方法被称为在调用super.onCreate(savedInstanceState)之前调用。
文档所述,
  • 我已将android:supportsRtl="true"添加到清单中。
  • 我已将所有具有leftright属性的xml属性更改为分别为startend
  • 我已将阿拉伯语字符串放入res\values-ar\strings文件夹中,并将可绘制资源放入res\drawable-ar文件夹中(其他资源同样)。
上述设置正常工作。将Locale更改为ar-AE后,我的活动中正确显示了阿拉伯文本和资源。
但是,所有Android设备版本8.0及以上的资源和布局方向存在问题。
在版本小于8.0的设备上,RTL屏幕正确地显示如下:

enter image description here

在所有8.0+的设备上,相同的屏幕会显示为以下内容:

enter image description here

这是错误的。

事实证明,方向和资源的显示都不正确。

这里有两个问题:

  • 整个应用程序配置中似乎没有更新正确的Locale
  • 文本和可绘制对象的方向与应该的相反。

关于方向,有一个奇怪的方法叫做setLayoutDirection(),我之前没有注意到。

我想知道这个问题是什么,为什么会在Oreo中出现,以及解决方法。请帮忙/评论此事。

编辑

根据API差异报告updateConfiguration()方法在Android 7.1(API级别25)中已被弃用。此外,找到了所有相关帖子。按重要性顺序排列如下:
1. Android N如何通过编程更改语言
2. Android context.getResources.updateConfiguration()已过时
3. 如何更改Android O / Oreo / api 26应用程序的语言
4. Android RTL问题在API 24及更高版本上当区域设置更改时
5. 在Android N 7.0 - API 24中编程更改语言 6. Android N - 在运行时更改区域设置 7. Android Oreo中RTL布局错误

你在谈论什么变化? - Yash Sampat
https://developer.android.com/guide/topics/resources/multilingual-support - Ben P.
1
@BenP。谢谢你,但是它在安卓7.0 / 7.1设备上运行正常。多语言支持似乎不是问题所在。 - Yash Sampat
嗯,好的。我只是很惊讶很多文本都变成了英文。 - Ben P.
你能检查一下设备的开发者选项吗?在设置中,检查一下是否打开了“强制使用RTL布局方向”。 - Bhuvaneshwaran Vellingiri
显示剩余5条评论
6个回答

4

在 API 等级 25 中,方法 Resources#updateConfiguration (Configuration config, DisplayMetrics metrics) 已被弃用。

文档建议使用Context#createConfigurationContext (Configuration overrideConfiguration)


你可以创建一个基础活动,它是所有活动的共同父类,如下所示。

public class BaseActivity
        extends AppCompatActivity {

    private static final String LANGUAGE_CODE_ENGLISH = "en";
    private static final String LANGUAGE_CODE_ARABIC = "ar";

    @Override
    protected void attachBaseContext(Context newBase) {
        super.attachBaseContext(getLanguageAwareContext(newBase));
    }

    private static Context getLanguageAwareContext(Context context) {
        Configuration configuration = context.getResources().getConfiguration();
        configuration.setLocale(new Locale(getLanguageCode()));
        return context.createConfigurationContext(configuration);
    }

    // Rewrite this method according to your needs
    private static String getLanguageCode() {
        return LANGUAGE_CODE_ARABIC;
    }
}

笔记

  • getLanguageCode() 应返回语言代码。通常情况下,语言代码或其他表示它的数据存储在首选项中。
  • 要动态更改语言,请在将适当的语言代码设置到首选项之后重新创建活动。
  • 使用活动上下文而不是应用程序上下文访问任何区域设置特定的资源。换句话说,从活动中使用 thisActivityName.this,从片段中使用 getActivity(),而不是使用 getApplicationContext()

4

updateConfiguration()方法已被弃用

现在我们需要使用createConfigurationContext()

我已经找到了解决方法

创建一个新的类ContextWrapper

import android.content.Context;
import android.content.res.Configuration;
import android.content.res.Resources;
import android.os.Build;
import android.os.LocaleList;

import java.util.Locale;

public class ContextWrapper extends android.content.ContextWrapper {

    public ContextWrapper(Context base) {
        super(base);
    }

    public static ContextWrapper wrap(Context context, Locale newLocale) {

        Resources res = context.getResources();
        Configuration configuration = res.getConfiguration();

        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
            configuration.setLocale(newLocale);

            LocaleList localeList = new LocaleList(newLocale);
            LocaleList.setDefault(localeList);
            configuration.setLocales(localeList);

            context = context.createConfigurationContext(configuration);

        } else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1) {
            configuration.setLocale(newLocale);
            context = context.createConfigurationContext(configuration);

        } else {
            configuration.locale = newLocale;
            res.updateConfiguration(configuration, res.getDisplayMetrics());
        }

        return new ContextWrapper(context);
    }}

创建一个新的BaseActivity类。
import android.content.Context;

import android.support.v7.app.AppCompatActivity;

import java.util.Locale;

/**
 * Created by nilesh on 20/3/18.
 */

public class BaseActivity extends AppCompatActivity {

    @Override
    protected void attachBaseContext(Context newBase) {

        Locale newLocale;

        String lang = new PrefManager(newBase).getLanguage();

        if (lang.equals("zh_CN")) {
            newLocale = new Locale("zh");
        } else {
            newLocale = new Locale(lang);
        }


        Context context = ContextWrapper.wrap(newBase, newLocale);
        super.attachBaseContext(context);
    }
}

创建一个PrefManager类来存储语言环境。
import android.content.Context;
import android.content.SharedPreferences;

public class PrefManager {

    private SharedPreferences.Editor editor;
    private Context mContext;
    private SharedPreferences prefs;
    private final String LANGUAGE = "language";
    private final String PREF = "user_data";

    public PrefManager(Context mContext) {
        this.mContext = mContext;
    }

    public String getLanguage() {
        this.prefs = this.mContext.getSharedPreferences(PREF, 0);
        return this.prefs.getString(LANGUAGE, "en_US");
    }

    public void setLanguage(String language) {
        this.editor = this.mContext.getSharedPreferences(PREF, 0).edit();
        this.editor.putString(LANGUAGE, language);
        this.editor.apply();
    }

}

现在你需要在所有的活动中扩展你的 BaseActivity,例如:
public class OrdersActivity extends BaseActivity

现在,当您需要更改Locale时,只需更新PrefManager中的值并重新启动您的活动。
    PrefManager prefManager= new PrefManager(this);
    prefManager.setLanguage("zh_CN");
    //  restart your activity

注意

您可以从Github仓库下载源代码。


2
它不起作用。更新除布局方向以外的所有内容。 - Zaid Mirza
@ZaidMirza 很抱歉听到这个消息,你能否详细解释一下?因为同样的代码在我的实际项目中运行良好。 - AskNilesh
1
我已经解决了这个问题。View.LAYOUT_DIRECTION_LOCALE在“奥利奥”上无法工作。 - Zaid Mirza
@ZaidMirza 我已经解决了这个问题 很高兴听到这个好消息,继续愉快的编码吧。 - AskNilesh

3

针对这个问题的完整解决方案包括三个步骤:

步骤 1:

在您的 BaseActivity(或所有 Activity)的 onCreate() 方法中,按以下方式设置 Locale

@Override
protected void onCreate(Bundle savedInstanceState) {

    // set the Locale the very first thing
    Utils.setLocale(Utils.getSavedLocale());
    requestWindowFeature(Window.FEATURE_NO_TITLE);
    super.onCreate(savedInstanceState);

    ......
    ......

}

getSavedLocale()是对应于当前地区的Locale(这将针对您的项目是特定的...)。

而方法Utils.setLocale(...)定义如下:

public static void setLocale(Locale locale){
    Context context = MyApplication.getInstance();
    Resources resources = context.getResources();
    Configuration configuration = resources.getConfiguration();
    Locale.setDefault(locale);
    configuration.setLocale(locale);
    configuration.setLayoutDirection(locale);

    // updateConfiguration(...) is deprecated in N
    if (Build.VERSION.SDK_INT >= 25) {
        context = context.getApplicationContext().createConfigurationContext(configuration);
        context = context.createConfigurationContext(configuration);
    }

    context.getResources().updateConfiguration(configuration,
            resources.getDisplayMetrics());
}

这将在每个活动中正确设置Locale。 对于支持API级别25的应用程序,这已足够。 对于API级别26及以上的应用程序,还需要执行STEP 2和STEP 3。 STEP 2
在您的BaseActivity中重写以下方法:
@Override
protected void attachBaseContext(Context newBase) {
    newBase = Utils.getLanguageAwareContext(newBase);
    super.attachBaseContext(newBase);
}

函数getLanguageAwareContext(...)的定义如下:

public static Context getLanguageAwareContext(Context context){
    Configuration configuration = context.getResources().getConfiguration();
    Locale locale = getIntendedLocale();
    configuration.setLocale(locale);
    configuration.setLayoutDirection(locale);
    return context.createConfigurationContext(configuration);
}

除了第一步之外,这将为API级别26及以上的应用程序中的每个Activity设置正确的Locale

然而,为了正确设置语言方向还需要一步...

第三步:

在您的BaseActivityonCreate()中添加以下代码:

@Override
protected void onCreate(Bundle savedInstanceState) {

    ....
    ....

    // yup, it's a legit bug ... :)
    if (Build.VERSION.SDK_INT >= 26) {
        getWindow().getDecorView().setLayoutDirection(Utils.isRTL()
                ? View.LAYOUT_DIRECTION_RTL : View.LAYOUT_DIRECTION_LTR);


    }

    ....
    ....
}

其中isRTL()函数定义如下:

public static boolean isRTL(){
    return TextUtilsCompat.getLayoutDirectionFromLocale(Locale.getDefault()) == View.LAYOUT_DIRECTION_RTL;
}

以上步骤应该能解决所有问题(至少关于在所有现有版本的Android上设置Locale和文本方向),请注意HTML标签。

0
public void setLocale(final Context ctx, final String lang) {
    AppSettings.getInstance(ctx).save(PrefKeys.language, lang);
    final Locale loc = new Locale(lang);
    Locale.setDefault(loc);
    final Configuration cfg = new Configuration();
    cfg.locale = loc;
    ctx.getResources().updateConfiguration(cfg, null);
}

切换为英语:setLocale(getActivity(), "en");

切换为阿拉伯语:setLocale(getActivity(), "ar");

完成以上操作后,您需要重新启动应用程序才能使语言更改生效。


1
谢谢。我正在调用resources.updateConfiguration(),在Android 8.0以下它可以正常工作。 - Yash Sampat

0

Resources.updateConfiguration已被弃用,请改用以下方法:

 fun setLocale(old: Context, locale: Locale): Context {
    val oldConfig = old.resources.configuration
    oldConfig.setLocale(locale)
    return old.createConfigurationContext(oldConfig)
}

override fun attachBaseContext(newBase: Context?) {
    super.attachBaseContext(newBase?.let { setLocale(it, Locale("ar")) })
}

在Java中

private Context setLocale(Context old, Locale locale) {
    Configuration oldConfig = old.getResources().getConfiguration();
    oldConfig.setLocale(locale);
    return old.createConfigurationContext(oldConfig);
}

@Override
protected void attachBaseContext(Context newBase) {
    super.attachBaseContext(setLocale(newBase, new Locale("ar")));
}

哇,那是什么语言?是 Kotlin 吗?看起来一点也不像 Java ... :) - Yash Sampat
1
@Y.S. 添加了Java :) 是的,它是Kotlin。 - svkaka

0
完全关闭应用程序,因为我认为它在后台进行缓存。
使用下面的代码,这就是我在我的情况下实现它的方式,你也可以试一试:
 Intent mStartActivity = new Intent(ctc, SplashActivity.class);
                int mPendingIntentId = 123456;
                PendingIntent mPendingIntent = PendingIntent.getActivity(ctc, mPendingIntentId,    mStartActivity, PendingIntent.FLAG_CANCEL_CURRENT);
                AlarmManager mgr = (AlarmManager)ctc.getSystemService(Context.ALARM_SERVICE);
                mgr.set(AlarmManager.RTC, System.currentTimeMillis() + 100, mPendingIntent);
                System.exit(0);

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接