Recentemente, context.getResources (). updateConfiguration () tornou-se obsoleto na API 25 do Android e é aconselhável usar contexto. em vez disso, createConfigurationContext () .
Alguém sabe como createConfigurationContext pode ser usado para substituir a localidade do sistema Android?
antes que isso fosse feito por:
Configuration config = getBaseContext().getResources().getConfiguration();
config.setLocale(locale);
context.getResources().updateConfiguration(config,
context.getResources().getDisplayMetrics());
android
locale
right-to-left
Bassel Mourjan
fonte
fonte
Respostas:
Inspirado na Caligrafia , acabei criando um wrapper de contexto. No meu caso, preciso substituir o idioma do sistema para fornecer aos usuários do meu aplicativo a opção de alterar o idioma do aplicativo, mas isso pode ser personalizado com qualquer lógica que você precise implementar.
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.N) { 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); } }
e para injetar seu wrapper, em cada atividade adicione o seguinte código:
@Override protected void attachBaseContext(Context newBase) { super.attachBaseContext(MyContextWrapper.wrap(newBase,"fr")); }
ATUALIZAÇÃO 22/12/2020 Após a implementação da biblioteca de materiais android do ContextThemeWrapper para suportar o modo escuro, a configuração de idioma seria interrompida e a configuração de idioma seria perdida. Após meses de coçar a cabeça, o problema foi resolvido adicionando o seguinte código ao método Activity and Fragment onCreate
Context context = MyContextWrapper.wrap(this/*in fragment use getContext() instead of this*/, "fr"); getResources().updateConfiguration(context.getResources().getConfiguration(), context.getResources().getDisplayMetrics());
ATUALIZAÇÃO 19/10/2018 Às vezes, após a mudança de orientação ou pausa / retomada da atividade, o objeto Configuração é redefinido para a configuração padrão do sistema e, como resultado, veremos o aplicativo exibindo o texto "en" em inglês, embora tenhamos agrupado o contexto com a localidade "fr" em francês . Portanto, como uma boa prática, nunca retenha o objeto Contexto / Atividade em uma variável global em atividades ou fragmentos.
além disso, crie e use o seguinte em um MyBaseFragment ou MyBaseActivity:
public Context getMyContext(){ return MyContextWrapper.wrap(getContext(),"fr"); }
Esta prática irá fornecer a você uma solução 100% livre de bugs.
fonte
Provavelmente assim:
Configuration overrideConfiguration = getBaseContext().getResources().getConfiguration(); overrideConfiguration.setLocales(LocaleList); Context context = createConfigurationContext(overrideConfiguration); Resources resources = context.getResources();
Bônus: um artigo de blog que usa createConfigurationContext ()
fonte
LocaleList
Inspirado por Calligraphy & Mourjan e por mim, eu criei isso.
primeiro você deve criar uma subclasse de Application:
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()); } } }
então você precisa definir isso para sua tag de aplicativo AndroidManifest.xml:
<application ... android:name="path.to.your.package.MyApplication" >
e adicione-o à sua tag de atividade AndroidManifest.xml.
<activity ... android:configChanges="locale" >
observe que pref_locale é um recurso de string como este:
<string name="pref_locale">fa</string>
e hardcode "en" é o idioma padrão se pref_locale não estiver definido
fonte
Não há solução 100% funcional. Você precisa usar
createConfigurationContext
eapplyOverrideConfiguration
. Caso contrário, mesmo se você substituirbaseContext
em todas as atividades por uma nova configuração, a atividade ainda será usada aResources
partir doContextThemeWrapper
antigo local.Então aqui está minha solução que funciona até a API 29:
Subclasse sua
MainApplication
classe de:abstract class LocalApplication : Application() { override fun attachBaseContext(base: Context) { super.attachBaseContext( base.toLangIfDiff( PreferenceManager .getDefaultSharedPreferences(base) .getString("langPref", "sys")!! ) ) } }
Também todos
Activity
de:abstract class LocalActivity : AppCompatActivity() { override fun attachBaseContext(newBase: Context) { super.attachBaseContext( PreferenceManager .getDefaultSharedPreferences(base) .getString("langPref", "sys")!! ) } override fun applyOverrideConfiguration(overrideConfiguration: Configuration) { super.applyOverrideConfiguration(baseContext.resources.configuration) } }
Adicionar
LocaleExt.kt
com as próximas funções de extensão:const val SYSTEM_LANG = "sys" const val ZH_LANG = "zh" const val SIMPLIFIED_CHINESE_SUFFIX = "rCN" private fun Context.isAppLangDiff(prefLang: String): Boolean { val appConfig: Configuration = this.resources.configuration val sysConfig: Configuration = Resources.getSystem().configuration val appLang: String = appConfig.localeCompat.language val sysLang: String = sysConfig.localeCompat.language return if (SYSTEM_LANG == prefLang) { appLang != sysLang } else { appLang != prefLang || ZH_LANG == prefLang } } fun Context.toLangIfDiff(lang: String): Context = if (this.isAppLangDiff(lang)) { this.toLang(lang) } else { this } @Suppress("DEPRECATION") fun Context.toLang(toLang: String): Context { val config = Configuration() val toLocale = langToLocale(toLang) Locale.setDefault(toLocale) if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) { config.setLocale(toLocale) val localeList = LocaleList(toLocale) LocaleList.setDefault(localeList) config.setLocales(localeList) } else { config.locale = toLocale } return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1) { config.setLayoutDirection(toLocale) this.createConfigurationContext(config) } else { this.resources.updateConfiguration(config, this.resources.displayMetrics) this } } /** * @param toLang - two character representation of language, could be "sys" - which represents system's locale */ fun langToLocale(toLang: String): Locale = when { toLang == SYSTEM_LANG -> Resources.getSystem().configuration.localeCompat toLang.contains(ZH_LANG) -> when { toLang.contains(SIMPLIFIED_CHINESE_SUFFIX) -> Locale.SIMPLIFIED_CHINESE Build.VERSION.SDK_INT >= Build.VERSION_CODES.N -> Locale(ZH_LANG, "Hant") else -> Locale.TRADITIONAL_CHINESE } else -> Locale(toLang) } @Suppress("DEPRECATION") private val Configuration.localeCompat: Locale get() = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) { this.locales.get(0) } else { this.locale }
Adicione aos seus
res/values/arrays.xml
idiomas suportados na matriz:<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>
Eu quero mencionar:
config.setLayoutDirection(toLocale);
para alterar a direção do layout ao usar localidades RTL como árabe, persa, etc."sys"
no código está um valor que significa "herdar o idioma padrão do sistema".ContextWraper
como postado aqui, basta definir o novo contexto retornado decreateConfigurationContext
como baseContextcreateConfigurationContext
você deve passar a configuração criada do zero e apenas com aLocale
propriedade definida. Não deve haver nenhuma outra propriedade definida para esta configuração. Porque se definirmos algumas outras propriedades para esta configuração ( orientação, por exemplo), substituiremos essa propriedade para sempre, e nosso contexto não mudará mais esta propriedade de orientação , mesmo se girarmos a tela.recreate
atuar quando o usuário seleciona um idioma diferente, porque applicationContext permanecerá com a localidade antiga e pode fornecer um comportamento inesperado. Portanto, ouça a mudança de preferência e reinicie toda a tarefa do aplicativo:fun Context.recreateTask() { this.packageManager .getLaunchIntentForPackage(context.packageName) ?.let { intent -> val restartIntent = Intent.makeRestartActivityTask(intent.component) this.startActivity(restartIntent) Runtime.getRuntime().exit(0) } }
fonte
recreateTask(Context context)
método não está funcionando corretamente, pois ainda vejo o layout sem nenhuma alteração.Aqui está a solução de @ bassel-mourjan com um pouco de bondade 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) }
E aqui está como você o usa:
override fun attachBaseContext(newBase: Context?) { super.attachBaseContext(ContextWrapper(newBase).wrap(defaultLocale.language)) }
fonte
val config = baseContext.resources.configuration
está muito errada. Você vai acabar com muitos bugs por causa disso. Em vez disso, você precisa criar uma nova configuração. Veja minha resposta.há uma solução simples com contextWrapper aqui: Android N altera a linguagem programaticamente Preste atenção ao método recreate ()
fonte
Experimente isto:
Configuration config = getBaseContext().getResources().getConfiguration(); config.setLocale(locale); context.createConfigurationContext(config);
fonte