前言
一個應用若需要國際化,至少需要支持中文和英語這兩種語言,而同時隨着谷歌的系統的更新,安卓系統可以設置當前語言的首選語言。因此,本文立足於此,多語言的切換方案為: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(?) ,在第三個框中選擇對應語言,在第四個框選擇語言的地區
如下面的圖所示👇
新建了 chinese和english的資源后,可以在res包內看到,👇
一般來說,系統自帶的values包內的strings中定義的字符串,需要同步地在其他所有的values包的strings中對相應id的字符串做出對應的語言解釋,否則多語言切換后會有找不到對應語言解釋的情況出現。如下圖:
三、額外的瑣碎工作
例如按照設計圖,某些帶文字的控件指定大小的寬度和高度,經過語言切換后,可能無法容納切換后的文字長度,如果通過插件對中文的語言直接翻譯成其他語言,沒有提前處理好的話,可能會出現換行、不顯示等問題。
例如的話,如下圖所示:
在簡體中文狀態:底部導航欄第一個item的文字為“會議管理”
切換到English:底部導航欄第一個item“會議管理”在English定義定義為Meeting Management
,可以看到原來簡體中文的位置的Meeting Management
已經不夠位置顯示,出現了省略號...(尬):
然鵝設計圖一般只有一套,如果要搞多語言,XX控件的寬度需不需要寫固定或者說切換語言后內容是否影響頁面,如果語言切換后的文字必定會超出范圍,需要怎么解決,這些事情要多規划和溝通~
總結
總的來看,Android的多語言在App內手動切換,實現起來也不是很難嘛。
不過嘛,在具體項目中的具體實現會有具體的細節改變,本文也只是提供一個比較通用簡單的方法。
謝謝觀看的同學~