流行好用的软件应该能够适用于不同地区的市场。下面记录一些在项目中国际化的应用,有关图片和文本资源的自适应。
Android采用XML资源文件来管理所有字符串消息,如果系统设置的Custom Locale,没有对应的本地化资源文件,那么程序就会取默认的res\values\strings.xml。在此我们默认的strings为英文,如下:
1
2
3
4
5
6
7
|
<?
xml
version
=
"1.0"
encoding
=
"utf-8"
?>
<
resources
>
<
string
name
=
"app_name"
>L10NTest</
string
>
<
string
name
=
"action_settings"
>Settings</
string
>
<
string
name
=
"hello_world"
>Hello world!</
string
>
<
string
name
=
"I_love_you"
>I love you.</
string
>
</
resources
>
|
我们添加有关中文的字符串资源,这需要为values目录添加中文的语言国家版本。values文件夹的命名方式为:values-语言代码-r国家代码。
如果想要支持中文的话,则需要在res目录下添加values-zh-rCN。该文件夹中的strings如下:
1
2
3
4
5
6
7
|
<?
xml
version
=
"1.0"
encoding
=
"utf-8"
?>
<
resources
>
<
string
name
=
"app_name"
>国际化测试</
string
>
<
string
name
=
"action_settings"
>设置</
string
>
<
string
name
=
"hello_world"
>世界,你好!</
string
>
<
string
name
=
"I_love_you"
>我爱你.</
string
>
</
resources
>
|
如果希望应用的图片也能随语言环境改变,则需要为drawable目录添加其他的语言国家版本。drawable文件夹的命名方式为:drawable-语言代码-r国家代码。同样支持中文的话需要在res目录下添加drawable-zh-rCN。
如果还需要为drawable目录按照分辨率提供文件夹,可以在其后追加分辨率后缀,如:drawable-zh-rCN-mdpi、drawable-en-rUS-xhdpi等等。
主Activity中没有写任何东西,下面是它的XML布局:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
|
<
LinearLayout
xmlns:android
=
"http://schemas.android.com/apk/res/android"
xmlns:tools
=
"http://schemas.android.com/tools"
android:layout_width
=
"match_parent"
android:layout_height
=
"match_parent"
android:gravity
=
"center"
android:orientation
=
"vertical"
>
<
ImageView
android:layout_width
=
"wrap_content"
android:layout_height
=
"wrap_content"
android:src
=
"@drawable/country"
tools:ignore
=
"ContentDescription"
/>
<!-- tools:ignore="ContentDescription",屏蔽掉对View设置备注说明的提示 -->
<
TextView
android:layout_width
=
"wrap_content"
android:layout_height
=
"wrap_content"
android:text
=
"@string/hello_world"
android:textSize
=
"20sp"
/>
<
TextView
android:layout_width
=
"wrap_content"
android:layout_height
=
"wrap_content"
android:text
=
"@string/I_love_you"
android:textSize
=
"20sp"
/>
</
LinearLayout
>
|
在android ADT 16.0以后的控件中,没有文本描述的view如果不加android:contentDescription="@string/……"的话就会有黄 色的下划线,提示“[Accessibility] Missing contentDescription attribute on image”。加“ tools:ignore="ContentDescription"“便是屏蔽掉该提示。这句代码不加也没什么影响,在界面上不会有其他效果,当然也可以写一些字符串数据做提示,方便他人阅读。或者直接写成:
1
2
3
4
5
|
<
ImageView
android:layout_width
=
"wrap_content"
android:layout_height
=
"wrap_content"
android:contentDescription
=
"@null"
android:src
=
"@drawable/country"
/>
|
也可。
以下是应用的res结构:
我增加了”drawable-en-rUS“、”drawable-zh-rCN“和”values-zh-rCN“三个文件夹,其中前两个文件夹中放了名称都是”country“的图片,只是前者为美国国旗图片,后者为中国国旗图片。第三个文件夹中的strings文件上面已给出。
运行程序,如果设置系统的语言和输入为中文,效果图如下:
如果设置为英文,效果图如下:
我并没有增加有关英文的本地化资源文件,所以程序就会取res\values\下的strings.xml,当然也可以增加对应的文件夹为:values-en-rUS,其中放置相关文本资源。
如果是其他国家的语言环境,那文件夹该如何命名呢,以字符串资源为例,如下:
中文(中国):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
事实上可以通过程序获取JAVA所有支持的语言和国家的,程序代码如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
|
import
java.util.Locale;
public
class
Test {
public
static
void
main(String[] args) {
//调用Locale类的getAvailableLocales方法获取,该方法返回一个数组,其中包含JAVA支持的语言和国家
Locale[] localeList = Locale.getAvailableLocales();
//依次获取所支持的国家和语言
for
(
int
i =
0
; i < localeList.length; i++) {
System.out.println(localeList[i].getDisplayCountry() +
"="
+ localeList[i].getCountry() +
""
+ localeList[i].getDisplayLanguage() +
"="
+ localeList[i].getLanguage());
}
}
}
|
输出结果如下(有些国家可能会使用多种语言):
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
|
马来西亚=MY马来文=ms
卡塔尔=QA阿拉伯文=ar
冰岛=IS冰岛文=is
芬兰=FI芬兰文=fi
=波兰文=pl
马耳他=MT英文=en
瑞士=CH意大利文=it
比利时=BE荷兰文=nl
沙特阿拉伯=SA阿拉伯文=ar
伊拉克=IQ阿拉伯文=ar
波多黎哥=PR西班牙文=es
智利=CL西班牙文=es
=芬兰文=fi
奥地利=AT德文=de
=丹麦文=da
英国=GB英文=en
巴拿马=PA西班牙文=es
=塞尔维亚文=sr
也门=YE阿拉伯文=ar
马其顿王国=MK马其顿文=mk
=马其顿文=mk
加拿大=CA英文=en
越南=VN越南文=vi
荷兰=NL荷兰文=nl
美国=US西班牙文=es
中国=CN中文=zh
洪都拉斯=HN西班牙文=es
美国=US英文=en
=法文=fr
=泰文=th
=阿拉伯文=ar
摩洛哥=MA阿拉伯文=ar
=拉托维亚文(列托)=lv
=德文=de
印度尼西亚=ID印度尼西亚文=in
=克罗地亚文=hr
南非=ZA英文=en
韩国=KR朝鲜文=ko
突尼斯=TN阿拉伯文=ar
=印度尼西亚文=in
=日文=ja
塞尔维亚=RS塞尔维亚文=sr
白俄罗斯=BY白俄罗斯文=be
台湾地区=TW中文=zh
苏丹=SD阿拉伯文=ar
=葡萄牙文=pt
=冰岛文=is
日本=JP日文=ja
玻利维亚=BO西班牙文=es
阿尔及利亚=DZ阿拉伯文=ar
=马来文=ms
阿根廷=AR西班牙文=es
阿拉伯联合酋长国=AE阿拉伯文=ar
加拿大=CA法文=fr
=斯洛文尼亚文=sl
=西班牙文=es
立陶宛=LT立陶宛文=lt
黑山=ME塞尔维亚文=sr
叙利亚=SY阿拉伯文=ar
俄罗斯=RU俄文=ru
比利时=BE法文=fr
西班牙=ES西班牙文=es
=保加利亚文=bg
以色列=IL希伯来文=iw
=瑞典文=sv
=英文=en
=希伯来文=iw
丹麦=DK丹麦文=da
哥斯达黎加=CR西班牙文=es
香港=HK中文=zh
=中文=zh
西班牙=ES加泰罗尼亚文=ca
泰国=TH泰文=th
乌克兰=UA乌克兰文=uk
多米尼加共和国=DO西班牙文=es
委内瑞拉=VE西班牙文=es
波兰=PL波兰文=pl
利比亚=LY阿拉伯文=ar
约旦=JO阿拉伯文=ar
=意大利文=it
=乌克兰文=uk
匈牙利=HU匈牙利文=hu
=爱尔兰文=ga
危地马拉=GT西班牙文=es
巴拉圭=PY西班牙文=es
保加利亚=BG保加利亚文=bg
克罗地亚=HR克罗地亚文=hr
波斯尼亚和黑山共和国=BA塞尔维亚文=sr
罗马尼亚=RO罗马尼亚文=ro
卢森堡=LU法文=fr
=挪威文=no
=立陶宛文=lt
新加坡=SG英文=en
厄瓜多尔=EC西班牙文=es
波斯尼亚和黑山共和国=BA塞尔维亚文=sr
尼加拉瓜=NI西班牙文=es
=斯洛伐克文=sk
=俄文=ru
=马耳他文=mt
萨尔瓦多=SV西班牙文=es
=荷兰文=nl
印度=IN印地文=hi
=爱沙尼亚文=et
希腊=GR希腊文=el
斯洛文尼亚=SI斯洛文尼亚文=sl
意大利=IT意大利文=it
日本=JP日文=ja
卢森堡=LU德文=de
瑞士=CH法文=fr
马耳他=MT马耳他文=mt
巴林=BH阿拉伯文=ar
=阿尔巴尼亚文=sq
=越南文=vi
黑山=ME塞尔维亚文=sr
巴西=BR葡萄牙文=pt
挪威=NO挪威文=no
=希腊文=el
瑞士=CH德文=de
新加坡=SG中文=zh
科威特=KW阿拉伯文=ar
埃及=EG阿拉伯文=ar
爱尔兰=IE爱尔兰文=ga
秘鲁=PE西班牙文=es
捷克共和国=CZ捷克文=cs
土耳其=TR土耳其文=tr
=捷克文=cs
乌拉圭=UY西班牙文=es
爱尔兰=IE英文=en
印度=IN英文=en
阿曼=OM阿拉伯文=ar
塞尔维亚及黑山=CS塞尔维亚文=sr
=加泰罗尼亚文=ca
=白俄罗斯文=be
=塞尔维亚文=sr
=朝鲜文=ko
阿尔巴尼亚=AL阿尔巴尼亚文=sq
葡萄牙=PT葡萄牙文=pt
拉脱维亚=LV拉托维亚文(列托)=lv
塞尔维亚=RS塞尔维亚文=sr
斯洛伐克=SK斯洛伐克文=sk
墨西哥=MX西班牙文=es
澳大利亚=AU英文=en
挪威=NO挪威文=no
新西兰=NZ英文=en
瑞典=SE瑞典文=sv
=罗马尼亚文=ro
黎巴嫩=LB阿拉伯文=ar
德国=DE德文=de
泰国=TH泰文=th
=土耳其文=tr
哥伦比亚=CO西班牙文=es
菲律宾=PH英文=en
爱沙尼亚=EE爱沙尼亚文=et
塞浦路斯=CY希腊文=el
=匈牙利文=hu
法国=FR法文=fr
|
在做项目时,我遇到这样一种情况,字符串资源需要JAVA代码来控制,而不是在XML文件中获取,下面提供该方法:
创建ConstantBase类来定义你需要的字符串,如下:
1
2
3
4
5
6
7
|
package
com.example.l10ntest;
public
class
ConstantBase {
public
String PUT_NAME;
public
String SAY_LOVE;
public
String NEXT;
public
String Second_Page;
}
|
中英文的字符串类分别命名如下,中文:
1
2
3
4
5
6
7
8
9
10
|
package
com.example.l10ntest;
public
class
ConstantCH
extends
ConstantBase {
public
ConstantCH() {
//此处定义需要的中文字符串
PUT_NAME =
"请输入你的名字。"
;
SAY_LOVE =
"我爱你"
;
NEXT =
"下一页"
;
Second_Page =
"第二页"
;
}
}
|
英文:
1
2
3
4
5
6
7
8
9
10
|
package
com.example.l10ntest;
public
class
ConstantEN
extends
ConstantBase {
public
ConstantEN() {
// 此处定义需要的英文字符串
PUT_NAME =
"Please put your name."
;
SAY_LOVE =
"I love you"
;
NEXT =
"next"
;
Second_Page =
"The second page"
;
}
}
|
我在新创建的Constant类中写了本地语言的判断方法,该类如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
|
package
com.example.l10ntest;
import
java.util.Locale;
public
class
Constant {
public
static
ConstantBase string = produceConstant();
public
static
ConstantBase produceConstant() {
if
(isLocalLanguageZH()) {
string =
new
ConstantCH();
}
else
{
string =
new
ConstantEN();
}
return
string;
}
//判断本地语言是否是中文
public
static
boolean
isLocalLanguageZH() {
if
(Locale.getDefault().getLanguage().contains(
"zh"
)
&& Locale.getDefault().getCountry().contains(
"CN"
)) {
return
true
;
}
return
false
;
}
}
|
主类的XML文件做一下修改(暂时将ImageView屏蔽掉,后面增加了一个Button),如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
|
<
LinearLayout
xmlns:android
=
"http://schemas.android.com/apk/res/android"
xmlns:tools
=
"http://schemas.android.com/tools"
android:layout_width
=
"match_parent"
android:layout_height
=
"match_parent"
android:gravity
=
"center"
android:orientation
=
"vertical"
>
<!-- <ImageView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:contentDescription="@null"
android:src="@drawable/country" /> -->
<!-- tools:ignore="ContentDescription",屏蔽掉对View设置备注说明的提示 -->
<
TextView
android:id
=
"@+id/putName"
android:layout_width
=
"wrap_content"
android:layout_height
=
"wrap_content"
android:text
=
"@null"
android:textSize
=
"20sp"
/>
<
TextView
android:id
=
"@+id/iloveyou"
android:layout_width
=
"wrap_content"
android:layout_height
=
"wrap_content"
android:text
=
"@null"
android:textSize
=
"20sp"
/>
<
Button
android:id
=
"@+id/next"
android:layout_width
=
"wrap_content"
android:layout_height
=
"wrap_content"
/>
</
LinearLayout
>
|
在主类中,写如下代码:
1
2
3
4
|
((TextView) findViewById(R.id.putName))
.setText(Constant.string.PUT_NAME);
((TextView) findViewById(R.id.iloveyou))
.setText(Constant.string.SAY_LOVE);
|
这样便可以通过JAVA代码来控制字符串的获取了。但是发现一个问题,就是当程序切换到后台时,你将手机终端的语言修改,比如当前中文修改为英文,再从后台将该程序拉起时,显示的文本信息并不会随着变换,如果以前是中文,修改系统语言后还是中文,国际化似乎不生效。最后发现只有将该程序进程杀死,再次启动,国际化才会生效。但是每次切换一下终端语言就人为杀进程重启应用,显然是不合理的。
事实上当locale信息改变之后,系统会发广播消息Intent.ACTION_LOCALE_CHANGED,我们可以写接收该广播的代码,并做一些操作。
不妨多写几个Activity页面做测试,我写了两个,MainActivity经过修改的XML前面已经给出了,MainActivity如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
|
package
com.example.l10ntest;
import
android.content.Intent;
import
android.os.Bundle;
import
android.view.View;
import
android.view.View.OnClickListener;
import
android.widget.Button;
import
android.widget.TextView;
public
class
MainActivity
extends
BaseActivity {
@Override
protected
void
onCreate(Bundle savedInstanceState) {
super
.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
((TextView) findViewById(R.id.putName))
.setText(Constant.string.PUT_NAME);
((TextView) findViewById(R.id.iloveyou))
.setText(Constant.string.SAY_LOVE);
Button btn = (Button) findViewById(R.id.next);
btn.setText(Constant.string.NEXT);
btn.setOnClickListener(
new
OnClickListener() {
@Override
public
void
onClick(View v) {
startActivity(
new
Intent(MainActivity.
this
,SecondActivity.
class
));
}
});
}
}
|
第二个Activity(SecondActivity)我没有写XML布局,代码如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
|
package
com.example.l10ntest;
import
android.os.Bundle;
import
android.view.Gravity;
import
android.widget.TextView;
public
class
SecondActivity
extends
BaseActivity {
@Override
protected
void
onCreate(Bundle savedInstanceState) {
super
.onCreate(savedInstanceState);
TextView textView =
new
TextView(
this
);
textView.setGravity(Gravity.CENTER);
textView.setText(Constant.string.Second_Page);
textView.setTextSize(
30
);
setContentView(textView);
}
}
|
事实上一个应用一般不会只有一个Activity的,我将两个Activity都继承了BaseActivity,在BaseActivity中对这两个Activity做统一管理,BaseActivity如下:
1
2
3
4
5
6
7
8
9
10
|
package
com.example.l10ntest;
import
android.app.Activity;
import
android.os.Bundle;
public
class
BaseActivity
extends
Activity {
@Override
protected
void
onCreate(Bundle savedInstanceState) {
super
.onCreate(savedInstanceState);
Utils.addToList(
this
);
}
}
|
只调用了Utils类里的一个方法,在Activity创建后,将它添加到List里面。Utils类代码如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
|
package
com.example.l10ntest;
import
java.util.ArrayList;
import
java.util.Iterator;
import
android.app.Activity;
import
android.util.Log;
public
class
Utils {
private
static
String TAG =
"Utils"
;
// 创建存放Activity的集合
private
static
ArrayList<Activity> activityList =
new
ArrayList<Activity>();
public
static
void
addToList(Activity activity) {
Log.i(TAG,
"activity name:"
+ activity.getClass().getName());
activityList.add(activity);
}
public
static
void
stopApp() {
Iterator<Activity> itr = activityList.iterator();
Activity a =
null
;
while
(itr.hasNext()) {
a = itr.next();
a.finish();
}
Log.i(TAG,
"stopApp end."
);
System.exit(
0
);
}
}
|
这里面有两个方法,一个就是添加Activity做统一管理,第二个是停止程序的方法,先将List中的Activity依次杀死,再杀死进程退出程序。
下面就是比较重要的接收系统广播的类了,代码如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
|
package
com.example.l10ntest;
import
android.content.BroadcastReceiver;
import
android.content.Context;
import
android.content.Intent;
public
class
AppExitReceiver
extends
BroadcastReceiver {
@Override
public
void
onReceive(Context context, Intent intent) {
if
(intent ==
null
|| intent.getAction() ==
null
)
return
;
if
(intent.getAction().equals(Intent.ACTION_LOCALE_CHANGED)) {
Utils.stopApp();
}
}
}
|
AndroidManifest.xml文件如下,配置了一个广播接收。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
|
<?
xml
version
=
"1.0"
encoding
=
"utf-8"
?>
<
manifest
xmlns:android
=
"http://schemas.android.com/apk/res/android"
package
=
"com.example.l10ntest"
android:versionCode
=
"1"
android:versionName
=
"1.0"
>
<
uses-sdk
android:minSdkVersion
=
"8"
android:targetSdkVersion
=
"17"
/>
<
application
android:allowBackup
=
"true"
android:icon
=
"@drawable/ic_launcher"
android:label
=
"@string/app_name"
android:theme
=
"@style/AppTheme"
>
<
activity
android:name
=
"com.example.l10ntest.MainActivity"
android:label
=
"@string/app_name"
>
<
intent-filter
>
<
action
android:name
=
"android.intent.action.MAIN"
/>
<
category
android:name
=
"android.intent.category.LAUNCHER"
/>
</
intent-filter
>
</
activity
>
<
activity
android:name
=
"com.example.l10ntest.SecondActivity"
android:label
=
"@string/app_name"
>
</
activity
>
<
receiver
android:name
=
"com.example.l10ntest.AppExitReceiver"
>
<
intent-filter
>
<
action
android:name
=
"android.intent.action.LOCALE_CHANGED"
/>
</
intent-filter
>
</
receiver
>
</
application
>
</
manifest
>
|
整理一下思路:
写了两个Activity,这两个Activity都用到了文本资源,如“((TextView) findViewById(R.id.iloveyou)).setText(Constant.string.SAY_LOVE);”和“textView.setText(Constant.string.Second_Page);”MainActivity中增加了跳转到SecondActivity的按钮。两个Activity都继承了BaseActivity,当它们创建后,会通过BaseActivity中的“Utils.addToList(this)”将它们依次添加到Utils类中的activityList里。
在终端切换系统语言,发送Intent.ACTION_LOCALE_CHANGED广播后,我们创建AppExitReceiver类来接收该广播,并调用“Utils.stopApp()“来将创建的Activity依次关闭,并且退出应用程序,这样不管该程序从后台切回来还是点击该程序的图标,程序都会重新启动,显示的文本信息会随着系统语言的变换而变换。
本文出自 “皓轩” 博客,请务必保留此出处http://wy521angel.blog.51cto.com/3262615/1643618