Android N 以编程方式更改语言
Android N change language programmatically
我发现了只在 Android N 台设备上重现的非常奇怪的错误。
在浏览我的应用程序时,可以更改语言。这是更改它的代码。
public void update(Locale locale) {
Locale.setDefault(locale);
Configuration configuration = res.getConfiguration();
if (BuildUtils.isAtLeast24Api()) {
LocaleList localeList = new LocaleList(locale);
LocaleList.setDefault(localeList);
configuration.setLocales(localeList);
configuration.setLocale(locale);
} else if (BuildUtils.isAtLeast17Api()){
configuration.setLocale(locale);
} else {
configuration.locale = locale;
}
res.updateConfiguration(configuration, res.getDisplayMetrics());
}
此代码在我的 activity 之旅(使用 recreate()
调用)中运行良好,但在接下来的所有活动中,所有 String 资源都是错误的。屏幕旋转修复它。我该怎么办这个问题?我应该以不同方式更改 Android N 的区域设置还是只是系统错误?
P.S。这是我发现的。第一次启动 MainActivity(在我的游览之后)Locale.getDefault()
是正确的,但资源是错误的。但是在其他活动中,它给了我错误的语言环境和来自该语言环境的错误资源。旋转屏幕后(或者可能是其他一些配置更改)Locale.getDefault()
是正确的。
好的。最后我设法找到了解决方案。
首先您应该知道在 25 API Resources.updateConfiguration(...)
中已弃用。所以你可以这样做:
1) 您需要创建自己的 ContextWrapper,它将覆盖 baseContext 中的所有配置参数。例如,这是我的 ContextWrapper 可以正确更改 Locale。注意context.createConfigurationContext(configuration)
方法。
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 (BuildUtils.isAtLeast24Api()) {
configuration.setLocale(newLocale);
LocaleList localeList = new LocaleList(newLocale);
LocaleList.setDefault(localeList);
configuration.setLocales(localeList);
context = context.createConfigurationContext(configuration);
} else if (BuildUtils.isAtLeast17Api()) {
configuration.setLocale(newLocale);
context = context.createConfigurationContext(configuration);
} else {
configuration.locale = newLocale;
res.updateConfiguration(configuration, res.getDisplayMetrics());
}
return new ContextWrapper(context);
}
}
2) 以下是您应该在 BaseActivity 中执行的操作:
@Override
protected void attachBaseContext(Context newBase) {
Locale newLocale;
// .. create or get your new Locale object here.
Context context = ContextWrapper.wrap(newBase, newLocale);
super.attachBaseContext(context);
}
注:
Remember to recreate your activity if you want to change Locale in
your App somewhere. You can override any configuration you want using
this solution.
受各种代码的启发(即:我们的 Whosebug 团队(喊出来的人)),我制作了一个更简单的版本。 ContextWrapper
扩展名是不必要的。
首先假设您有 2 个按钮用于 2 种语言,EN 和 KH。在按钮的 onClick 中将语言代码保存到 SharedPreferences
,然后调用 activity recreate()
方法。
示例:
@Override
public void onClick(View v) {
switch(v.getId()) {
case R.id.btn_lang_en:
//save "en" to SharedPref here
break;
case R.id.btn_lang_kh:
//save "kh" to SharedPref here
break;
default:
break;
}
getActivity().recreate();
}
然后创建一个 returns ContextWrapper
的静态方法,也许在 Utils class 中(因为我就是这么做的,lul)。
public static ContextWrapper changeLang(Context context, String lang_code){
Locale sysLocale;
Resources rs = context.getResources();
Configuration config = rs.getConfiguration();
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
sysLocale = config.getLocales().get(0);
} else {
sysLocale = config.locale;
}
if (!lang_code.equals("") && !sysLocale.getLanguage().equals(lang_code)) {
Locale locale = new Locale(lang_code);
Locale.setDefault(locale);
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
config.setLocale(locale);
} else {
config.locale = 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 ContextWrapper(context);
}
最后,在 ALL ACTIVITY'S attachBaseContext(Context newBase)
方法中加载来自 SharedPreferences
的语言代码。
@Override
protected void attachBaseContext(Context newBase) {
String lang_code = "en"; //load it from SharedPref
Context context = Utils.changeLang(newBase, lang_code);
super.attachBaseContext(context);
}
奖励:为了节省键盘上的手汗,我创建了一个 LangSupportBaseActivity
class 来扩展 Activity
并在那里使用最后一段代码。我还有所有其他活动扩展 LangSupportBaseActivity
.
示例:
public class LangSupportBaseActivity extends Activity{
...blab blab blab so on and so forth lines of neccessary code
@Override
protected void attachBaseContext(Context newBase) {
String lang_code = "en"; //load it from SharedPref
Context context = Utils.changeLang(newBase, lang_code);
super.attachBaseContext(context);
}
}
public class HomeActivity extends LangSupportBaseActivity{
...blab blab blab
}
以上答案让我走上了正确的轨道,但留下了几个问题
- 在 android 7 和 9 上,我可以愉快地更改为应用默认语言以外的任何语言。当我改回应用程序默认语言时,它显示了最后选择的语言——这并不奇怪,因为这已经覆盖了默认设置(尽管有趣的是,这在 Android 8 上不是问题!)。
- 对于 RTL 语言,它没有将布局更新为 RTL
为了修复第一项,我在应用程序启动时存储了默认语言环境。
注意 如果您的默认语言设置为 "en",则 "enGB" 或 "enUS" 的区域设置都需要与默认区域设置匹配(除非您为它们提供单独的本地化版本)。类似地,在下面的示例中,如果用户的 phone 语言环境是 arEG(埃及阿拉伯语),则 defLanguage 需要是 "ar" 而不是 "arEG"
private Locale defLocale = Locale.getDefault();
private Locale locale = Locale.getDefault();
public static myApplication myApp;
public static Resources res;
private static String defLanguage = Locale.getDefault().getLanguage() + Locale.getDefault().getCountry();
private static sLanguage = "en";
private static final Set<String> SUPPORTEDLANGUAGES = new HashSet<>(Arrays.asList(new String[]{"en", "ar", "arEG"}));
@Override
protected void attachBaseContext(Context base) {
if (myApp == null) myApp = this;
if (base == null) super.attachBaseContext(this);
else super.attachBaseContext(setLocale(base));
}
@Override
public void onCreate() {
myApp = this;
if (!SUPPORTEDLANGUAGES.contains(test)) {
// The default locale (eg enUS) is not in the supported list - lets see if the language is
if (SUPPORTEDLANGUAGES.contains(defLanguage.substring(0,2))) {
defLanguage = defLanguage.substring(0,2);
}
}
}
private static void setLanguage(String sLang) {
Configuration baseCfg = myApp.getBaseContext().getResources().getConfiguration();
if ( sLang.length() > 2 ) {
String s[] = sLang.split("_");
myApp.locale = new Locale(s[0],s[1]);
sLanguage = s[0] + s[1];
}
else {
myApp.locale = new Locale(sLang);
sLanguage = sLang;
}
}
public static Context setLocale(Context ctx) {
Locale.setDefault(myApp.locale);
Resources tempRes = ctx.getResources();
Configuration config = tempRes.getConfiguration();
if (Build.VERSION.SDK_INT >= 24) {
// If changing to the app default language, set locale to the default locale
if (sLanguage.equals(myApp.defLanguage)) {
config.setLocale(myApp.defLocale);
// restored the default locale as well
Locale.setDefault(myApp.defLocale);
}
else config.setLocale(myApp.locale);
ctx = ctx.createConfigurationContext(config);
// update the resources object to point to the current localisation
res = ctx.getResources();
} else {
config.locale = myApp.locale;
tempRes.updateConfiguration(config, tempRes.getDisplayMetrics());
}
return ctx;
}
为了修复 RTL 问题,我根据
中的 Fragments 评论扩展了 AppCompatActivity
public class myCompatActivity extends AppCompatActivity {
@Override
protected void attachBaseContext(Context base) {
super.attachBaseContext(myApplication.setLocale(base));
}
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
if (Build.VERSION.SDK_INT >= 17) {
getWindow().getDecorView().setLayoutDirection(myApplication.isRTL() ?
View.LAYOUT_DIRECTION_RTL : View.LAYOUT_DIRECTION_LTR);
}
}
}
自 Android 7.0+ 以来,我的应用程序的某些部分不再更改其语言。即使使用上面提出的新方法。更新应用程序和 activity 上下文对我有帮助。这是 Activity 子类覆盖的 Kotlin 示例:
private fun setApplicationLanguage(newLanguage: String) {
val activityRes = resources
val activityConf = activityRes.configuration
val newLocale = Locale(newLanguage)
activityConf.setLocale(newLocale)
activityRes.updateConfiguration(activityConf, activityRes.displayMetrics)
val applicationRes = applicationContext.resources
val applicationConf = applicationRes.configuration
applicationConf.setLocale(newLocale)
applicationRes.updateConfiguration(applicationConf,
applicationRes.displayMetrics)
}
override fun attachBaseContext(newBase: Context?) {
super.attachBaseContext(newBase)
setApplicationLanguage("fa");
}
注意:updateConfiguration 已弃用,但无论如何,每个 Activity 的 createConfigurationContext 都会保留一些字符串不变。
在 Android 应用程序中以编程方式更改区域设置很痛苦ui。我花了很多时间来寻找目前在生产中有效的工作解决方案。
您需要在每个 Activity
以及您的 Application
class 中覆盖上下文,否则您最终会在 ui 中使用混合语言。
所以这是我的解决方案,可以达到 API 29:
子class 你的 MainApplication
class 来自:
abstract class LocalApplication : Application() {
override fun attachBaseContext(base: Context) {
super.attachBaseContext(
base.toLangIfDiff(
PreferenceManager
.getDefaultSharedPreferences(base)
.getString("langPref", "sys")!!
)
)
}
}
还有每个 Activity
来自:
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)
}
}
使用下一个扩展函数添加 LocaleExt.kt
:
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
}
将支持的语言添加到 res/values/arrays.xml
数组中:
<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"
是一个值,表示"inherit system default language".
- 此处 "langPref" 是您放置用户当前语言的首选项键。
- 如果已经使用了 needed,则无需重新创建上下文
语言环境。
- 这里不需要
ContextWraper
,只需将 createConfigurationContext
返回的新上下文设置为 baseContext
- 这很重要!当您调用
createConfigurationContext
时,您应该通过 从头开始 并且仅设置 Locale
属性 的配置。不应为此配置设置任何其他 属性。因为如果我们为此配置设置一些其他属性(例如 orientation),我们将永远覆盖 属性,并且我们的上下文不再更改此 orientation 属性 即使我们旋转屏幕。
- 当用户选择不同的语言时,仅
recreate
activity 是不够的,因为 applicationContext 将保留旧语言环境并且它可能会提供意外行为。因此,听取偏好更改并重新启动整个应用程序任务:
fun Context.recreateTask() {
this.packageManager
.getLaunchIntentForPackage(context.packageName)
?.let { intent ->
val restartIntent = Intent.makeRestartActivityTask(intent.component)
this.startActivity(restartIntent)
Runtime.getRuntime().exit(0)
}
}
这是我的代码,它有效!
如果有问题,请告诉我:
protected void attachBaseContext(Context newBase) {
String lang = "en"; // your language or load from SharedPref
Locale locale = new Locale(lang);
Configuration config = new Configuration(newBase.getResources().getConfiguration());
Locale.setDefault(locale);
config.setLocale(locale);
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1) {
newBase = newBase.createConfigurationContext(config);
} else {
newBase.getResources().updateConfiguration(config, newBase.getResources().getDisplayMetrics());
}
super.attachBaseContext(newBase);
}
2020 年 9 月更新
对于最新的 Androidx Appcombat 稳定版 1.2.0,删除 1.1.0 的所有解决方法并添加这个
package androidx.appcompat.app
import android.content.Context
import android.content.res.Configuration
import android.os.Bundle
import android.util.AttributeSet
import android.view.MenuInflater
import android.view.View
import android.view.ViewGroup
import androidx.appcompat.view.ActionMode
import androidx.appcompat.widget.Toolbar
class BaseContextWrappingDelegate(private val superDelegate:
AppCompatDelegate) : AppCompatDelegate() {
override fun getSupportActionBar() = superDelegate.supportActionBar
override fun setSupportActionBar(toolbar: Toolbar?) = superDelegate.setSupportActionBar(toolbar)
override fun getMenuInflater(): MenuInflater? = superDelegate.menuInflater
override fun onCreate(savedInstanceState: Bundle?) {
superDelegate.onCreate(savedInstanceState)
removeActivityDelegate(superDelegate)
addActiveDelegate(this)
}
override fun onPostCreate(savedInstanceState: Bundle?) = superDelegate.onPostCreate(savedInstanceState)
override fun onConfigurationChanged(newConfig: Configuration?) = superDelegate.onConfigurationChanged(newConfig)
override fun onStart() = superDelegate.onStart()
override fun onStop() = superDelegate.onStop()
override fun onPostResume() = superDelegate.onPostResume()
override fun setTheme(themeResId: Int) = superDelegate.setTheme(themeResId)
override fun <T : View?> findViewById(id: Int) = superDelegate.findViewById<T>(id)
override fun setContentView(v: View?) = superDelegate.setContentView(v)
override fun setContentView(resId: Int) = superDelegate.setContentView(resId)
override fun setContentView(v: View?, lp: ViewGroup.LayoutParams?) = superDelegate.setContentView(v, lp)
override fun addContentView(v: View?, lp: ViewGroup.LayoutParams?) = superDelegate.addContentView(v, lp)
override fun attachBaseContext2(context: Context) = wrap(superDelegate.attachBaseContext2(super.attachBaseContext2(context)))
override fun setTitle(title: CharSequence?) = superDelegate.setTitle(title)
override fun invalidateOptionsMenu() = superDelegate.invalidateOptionsMenu()
override fun onDestroy() {
superDelegate.onDestroy()
removeActivityDelegate(this)
}
override fun getDrawerToggleDelegate() = superDelegate.drawerToggleDelegate
override fun requestWindowFeature(featureId: Int) = superDelegate.requestWindowFeature(featureId)
override fun hasWindowFeature(featureId: Int) = superDelegate.hasWindowFeature(featureId)
override fun startSupportActionMode(callback: ActionMode.Callback) = superDelegate.startSupportActionMode(callback)
override fun installViewFactory() = superDelegate.installViewFactory()
override fun createView(parent: View?, name: String?, context: Context, attrs: AttributeSet): View? = superDelegate.createView(parent, name, context, attrs)
override fun setHandleNativeActionModesEnabled(enabled: Boolean) {
superDelegate.isHandleNativeActionModesEnabled = enabled
}
override fun isHandleNativeActionModesEnabled() = superDelegate.isHandleNativeActionModesEnabled
override fun onSaveInstanceState(outState: Bundle?) = superDelegate.onSaveInstanceState(outState)
override fun applyDayNight() = superDelegate.applyDayNight()
override fun setLocalNightMode(mode: Int) {
superDelegate.localNightMode = mode
}
override fun getLocalNightMode() = superDelegate.localNightMode
private fun wrap(context: Context): Context {
TODO("your wrapping implementation here")
}
}
将您的语言环境逻辑添加到函数包装中(您可以在上面接受的答案中添加 ContextWrapper)。这个 class 必须在 androidx.appcompat.app 包内,因为唯一现有的 AppCompatDelegate 构造函数是 package private
然后在你的基础 activity class 中删除所有 1.1.0 解决方法并简单地添加这个
private var baseContextWrappingDelegate: AppCompatDelegate? = null
override fun getDelegate() = baseContextWrappingDelegate ?:
BaseContextWrappingDelegate(super.getDelegate()).apply {
baseContextWrappingDelegate = this
}
配置更改可能会中断区域设置更改。解决这个问题
override fun createConfigurationContext(overrideConfiguration: Configuration)
: Context {
val context = super.createConfigurationContext(overrideConfiguration)
TODO("your wrapping implementation here")
}
就是这样。您可以使用最新的 1.2.0 appCombat
2020 年 11 月更新
大家好,我只是想分享一下我的经验。几天前,我开始收到有关 Android N 设备中的一个问题的报告,即我的应用程序设置中的语言没有发生变化。我搜索了很多,在尝试对我的代码进行多次更改后,我开始知道它们在我的代码中没有问题,问题是由于 androidx 约束布局 gradle 依赖版本 2.0.0 并将其降级为 1.1.3 后,语言问题已解决。
我使用此版本的 ConstraintLayout 库解决了我的问题。
implementation 'androidx.constraintlayout:constraintlayout:1.1.3'
这对我有用,我正在使用 androidx.appcompat:appcompat:1.2.0
override fun attachBaseContext(newBase: Context?) {
val sp = PreferenceManager.getDefaultSharedPreferences(newBase)
val locale = when(sp.getString("app_language", "")) {
"en" -> { Locale("en") }
"hu" -> { Locale("hu") }
else -> {
if (Build.VERSION.SDK_INT >= 24) {
Resources.getSystem().configuration.locales.get(0);
}
else {
Resources.getSystem().configuration.locale
}
}
}
if(newBase != null) {
Locale.setDefault(locale)
newBase.resources.configuration.setLocale(locale)
applyOverrideConfiguration(newBase.resources.configuration)
}
super.attachBaseContext(newBase)
}
在我的例子中,Xamarin.Android:
创建上下文包装器:
public class LanguageContextWrapper : Android.Content.ContextWrapper
{
public LanguageContextWrapper(Context @base) : base(@base)
{
}
public static ContextWrapper Wrap(Context context, string newLocale)
{
Locale.Default = new Locale(newLocale);
Configuration config = new Configuration();
config.SetLocale(Locale.Default);
context = context.CreateConfigurationContext(config);
return new ContextWrapper(context);
}
}
并在所有 activity 中使用:
protected override void AttachBaseContext(Context newBase)
{
Context context = LanguageContextWrapper.Wrap(newBase, "en"); //need use short name of locale language
base.AttachBaseContext(context);
}
并在 android 10,11,12 工作我没有检查下面。
我发现了只在 Android N 台设备上重现的非常奇怪的错误。
在浏览我的应用程序时,可以更改语言。这是更改它的代码。
public void update(Locale locale) {
Locale.setDefault(locale);
Configuration configuration = res.getConfiguration();
if (BuildUtils.isAtLeast24Api()) {
LocaleList localeList = new LocaleList(locale);
LocaleList.setDefault(localeList);
configuration.setLocales(localeList);
configuration.setLocale(locale);
} else if (BuildUtils.isAtLeast17Api()){
configuration.setLocale(locale);
} else {
configuration.locale = locale;
}
res.updateConfiguration(configuration, res.getDisplayMetrics());
}
此代码在我的 activity 之旅(使用 recreate()
调用)中运行良好,但在接下来的所有活动中,所有 String 资源都是错误的。屏幕旋转修复它。我该怎么办这个问题?我应该以不同方式更改 Android N 的区域设置还是只是系统错误?
P.S。这是我发现的。第一次启动 MainActivity(在我的游览之后)Locale.getDefault()
是正确的,但资源是错误的。但是在其他活动中,它给了我错误的语言环境和来自该语言环境的错误资源。旋转屏幕后(或者可能是其他一些配置更改)Locale.getDefault()
是正确的。
好的。最后我设法找到了解决方案。
首先您应该知道在 25 API Resources.updateConfiguration(...)
中已弃用。所以你可以这样做:
1) 您需要创建自己的 ContextWrapper,它将覆盖 baseContext 中的所有配置参数。例如,这是我的 ContextWrapper 可以正确更改 Locale。注意context.createConfigurationContext(configuration)
方法。
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 (BuildUtils.isAtLeast24Api()) {
configuration.setLocale(newLocale);
LocaleList localeList = new LocaleList(newLocale);
LocaleList.setDefault(localeList);
configuration.setLocales(localeList);
context = context.createConfigurationContext(configuration);
} else if (BuildUtils.isAtLeast17Api()) {
configuration.setLocale(newLocale);
context = context.createConfigurationContext(configuration);
} else {
configuration.locale = newLocale;
res.updateConfiguration(configuration, res.getDisplayMetrics());
}
return new ContextWrapper(context);
}
}
2) 以下是您应该在 BaseActivity 中执行的操作:
@Override
protected void attachBaseContext(Context newBase) {
Locale newLocale;
// .. create or get your new Locale object here.
Context context = ContextWrapper.wrap(newBase, newLocale);
super.attachBaseContext(context);
}
注:
Remember to recreate your activity if you want to change Locale in your App somewhere. You can override any configuration you want using this solution.
受各种代码的启发(即:我们的 Whosebug 团队(喊出来的人)),我制作了一个更简单的版本。 ContextWrapper
扩展名是不必要的。
首先假设您有 2 个按钮用于 2 种语言,EN 和 KH。在按钮的 onClick 中将语言代码保存到 SharedPreferences
,然后调用 activity recreate()
方法。
示例:
@Override
public void onClick(View v) {
switch(v.getId()) {
case R.id.btn_lang_en:
//save "en" to SharedPref here
break;
case R.id.btn_lang_kh:
//save "kh" to SharedPref here
break;
default:
break;
}
getActivity().recreate();
}
然后创建一个 returns ContextWrapper
的静态方法,也许在 Utils class 中(因为我就是这么做的,lul)。
public static ContextWrapper changeLang(Context context, String lang_code){
Locale sysLocale;
Resources rs = context.getResources();
Configuration config = rs.getConfiguration();
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
sysLocale = config.getLocales().get(0);
} else {
sysLocale = config.locale;
}
if (!lang_code.equals("") && !sysLocale.getLanguage().equals(lang_code)) {
Locale locale = new Locale(lang_code);
Locale.setDefault(locale);
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
config.setLocale(locale);
} else {
config.locale = 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 ContextWrapper(context);
}
最后,在 ALL ACTIVITY'S attachBaseContext(Context newBase)
方法中加载来自 SharedPreferences
的语言代码。
@Override
protected void attachBaseContext(Context newBase) {
String lang_code = "en"; //load it from SharedPref
Context context = Utils.changeLang(newBase, lang_code);
super.attachBaseContext(context);
}
奖励:为了节省键盘上的手汗,我创建了一个 LangSupportBaseActivity
class 来扩展 Activity
并在那里使用最后一段代码。我还有所有其他活动扩展 LangSupportBaseActivity
.
示例:
public class LangSupportBaseActivity extends Activity{
...blab blab blab so on and so forth lines of neccessary code
@Override
protected void attachBaseContext(Context newBase) {
String lang_code = "en"; //load it from SharedPref
Context context = Utils.changeLang(newBase, lang_code);
super.attachBaseContext(context);
}
}
public class HomeActivity extends LangSupportBaseActivity{
...blab blab blab
}
以上答案让我走上了正确的轨道,但留下了几个问题
- 在 android 7 和 9 上,我可以愉快地更改为应用默认语言以外的任何语言。当我改回应用程序默认语言时,它显示了最后选择的语言——这并不奇怪,因为这已经覆盖了默认设置(尽管有趣的是,这在 Android 8 上不是问题!)。
- 对于 RTL 语言,它没有将布局更新为 RTL
为了修复第一项,我在应用程序启动时存储了默认语言环境。
注意 如果您的默认语言设置为 "en",则 "enGB" 或 "enUS" 的区域设置都需要与默认区域设置匹配(除非您为它们提供单独的本地化版本)。类似地,在下面的示例中,如果用户的 phone 语言环境是 arEG(埃及阿拉伯语),则 defLanguage 需要是 "ar" 而不是 "arEG"
private Locale defLocale = Locale.getDefault();
private Locale locale = Locale.getDefault();
public static myApplication myApp;
public static Resources res;
private static String defLanguage = Locale.getDefault().getLanguage() + Locale.getDefault().getCountry();
private static sLanguage = "en";
private static final Set<String> SUPPORTEDLANGUAGES = new HashSet<>(Arrays.asList(new String[]{"en", "ar", "arEG"}));
@Override
protected void attachBaseContext(Context base) {
if (myApp == null) myApp = this;
if (base == null) super.attachBaseContext(this);
else super.attachBaseContext(setLocale(base));
}
@Override
public void onCreate() {
myApp = this;
if (!SUPPORTEDLANGUAGES.contains(test)) {
// The default locale (eg enUS) is not in the supported list - lets see if the language is
if (SUPPORTEDLANGUAGES.contains(defLanguage.substring(0,2))) {
defLanguage = defLanguage.substring(0,2);
}
}
}
private static void setLanguage(String sLang) {
Configuration baseCfg = myApp.getBaseContext().getResources().getConfiguration();
if ( sLang.length() > 2 ) {
String s[] = sLang.split("_");
myApp.locale = new Locale(s[0],s[1]);
sLanguage = s[0] + s[1];
}
else {
myApp.locale = new Locale(sLang);
sLanguage = sLang;
}
}
public static Context setLocale(Context ctx) {
Locale.setDefault(myApp.locale);
Resources tempRes = ctx.getResources();
Configuration config = tempRes.getConfiguration();
if (Build.VERSION.SDK_INT >= 24) {
// If changing to the app default language, set locale to the default locale
if (sLanguage.equals(myApp.defLanguage)) {
config.setLocale(myApp.defLocale);
// restored the default locale as well
Locale.setDefault(myApp.defLocale);
}
else config.setLocale(myApp.locale);
ctx = ctx.createConfigurationContext(config);
// update the resources object to point to the current localisation
res = ctx.getResources();
} else {
config.locale = myApp.locale;
tempRes.updateConfiguration(config, tempRes.getDisplayMetrics());
}
return ctx;
}
为了修复 RTL 问题,我根据
public class myCompatActivity extends AppCompatActivity {
@Override
protected void attachBaseContext(Context base) {
super.attachBaseContext(myApplication.setLocale(base));
}
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
if (Build.VERSION.SDK_INT >= 17) {
getWindow().getDecorView().setLayoutDirection(myApplication.isRTL() ?
View.LAYOUT_DIRECTION_RTL : View.LAYOUT_DIRECTION_LTR);
}
}
}
自 Android 7.0+ 以来,我的应用程序的某些部分不再更改其语言。即使使用上面提出的新方法。更新应用程序和 activity 上下文对我有帮助。这是 Activity 子类覆盖的 Kotlin 示例:
private fun setApplicationLanguage(newLanguage: String) {
val activityRes = resources
val activityConf = activityRes.configuration
val newLocale = Locale(newLanguage)
activityConf.setLocale(newLocale)
activityRes.updateConfiguration(activityConf, activityRes.displayMetrics)
val applicationRes = applicationContext.resources
val applicationConf = applicationRes.configuration
applicationConf.setLocale(newLocale)
applicationRes.updateConfiguration(applicationConf,
applicationRes.displayMetrics)
}
override fun attachBaseContext(newBase: Context?) {
super.attachBaseContext(newBase)
setApplicationLanguage("fa");
}
注意:updateConfiguration 已弃用,但无论如何,每个 Activity 的 createConfigurationContext 都会保留一些字符串不变。
在 Android 应用程序中以编程方式更改区域设置很痛苦ui。我花了很多时间来寻找目前在生产中有效的工作解决方案。
您需要在每个 Activity
以及您的 Application
class 中覆盖上下文,否则您最终会在 ui 中使用混合语言。
所以这是我的解决方案,可以达到 API 29:
子class 你的 MainApplication
class 来自:
abstract class LocalApplication : Application() {
override fun attachBaseContext(base: Context) {
super.attachBaseContext(
base.toLangIfDiff(
PreferenceManager
.getDefaultSharedPreferences(base)
.getString("langPref", "sys")!!
)
)
}
}
还有每个 Activity
来自:
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)
}
}
使用下一个扩展函数添加 LocaleExt.kt
:
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
}
将支持的语言添加到 res/values/arrays.xml
数组中:
<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"
是一个值,表示"inherit system default language".- 此处 "langPref" 是您放置用户当前语言的首选项键。
- 如果已经使用了 needed,则无需重新创建上下文 语言环境。
- 这里不需要
ContextWraper
,只需将createConfigurationContext
返回的新上下文设置为 baseContext - 这很重要!当您调用
createConfigurationContext
时,您应该通过 从头开始 并且仅设置Locale
属性 的配置。不应为此配置设置任何其他 属性。因为如果我们为此配置设置一些其他属性(例如 orientation),我们将永远覆盖 属性,并且我们的上下文不再更改此 orientation 属性 即使我们旋转屏幕。 - 当用户选择不同的语言时,仅
recreate
activity 是不够的,因为 applicationContext 将保留旧语言环境并且它可能会提供意外行为。因此,听取偏好更改并重新启动整个应用程序任务:
fun Context.recreateTask() {
this.packageManager
.getLaunchIntentForPackage(context.packageName)
?.let { intent ->
val restartIntent = Intent.makeRestartActivityTask(intent.component)
this.startActivity(restartIntent)
Runtime.getRuntime().exit(0)
}
}
这是我的代码,它有效! 如果有问题,请告诉我:
protected void attachBaseContext(Context newBase) {
String lang = "en"; // your language or load from SharedPref
Locale locale = new Locale(lang);
Configuration config = new Configuration(newBase.getResources().getConfiguration());
Locale.setDefault(locale);
config.setLocale(locale);
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1) {
newBase = newBase.createConfigurationContext(config);
} else {
newBase.getResources().updateConfiguration(config, newBase.getResources().getDisplayMetrics());
}
super.attachBaseContext(newBase);
}
2020 年 9 月更新
对于最新的 Androidx Appcombat 稳定版 1.2.0,删除 1.1.0 的所有解决方法并添加这个
package androidx.appcompat.app
import android.content.Context
import android.content.res.Configuration
import android.os.Bundle
import android.util.AttributeSet
import android.view.MenuInflater
import android.view.View
import android.view.ViewGroup
import androidx.appcompat.view.ActionMode
import androidx.appcompat.widget.Toolbar
class BaseContextWrappingDelegate(private val superDelegate:
AppCompatDelegate) : AppCompatDelegate() {
override fun getSupportActionBar() = superDelegate.supportActionBar
override fun setSupportActionBar(toolbar: Toolbar?) = superDelegate.setSupportActionBar(toolbar)
override fun getMenuInflater(): MenuInflater? = superDelegate.menuInflater
override fun onCreate(savedInstanceState: Bundle?) {
superDelegate.onCreate(savedInstanceState)
removeActivityDelegate(superDelegate)
addActiveDelegate(this)
}
override fun onPostCreate(savedInstanceState: Bundle?) = superDelegate.onPostCreate(savedInstanceState)
override fun onConfigurationChanged(newConfig: Configuration?) = superDelegate.onConfigurationChanged(newConfig)
override fun onStart() = superDelegate.onStart()
override fun onStop() = superDelegate.onStop()
override fun onPostResume() = superDelegate.onPostResume()
override fun setTheme(themeResId: Int) = superDelegate.setTheme(themeResId)
override fun <T : View?> findViewById(id: Int) = superDelegate.findViewById<T>(id)
override fun setContentView(v: View?) = superDelegate.setContentView(v)
override fun setContentView(resId: Int) = superDelegate.setContentView(resId)
override fun setContentView(v: View?, lp: ViewGroup.LayoutParams?) = superDelegate.setContentView(v, lp)
override fun addContentView(v: View?, lp: ViewGroup.LayoutParams?) = superDelegate.addContentView(v, lp)
override fun attachBaseContext2(context: Context) = wrap(superDelegate.attachBaseContext2(super.attachBaseContext2(context)))
override fun setTitle(title: CharSequence?) = superDelegate.setTitle(title)
override fun invalidateOptionsMenu() = superDelegate.invalidateOptionsMenu()
override fun onDestroy() {
superDelegate.onDestroy()
removeActivityDelegate(this)
}
override fun getDrawerToggleDelegate() = superDelegate.drawerToggleDelegate
override fun requestWindowFeature(featureId: Int) = superDelegate.requestWindowFeature(featureId)
override fun hasWindowFeature(featureId: Int) = superDelegate.hasWindowFeature(featureId)
override fun startSupportActionMode(callback: ActionMode.Callback) = superDelegate.startSupportActionMode(callback)
override fun installViewFactory() = superDelegate.installViewFactory()
override fun createView(parent: View?, name: String?, context: Context, attrs: AttributeSet): View? = superDelegate.createView(parent, name, context, attrs)
override fun setHandleNativeActionModesEnabled(enabled: Boolean) {
superDelegate.isHandleNativeActionModesEnabled = enabled
}
override fun isHandleNativeActionModesEnabled() = superDelegate.isHandleNativeActionModesEnabled
override fun onSaveInstanceState(outState: Bundle?) = superDelegate.onSaveInstanceState(outState)
override fun applyDayNight() = superDelegate.applyDayNight()
override fun setLocalNightMode(mode: Int) {
superDelegate.localNightMode = mode
}
override fun getLocalNightMode() = superDelegate.localNightMode
private fun wrap(context: Context): Context {
TODO("your wrapping implementation here")
}
}
将您的语言环境逻辑添加到函数包装中(您可以在上面接受的答案中添加 ContextWrapper)。这个 class 必须在 androidx.appcompat.app 包内,因为唯一现有的 AppCompatDelegate 构造函数是 package private
然后在你的基础 activity class 中删除所有 1.1.0 解决方法并简单地添加这个
private var baseContextWrappingDelegate: AppCompatDelegate? = null
override fun getDelegate() = baseContextWrappingDelegate ?:
BaseContextWrappingDelegate(super.getDelegate()).apply {
baseContextWrappingDelegate = this
}
配置更改可能会中断区域设置更改。解决这个问题
override fun createConfigurationContext(overrideConfiguration: Configuration)
: Context {
val context = super.createConfigurationContext(overrideConfiguration)
TODO("your wrapping implementation here")
}
就是这样。您可以使用最新的 1.2.0 appCombat
2020 年 11 月更新
大家好,我只是想分享一下我的经验。几天前,我开始收到有关 Android N 设备中的一个问题的报告,即我的应用程序设置中的语言没有发生变化。我搜索了很多,在尝试对我的代码进行多次更改后,我开始知道它们在我的代码中没有问题,问题是由于 androidx 约束布局 gradle 依赖版本 2.0.0 并将其降级为 1.1.3 后,语言问题已解决。 我使用此版本的 ConstraintLayout 库解决了我的问题。
implementation 'androidx.constraintlayout:constraintlayout:1.1.3'
这对我有用,我正在使用 androidx.appcompat:appcompat:1.2.0
override fun attachBaseContext(newBase: Context?) {
val sp = PreferenceManager.getDefaultSharedPreferences(newBase)
val locale = when(sp.getString("app_language", "")) {
"en" -> { Locale("en") }
"hu" -> { Locale("hu") }
else -> {
if (Build.VERSION.SDK_INT >= 24) {
Resources.getSystem().configuration.locales.get(0);
}
else {
Resources.getSystem().configuration.locale
}
}
}
if(newBase != null) {
Locale.setDefault(locale)
newBase.resources.configuration.setLocale(locale)
applyOverrideConfiguration(newBase.resources.configuration)
}
super.attachBaseContext(newBase)
}
在我的例子中,Xamarin.Android:
创建上下文包装器:
public class LanguageContextWrapper : Android.Content.ContextWrapper
{
public LanguageContextWrapper(Context @base) : base(@base)
{
}
public static ContextWrapper Wrap(Context context, string newLocale)
{
Locale.Default = new Locale(newLocale);
Configuration config = new Configuration();
config.SetLocale(Locale.Default);
context = context.CreateConfigurationContext(config);
return new ContextWrapper(context);
}
}
并在所有 activity 中使用:
protected override void AttachBaseContext(Context newBase)
{
Context context = LanguageContextWrapper.Wrap(newBase, "en"); //need use short name of locale language
base.AttachBaseContext(context);
}
并在 android 10,11,12 工作我没有检查下面。