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

Resources and layout direction rendered incorrectly only on Android 8.0 and above

我有一个多语言应用程序,主要语言是英语,第二语言是阿拉伯语。

我在我的应用程序中每个 ActivityonCreate() 中调用 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 是以下之一:

上述方法在 super.onCreate(savedInstanceState) 被调用之前被调用。

documentation

中所述

以上设置工作正常。将 Locale 更改为 ar-AE 后,阿拉伯语文本和资源在我的活动中正确显示。

但是,所有Android版本8.0及以上的设备在资源和布局方向上都存在问题。

在版本低于 8.0 的设备上,RTL 屏幕 正确 如下所示:

在所有 8.0+ 的设备上,相同的屏幕显示如下:

这是错误的

It turns out that both the direction and the resources are getting displayed incorrectly.

这里有两个个问题:

关于方向,存在一种名为 setLayoutDirection() 的奇怪方法,我以前没有注意到。

我想知道这个问题是什么,为什么会出现在奥利奥上,有什么解决办法。请对此提供帮助/评论。

编辑:

According to the API Differences report, the updateConfiguration() method was indeed deprecated in Android 7.1 (API level 25).

此外,找到了所有相关帖子。按重要性排序:

1. .

2. .

3. .

4.

5. .

6. .

7. .

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

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


您可以简单地创建一个基础 activity,它是所有活动的共同父级,如下所示。

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() 应return 语言代码。通常,语言代码或表示它的任何其他数据存储在首选项中。
  • 要动态更改语言,请在首选项中设置适当的语言代码后重新创建 activity。
  • 使用 activity 上下文而不是应用程序上下文来访问任何特定于语言环境的资源。换句话说,使用活动中的 thisActivityName.this 和片段中的 getActivity() 而不是 getApplicationContext().
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");

之后您需要重新启动应用程序才能获得语言更改效果。

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")));
}

updateConfiguration() 方法已 弃用

现在我们需要使用createConfigurationContext()

我已经做到了

create a new class 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);
    }}

create a new class of 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);
    }
}

Create a PrefManager class to store locale

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();
    }

}

Now you need to extends your BaseActivity in your all activity like

public class OrdersActivity extends BaseActivity

Now when your need to change Locale just update the value in PrefManager and restart your activity

    PrefManager prefManager= new PrefManager(this);
    prefManager.setLanguage("zh_CN");
    //  restart your activity

注意

You can download source code from github repo

完全关闭应用程序,因为我认为它正在后台进行缓存。

在我的案例中使用下面的代码就是这样实现的,你也可以试试看:

 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);

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

步骤 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());
}

这会在每个 Activity 中设置正确的 Locale。这对于支持 API 级别 25 的应用程序来说已经足够了。对于 API 级别 26 及以上,还需要步骤 2 和步骤 3。

第 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);
}

这与第 1 步一起,为 API 26 级及以上的应用程序的每个 Activity 设置正确的 Locale

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

步骤 3:

在您的 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 和文本方向)