一、新建针对美国、英国用户的BMI
-
改造计划
- 以原 BMI 应用程序为基础;
- 精简界面,只留下必要的功能;
- 将界面与文字消息由中文改成英文,以符合目标用户特性;
- 将计量单位由公制改为英制,并修改相关表达式;
- 改进身高的输入方式,由原本用一个“文字编辑文本框”(TextEdit)输入身高,改成用两个“下拉菜单”(Spinner)选择身高英尺、英寸。
res/layout/activity_main.xml
这里暂时使用前面“BMI”应用程序用到的“文字编辑字段”来直接输入英尺、英寸,以后再用下拉菜单组件来替换这两个字段
<?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools" android:orientation="vertical" android:layout_width="fill_parent" android:layout_height="fill_parent" tools:context=".MainActivity"> <TextView android:layout_width="fill_parent" android:layout_height="wrap_content" android:text="@string/us_height" /> <EditText android:id="@+id/feet" android:layout_width="fill_parent" android:layout_height="wrap_content" android:text="" /> <EditText android:id="@+id/inch" android:layout_width="fill_parent" android:layout_height="wrap_content" android:text="" /> <TextView android:layout_width="fill_parent" android:layout_height="wrap_content" android:text="@string/pound"/> <EditText android:id="@+id/weight" android:layout_width="fill_parent" android:layout_height="wrap_content" android:text="" android:inputType="numberDecimal" /> <Button android:id="@+id/submit" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="@string/bmi_btn"/> <TextView android:id="@+id/result" android:layout_width="fill_parent" android:layout_height="wrap_content" android:singleLine="true" android:text="" /> <TextView android:id="@+id/suggest" android:layout_width="fill_parent" android:layout_height="wrap_content" android:text="" /> </LinearLayout>
res/value/string.xml
<resources> <string name="app_name">aBMI</string> <string name="app_label">BMI for the British system</string> <string name="us_height">Height</string> <string name="feet_prompt">Select Feet</string> <string name="inch_prompt">Select Inches</string> <string name="pound">Weight (lbs)</string> <string name="bmi_btn">Calc BMI</string> <string name="bmi_result">Your BMI is ...</string> <string name="input_error">oops,you should type numbers only</string> <string name="advice_light">You should eat more</string> <string name="advice_average">You are in good shape,keep it!</string> <string name="advice_heavy">You should lose some weight</string> <string name="advice_fat">You should do something to improve your situation,right now</string> <string name="about_label">About...</string> <string name="about_title">About aBMI 1.0</string> <string name="about_msg">aBMI is a BMI calculator for the British system,written by sc</string> <string name="ok_label">OK</string> <string-array name="feets"> <item>2 feet</item> <item>3 feet</item> <item>4 feet</item> <item>5 feet</item> <item>6 feet</item> <item>7 feet</item> <item>8 feet</item> </string-array> <string-array name="inches"> <item>0 inches</item> <item>1 inches</item> <item>2 inches</item> <item>3 inches</item> <item>4 inches</item> <item>5 inches</item> <item>6 inches</item> <item>7 inches</item> <item>8 inches</item> <item>9 inches</item> <item>10 inches</item> <item>11 inches</item> </string-array> </resources>
MainActivity.java
在“aBMI应用程序”中,我们只保留了“关于...”(MENU_aBOUT)的选项,把另一个“结束”的选项移除了。在“关于...”页面中,也将连接首页的按钮移除了。
package com.demo.android.abmi; import android.app.Activity; import android.app.AlertDialog; import android.content.DialogInterface; import android.os.Bundle; import android.util.Log; import android.view.Menu; import android.view.MenuItem; import android.view.View; import android.widget.Button; import android.widget.EditText; import android.widget.TextView; import android.widget.Toast; import java.text.DecimalFormat; //aBMI is for British system public class MainActivity extends Activity { private static final String TAG = "aBmi"; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); findViews(); setListensers(); } private Button button_calc; private EditText field_feet; private EditText field_inch; private EditText field_weight; private TextView view_result; private TextView view_suggest; private void findViews() { Log.d(TAG, "find Views"); button_calc = (Button) findViewById(R.id.submit); field_feet = (EditText) findViewById(R.id.feet); field_inch = (EditText) findViewById(R.id.inch); field_weight = (EditText) findViewById(R.id.weight); view_result = (TextView) findViewById(R.id.result); view_suggest = (TextView) findViewById(R.id.suggest); } //Listen for button clicks private void setListensers() { Log.d(TAG, "set Listensers"); button_calc.setOnClickListener(calcUsBMI); } private Button.OnClickListener calcUsBMI = new Button.OnClickListener() { @Override public void onClick(View view) { DecimalFormat nf = new DecimalFormat("0.00"); try { double height = (Double.parseDouble(field_feet.getText().toString())*12+Double.parseDouble(field_inch.getText().toString()))*2.54/100; double weight = Double.parseDouble(field_weight.getText().toString())*0.45359; double BMI = weight / (height * height); //Present result view_result.setText(getText(R.string.bmi_result) + nf.format(BMI)); //Give health advice if(BMI > 27) { view_suggest.setText(R.string.advice_fat); } else if(BMI > 25) { view_suggest.setText(R.string.advice_heavy); } else if(BMI < 20) { view_suggest.setText(R.string.advice_light); } else { view_suggest.setText(R.string.advice_average); } } catch (Exception obj) { Toast.makeText(MainActivity.this, getString(R.string.input_error), Toast.LENGTH_SHORT).show(); } } }; protected static final int MENU_ABOUT = Menu.FIRST; @Override public boolean onCreateOptionsMenu(Menu menu) { super.onCreateOptionsMenu(menu); Log.d(TAG, "open Menu"); menu.add(0, MENU_ABOUT, 0, R.string.about_label); return true; } public boolean onOptionsItemSelected(MenuItem item) { super.onOptionsItemSelected(item); Log.d(TAG, "select Menu Item"); switch (item.getItemId()) { case MENU_ABOUT: openOptionsDialog(); break; } return true; } private void openOptionsDialog() { Log.d(TAG, "open Dialog"); new AlertDialog.Builder(this) .setTitle(R.string.about_title) .setMessage(R.string.about_msg) .setPositiveButton(R.string.ok_label, new DialogInterface.OnClickListener() { @Override public void onClick(DialogInterface dialogInterface, int i) { } }) .show(); } }
二、支持多国语言
要让应用程序支持多个语言界面,不需要置换原有的语言界面。只需要在“res”目录下新建相应的语言目录,新建“res/values-en”来放“英文字符串”,新建“res/values-zh-rCN”来放“简体中文字符串”。
以“简体中文字符串”的“values-zh-rCN”目录名称命名方式为例,“zh”是主语系,“h”代表“中文”。“r”字母之后的“CN”是分支,表示“简体中文”。“r”是“revision”的意思。
values-zh-rCN/strings.xml
<resources> <string name="app_name">aBMI</string> <string name="app_label">BMI 英制计算器</string> <string name="us_height">身高</string> <string name="feet_prompt">选择英尺</string> <string name="inch_prompt">选择英寸</string> <string name="pound">体重 (lbs)</string> <string name="bmi_btn">计算 BMI 值</string> <string name="bmi_result">你的 BMI 值是</string> <string name="input_error">哦哦,字段中只能输入数字喔</string> <string name="advice_light">你该多吃点</string> <string name="advice_average">你的体型很不错喔,好好保持吧!</string> <string name="advice_heavy">你应该减肥啰!</string> <string name="advice_fat">为了健康,你应该马上采取一些行动啰!</string> <string name="about_label">关于...</string> <string name="about_title">关于 aBMI 1.0</string> <string name="about_msg">aBMI 是个 BMI 值计算器程序,由 sc 编写</string> <string name="ok_label">OK</string> <string-array name="feets"> <item>2 英尺</item> <item>3 英尺</item> <item>4 英尺</item> <item>5 英尺</item> <item>6 英尺</item> <item>7 英尺</item> <item>8 英尺</item> </string-array> <string-array name="inches"> <item>0 英寸</item> <item>1 英寸</item> <item>2 英寸</item> <item>3 英寸</item> <item>4 英寸</item> <item>5 英寸</item> <item>6 英寸</item> <item>7 英寸</item> <item>8 英寸</item> <item>9 英寸</item> <item>10 英寸</item> <item>11 英寸</item> </string-array> </resources>
- “values”目录名称,与改写后对应的支持语言关系如下
- 繁体中文:values-zh-rTW
- 简体中文:values-zh-rCN
- 日文:values-ja
- 英文:values-en
- 美式英文:values-en-rUS
- 英式英文:values-en-rUK
- 切换语言
切换手机设置的语言,应用程序会改成对应的语言
三、使用接口(Adapter)
接口:负责转换数据源提供给界面组件的函数
Android平台,不允许直接将字符串数组应用在界面组件中。界面菜单的项目,都得要靠接口来提供,这样做是“为了保持程序的弹性”。Android平台默认提供的接口类型有很多种,“ArraryAdapter”的作用是读入程序中已声明的数组,并转换成界面组件看得懂的接口组件。还有“SimpleAdapter”(从XML文字字符串文件读入数组)、“CursorAdapter”(从ContentProvider读入数组)等。
-
数组接口(ArrayAdapter)与字符串数组
我们可以将字符串数组写入“res/values/string.xml”字符串文件,在“<resource>”标签中添加如下语句:
<resources> ... <string-array name="feets"> <item>2 feet</item> <item>3 feet</item> <item>4 feet</item> <item>5 feet</item> <item>6 feet</item> <item>7 feet</item> <item>8 feet</item> </string-array> <string-array name="inches"> <item>0 inches</item> <item>1 inches</item> <item>2 inches</item> <item>3 inches</item> <item>4 inches</item> <item>5 inches</item> <item>6 inches</item> <item>7 inches</item> <item>8 inches</item> <item>9 inches</item> <item>10 inches</item> <item>11 inches</item> </string-array> </resources>
“string-array”组件可以提供“R.array.标识符”格式的参考资源。
在“MainActivity.java”文件中,定义“ArrayAdapter”
private void findViews() { ... ArrayAdapter<CharSequence> adapter_feet = ArrayAdapter.createFromResource(this, R.array.feets, android.R.layout.simple_spinner_item); adapter_feet.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item); ArrayAdapter<CharSequence> adapter_inch = ArrayAdapter.createFromResource(this, R.array.inches, android.R.layout.simple_spinner_item); adapter_inch.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item); }
“ArrayAdapter”接口使用“createFromResource”函数,接受3个参数。第1个参数是指定Activity,第2个参数指定“String.xml”字符串资源,第3个参数“android.R.layout.simple_spinner_item”为Android内置的默认下拉菜单选项格式。
“setDropDownViewResource”方法用来定义下拉查看菜单(DropDownView)的格式
四、添加下拉菜单组件(Spinner)
修改 “res/layout/activity_main.xml”,添加Spinner
... <Spinner android:id="@+id/feet" android:layout_width="fill_parent" android:layout_height="wrap_content" android:drawSelectorOnTop = "true" android:prompt="@string/feet_prompt" /> <Spinner android:id="@+id/inch" android:layout_width="fill_parent" android:layout_height="wrap_content" android:drawSelectorOnTop = "true" android:prompt="@string/inch_prompt" /> ...
我们将原本使用“TextEdit”界面组件的“@+id/feet”、“@+id/inch”两个字段,改为使用“Spinner”界面组件。
android:drawSelectorOnTop = "true" 属性的作用是指定这个下拉菜单是否可以显示在其他菜单的上层。这个属性只有在多层菜单上才会显示出效果
android:prompt 属性的作用是指定下拉菜单弹出菜单选项的提示语句(弹出菜单选项的标题)。
MainActivity.java
... // private EditText field_feet; // private EditText field_inch; private Spinner field_feet; private Spinner field_inch; ... private void findViews() { ... // field_feet = (EditText) findViewById(R.id.feet); // field_inch = (EditText) findViewById(R.id.inch); field_feet = (Spinner) findViewById(R.id.feet); field_inch = (Spinner) findViewById(R.id.inch); ... ArrayAdapter<CharSequence> adapter_feet = ArrayAdapter.createFromResource(this, R.array.feets, android.R.layout.simple_spinner_item); adapter_feet.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item); field_feet.setAdapter(adapter_feet); ArrayAdapter<CharSequence> adapter_inch = ArrayAdapter.createFromResource(this, R.array.inches, android.R.layout.simple_spinner_item); adapter_inch.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item); field_inch.setAdapter(adapter_inch); } //Listen for button clicks private void setListensers() { ... field_feet.setOnItemSelectedListener(getFeet); field_inch.setOnItemSelectedListener(getInch); ... } private int feet; private int inch; private Spinner.OnItemSelectedListener getFeet = new Spinner.OnItemSelectedListener() { @Override public void onItemSelected(AdapterView parent, View v, int position, long id) { feet = parent.getSelectedItemPosition() + 2; } public void onNothingSelected(AdapterView parent) { } }; private Spinner.OnItemSelectedListener getInch = new Spinner.OnItemSelectedListener() { @Override public void onItemSelected(AdapterView parent, View v, int position, long id) { inch = parent.getSelectedItemPosition() + 1; } public void onNothingSelected(AdapterView parent) { } }; private Button.OnClickListener calcUsBMI = new Button.OnClickListener() { @Override public void onClick(View view) { ... double height = (feet * 12 + inch) * 2.54 / 100; double weight = Double.parseDouble(field_weight.getText().toString())*0.45359; double BMI = weight / (height * height); //Present result view_result.setText(getText(R.string.bmi_result) + nf.format(BMI)); ... } }; ... }
在“findViews”函数中,声明了“field_feet”、“field_inch”两个“Spinner”界面组件,和“adapter_feet”、“adapter_inch”两个接口。最后“field_feet”组件加上“setAdapter”函数,将界面组件与接口接在一起,这样下拉菜单(Spinner)通过接口显示String.xml定义的数组。
在“setListensers”函数中,“field_feet”下拉菜单的“setOnItemSelectedListener”方法负责设置按下“Spinner”菜单后续动作的函数,范例中方法参数“getFeet”函数来完成进一步的处理。
自定义函数“getFeet”函数,函数类型“Spinner.OnItemSelectedListener”,在“OnItemSelectedListener”里面定义一个“onItemSelected”函数与另一个“onNothingSelected”函数。
“onItemSelected”函数默认传入参数有4个,第1个参数是你当前操作的下拉菜单实体“Spinner”;第2个参数对于手机App开发用处不大;第3个参数是“Spinner”选项选中的位置,一般自上而下从0开始;第4个参数是你选中的某个Spinner中的某个下来值所在的行,也是自上而下从0开始,一般只有在从数据表中取得的数据时,才会使用到。
本例语句“feet = parent.getSelectedItemPosition() + 2;”,“getSelectedItemPosition()”获取的“position”从0开始。而我们的项目“string-array”从“2 feet”开始,值从2开始。为了方便计算“feet 变量”,所以“+2”,这样以后存储的“feet 变量”的值就从2开始。
“onNothingSelected”函数则是处理用户什么选项都没有选的情况。当我们将函数内容留下空白,表示当用户什么选项都没选时,不作处理。