以编程方式更改语言 (Android N 7.0 - API 24)
Change language programmatically (Android N 7.0 - API 24)
我正在使用以下代码在我的应用程序中设置特定语言。语言保存在应用程序内的 SharedPreferences
中。 它在 API 23 级之前都能完美运行。 使用 Android N SharedPreferences
也能正常运行,returns 正确的语言代码-string,但它不会更改语言环境(设置 phone 的默认语言)。有什么问题吗?
更新 1: 当我在 res.updateConfiguration(config, dm)
之后立即使用 Log.v("MyLog", config.locale.toString());
时 returns 正确的语言环境,但应用程序的语言没有改变.
更新 2: 我还提到,如果我更改语言环境然后重新启动 activity(使用新意图并完成旧意图),它会改变语言正确,它甚至在旋转后显示正确的语言。但是当我关闭应用程序并再次打开它时,我会看到默认语言。很奇怪。
public class ActivityMain extends AppCompatActivity {
//...
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
// Set locale
SharedPreferences pref = PreferenceManager.getDefaultSharedPreferences(this);
String lang = pref.getString(ActivityMain.LANGUAGE_SAVED, "no_language");
if (!lang.equals("no_language")) {
Resources res = context.getResources();
Locale locale = new Locale(lang);
Locale.setDefault(locale);
DisplayMetrics dm = res.getDisplayMetrics();
Configuration config = res.getConfiguration();
if (Build.VERSION.SDK_INT >= 17) {
config.setLocale(locale);
} else {
config.locale = locale;
}
}
res.updateConfiguration(config, dm);
setContentView(R.layout.activity_main);
//...
}
//...
}
更新 3: 答案也在这里:
创建一个新的 class extends ContextWrapper
public class MyContextWrapper extends ContextWrapper {
public MyContextWrapper(Context base) {
super(base);
}
@TargetApi(Build.VERSION_CODES.N)
public static ContextWrapper wrap(Context context, Locale newLocale) {
Resources res = context.getResources();
Configuration configuration = res.getConfiguration();
if (VersionUtils.isAfter24()) {
configuration.setLocale(newLocale);
LocaleList localeList = new LocaleList(newLocale);
LocaleList.setDefault(localeList);
configuration.setLocales(localeList);
context = context.createConfigurationContext(configuration);
} else if (VersionUtils.isAfter17()) {
configuration.setLocale(newLocale);
context = context.createConfigurationContext(configuration);
} else {
configuration.locale = newLocale;
res.updateConfiguration(configuration, res.getDisplayMetrics());
}
return new ContextWrapper(context);
}
}
重写 Activity 的 attachBaseContext 方法
@Override
protected void attachBaseContext(Context newBase) {
Locale languageType = LanguageUtil.getLanguageType(mContext);
super.attachBaseContext(MyContextWrapper.wrap(newBase, languageType));
}
完成activity并重新开始,新的语言环境将生效。
此方法适用于所有 api 级别的设备,确保以编程方式更改语言时重新创建 activity。
1.Use attachBaseContext 的基础 Activity 以设置语言环境并为所有活动扩展此 activity
open class BaseAppCompactActivity() : AppCompatActivity() {
override fun attachBaseContext(newBase: Context) {
super.attachBaseContext(LocaleHelper.onAttach(newBase))
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
}
}
2.Use 应用程序 attachBaseContext 和 onConfigurationChanged 设置区域设置语言
public class MyApplication extends Application {
private static MyApplication application;
@Override
public void onCreate() {
super.onCreate();
}
public static MyApplication getApplication () {
return application;
}
/**
* overide to change local sothat language can be chnaged from android device nogaut and above
*/
@Override
protected void attachBaseContext(Context base) {
super.attachBaseContext(LocaleHelper.INSTANCE.onAttach(base));
}
@Override
public void onConfigurationChanged(Configuration newConfig) {
/**
* also handle change language if device language changed
*/
super.onConfigurationChanged(newConfig);
}
}
3.Use 用于处理语言更改的 Locale Helper,此方法适用于所有设备
object LocaleHelper {
fun onAttach(context: Context, defaultLanguage: String): Context {
return setLocale(context, defaultLanguage)
}
fun setLocale(context: Context, language: String): Context {
return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
updateResources(context, language)
} else updateResourcesLegacy(context, language)
}
@TargetApi(Build.VERSION_CODES.N)
private fun updateResources(context: Context, language: String): Context {
val locale = Locale(language)
Locale.setDefault(locale)
val configuration = context.getResources().getConfiguration()
configuration.setLocale(locale)
configuration.setLayoutDirection(locale)
return context.createConfigurationContext(configuration)
}
private fun updateResourcesLegacy(context: Context, language: String): Context {
val locale = Locale(language)
Locale.setDefault(locale)
val resources = context.getResources()
val configuration = resources.getConfiguration()
configuration.locale = locale
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1) {
configuration.setLayoutDirection(locale)
}
resources.updateConfiguration(configuration, resources.getDisplayMetrics())
return context
}
}
当我开始以 SDK 29 为目标时,我遇到了这个问题。我创建的 LocaleHelper 适用于从我的最小 SDK(21) 到 29 的每个版本,但特别是在 Android N 上它没有使用工作。因此,在搜索了许多堆栈溢出答案并访问 https://issuetracker.google.com/issues/37123942 之后,我设法 find/modify 一个解决方案,该解决方案现在适用于所有 android 版本。
所以,首先我在 https://gunhansancar.com/change-language-programmatically-in-android/ 的帮助下获得了语言环境助手。然后我像这样修改它以满足我的需要:
object LocaleHelper {
const val TAG = "LocaleHelper"
fun updateLocale(base: Context): Context {
Log.e(TAG, "updateLocale: called");
val preferenceHelper = PreferenceHelper(base)
preferenceHelper.getStringPreference(PreferenceHelper.KEY_LANGUAGE).let {
return if (it.isNotEmpty()) {
if (Build.VERSION.SDK_INT > Build.VERSION_CODES.N) {
updateResources(base, it)
} else {
updateResourcesLegacy(base, it)
}
} else {
base
}
}
}
private fun updateResources(base: Context, language: String): Context{
val loc = Locale(language)
Locale.setDefault(loc)
val configuration = base.resources.configuration
configuration.setLocale(loc)
return base.createConfigurationContext(configuration)
}
@Suppress("DEPRECATION")
private fun updateResourcesLegacy(base: Context, language: String): Context{
val locale = Locale(language)
Locale.setDefault(locale)
val configuration = base.resources.configuration
configuration.locale = locale
configuration.setLayoutDirection(locale)
base.resources.updateConfiguration(configuration, base.resources.displayMetrics)
// Log.e(TAG, "updateLocale: returning for below N")
return base
}
}
然后,在基础 activity 中,覆盖 attachBaseContext 方法,如:
/**
* While attaching the base context, make sure to attach it using locale helper.
* This helps in getting localized resources in every activity
*/
override fun attachBaseContext(newBase: Context?) {
var context = newBase
newBase?.let {
context = LocaleHelper.updateLocale(it)
}
super.attachBaseContext(context)
}
但我发现这曾经适用于牛轧糖及更高版本,而不适用于棉花糖和棒棒糖。所以,我尝试了这个:
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
//Update the locale here before loading the layout to get localized strings.
LocaleHelper.updateLocale(this)
在 base activity 的 oncreate 中我调用了这个方法。它开始工作了。但在某些情况下,如果我们从最近的任务中终止应用程序然后启动它,第一个屏幕不会本地化。因此,为了解决这个问题,我在应用 class 中创建了一个静态临时变量,用于保存初始应用实例的值。
companion object {
val TAG = "App"
var isFirstLoad = true
}
在我的第一个应用程序屏幕中,我在 oncreate 中执行了此操作:
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
//Need to call this so that when the application is opened for first time and we need to update the locale.
if(App.isFirstLoad) {
LocaleHelper.updateLocale(this)
App.isFirstLoad = false
}
setContentView(R.layout.activity_dash_board)
现在我在 redmi 4a (Android 7) 上进行了测试,之前无法在其上运行,sdk 21、23、27、29 模拟器以及 redmi note 5 pro 和像素设备。在所有这些中,应用内语言选择都正常工作。
抱歉这么长 post,但请提出优化方法!
谢谢!
编辑 1:
所以我遇到了这个问题,它在 android 7.1 (sdk 25) 上不起作用。我向gunhan寻求帮助,然后进行了此更改。
override fun applyOverrideConfiguration(overrideConfiguration: Configuration?) {
super.applyOverrideConfiguration(LocaleHelper.applyOverrideConfiguration(baseContext, overrideConfiguration))
}
我在 LocaleHelper 中添加了这个功能:
fun applyOverrideConfiguration(base: Context, overrideConfiguration: Configuration?): Configuration? {
if (overrideConfiguration != null && Build.VERSION.SDK_INT < Build.VERSION_CODES.O) {
val uiMode = overrideConfiguration.uiMode
overrideConfiguration.setTo(base.resources.configuration)
overrideConfiguration.uiMode = uiMode
}
return overrideConfiguration
}
现在终于可以用了。
谢谢 Gunhan!
我遇到了同样的问题,但是 Android 5、6 和 7。经过大量搜索,解决方案在这个 post 中:(请参阅评论Ehsan Shadi 而不是“解决方案”)。
首先,您必须将您的库 appcompat 更新为 1.2.0(修复区域设置,请参阅此处:https://developer.android.com/jetpack/androidx/releases/appcompat#1.2.0)并且您必须覆盖基础 activity 中的 applyOverrideConfiguration 函数。
我已经在所有 API(19 到 30 - Android 4.4 到 11)上测试了这个解决方案并且它工作得很好:)
我正在使用以下代码在我的应用程序中设置特定语言。语言保存在应用程序内的 SharedPreferences
中。 它在 API 23 级之前都能完美运行。 使用 Android N SharedPreferences
也能正常运行,returns 正确的语言代码-string,但它不会更改语言环境(设置 phone 的默认语言)。有什么问题吗?
更新 1: 当我在 res.updateConfiguration(config, dm)
之后立即使用 Log.v("MyLog", config.locale.toString());
时 returns 正确的语言环境,但应用程序的语言没有改变.
更新 2: 我还提到,如果我更改语言环境然后重新启动 activity(使用新意图并完成旧意图),它会改变语言正确,它甚至在旋转后显示正确的语言。但是当我关闭应用程序并再次打开它时,我会看到默认语言。很奇怪。
public class ActivityMain extends AppCompatActivity {
//...
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
// Set locale
SharedPreferences pref = PreferenceManager.getDefaultSharedPreferences(this);
String lang = pref.getString(ActivityMain.LANGUAGE_SAVED, "no_language");
if (!lang.equals("no_language")) {
Resources res = context.getResources();
Locale locale = new Locale(lang);
Locale.setDefault(locale);
DisplayMetrics dm = res.getDisplayMetrics();
Configuration config = res.getConfiguration();
if (Build.VERSION.SDK_INT >= 17) {
config.setLocale(locale);
} else {
config.locale = locale;
}
}
res.updateConfiguration(config, dm);
setContentView(R.layout.activity_main);
//...
}
//...
}
更新 3: 答案也在这里:
创建一个新的 class extends ContextWrapper
public class MyContextWrapper extends ContextWrapper {
public MyContextWrapper(Context base) {
super(base);
}
@TargetApi(Build.VERSION_CODES.N)
public static ContextWrapper wrap(Context context, Locale newLocale) {
Resources res = context.getResources();
Configuration configuration = res.getConfiguration();
if (VersionUtils.isAfter24()) {
configuration.setLocale(newLocale);
LocaleList localeList = new LocaleList(newLocale);
LocaleList.setDefault(localeList);
configuration.setLocales(localeList);
context = context.createConfigurationContext(configuration);
} else if (VersionUtils.isAfter17()) {
configuration.setLocale(newLocale);
context = context.createConfigurationContext(configuration);
} else {
configuration.locale = newLocale;
res.updateConfiguration(configuration, res.getDisplayMetrics());
}
return new ContextWrapper(context);
}
}
重写 Activity 的 attachBaseContext 方法
@Override
protected void attachBaseContext(Context newBase) {
Locale languageType = LanguageUtil.getLanguageType(mContext);
super.attachBaseContext(MyContextWrapper.wrap(newBase, languageType));
}
完成activity并重新开始,新的语言环境将生效。
此方法适用于所有 api 级别的设备,确保以编程方式更改语言时重新创建 activity。
1.Use attachBaseContext 的基础 Activity 以设置语言环境并为所有活动扩展此 activity
open class BaseAppCompactActivity() : AppCompatActivity() {
override fun attachBaseContext(newBase: Context) {
super.attachBaseContext(LocaleHelper.onAttach(newBase))
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
}
}
2.Use 应用程序 attachBaseContext 和 onConfigurationChanged 设置区域设置语言
public class MyApplication extends Application {
private static MyApplication application;
@Override
public void onCreate() {
super.onCreate();
}
public static MyApplication getApplication () {
return application;
}
/**
* overide to change local sothat language can be chnaged from android device nogaut and above
*/
@Override
protected void attachBaseContext(Context base) {
super.attachBaseContext(LocaleHelper.INSTANCE.onAttach(base));
}
@Override
public void onConfigurationChanged(Configuration newConfig) {
/**
* also handle change language if device language changed
*/
super.onConfigurationChanged(newConfig);
}
}
3.Use 用于处理语言更改的 Locale Helper,此方法适用于所有设备
object LocaleHelper {
fun onAttach(context: Context, defaultLanguage: String): Context {
return setLocale(context, defaultLanguage)
}
fun setLocale(context: Context, language: String): Context {
return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
updateResources(context, language)
} else updateResourcesLegacy(context, language)
}
@TargetApi(Build.VERSION_CODES.N)
private fun updateResources(context: Context, language: String): Context {
val locale = Locale(language)
Locale.setDefault(locale)
val configuration = context.getResources().getConfiguration()
configuration.setLocale(locale)
configuration.setLayoutDirection(locale)
return context.createConfigurationContext(configuration)
}
private fun updateResourcesLegacy(context: Context, language: String): Context {
val locale = Locale(language)
Locale.setDefault(locale)
val resources = context.getResources()
val configuration = resources.getConfiguration()
configuration.locale = locale
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1) {
configuration.setLayoutDirection(locale)
}
resources.updateConfiguration(configuration, resources.getDisplayMetrics())
return context
}
}
当我开始以 SDK 29 为目标时,我遇到了这个问题。我创建的 LocaleHelper 适用于从我的最小 SDK(21) 到 29 的每个版本,但特别是在 Android N 上它没有使用工作。因此,在搜索了许多堆栈溢出答案并访问 https://issuetracker.google.com/issues/37123942 之后,我设法 find/modify 一个解决方案,该解决方案现在适用于所有 android 版本。
所以,首先我在 https://gunhansancar.com/change-language-programmatically-in-android/ 的帮助下获得了语言环境助手。然后我像这样修改它以满足我的需要:
object LocaleHelper {
const val TAG = "LocaleHelper"
fun updateLocale(base: Context): Context {
Log.e(TAG, "updateLocale: called");
val preferenceHelper = PreferenceHelper(base)
preferenceHelper.getStringPreference(PreferenceHelper.KEY_LANGUAGE).let {
return if (it.isNotEmpty()) {
if (Build.VERSION.SDK_INT > Build.VERSION_CODES.N) {
updateResources(base, it)
} else {
updateResourcesLegacy(base, it)
}
} else {
base
}
}
}
private fun updateResources(base: Context, language: String): Context{
val loc = Locale(language)
Locale.setDefault(loc)
val configuration = base.resources.configuration
configuration.setLocale(loc)
return base.createConfigurationContext(configuration)
}
@Suppress("DEPRECATION")
private fun updateResourcesLegacy(base: Context, language: String): Context{
val locale = Locale(language)
Locale.setDefault(locale)
val configuration = base.resources.configuration
configuration.locale = locale
configuration.setLayoutDirection(locale)
base.resources.updateConfiguration(configuration, base.resources.displayMetrics)
// Log.e(TAG, "updateLocale: returning for below N")
return base
}
}
然后,在基础 activity 中,覆盖 attachBaseContext 方法,如:
/**
* While attaching the base context, make sure to attach it using locale helper.
* This helps in getting localized resources in every activity
*/
override fun attachBaseContext(newBase: Context?) {
var context = newBase
newBase?.let {
context = LocaleHelper.updateLocale(it)
}
super.attachBaseContext(context)
}
但我发现这曾经适用于牛轧糖及更高版本,而不适用于棉花糖和棒棒糖。所以,我尝试了这个:
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
//Update the locale here before loading the layout to get localized strings.
LocaleHelper.updateLocale(this)
在 base activity 的 oncreate 中我调用了这个方法。它开始工作了。但在某些情况下,如果我们从最近的任务中终止应用程序然后启动它,第一个屏幕不会本地化。因此,为了解决这个问题,我在应用 class 中创建了一个静态临时变量,用于保存初始应用实例的值。
companion object {
val TAG = "App"
var isFirstLoad = true
}
在我的第一个应用程序屏幕中,我在 oncreate 中执行了此操作:
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
//Need to call this so that when the application is opened for first time and we need to update the locale.
if(App.isFirstLoad) {
LocaleHelper.updateLocale(this)
App.isFirstLoad = false
}
setContentView(R.layout.activity_dash_board)
现在我在 redmi 4a (Android 7) 上进行了测试,之前无法在其上运行,sdk 21、23、27、29 模拟器以及 redmi note 5 pro 和像素设备。在所有这些中,应用内语言选择都正常工作。
抱歉这么长 post,但请提出优化方法! 谢谢!
编辑 1: 所以我遇到了这个问题,它在 android 7.1 (sdk 25) 上不起作用。我向gunhan寻求帮助,然后进行了此更改。
override fun applyOverrideConfiguration(overrideConfiguration: Configuration?) {
super.applyOverrideConfiguration(LocaleHelper.applyOverrideConfiguration(baseContext, overrideConfiguration))
}
我在 LocaleHelper 中添加了这个功能:
fun applyOverrideConfiguration(base: Context, overrideConfiguration: Configuration?): Configuration? {
if (overrideConfiguration != null && Build.VERSION.SDK_INT < Build.VERSION_CODES.O) {
val uiMode = overrideConfiguration.uiMode
overrideConfiguration.setTo(base.resources.configuration)
overrideConfiguration.uiMode = uiMode
}
return overrideConfiguration
}
现在终于可以用了。 谢谢 Gunhan!
我遇到了同样的问题,但是 Android 5、6 和 7。经过大量搜索,解决方案在这个 post 中:
首先,您必须将您的库 appcompat 更新为 1.2.0(修复区域设置,请参阅此处:https://developer.android.com/jetpack/androidx/releases/appcompat#1.2.0)并且您必须覆盖基础 activity 中的 applyOverrideConfiguration 函数。
我已经在所有 API(19 到 30 - Android 4.4 到 11)上测试了这个解决方案并且它工作得很好:)