Ресурсы и направление макета отображаются некорректно только на Android 8.0 и выше
У меня есть многоязычное приложение с основным языковым английским и арабским языками.
Я setLocale()
в onCreate()
каждого Activity
в моем приложении:
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"
в манифесте. - Я изменил все свойства xml с
left
и right
атрибутами для start
и end
соответственно. - Я поместил строки арабского языка в папку
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 и каково ее решение. Пожалуйста, помогите/прокомментируйте это.
EDIT:
Согласно отчету API Differences, метод updateConfiguration()
действительно устарел в Android 7.1 (уровень API 25).
Кроме того, были найдены все соответствующие посты. В порядке важности:
1. Язык программирования Android N программно.
2. Android context.getResources.updateConfiguration() устарел.
3. Как изменить язык приложений Android O/Oreo/api 26.
4. Проблема с RTL для Android в API 24 и выше при изменении локали
5. Измените язык программно (Android N 7.0 - API 24).
6. Android N - Изменить локаль во время выполнения.
7. Исправлена ошибка RTL в android Oreo.
Ответы
Ответ 1
Полное решение этой проблемы состоит из трех этапов:
ШАГ 1:
В onCreate()
вашей BaseActivity
(или всех ваших Activity
) установите 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
в каждом Activity
. Этого достаточно для приложений, поддерживающих API уровня 25. Для API уровня 26 и выше также требуются STEP 2 и STEP 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);
}
Это, наряду с STEP 1, устанавливает правильную Locale
в каждой Activity
вашего приложения для уровня API 26 и выше.
Однако для правильной установки направления языка требуется еще один шаг...
ШАГ 3:
В onCreate()
вашей BaseActivity
добавьте следующий код:
@Override
protected void onCreate(Bundle savedInstanceState) {
....
....
// yup, it 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;
}
Описанные выше шаги должны решить все проблемы (по крайней мере, касающиеся установки Locale
и направления текста) на всех существующих версиях Android.
Ответ 2
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();
}
}
Теперь вам нужно расширить базовую активность во всех ваших действиях, например
public class OrdersActivity extends BaseActivity
Теперь, когда вам нужно изменить Locale
, просто обновите значение в PrefManager
и перезапустите свою деятельность
PrefManager prefManager= new PrefManager(this);
prefManager.setLanguage("zh_CN");
// restart your activity
ПРИМЕЧАНИЕ
Вы можете скачать исходный код из репозитория github
Ответ 3
Метод Resources#updateConfiguration (Configuration config, DisplayMetrics metrics)
устарел на уровне API 25.
Документ предлагает использовать 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(CommonUtils.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()
должен возвращать код языка. Обычно код языка или любые другие данные, представляющие его, сохраняются в настройках. - Чтобы динамически изменять языки, воссоздайте активность после установки соответствующего языкового кода в предпочтения.
- Используйте контекст активности, а не контекст приложения, для доступа к любым языковым ресурсам. Другими словами, используйте
this
или ActivityName.this
из действий и getActivity()
из фрагментов вместо getApplicationContext()
.
Ответ 4
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");
После этого вам нужно перезапустить приложение, чтобы получить эффекты изменения языка.
Ответ 5
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")) })
}
На яве
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")));
}
Ответ 6
Полностью закрыл приложение, потому что я думаю, что он делает кеш в фоновом режиме.
Используйте ниже код, как я это сделал в моем случае, вы также можете попробовать:
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);