本文出自 “阿敏其人” 简书博客,转载或引用请注明出处。
前言:
我们知道,Android的国际化需要在values那里做折腾,多配置几个string文件,结合Resources和Configuration等。礼拜天闲来无事,折腾一个Demo出来。
先上图,再扯淡
从上图可以看出,当我们在启动页切换语言的时候,实际上是打开了一次启动页。这样是为了“让一切重新开始”。这个后面会说明缘由。
一、从values文件夹说起。
我们知道我们新建工程的时候会带有一个values文件夹,里面string.xml文件就是我们放硬编码索引的文件。
现在,假设我们在res目录下新建多三个文件夹,跟values同级。分别如下
values-zh
values-zh-rCN
values-zh-rTW
其中values保持不变,后缀的zh表示中文(en则表示英文),后缀的rCN、rTW其中‘r’是一个标记,表示后面跟着的CN、TW是国家或地区标志。(后面我们会附上一份各个国家和地区的语言信息)
所以以上三个资源文件夹表示所对应的语言环境分别为:
中文
中文-中国 (即中文简体)
中文-台湾 (即中文繁体)
一、1 这几个文件的作用以及采用策略
默认情况下,Android会根据系统的语言地区设置,自动选择对应的资源。
首先尝试语言地区全匹配,如果没有权匹配的资源包,则会尝试匹配语言,最后则会取默认的。
比如如果Android系统的语言地区是中文简体,则首先会尝试从/values-zh-rCN中获取资源,如果没有此文件夹或者文件夹中没有响应的资源,则会尝试/values-zh,都获取不到的情况下即从/values中获取。(/values是必须存在的,否则不能通过编译)
在上面的demo中,我们只分成了3种语言,分别是简体,繁体和英文。
又由于我们默认的(values)是简体中文,所以本文中之另外建立了values-zh-rTW和values-en两个文件夹,分别表示繁体中文和英文。
二、代码
怎么改变app采用的语言?
Resources resources = getContext().getResources();
DisplayMetrics dm = resources.getDisplayMetrics();
Configuration config = resources.getConfiguration();
// 应用用户选择语言
config.locale = Locale.ENGLISH;
resources.updateConfiguration(config, dm);
本文用了 Locale 中的预设值
Locale.ENGLISH
Locale.TRADITIONAL_CHINESE
Locale.SIMPLIFIED_CHINESE,
分别表示英语,繁体中文,简体中文。
注:跟随系统设置是 Locale.getDefault()
我们上面的gif中,切换语言之后回到了启动页,这样是为了让所有页面都切换到正确语言,比如现在有ABC三个页面,我们依次顺序打开,如果只在C执行改变语言的代码,是没有办法让整个app的语言都进行切换的,所以我们才要回到“最开始的地方”,保证所有页面的遇到都得到切换。至于这点微博也是如此操作,至于微信,他是回到主页,个人估计是在Splash的一个遥望星球的页面做了处理。
接下来看一下我们启动页MainActivity的代码
public class MainActivity extends Activity {
private TextView mTvChooseLan;
private TextView mTvSetting;
private TextView mTvOther;
private TextView mTvNesTask;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
mTvChooseLan = (TextView) findViewById(R.id.mTvChooseLan);
mTvSetting = (TextView) findViewById(R.id.mTvSetting);
mTvOther = (TextView) findViewById(R.id.mTvOther);
mTvNesTask = (TextView) findViewById(R.id.mTvNesTask);
mTvOther.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
startActivity(new Intent(MainActivity.this,OtherActivity.class));
}
});
mTvSetting.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
startActivity(new Intent(MainActivity.this,SettingActivity.class));
}
});
mTvNesTask.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
startActivity(new Intent(MainActivity.this,SingleInstanceActivity.class));
}
});
mTvChooseLan.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
initPopupWindow(mTvChooseLan);
}
});
}
// 选择语言的pop
PopupWindow popupWindow;
public void initPopupWindow(View view) {
if (popupWindow == null) {
View popupView = LayoutInflater.from(MainActivity.this).inflate(R.layout.item_swich_language, null);
popupWindow = new PopupWindow(popupView, ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT, true);
initClick(popupView);
}
popupWindow.setOutsideTouchable(true);
popupWindow.setBackgroundDrawable(new BitmapDrawable());
popupWindow.setFocusable(true);
popupWindow.setAnimationStyle(android.R.style.Animation_InputMethod);
popupWindow.showAtLocation(view, Gravity.CENTER, 0, 0);
}
private void initClick(View popupView) {
TextView mTvSimpleChinese = (TextView) popupView.findViewById(R.id.mTvSimpleChinese);
TextView mTvTwChinese = (TextView) popupView.findViewById(R.id.mTvTwChinese);
TextView mTvEnglish = (TextView) popupView.findViewById(R.id.mTvEnglish);
mTvSimpleChinese.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
switchLanguage("zh_simple");
popupWindow.dismiss();
}
});
mTvTwChinese.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
switchLanguage("zh_tw");
popupWindow.dismiss();
}
});
mTvEnglish.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
switchLanguage("en");
popupWindow.dismiss();
}
});
}
/**
* 切换语言,这里参数之所以传String不传Local是为了Sp方便存值
* @param language
*/
private void switchLanguage(String language) {
//设置应用语言类型
Resources resources = getResources();
Configuration config = resources.getConfiguration();
DisplayMetrics dm = resources.getDisplayMetrics();
if (language.equals("zh_simple")) {
config.locale = Locale.SIMPLIFIED_CHINESE;
}else if(language.equals("zh_tw")){
config.locale = Locale.TRADITIONAL_CHINESE;
} else if(language.equals("en")){
config.locale = Locale.ENGLISH;
}else{
config.locale = Locale.getDefault();
}
resources.updateConfiguration(config, dm);
//保存设置语言的类型
CacheUtils.setString(MainActivity.this, AppConstant.LANGUAGE_RUN, language);
//更新语言后,destroy当前页面,重新绘制
finish();
Intent it = new Intent(MainActivity.this, MainActivity.class);
//清空任务栈确保当前打开activit为前台任务栈栈顶
it.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK);
startActivity(it);
}
}
其中核心代码无非就是这段
private void switchLanguage(String language) {
//设置应用语言类型
Resources resources = getResources();
Configuration config = resources.getConfiguration();
DisplayMetrics dm = resources.getDisplayMetrics();
if (language.equals("zh_simple")) {
config.locale = Locale.SIMPLIFIED_CHINESE;
}else if(language.equals("zh_tw")){
config.locale = Locale.TRADITIONAL_CHINESE;
} else if(language.equals("en")){
config.locale = Locale.ENGLISH;
}else{
config.locale = Locale.getDefault();
}
resources.updateConfiguration(config, dm);
//保存设置语言的类型
CacheUtils.setString(MainActivity.this, AppConstant.LANGUAGE_RUN, language);
//更新语言后,destroy当前页面,重新绘制
finish();
Intent it = new Intent(MainActivity.this, MainActivity.class);
//清空任务栈确保当前打开activit为前台任务栈栈顶
it.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK);
startActivity(it);
}
点击Pop之后之后,根据传入值改变语言,并且我们将选择的语言存进了Sp,可以会根据需求在合适的地方取出并且设语言。最后重新打开启动页MainActivity,并且清空所有之前打开的页面。(加一层保证)
通常来说如果是在登录页做改变语言的话这样就可以了。
但是如果想在设置页面改变语言,我们就需要考虑多一个问题:
1、我们普通的Activity都是存在于同一个任务栈的,语言改变都是改变同一个任务栈Activity。
2、对于singleInstance这一启动模式自己有独立的任务栈。
假设我们现在A是启动页,B是singleInstance启动模式,C是设置页。这时候我们A打开B,B在再开C,这时候BA在任务栈S1,B为S1的栈顶,C在任务栈S2里面,这个程序有S1和S2两个任务栈,并且S2为前台任务栈。
TaskRecord{3fb53a50 #52 A=com.amqr.mutilanguage U=0 sz=2}
Run #5: ActivityRecord{151fbb29 u0 com.amqr.mutilanguage/.SettingActivity t52}
TaskRecord{116c20b0 #53 A=com.amqr.mutilanguage U=0 sz=1}
Run #4: ActivityRecord{108ffc05 u0 com.amqr.mutilanguage/.SingleInstanceActivity t53}
TaskRecord{3fb53a50 #52 A=com.amqr.mutilanguage U=0 sz=2}
Run #3: ActivityRecord{375ad5f2 u0 com.amqr.mutilanguage/.MainActivity t52}
这时候在C页面改变系统比语言,把中文切换英文,系统记住这个时候语言要改变,按照我们前面做的自然是S1成为栈顶,至此S1的所有Activity都会变成我们指定的语言。这点没问题,但是S2可不这么想,他是一个独立的任务栈啊。也就是说,你A开启B的时候,人家B记住的状态时中文,当你在C切为A指定切换成英文的时候,没错你的任务栈S1可以可以全部变成英文,但是人家S2记住的状态是S2你改变不了。这时会你从A打开B,会发B还是中文状态。
所以当我们在标记为singleInstance的之后打开的页面改变语言的时候,我们可以可以直接杀掉当前 App 的进程,保证是“整个”程序重启。这样那些“S2”也会被干掉,整个程序的语言就一致了。
杀死当前程序进程,可以采用两行代码
android.os.Process.killProcess(android.os.Process.myPid());
System.exit(0);
根据上面的分析的,我们来看看我们的SettingActivity的代码:
public class SettingActivity extends Activity{
private TextView mTvOtherPageSwitch;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_setting);
mTvOtherPageSwitch = (TextView) findViewById(R.id.mTvOtherPageSwitch);
mTvOtherPageSwitch.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
initPopupWindow(mTvOtherPageSwitch);
}
});
}
// 选择语言的pop
PopupWindow popupWindow;
public void initPopupWindow(View view) {
if (popupWindow == null) {
View popupView = LayoutInflater.from(SettingActivity.this).inflate(R.layout.item_swich_language, null);
popupWindow = new PopupWindow(popupView, ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT, true);
initClick(popupView);
}
popupWindow.setOutsideTouchable(true);
popupWindow.setBackgroundDrawable(new BitmapDrawable());
popupWindow.setFocusable(true);
popupWindow.setAnimationStyle(android.R.style.Animation_InputMethod);
popupWindow.showAtLocation(view, Gravity.CENTER, 0, 0);
}
private void initClick(View popupView) {
TextView mTvSimpleChinese = (TextView) popupView.findViewById(R.id.mTvSimpleChinese);
TextView mTvTwChinese = (TextView) popupView.findViewById(R.id.mTvTwChinese);
TextView mTvEnglish = (TextView) popupView.findViewById(R.id.mTvEnglish);
mTvSimpleChinese.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
switchLanguage("zh_simple");
popupWindow.dismiss();
}
});
mTvTwChinese.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
switchLanguage("zh_tw");
popupWindow.dismiss();
}
});
mTvEnglish.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
switchLanguage("en");
popupWindow.dismiss();
}
});
}
private void switchLanguage(String language) {
//设置应用语言类型
Resources resources = getResources();
Configuration config = resources.getConfiguration();
DisplayMetrics dm = resources.getDisplayMetrics();
if (language.equals("zh_simple")) {
config.locale = Locale.SIMPLIFIED_CHINESE;
}else if(language.equals("zh_tw")){
config.locale = Locale.TRADITIONAL_CHINESE;
} else if(language.equals("en")){
config.locale = Locale.ENGLISH;
}else{
config.locale = Locale.getDefault();
}
resources.updateConfiguration(config, dm);
//保存设置语言的类型
CacheUtils.setString(SettingActivity.this, AppConstant.LANGUAGE_RUN, language);
//更新语言后,destroy当前页面,重新绘制
finish();
Intent it = new Intent(SettingActivity.this, MainActivity.class);
startActivity(it);
android.os.Process.killProcess(android.os.Process.myPid());
System.exit(0);
}
}
主要的区别就是下面两行代码
//更新语言后,destroy当前页面,重新绘制
finish();
Intent it = new Intent(SettingActivity.this, MainActivity.class);
startActivity(it);
android.os.Process.killProcess(android.os.Process.myPid());
System.exit(0);
这样关于启动页和app设置页面改变语言的问题应该算大致解决了。
但是app替换安装或者改变手机系统语言的还是存在一些小问题的,语言不跟着换。
三、其他问题的解决
由于前面我们改变的语言的时候已经选择结果缓存到了sp。现在这个sp终于有用了。
三、1、关于替换安装的app的问题解决
弄一个Application,
public class MyApplication extends Application{
@Override
public void onCreate() {
super.onCreate();
String lan = CacheUtils.getString(getApplicationContext(), AppConstant.LANGUAGE_RUN, "def");
System.out.println("======之前选择的语言: "+lan);
getLanguage(lan);
}
private void getLanguage(String lan){
Resources resources = getResources();
Configuration config = resources.getConfiguration();
DisplayMetrics dm = resources.getDisplayMetrics();
if (lan.equals("zh_simple")) {
config.locale = Locale.SIMPLIFIED_CHINESE;
}else if(lan.equals("zh_tw")){
config.locale = Locale.TRADITIONAL_CHINESE;
} else if(lan.equals("en")){
config.locale = Locale.ENGLISH;
}else{
config.locale = Locale.getDefault();
}
resources.updateConfiguration(config, dm);
}
}
三、2、关于用户切换系统语言
此法偏流氓
public class BaseActivity extends Activity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
System.out.println("=============== BaseActivity onConfigurationChanged 执行");
String lan = CacheUtils.getString(getApplicationContext(), AppConstant.LANGUAGE_RUN, "def");
System.out.println("======之前选择的语言: " + lan);
getLanguage(lan);
}
private void getLanguage(String lan) {
Resources resources = getResources();
Configuration config = resources.getConfiguration();
DisplayMetrics dm = resources.getDisplayMetrics();
if (lan.equals("zh_simple")) {
config.locale = Locale.SIMPLIFIED_CHINESE;
} else if (lan.equals("zh_tw")) {
config.locale = Locale.TRADITIONAL_CHINESE;
} else if (lan.equals("en")) {
config.locale = Locale.ENGLISH;
} else {
config.locale = Locale.getDefault();
}
resources.updateConfiguration(config, dm);
}
}
四、values对应的国家和地区
输入简体字,点下面繁体字按钮进行在线转换在res目录下建立不同名称的values文件来调用不同的语言包
Values文件汇总如下:
中文(中国):values-zh-rCN中文(台湾):values-zh-rTW
中文(香港):values-zh-rHK
英语(美国):values-en-rUS
英语(英国):values-en-rGB
英文(澳大利亚):values-en-rAU
英文(加拿大):values-en-rCA
英文(爱尔兰):values-en-rIE
英文(印度):values-en-rIN
英文(新西兰):values-en-rNZ
英文(新加坡):values-en-rSG
英文(南非):values-en-rZA
阿拉伯文(埃及):values-ar-rEG
阿拉伯文(以色列):values-ar-rIL
保加利亚文: values-bg-rBG
加泰罗尼亚文:values-ca-rES
捷克文:values-cs-rCZ
丹麦文:values-da-rDK
德文(奥地利):values-de-rAT
德文(瑞士):values-de-rCH
德文(德国):values-de-rDE
德文(列支敦士登):values-de-rLI
希腊文:values-el-rGR
西班牙文(西班牙):values-es-rES
西班牙文(美国):values-es-rUS
芬兰文(芬兰):values-fi-rFI
法文(比利时):values-fr-rBE
法文(加拿大):values-fr-rCA
法文(瑞士):values-fr-rCH
法文(法国):values-fr-rFR
希伯来文:values-iw-rIL
印地文:values-hi-rIN
克罗里亚文:values-hr-rHR
匈牙利文:values-hu-rHU
印度尼西亚文:values-in-rID
意大利文(瑞士):values-it-rCH
意大利文(意大利):values-it-rIT
日文:values-ja-rJP
韩文:values-ko-rKR
立陶宛文:valueslt-rLT
拉脱维亚文:values-lv-rLV
挪威博克马尔文:values-nb-rNO
荷兰文(比利时):values-nl-BE
荷兰文(荷兰):values-nl-rNL
波兰文:values-pl-rPL
葡萄牙文(巴西):values-pt-rBR
葡萄牙文(葡萄牙):values-pt-rPT
罗马尼亚文:values-ro-rRO
俄文:values-ru-rRU
斯洛伐克文:values-sk-rSK
斯洛文尼亚文:values-sl-rSI
塞尔维亚文:values-sr-rRS
瑞典文:values-sv-rSE
泰文:values-th-rTH
塔加洛语:values-tl-rPH
土耳其文:values–r-rTR
乌克兰文:values-uk-rUA
越南文:values-vi-rVN
五、参考学习
六、下载链接
本篇完。