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(?) ,在第三個框中選擇對應語言,在第四個框選擇語言的地區

如下面的圖所示👇

english

chinese

新建了 chinese和english的資源後,可以在res包內看到,👇

最基本所需的資源

一般來說,系統自帶的values包內的strings中定義的字符串,需要同步地在其他所有的values包的strings中對相應id的字符串做出對應的語言解釋,否則多語言切換後會有找不到對應語言解釋的情況出現。如下圖:

三、額外的瑣碎工作

例如按照設計圖,某些帶文字的控件指定大小的寬度和高度,經過語言切換後,可能無法容納切換後的文字長度,如果通過插件對中文的語言直接翻譯成其他語言,沒有提前處理好的話,可能會出現換行、不顯示等問題。

例如的話,如下圖所示:

在簡體中文狀態:底部導航欄第一個item的文字爲“會議管理”

切換到English:底部導航欄第一個item“會議管理”在English定義定義爲Meeting Management,可以看到原來簡體中文的位置的Meeting Management已經不夠位置顯示,出現了省略號…(尬):

然鵝設計圖一般只有一套,如果要搞多語言,XX控件的寬度需不需要寫固定或者說切換語言後內容是否影響頁面,如果語言切換後的文字必定會超出範圍,需要怎麼解決,這些事情要多規劃和溝通~

總結

總的來看,Android的多語言在App內手動切換,實現起來也不是很難嘛。

不過嘛,在具體項目中的具體實現會有具體的細節改變,本文也只是提供一個比較通用簡單的方法。

謝謝觀看的同學~

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章