Android context.getResources.updateConfiguration() устарел
Совсем недавно context.getResources(). updateConfiguration() устарел в Android API 25, и рекомендуется использовать контекст. createConfigurationContext().
Кто-нибудь знает, как createConfigurationContext можно использовать для переопределения локали системы Android?
прежде чем это будет сделано:
Configuration config = getBaseContext().getResources().getConfiguration();
config.setLocale(locale);
context.getResources().updateConfiguration(config,
context.getResources().getDisplayMetrics());
Ответы
Ответ 1
Вдохновленный Каллиграфией, я создал контекстную оболочку.
В моем случае мне нужно переписать системный язык, чтобы предоставить пользователям моего приложения возможность изменить язык приложения, но это можно настроить с помощью любой логики, которую вам нужно реализовать.
import android.annotation.TargetApi;
import android.content.Context;
import android.content.ContextWrapper;
import android.content.res.Configuration;
import android.os.Build;
import java.util.Locale;
public class MyContextWrapper extends ContextWrapper {
public MyContextWrapper(Context base) {
super(base);
}
@SuppressWarnings("deprecation")
public static ContextWrapper wrap(Context context, String language) {
Configuration config = context.getResources().getConfiguration();
Locale sysLocale = null;
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
sysLocale = getSystemLocale(config);
} else {
sysLocale = getSystemLocaleLegacy(config);
}
if (!language.equals("") && !sysLocale.getLanguage().equals(language)) {
Locale locale = new Locale(language);
Locale.setDefault(locale);
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
setSystemLocale(config, locale);
} else {
setSystemLocaleLegacy(config, locale);
}
}
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1) {
context = context.createConfigurationContext(config);
} else {
context.getResources().updateConfiguration(config, context.getResources().getDisplayMetrics());
}
return new MyContextWrapper(context);
}
@SuppressWarnings("deprecation")
public static Locale getSystemLocaleLegacy(Configuration config){
return config.locale;
}
@TargetApi(Build.VERSION_CODES.N)
public static Locale getSystemLocale(Configuration config){
return config.getLocales().get(0);
}
@SuppressWarnings("deprecation")
public static void setSystemLocaleLegacy(Configuration config, Locale locale){
config.locale = locale;
}
@TargetApi(Build.VERSION_CODES.N)
public static void setSystemLocale(Configuration config, Locale locale){
config.setLocale(locale);
}
}
и для вставки вашей оболочки добавьте следующий код:
@Override
protected void attachBaseContext(Context newBase) {
super.attachBaseContext(MyContextWrapper.wrap(newBase,"fr"));
}
ОБНОВЛЕНИЕ 19/19/2018
Иногда после изменения ориентации или приостановки/возобновления действия объект конфигурации сбрасывается до конфигурации системы по умолчанию, и в результате мы увидим приложение, отображающее текст на английском языке "en", даже если мы обернули контекст французским языком "fr". Поэтому и не рекомендуется сохранять объект Context/Activity в глобальной переменной в действиях или фрагментах.
более того, создайте и используйте следующее в MyBaseFragment или MyBaseActivity:
public Context getMyContext(){
return MyContextWrapper.wrap(getContext(),"fr");
}
Эта практика предоставит вам 100% безошибочное решение.
Ответ 2
Вероятно, вот так:
Configuration overrideConfiguration = getBaseContext().getResources().getConfiguration();
overrideConfiguration.setLocales(LocaleList);
Context context = createConfigurationContext(overrideConfiguration);
Resources resources = context.getResources();
Бонус: Статья в блоге, которая использует createConfigurationContext()
Ответ 3
Вдохновленный Каллиграфией и Моуржаном и мной, я создал это.
сначала вы должны создать подкласс приложения:
public class MyApplication extends Application {
private Locale locale = null;
@Override
public void onCreate() {
super.onCreate();
SharedPreferences preferences = PreferenceManager.getDefaultSharedPreferences(this);
Configuration config = getBaseContext().getResources().getConfiguration();
String lang = preferences.getString(getString(R.string.pref_locale), "en");
String systemLocale = getSystemLocale(config).getLanguage();
if (!"".equals(lang) && !systemLocale.equals(lang)) {
locale = new Locale(lang);
Locale.setDefault(locale);
setSystemLocale(config, locale);
updateConfiguration(config);
}
}
@Override
public void onConfigurationChanged(Configuration newConfig) {
super.onConfigurationChanged(newConfig);
if (locale != null) {
setSystemLocale(newConfig, locale);
Locale.setDefault(locale);
updateConfiguration(newConfig);
}
}
@SuppressWarnings("deprecation")
private static Locale getSystemLocale(Configuration config) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
return config.getLocales().get(0);
} else {
return config.locale;
}
}
@SuppressWarnings("deprecation")
private static void setSystemLocale(Configuration config, Locale locale) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
config.setLocale(locale);
} else {
config.locale = locale;
}
}
@SuppressWarnings("deprecation")
private void updateConfiguration(Configuration config) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1) {
getBaseContext().createConfigurationContext(config);
} else {
getBaseContext().getResources().updateConfiguration(config, getBaseContext().getResources().getDisplayMetrics());
}
}
}
то вам нужно установить его в свой тег приложения AndroidManifest.xml:
<application
...
android:name="path.to.your.package.MyApplication"
>
и добавьте это в свой тег активности AndroidManifest.xml.
<activity
...
android:configChanges="locale"
>
обратите внимание, что pref_locale является строковым ресурсом, подобным этому:
<string name="pref_locale">fa</string>
и hardcode "en" по умолчанию lang, если pref_locale не установлен
Ответ 4
Здесь @bassel-mourjan решение с небольшим количеством совершенства kotlin :):
import android.annotation.TargetApi
import android.content.ContextWrapper
import android.os.Build
import java.util.*
@Suppress("DEPRECATION")
fun ContextWrapper.wrap(language: String): ContextWrapper {
val config = baseContext.resources.configuration
val sysLocale: Locale = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
this.getSystemLocale()
} else {
this.getSystemLocaleLegacy()
}
if (!language.isEmpty() && sysLocale.language != language) {
val locale = Locale(language)
Locale.setDefault(locale)
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
this.setSystemLocale(locale)
} else {
this.setSystemLocaleLegacy(locale)
}
}
return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1) {
val context = baseContext.createConfigurationContext(config)
ContextWrapper(context)
} else {
baseContext.resources.updateConfiguration(config, baseContext.resources.displayMetrics)
ContextWrapper(baseContext)
}
}
@Suppress("DEPRECATION")
fun ContextWrapper.getSystemLocaleLegacy(): Locale {
val config = baseContext.resources.configuration
return config.locale
}
@TargetApi(Build.VERSION_CODES.N)
fun ContextWrapper.getSystemLocale(): Locale {
val config = baseContext.resources.configuration
return config.locales[0]
}
@Suppress("DEPRECATION")
fun ContextWrapper.setSystemLocaleLegacy(locale: Locale) {
val config = baseContext.resources.configuration
config.locale = locale
}
@TargetApi(Build.VERSION_CODES.N)
fun ContextWrapper.setSystemLocale(locale: Locale) {
val config = baseContext.resources.configuration
config.setLocale(locale)
}
А вот как вы это используете:
override fun attachBaseContext(newBase: Context?) {
super.attachBaseContext(ContextWrapper(newBase).wrap(defaultLocale.language))
}
Ответ 5
Попробуйте следующее:
Configuration config = getBaseContext().getResources().getConfiguration();
config.setLocale(locale);
context.createConfigurationContext(config);
Ответ 6
существует простое решение с contextWrapper здесь: Android N меняет язык программно
Обратите внимание на метод recreate()
Ответ 7
Здесь нет 100% рабочего решения. Вам нужно использовать createConfigurationContext
и applyOverrideConfiguration
. В противном случае, даже если вы замените baseContext
в каждом действии новой конфигурацией, действие все равно будет использовать Resources
из ContextThemeWrapper
со старым языковым стандартом.
Итак, вот мое решение, которое работает до API 29:
В вашем классе приложений:
public class MainApplication extends Application {
protected void attachBaseContext(Context base) {
super.attachBaseContext(LocaleHelper.changeLanguageIfDiff(
base, PreferenceManager.getDefaultSharedPreferences(base).getString("langPref", "sys")));
}
}
Также в каждом занятии:
public class MainActivity extends AppCompatActivity {
@Override
protected void attachBaseContext(Context newBase) {
super.attachBaseContext(LocaleHelper.changeLanguageIfDiff(
newBase, PreferenceManager.getDefaultSharedPreferences(base).getString("langPref", "sys")));
}
@Override
public void applyOverrideConfiguration(Configuration overrideConfiguration) {
super.applyOverrideConfiguration(getBaseContext().getResources().getConfiguration());
}
}
А вот и LocaleHelper
:
public class LocaleHelper {
public static boolean isAppLangDiff(final Context context, final String prefLang) {
final Configuration appConfig = context.getResources().getConfiguration();
final Configuration sysConfig = Resources.getSystem().getConfiguration();
final String appLang = getLocale(appConfig).getLanguage();
final String sysLang = getLocale(sysConfig).getLanguage();
if ("sys".equals(prefLang)) { // "System default" preference value
return !appLang.equals(sysLang);
} else {
return !appLang.equals(prefLang);
}
}
public static Context changeLanguageIfDiff(final Context context, final String prefLang) {
return isAppLangDiff(context, prefLang)
? changeLanguage(context, prefLang)
: context;
}
public static Context changeLanguage(final Context context, final String toLang) {
final Resources res = context.getResources();
final Configuration config = res.getConfiguration();
// Here some workaround with Chinese Simplified/Traditional
final Locale toLocale = toLang.contains("zh")
? (toLang.contains("rCN")
? Locale.SIMPLIFIED_CHINESE : Build.VERSION.SDK_INT >= Build.VERSION_CODES.N
? new Locale("zh", "Hant") : Locale.TRADITIONAL_CHINESE)
: new Locale(toLang);
Locale.setDefault(toLocale);
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
config.setLocale(toLocale);
final LocaleList localeList = new LocaleList(toLocale);
LocaleList.setDefault(localeList);
config.setLocales(localeList);
} else {
config.locale = toLocale;
}
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1) {
config.setLayoutDirection(toLocale);
return context.createConfigurationContext(config);
} else {
res.updateConfiguration(config, res.getDisplayMetrics());
return context;
}
}
public static Locale getLocale(Configuration config) {
return Build.VERSION.SDK_INT >= Build.VERSION_CODES.N
? config.getLocales().get(0)
: config.locale;
}
}
Я использую следующие языковые значения для предпочтения:
<string-array name="lang_values" translatable="false">
<item>sys</item> <!-- System default -->
<item>ar</item>
<item>de</item>
<item>en</item>
<item>es</item>
<item>fa</item>
...
<item>zh</item> <!-- Traditional Chinese -->
<item>zh-rCN</item> <!-- Simplified Chinese -->
</string-array>
Я хочу упомянуть:
- Используйте
config.setLayoutDirection(toLocale);
, чтобы изменить направление макета
когда вы используете RTL локали, такие как арабский, персидский и т.д.
"sys"
в коде является значением, которое означает "наследовать язык системы по умолчанию".
- Нет необходимости воссоздавать контекст, если он уже использует необходимый
локали.
- Нет необходимости в
ContextWraper
, как здесь опубликовано, просто установите новый контекст, возвращаемый из createConfigurationContext
, как baseContext
- Недостаточно только действия
recreate
, когда пользователь выбирает другой язык, потому что applicationContext останется со старой локалью и может обеспечить непредвиденное поведение. Поэтому прослушайте изменение предпочтений и перезапустите всю задачу приложения:
public static void recreateTask(final Context context) {
final PackageManager pm = context.getPackageManager();
final Intent intent = pm.getLaunchIntentForPackage(context.getPackageName());
final ComponentName componentName = intent.getComponent();
final Intent mainIntent = Intent.makeRestartActivityTask(componentName);
context.startActivity(mainIntent);
Runtime.getRuntime().exit(0);
}