资源和布局方向仅在 Android 8.0 及更高版本上呈现不正确
Resources and layout direction rendered incorrectly only on Android 8.0 and above
我有一个多语言应用程序,主要语言是英语,第二语言是阿拉伯语。
我在我的应用程序中每个 Activity
的 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
是以下之一:
上述方法在 super.onCreate(savedInstanceState)
被调用之前被调用。
中所述
- 我在清单中添加了
android:supportsRtl="true"
。
- 我已将所有具有
left
和 right
属性的 xml 属性分别更改为 start
和 end
。
- 我已将阿拉伯语字符串放入
res\values-ar\strings
文件夹,将可绘制资源放入 res\drawable-ar
文件夹(其他资源也类似)。
以上设置工作正常。将 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.
这里有两个个问题:
- 正确的
Locale
似乎没有在整个应用程序配置中更新。
- 文本和绘图的方向与应有的方向相反。
关于方向,存在一种名为 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 上下文而不是应用程序上下文来访问任何特定于语言环境的资源。换句话说,使用活动中的
this
或 ActivityName.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
注意
完全关闭应用程序,因为我认为它正在后台进行缓存。
在我的案例中使用下面的代码就是这样实现的,你也可以试试看:
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:
在您的 BaseActivity
的 onCreate()
中,添加以下代码:
@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
和文本方向)
我有一个多语言应用程序,主要语言是英语,第二语言是阿拉伯语。
我在我的应用程序中每个 Activity
的 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
是以下之一:
上述方法在 super.onCreate(savedInstanceState)
被调用之前被调用。
- 我在清单中添加了
android:supportsRtl="true"
。 - 我已将所有具有
left
和right
属性的 xml 属性分别更改为start
和end
。 - 我已将阿拉伯语字符串放入
res\values-ar\strings
文件夹,将可绘制资源放入res\drawable-ar
文件夹(其他资源也类似)。
以上设置工作正常。将 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.
这里有两个个问题:
- 正确的
Locale
似乎没有在整个应用程序配置中更新。 - 文本和绘图的方向与应有的方向相反。
关于方向,存在一种名为 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 上下文而不是应用程序上下文来访问任何特定于语言环境的资源。换句话说,使用活动中的
this
或ActivityName.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 inPrefManager
and restart your activity
PrefManager prefManager= new PrefManager(this);
prefManager.setLanguage("zh_CN");
// restart your activity
注意
完全关闭应用程序,因为我认为它正在后台进行缓存。
在我的案例中使用下面的代码就是这样实现的,你也可以试试看:
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:
在您的 BaseActivity
的 onCreate()
中,添加以下代码:
@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
和文本方向)