以编程方式更改语言(Android N 7.0 - API 24)

时间:2016-08-17 12:59:17

标签: android locale android-7.0-nougat

我使用以下代码在我的应用中设置特定语言。语言将保存到应用内的SharedPreferences它完全符合API级别23。 Android N SharedPreferences效果也很好,它会返回正确的语言代码字符串,但不会更改区域设置(设置默认语言)电话)。可能有什么不对?

更新1:当我在Log.v("MyLog", config.locale.toString());之后立即使用res.updateConfiguration(config, dm)时,它会返回正确的区域设置,但应用的语言不会更改。

更新2:我还提到如果我更改区域设置然后重新启动活动(使用新意图并完成旧版本),它会正确更改语言,甚至可以显示正确的语言回转。但是当我关闭应用程序并再次打开它时,我会得到默认语言。这很奇怪。

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: 答案也在这里: https://stackoverflow.com/a/40849142/3935063

4 个答案:

答案 0 :(得分:10)

创建一个新的类扩展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));
}

完成活动并再次启动,新的语言环境将生效。

演示:https://github.com/fanturbo/MultiLanguageDemo

答案 1 :(得分:3)

当我开始以SDK 29为目标时,我遇到了这个问题。我创建的LocaleHelper适用于从我的最小SDK(21)到29的每个版本,但特别是在Android N上却无法使用。因此,在搜索了许多堆栈溢出答案并访问https://issuetracker.google.com/issues/37123942之后,我设法找到/修改了一个解决方案,该解决方案现在可在所有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
    }
}

然后,在基本活动中,覆盖如下的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)

在基本活动的oncreate中,我将此方法称为。它开始工作。但是在某些情况下,如果我们从最近的任务中杀死该应用程序然后启动它,则第一个屏幕未本地化。因此,为了解决这个问题,我在应用程序类中创建了一个静态temp变量,用于保存初始应用程序实例的值。

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和Pixel设备中进行了测试。在所有这些方面,应用程序内语言选择均能正常工作。

很长的帖子,很抱歉,但是请提出建议以优化方式! 谢谢!

编辑1: 所以我在Android 7.1(sdk 25)上无法正常工作时遇到了这个问题。我向甘汉寻求帮助,然后进行了更改。

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
}

现在终于可以了。 谢谢甘汉!

答案 2 :(得分:1)

 This approach will work on all api level device, Make sure to recreate activity on changing language programatically.

1。对BaseBaseContext使用Base 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 Application attachBaseContext and onConfigurationChanged to set the locale language

 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) {

/ ***如果设备语言被更改,也可以处理语言** /

        super.onConfigurationChanged(newConfig);

    }



3. Use Locale Helper  for handling language changes ,this approch work on all device 

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
    }
}

答案 3 :(得分:1)

我在使用 Android 5、6 和 7 时遇到了同样的问题。经过大量搜索,解决方案在这篇文章中:Change Locale not work after migrate to Androidx(请参阅 Ehsan Shadi 的评论,而不是“解决方案”)。

首先,您必须将库 appcompat 更新到 1.2.0(针对区域设置进行修复,请参见此处:https://developer.android.com/jetpack/androidx/releases/appcompat#1.2.0),并且您必须覆盖基础活动中的 applyOverrideConfiguration 函数。

我已经在所有 API(19 到 30 - Android 4.4 到 11)上测试了这个解决方案,它运行得很好:)