1.需求
三方APP实现点击切换语言功能(类似于系统Settings中点击语言自动切换)
2.实现
2.1 跳转到系统Settings的语言选择界面,实现功能
跳转代码:
Intent intent = new Intent(Settings.ACTION_LOCALE_SETTINGS);
startActivity(intent);
优点:
最简单的方式,使用系统的功能,最方便,必然修改成功
缺点:
用户体验差,跳转到Settings后跟自己app界面风格完全不搭
2.2 App中实现Settings的功能
2.2.1 获取系统的语言设置列表数据
先上代码再解释
import java.text.Collator;
import java.util.Locale;
public class LanguageInfoEntity implements Comparable<LanguageInfoEntity>{
static final Collator sCollator = Collator.getInstance();
public String label;
public Locale locale;
public LanguageInfoEntity(String label, Locale locale) {
this.label = label;
this.locale = locale;
}
public String getLabel() {
return label;
}
public Locale getLocale() {
return locale;
}
@Override
public String toString() {
return this.label;
}
@Override
public int compareTo(LanguageInfoEntity another) {
return sCollator.compare(this.label, another.label);
}
}
import android.content.res.Resources;
import android.util.Log;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Locale;
public class LanguageControlImpl implements LanguageControl {
private static final String TAG = LanguageControlImpl.class.getName();
private static LanguageControlImpl mLanguageControlImpl;
private LanguageControlImpl() {
}
public static synchronized LanguageControlImpl getInstance() {
if (mLanguageControlImpl == null) {
mLanguageControlImpl = new LanguageControlImpl();
}
return mLanguageControlImpl;
}
@Override
public List<LanguageInfoEntity> getLanguageList() {
ArrayList<String> localeList = new ArrayList<String>(Arrays.asList(
Resources.getSystem().getAssets().getLocales()));
String[] locales = new String[localeList.size()];
locales = localeList.toArray(locales);
Arrays.sort(locales);
final int origSize = locales.length;
Log.i(TAG, "origSize : " + origSize);
final String[] specialLocaleCodes = {"zh_CN", "zh_TW"};
final String[] specialLocaleNames = {"中文(简体)", "中文(繁體)"};
Arrays.sort(locales);
final LanguageInfoEntity[] preprocess = new LanguageInfoEntity[origSize];
int finalSize = 0;
for (int i = 0; i < origSize; i++) {
final String s = locales[i];
final int len = s.length();
if (len == 5) {
String language = s.substring(0, 2);
String country = s.substring(3, 5);
final Locale l = new Locale(language, country);
if (finalSize == 0) {
Log.v(TAG, "adding initial " + toTitleCase(l.getDisplayLanguage(l)));
preprocess[finalSize++] =
new LanguageInfoEntity(toTitleCase(l.getDisplayLanguage(l)), l);
} else {
// check previous entry:
// same lang and a country -> upgrade to full name and
// insert ours with full name
// diff lang -> insert ours with lang-only name
if (preprocess[finalSize - 1].locale.getLanguage().equals(
language) &&
!preprocess[finalSize - 1].locale.getLanguage().equals("zz")) {
Log.v(TAG, "backing up and fixing " +
preprocess[finalSize - 1].label + " to " +
getDisplayName(preprocess[finalSize - 1].locale,
specialLocaleCodes, specialLocaleNames));
preprocess[finalSize - 1].label = toTitleCase(
getDisplayName(preprocess[finalSize - 1].locale,
specialLocaleCodes, specialLocaleNames));
Log.v(TAG, " and adding " + toTitleCase(
getDisplayName(l, specialLocaleCodes, specialLocaleNames)));
preprocess[finalSize++] =
new LanguageInfoEntity(toTitleCase(
getDisplayName(
l, specialLocaleCodes, specialLocaleNames)), l);
} else {
String displayName;
if (s.equals("zz_ZZ")) {
displayName = "[Developer] Accented English";
} else if (s.equals("zz_ZY")) {
displayName = "[Developer] Fake Bi-Directional";
} else {
displayName = toTitleCase(l.getDisplayLanguage(l));
}
Log.v(TAG, "adding " + displayName);
preprocess[finalSize++] = new LanguageInfoEntity(displayName, l);
}
}
}
}
final LanguageInfoEntity[] localeInfos = new LanguageInfoEntity[finalSize];
for (int i = 0; i < finalSize; i++) {
localeInfos[i] = preprocess[i];
}
List<LanguageInfoEntity> resultList = new ArrayList<LanguageInfoEntity>();
//如果无法获取列表,那就是这里的有问题,在排序的时候异常,注释掉也可以
Arrays.sort(localeInfos);
for (LanguageInfoEntity localeInfo : localeInfos) {
if (!localeInfo.getLabel().isEmpty()) {
resultList.add(localeInfo);
}
}
return resultList;
}
private static String toTitleCase(String s) {
if (s.length() == 0) {
return s;
}
return Character.toUpperCase(s.charAt(0)) + s.substring(1);
}
private static String getDisplayName(
Locale l, String[] specialLocaleCodes, String[] specialLocaleNames) {
String code = l.toString();
for (int i = 0; i < specialLocaleCodes.length; i++) {
if (specialLocaleCodes[i].equals(code)) {
return specialLocaleNames[i];
}
}
return l.getDisplayName(l);
}
}
通过getLanguageList方法我们获取到了一个list,这个list里存放的就是多国语言对应信息,后面会用到。这里需要注意的是又不断的Arrays.sort(…)在排序,这里的排序结果是根据语言来的,比如说在英文状态下就是a开头的排在前面,但是在中文状态下,就是中文(简体)在队列的最前面。
2.2.2 展示和点击相应
有了列表,展示无非就是RecycleView显示,这里不做赘述,下面讲一下点击了某个语言,响应部分代码
public void onItemClick(LocaleInfoBean localeInfoBean) {
try {
Class classActivityManagerNative = Class.forName("android.app.ActivityManagerNative");
Method getDefault = classActivityManagerNative.getDeclaredMethod("getDefault");
Object objIActivityManager = getDefault.invoke(classActivityManagerNative);
Class classIActivityManager = Class.forName("android.app.IActivityManager");
Method getConfiguration = classIActivityManager.getDeclaredMethod("getConfiguration");
Configuration config = (Configuration) getConfiguration.invoke(objIActivityManager);
config.setLocale(localeInfoBean.getLocale());
//config.userSetLocale = true;
Class clzConfig = Class
.forName("android.content.res.Configuration");
java.lang.reflect.Field userSetLocale = clzConfig
.getField("userSetLocale");
userSetLocale.set(config, true);
Class[] clzParams = {Configuration.class};
Method updateConfiguration = classIActivityManager.getDeclaredMethod("updateConfiguration", clzParams);
updateConfiguration.invoke(objIActivityManager, config);
BackupManager.dataChanged("com.android.providers.settings");
} catch (Exception e) {
Log.i(TAG, "changeSystemLanguage: " + e.getLocalizedMessage());
e.printStackTrace();
}
}
这里要说明下,这个修改方式是针对6.0以前版本的,6.0以后的版本,更新语言接口有修改,必须要注意。
想要修改语言不仅要有代码,而且还要加上对应的权限,权限代码如下
coreApp="true" android:sharedUserId="android.uid.system"
android:configChanges="keyboardHidden|orientation|screenSize"
<uses-permission android:name="android.permission.CHANGE_CONFIGURATION" />
如果没有系统签名,应该是无法修改语言的,直接报错
没必要在configchanges里加入“locale|layoutDirection”
特别注意:
现在特别流行的Android MVVM框架,如果你使用了databinding那套,那么可能会发生一种情况,一个activity中既有一部分固定view,又有一个fragment。fragment展示语言列表并响应语言切换,但是fragment显示了新切换的语言,但是activity中固定的view不会跟着刷新,而activity的生命周期已经重新执行过一遍了,这个强制finish也不会刷新activity中固定的view 显示的子串的。
系统语言已经成功切换了,但是view不刷新,这个不是语言设置问题,如果你没有采用databinding那套,activity中固定view是可以正常刷新显示对应语言字串的,应该是databinding持久化了数据,导致没有刷新,框架问题,无解。
那么我们可以换一个规避方案,在View的Text部分,也采用对应的观察者方式即:
android:text="@{context.getResources().getString(viewModel.test)}"
Activity刷新生命周期的时候,在ViewModle里直接set一个String对应的resid即可,引发数据的变化,重新引用就可以了。
public final ObservableInt test = new ObservableInt(R.string.xxx);
test.set(R.string.xxx);