问题
Just recently context.getResources().updateConfiguration() has been deprecated in Android API 25 and it is advised to use context.createConfigurationContext() instead.
Does anyone know how createConfigurationContext can be used to override android system locale?
before this would be done by:
Configuration config = getBaseContext().getResources().getConfiguration();
config.setLocale(locale);
context.getResources().updateConfiguration(config,
context.getResources().getDisplayMetrics());
回答1:
Inspired by Calligraphy, I ended up creating a context wrapper. In my case, I need to overwrite system language to provide my app users with the option of changing app language but this can be customized with any logic that you need to implement.
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);
}
}
and to inject your wrapper, in every activity add the following code:
@Override
protected void attachBaseContext(Context newBase) {
super.attachBaseContext(MyContextWrapper.wrap(newBase,"fr"));
}
UPDATE 10/19/2018 Sometimes after orientation change, or activity pause/resume the Configuration object resets to default system Configuration and in result we will see the app displaying English "en" text even though we wrapped the context with French "fr" locale. Therefore and as a good practice, never retain the Context/Activity object in a global variable in activities or fragments.
furthermore, create and use the following in a MyBaseFragment or MyBaseActivity:
public Context getMyContext(){
return MyContextWrapper.wrap(getContext(),"fr");
}
This practice will provide you with 100% bug free solution.
回答2:
Probably like this :
Configuration overrideConfiguration = getBaseContext().getResources().getConfiguration();
overrideConfiguration.setLocales(LocaleList);
Context context = createConfigurationContext(overrideConfiguration);
Resources resources = context.getResources();
Bonus : A blog article who use createConfigurationContext()
回答3:
Inspired by Calligraphy & Mourjan & myself, i created this.
first you must create a subclass of 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());
}
}
}
then you need set this to your AndroidManifest.xml application tag:
<application
...
android:name="path.to.your.package.MyApplication"
>
and add this to your AndroidManifest.xml activity tag.
<activity
...
android:configChanges="locale"
>
note that pref_locale is a string resource like this:
<string name="pref_locale">fa</string>
and hardcode "en" is default lang if pref_locale is not setted
回答4:
Here's @bassel-mourjan's solution with a bit of kotlin goodness :) :
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)
}
And here is how you use it:
override fun attachBaseContext(newBase: Context?) {
super.attachBaseContext(ContextWrapper(newBase).wrap(defaultLocale.language))
}
回答5:
Here is no 100% working solution. You need to use both createConfigurationContext
and applyOverrideConfiguration
. Otherwise even if you replace baseContext
in every activity with new configuration, activity would still use Resources
from ContextThemeWrapper
with old locale.
So here is mine solution which works up to API 29:
In your Application class:
public class MainApplication extends Application {
protected void attachBaseContext(Context base) {
super.attachBaseContext(LocaleHelper.changeLanguageIfDiff(
base, PreferenceManager.getDefaultSharedPreferences(base).getString("langPref", "sys")));
}
}
Also in every Activity:
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());
}
}
And here is 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;
}
}
I use next language values for preference:
<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>
I want to mention:
- Use
config.setLayoutDirection(toLocale);
to change layout direction when you use RTL locales like Arabic, Persian, etc. "sys"
in the code is a value that means "inherit system default language".- There is no need to recreate the context if it already uses needed locale.
- There is no need for
ContextWraper
as posted here, just set new context returned fromcreateConfigurationContext
as baseContext - It is not enough only to
recreate
activity when user selects a different language, because applicationContext will remain with old locale and it could provide unexpected behaviour. So listen to preference change and restart whole application task instead:
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);
}
回答6:
Try this:
Configuration config = getBaseContext().getResources().getConfiguration();
config.setLocale(locale);
context.createConfigurationContext(config);
回答7:
there is a simple solution with contextWrapper here : Android N change language programatically Pay attention to the recreate() method
来源:https://stackoverflow.com/questions/40221711/android-context-getresources-updateconfiguration-deprecated