我有一个支持多语言的旧项目。我要升级支持库和目标平台,在迁移到 Androidx
之前一切正常,但现在更改语言不起作用!
我使用此代码更改 App 的默认语言环境
private static Context updateResources(Context context, String language)
{
Locale locale = new Locale(language);
Locale.setDefault(locale);
Configuration configuration = context.getResources().getConfiguration();
configuration.setLocale(locale);
return context.createConfigurationContext(configuration);
}
并通过覆盖
attachBaseContext
在每个 Activity 上调用此方法像这样:@Override
protected void attachBaseContext(Context newBase)
{
SharedPreferences preferences = PreferenceManager.getDefaultSharedPreferences(context);
String language = preferences.getString(SELECTED_LANGUAGE, "fa");
super.attachBaseContext(updateResources(newBase, language));
}
我尝试了其他方法来获取字符串,我注意到
getActivity().getBaseContext().getString
工作和getActivity().getString
不行。甚至下面的代码也不起作用并且总是显示app_name
默认资源 string.xml 中的值。<TextView
android:id="@+id/textView"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/app_name"/>
我在 https://github.com/Freydoonk/LanguageTest 中分享了一个示例代码
还有
getActivity()..getResources().getIdentifier
不起作用,总是返回 0!
最佳答案
2020 年 8 月 21 日更新:
AppCompat 1.2.0 终于发布了。如果您不使用 ContextWrapper
或 ContextThemeWrapper
根本没有其他事情可做,您应该能够从 1.1.0 中删除任何解决方法!
如果您确实使用 ContextWrapper
或 ContextThemeWrapper
里面 attachBaseContext
, 语言环境更改将中断,因为当您将包装的上下文传递给 super 时,
AppCompatActivity
进行内部调用,将您的 ContextWrapper
包装起来在另一个 ContextThemeWrapper
, ContextThemeWrapper
,将其配置覆盖为空白,类似于 1.1.0 中发生的情况。 但解决方案总是一样的。对于情况 2,我尝试了多种其他解决方案,但正如 @Kreiri 在评论中指出的那样(感谢您的调查帮助!),
AppCompatDelegateImpl
总是以剥离语言环境而告终。最大的障碍是,与 1.1.0 不同,applyOverrideConfiguration
在您的基本上下文而不是您的主机 Activity 上调用,因此您不能像在 1.1.0 中那样在 Activity 中覆盖该方法并修复语言环境。我知道的唯一可行的解决方案是通过覆盖 getDelegate()
来反转包装。确保您的包装和/或语言环境覆盖在最后。首先,添加下面的类:Kotlin 示例(请注意,该类必须位于
androidx.appcompat.app
包内,因为唯一现有的 AppCompatDelegate
构造函数是包私有(private)的)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")
}
}
然后在我们的基本 Activity 类中删除所有 1.1.0 解决方法并简单地添加:private var baseContextWrappingDelegate: AppCompatDelegate? = null
override fun getDelegate() = baseContextWrappingDelegate ?: BaseContextWrappingDelegate(super.getDelegate()).apply {
baseContextWrappingDelegate = this
}
取决于 ContextWrapper
您正在使用的实现,配置更改可能会破坏主题或语言环境的更改。要解决此问题,请另外添加以下内容:override fun createConfigurationContext(overrideConfiguration: Configuration) : Context {
val context = super.createConfigurationContext(overrideConfiguration)
TODO("your wrapping implementation here")
}
而且你很好!您可以期待 Google 在 1.3.0 中再次打破这一点。我会在那里修好它……再见,太空牛仔!APPCOMPAT 1.1.0 的旧答案和解决方案:
基本上,在后台发生的事情是,当您在
attachBaseContext
中正确设置了配置时, AppCompatDelegateImpl
然后将配置覆盖为没有语言环境的全新配置: final Configuration conf = new Configuration();
conf.uiMode = newNightMode | (conf.uiMode & ~Configuration.UI_MODE_NIGHT_MASK);
try {
...
((android.view.ContextThemeWrapper) mHost).applyOverrideConfiguration(conf);
handled = true;
} catch (IllegalStateException e) {
...
}
In an unreleased commit by Chris Banes这实际上是固定的:新配置是基本上下文配置的深拷贝。final Configuration conf = new Configuration(baseConfiguration);
conf.uiMode = newNightMode | (conf.uiMode & ~Configuration.UI_MODE_NIGHT_MASK);
try {
...
((android.view.ContextThemeWrapper) mHost).applyOverrideConfiguration(conf);
handled = true;
} catch (IllegalStateException e) {
...
}
在此版本发布之前,可以手动执行完全相同的操作。要继续使用 1.1.0 版,请将其添加到您的 attachBaseContext
下方。 :Kotlin 解决方案
override fun applyOverrideConfiguration(overrideConfiguration: Configuration?) {
if (overrideConfiguration != null) {
val uiMode = overrideConfiguration.uiMode
overrideConfiguration.setTo(baseContext.resources.configuration)
overrideConfiguration.uiMode = uiMode
}
super.applyOverrideConfiguration(overrideConfiguration)
}
Java解决方案@Override
public void applyOverrideConfiguration(Configuration overrideConfiguration) {
if (overrideConfiguration != null) {
int uiMode = overrideConfiguration.uiMode;
overrideConfiguration.setTo(getBaseContext().getResources().getConfiguration());
overrideConfiguration.uiMode = uiMode;
}
super.applyOverrideConfiguration(overrideConfiguration);
}
这段代码的作用与 Configuration(baseConfiguration)
完全相同。确实在引擎盖下,但是因为我们是在 AppCompatDelegate
之后做的已经设置了正确的uiMode
,我们必须确保采用被覆盖的 uiMode
在我们修复它之后结束,这样我们就不会丢失暗/亮模式设置。请注意 如果您不指定
configChanges="uiMode"
,这只会自行起作用在你的 list 里面。如果你这样做了,那么还有另一个错误:内部 onConfigurationChanged
newConfig.uiMode
不会被 AppCompatDelegateImpl
设置的onConfigurationChanged
.如果您复制所有代码 AppCompatDelegateImpl
,也可以修复此问题。用于计算当前夜间模式到您的基本 Activity 代码,然后在 super.onConfigurationChanged
之前覆盖它称呼。在 Kotlin 中,它看起来像这样:private var activityHandlesUiMode = false
private var activityHandlesUiModeChecked = false
private val isActivityManifestHandlingUiMode: Boolean
get() {
if (!activityHandlesUiModeChecked) {
val pm = packageManager ?: return false
activityHandlesUiMode = try {
val info = pm.getActivityInfo(ComponentName(this, javaClass), 0)
info.configChanges and ActivityInfo.CONFIG_UI_MODE != 0
} catch (e: PackageManager.NameNotFoundException) {
false
}
}
activityHandlesUiModeChecked = true
return activityHandlesUiMode
}
override fun onConfigurationChanged(newConfig: Configuration) {
if (isActivityManifestHandlingUiMode) {
val nightMode = if (delegate.localNightMode != AppCompatDelegate.MODE_NIGHT_UNSPECIFIED)
delegate.localNightMode
else
AppCompatDelegate.getDefaultNightMode()
val configNightMode = when (nightMode) {
AppCompatDelegate.MODE_NIGHT_YES -> Configuration.UI_MODE_NIGHT_YES
AppCompatDelegate.MODE_NIGHT_NO -> Configuration.UI_MODE_NIGHT_NO
else -> applicationContext.configuration.uiMode and Configuration.UI_MODE_NIGHT_MASK
}
newConfig.uiMode = configNightMode or (newConfig.uiMode and Configuration.UI_MODE_NIGHT_MASK.inv())
}
super.onConfigurationChanged(newConfig)
}
关于android - 迁移到 Androidx 后更改区域设置不起作用,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/55265834/