Android原生多语言切换方案,兼容Android10

前言

一个应用若需要国际化,至少需要支持中文和英语这两种语言,而同时随着谷歌的系统的更新,安卓系统可以设置当前语言的首选语言。因此,本文立足于此,多语言的切换方案为:App固定的文字内容,跟随系统,中文,英文,三种切换,选择后重启应用生效;

本文代码参考链接,感觉原作者~不过直接使用链接文章中的工具类可能会在系统兼容上会有一点点问题,我在项目实践过程改进了,分享出来

特别说明:工具类由Java编写,项目中的页面及相关的Application类使用了kotlin编写,请谅解

具体步骤

一、切换语言的代码逻辑

1、Application的onCreate中初始化,根据本地保存的多语言选项(用户所选)来确定app中显示哪种语言

2、在设置语言界面选择对应语言,然后把语言选项持久化后中,重启应用(返回第一个Activity)

其中获取系统首选语言,设置语言信息,注册Activity生命周期监听回调,修改应用内语言设置等操作可以放在一个工具类内进行使用;下面是多语言切换工具类的具体代码:其中SPUtils为项目中一个操作SharedPreferences的一个工具类

public class MultiLanguageUtils {

    /**
     * 修改应用内语言设置
     * @param language  语言
     * @param area      地区
     */
    public static void changeLanguage(Context context,String language, String area) {
        if (TextUtils.isEmpty(language) && TextUtils.isEmpty(area)) {
            //如果语言和地区都是空,那么跟随系统
            SPUtils.getInstance().put(Constants.SP_LANGUAGE,"");
            SPUtils.getInstance().put(Constants.SP_LANGUAGE,"");
        } else {
            //不为空,修改app语言,持久化语言选项信息
            Locale newLocale = new Locale(language, area);
            setAppLanguage(context,newLocale);
            saveLanguageSetting(context, newLocale);
        }
    }

    /**
     * 更新应用语言(核心)
     * @param context
     * @param locale
     */
    private static void setAppLanguage(Context context, Locale locale) {
        Resources resources = context.getResources();
        DisplayMetrics metrics = resources.getDisplayMetrics();
        Configuration configuration = resources.getConfiguration();
        //Android 7.0以上的方法
        if (Build.VERSION.SDK_INT >= 24) {
            configuration.setLocale(locale);
            configuration.setLocales(new LocaleList(locale));
            context.createConfigurationContext(configuration);
            //实测,updateConfiguration这个方法虽然很多博主说是版本不适用
            //但是我的生产环境androidX+Android Q环境下,必须加上这一句,才可以通过重启App来切换语言
            resources.updateConfiguration(configuration,metrics);
           
        } else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1) {
            //Android 4.1 以上方法
            configuration.setLocale(locale);
            resources.updateConfiguration(configuration,metrics);
        } else {
            configuration.locale = locale;
            resources.updateConfiguration(configuration,metrics);
        }
    }

    /**
     * 跟随系统语言
     */
    public static Context attachBaseContext(Context context) {
        String spLanguage = SPUtils.getInstance().getString(Constants.SP_LANGUAGE , "");
        String spCountry = SPUtils.getInstance().getString(Constants.SP_COUNTRY,"");
        if(!TextUtils.isEmpty(spLanguage)&&!TextUtils.isEmpty(spCountry)){
            Locale  locale = new Locale(spLanguage, spCountry);
            setAppLanguage(context, locale);
        }
        return context;
    }

    /**
     * 判断SharedPrefences中存储和app中的多语言信息是否相同
     */
    public static boolean isSameWithSetting(Context context) {
        Locale locale = getAppLocale(context);
        String language = locale.getLanguage();
        String country = locale.getCountry();
        String sp_language = SPUtils.getInstance().getString(Constants.SP_LANGUAGE , "");
        String sp_country = SPUtils.getInstance().getString(Constants.SP_COUNTRY,"");
        if (language.equals(sp_language) && country.equals(sp_country)) {
            return true;
        } else {
            return false;
        }
    }

    /**
     * 保存多语言信息到sp中
     */
    public static void saveLanguageSetting(Context context, Locale locale) {
        SPUtils.getInstance().put(Constants.SP_LANGUAGE , locale.getLanguage());
        SPUtils.getInstance().put(Constants.SP_COUNTRY , locale.getCountry());
    }

    /**
     * 获取应用语言
     */
    public static Locale getAppLocale(Context context){
        Locale local;
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
            local =context.getResources().getConfiguration().getLocales().get(0);
        } else {
            local =context.getResources().getConfiguration().locale;
        }
        return local;
    }

    /**
     * 获取系统语言
     */
    public static LocaleListCompat getSystemLanguage(){
        Configuration configuration = Resources.getSystem().getConfiguration();
        LocaleListCompat locales = ConfigurationCompat.getLocales(configuration);
        return locales;
    }

    //在Application实现类注册Activity生命周期监听回调,有些版本不加的话多语言切换不回来
    //registerActivityLifecycleCallbacks(callbacks);
    public static  Application.ActivityLifecycleCallbacks callbacks = new Application.ActivityLifecycleCallbacks() {
        @Override
        public void onActivityCreated(Activity activity, Bundle savedInstanceState) {
          String language = SPUtils.getInstance().getString(Constants.SP_LANGUAGE , "");
          String country  = SPUtils.getInstance().getString(Constants.SP_COUNTRY  ,"");
          if (!TextUtils.isEmpty(language) && !TextUtils.isEmpty(country)) {
                //强制修改应用语言
                if (!isSameWithSetting(activity)) {
                    Locale locale = new Locale(language, country);
                    setAppLanguage(activity,locale);
                }

            }
        }

        @Override
        public void onActivityStarted(Activity activity) {

        }

        @Override
        public void onActivityResumed(Activity activity) {

        }

        @Override
        public void onActivityPaused(Activity activity) {

        }

        @Override
        public void onActivityStopped(Activity activity) {

        }

        @Override
        public void onActivitySaveInstanceState(Activity activity, Bundle outState) {

        }

        @Override
        public void onActivityDestroyed(Activity activity) {

        }
    };


    /**
     * 设置语言信息
     *
     * 说明:
     * 该方法建议在attachBaseContext和onConfigurationChange中调用,attachBaseContext可以保证页面加载时修改语言信息,
     * 而onConfigurationChange则是为了对应横竖屏切换时系统更新Resource的问题
     *
     * @param context application context
     */
    public static void setConfiguration(Context context) {
        if (context == null) {
            return;
        }
        /*
         * 为防止传入非ApplicationContext,这里做一次强制转化,目的是避免onConfigurationChange可能导致的问题,
         * 因为onConfigurationChange被触发时系统会更新ApplicationContext中的Resource,如果页面包含Runtime资源
         * (运行时动态加载的资源)时,有可能语言显示不一致。
         */
        Context appContext = context.getApplicationContext();
        Locale preferredLocale = getSysPreferredLocale();
        Configuration configuration = appContext.getResources().getConfiguration();
        if (Build.VERSION.SDK_INT >= 17) {
            configuration.setLocale(preferredLocale);
        } else {
            configuration.locale = preferredLocale;
        }
        // 更新context中的语言设置
        Resources resources = appContext.getResources();
        DisplayMetrics dm = resources.getDisplayMetrics();
        resources.updateConfiguration(configuration, dm);
    }

    /**
     * 获取系统首选语言
     *
     * 注意:该方法获取的是用户实际设置的不经API调整的系统首选语言
     *
     * @return
     */
    public static   Locale getSysPreferredLocale() {
        Locale locale;
        //7.0以下直接获取系统默认语言
        if (Build.VERSION.SDK_INT < 24) {
            // 等同于context.getResources().getConfiguration().locale;
            locale = Locale.getDefault();
            // 7.0以上获取系统首选语言
        } else {
            /*
             * 以下两种方法等价,都是获取经API调整过的系统语言列表(可能与用户实际设置的不同)
             * 1.context.getResources().getConfiguration().getLocales()
             * 2.LocaleList.getAdjustedDefault()
             */
            // 获取用户实际设置的语言列表
            locale = LocaleList.getDefault().get(0);
        }
        return locale;
    }

}

下面是语言设置的页面中具体调用的方法(核心过程),有详细注释

ActivityUtils 是一个项目内关于Activity操作的工具类,此处代码我用来关闭当前所有Activity并显示首页Activity以达到重启的效果,您可以选择其他方式实现

class MultiLanguageFragment : BaseFragment() {
    //枚举类,判断用户具体的选项
     enum class MultiLanguage{
        FOLLOW_SYSTEM,
        SIMPLIFY_CHINESE,
        ENGLISH
    }
    private var flag = -1

    override fun initViews() {
       tbChangeLanguage.addRightTextButton(getString(R.string.save), Constants.BUTTON_SAVE_MULTI_LANGUAGE )
            .setOnClickListener {
                //记录选择了哪种语言,执行切换语言
               when(flag){
                  MultiLanguage.FOLLOW_SYSTEM.ordinal -> {
                     //获取手机系统的首要语言
                     //此处可以有逻辑,如系统第一语言APP不提供,则顺次判断系统语言是否符合APP提供语言
                     val locale: Locale = MultiLanguageUtils.getSystemLanguage()[0]
                     val language: String = locale.language
                     val country: String = locale.country
                     //将APP内语言切换成手机系统语言  
                     MultiLanguageUtils.changeLanguage(activity, language, country)
                     //清空SP数据 ,用于当系统切换语言时 应用可以同步保持切换
                     //例:系统转换成英文 则应用语言也会变成英文
                     MultiLanguageUtils.changeLanguage(activity, null, null)
                     //重启APP,到第一个Activity    
                     ActivityUtils.finishAllActivities()
                     ActivityUtils.startActivity(this.requireActivity()::class.java)
                    }
                   
                  //选择简体中文 
                  MultiLanguage.SIMPLIFY_CHINESE.ordinal -> {
                    MultiLanguageUtils.changeLanguage(this.requireActivity(), "zh", "ZH")
                    ActivityUtils.finishAllActivities()
                    ActivityUtils.startActivity(this.requireActivity()::class.java)
                   }
                  //选择English  
                  MultiLanguage.ENGLISH.ordinal -> {
                    MultiLanguageUtils.changeLanguage(_mActivity, "en", "US")
                    ActivityUtils.finishAllActivities()
                    ActivityUtils.startActivity(this.requireActivity()::class.java)
                  }
                   
                  else -> {
                        ToastUtils.showLong(getString(R.string.none_chosen_language))
                    }
                }
        }
    }
}

App中application的实现类,对每个Activity设置切换语言的回调,确保重启后可以重启应用

class App : Application() {

    override fun onCreate() {
        super.onCreate()
        //多语言设置
        registerActivityLifecycleCallbacks(MultiLanguageUtils.callbacks)
        context = applicationContext
    }

二、切换语言需要准备的内容

这语言内容其实就是指,建立提供的多语言切换准备的资源文件夹,最基础的就是定义多语言所需的字符串内容

values-zh、values-en、…………

下面是新建的方法:在res文件夹右键new Android Resource File中,在第一个竖框内选择Locale,然后点击 >> 按钮,在第二个框中点击 Locale(?) ,在第三个框中选择对应语言,在第四个框选择语言的地区

如下面的图所示

Android原生多语言切换方案,兼容Android10_第1张图片

Android原生多语言切换方案,兼容Android10_第2张图片

新建了 chinese和english的资源后,可以在res包内看到,

Android原生多语言切换方案,兼容Android10_第3张图片

一般来说,系统自带的values包内的strings中定义的字符串,需要同步地在其他所有的values包的strings中对相应id的字符串做出对应的语言解释,否则多语言切换后会有找不到对应语言解释的情况出现。如下图:

Android原生多语言切换方案,兼容Android10_第4张图片

三、额外的琐碎工作

例如按照设计图,某些带文字的控件指定大小的宽度和高度,经过语言切换后,可能无法容纳切换后的文字长度,如果通过插件对中文的语言直接翻译成其他语言,没有提前处理好的话,可能会出现换行、不显示等问题。

例如的话,如下图所示:

在简体中文状态:底部导航栏第一个item的文字为“会议管理”

Android原生多语言切换方案,兼容Android10_第5张图片

切换到English:底部导航栏第一个item“会议管理”在English定义定义为Meeting Management,可以看到原来简体中文的位置的Meeting Management已经不够位置显示,出现了省略号…(尬):

Android原生多语言切换方案,兼容Android10_第6张图片

然鹅设计图一般只有一套,如果要搞多语言,XX控件的宽度需不需要写固定或者说切换语言后内容是否影响页面,如果语言切换后的文字必定会超出范围,需要怎么解决,这些事情要多规划和沟通~

总结

总的来看,Android的多语言在App内手动切换,实现起来也不是很难嘛。

不过嘛,在具体项目中的具体实现会有具体的细节改变,本文也只是提供一个比较通用简单的方法。

谢谢观看的同学~

你可能感兴趣的:(Android,开源)