《Android应用开发攻略》——1.15 程序:Android OS下的小费计算器Tipster

1.15 程序:Android OS下的小费计算器Tipster

Sunit Katkar
1.15.1 问题
当你和朋友前往饭店就餐并且希望计算各自的账单和小费时,可能陷入许多手动计算和分歧之中。你希望使用一个应用程序,简单地将小费比例加到总额上,并且按照就餐人数分配。Tipster就是Android中的一个实现,用它展示一个完整的应用程序。
1.15.2 解决方案
这是一个简单的练习,使用Android中的基本GUI元素,然后用一些简单的计算和事件驱动UI代码将它们组合起来。将用到如下GUI组件:
TableLayout
该组件很好地提供了对屏幕布局的控制。可以使用HTML的Table标记来设计窗口部件。
TableRow
这个组件定义了TableLayout中的一行。类似于HTML TR和TD标记的组合。
TextView
这个View子类为屏幕上显示的静态文本提供标签。
EditText
这个View子类提供输入用的文本字段。
RadioGroup
组合单选按钮。
RadioButton
提供单选按钮
Button
常规按钮。
View
将使用View创建具有特定高度和颜色属性的视觉分隔符。
1.15.3 讨论
Android用XML文件来设计窗口部件的布局。在示例项目中,Eclipse的Android插件生成用于布局的main.xml文件。该文件包含基于XML的不同窗口部件及其容器的定义。
strings.xml文件包含应用程序中的所有字符串资源。默认的icon.png文件用于应用程序图标。
然后是自动生成的R.java文件(修改main.xml时将会更新)。这个文件包含为每个布局和窗口部件定义的常量。不要手动编辑该文件;当修改XML文件时,插件会自动进行相应的修改。
在例子中,Tipster.java是用于Activity的主Java文件。
攻略1.4和各种Google教程强调了该插件的使用方法。使用Eclipse插件,创建Android项目Tipster。最终的结果将是外观类似于图1-39的项目布局。
创建布局并放置窗口部件
本攻略的最终目标是创建一个类似于图1-39的布局。
对于这个屏幕布局,将使用如下布局和窗口部件:
TableLayout
提供对屏幕布局的控制。这个布局使用HTML Table标记范例来设计窗口部件的布局。
TableRow
定义TableLayout中的行,类似于HTML TR和TD标记的组合。
TextView
这个View子类为屏幕上显示的静态文本提供标签。
EditText
这个View子类提供输入数值的文本字段。
RadioGroup
组合单选按钮。
RadioButton
提供单选按钮。
Button
常规按钮。
View
使用View类创建具有特定高度和颜色属性的视觉分隔符。
因为你所构建的应用程序中将大量使用这些窗口部件,所以要自己动手熟悉它们。当你查看布局和窗口部件的Javadoc时,仔细观察XML属性。这将帮助你建立main.xml布局文件中的用法与访问该文件的Java代码(Tipster.java和R.java)之间的关联。
Eclipse ADT还有一个可视化布局编辑器,以及单独的UI工具DroidDraw,这两者都可以通过从工具面板上拖放窗口部件来创建布局,就像所有表单设计工具一样。但是,我建议你手动地在XML中创建布局,至少在Android的初学阶段要这么做。以后,当你学习到XML布局API的微妙之处,可以将这一任务交给上述工具。
布局文件main.xml包含布局信息(见例1-6)。TableRow部件在TableLayout中创建一行,可以使用任意多个TableRow。在这个教程中,将使用8个TableRow,其中5个用于按钮下面视觉分隔符之上的窗口部件,另外三个用于按钮和分隔符之下的结果区域。
例1-6:/res/layout/main.xml

<?xml version="1.0" encoding="utf-8"?>
<!-- Using table layout to have HTML table like control over layout -->
<TableLayout
        android:id="@+id/TableLayout01"
        android:layout_width="fill_parent"
        android:layout_height="fill_parent"
        android:stretchColumns="1"
        xmlns:android="http://schemas.android.com/apk/res/android">
<!—第1行:文本标签放在第0列,
    文本字段放在第2列并允许跨跃2列。
    这一行共有4列 -->
   <TableRow>
   <TextView
           android:id="@+id/txtLbl1"
           android:layout_width="wrap_content"
           android:layout_height="wrap_content"
           android:layout_column="0"
           android:text="@string/textLbl1"/>
   <EditText①
           android:id="@+id/txtAmount"
           android:layout_width="wrap_content"
           android:layout_height="wrap_content"
           android:numeric="decimal"
           android:layout_column="2"
           android:layout_span="2"
           />
   </TableRow>
<!--第2行:文本标签放在第0列,
    文本字段放在第2列并允许跨跃2列。
    这一行共有4列  -->
   <TableRow>
   <TextView
           android:id="@+id/txtLbl2"
           android:layout_width="wrap_content"
           android:layout_height="wrap_content"
           android:layout_column="0"
           android:text="@string/textLbl2"/>
   <EditText
           android:id="@+id/txtPeople"
           android:layout_width="wrap_content"
           android:layout_height="wrap_content"
           android:numeric="integer"
           android:layout_column="2"
           android:layout_span="3"/>
   </TableRow>
<!--第3行:只在第0列有一个文本标签 -->
   <TableRow>
   <TextView
           android:id="@+id/txtLbl3"
           android:layout_width="wrap_content"
           android:layout_height="wrap_content"
           android:text="@string/textLbl3"/>
   </TableRow>
<!—第4行: RadioButton组成的RadioGroup放在第0列,
    跨越3列,该行的每个表格单元有一个单选按钮。
    最后一个单元(4)有用于输入自定义小费比例的文本字段-->
   <TableRow>
   <RadioGroup
           android:id="@+id/RadioGroupTips"
           android:orientation="horizontal"
           android:layout_width="wrap_content"
           android:layout_height="wrap_content"
           android:layout_column="0"
           android:layout_span="3"
           android:checkedButton="@+id/radioFifteen">
           <RadioButton android:id="@+id/radioFifteen"
                   android:layout_width="wrap_content"
                   android:layout_height="wrap_content"
                   android:text="@string/rdoTxt15"
                   android:textSize="15sp" />
           <RadioButton android:id="@+id/radioTwenty"
                   android:layout_width="wrap_content"
                   android:layout_height="wrap_content"
                   android:text="@string/rdoTxt20"
                   android:textSize="15sp" />
           <RadioButton android:id="@+id/radioOther"
                   android:layout_width="wrap_content"
                   android:layout_height="wrap_content"
                   android:text="@string/rdoTxtOther"
                   android:textSize="15sp" />
   </RadioGroup>
           <EditText
                   android:id="@+id/txtTipOther"
                   android:layout_width="fill_parent"
                   android:layout_height="wrap_content"
                   android:numeric="decimal"/>
   </TableRow>
<!--这行用于放置 Calculate 和Reset按钮,
    Calculate按钮放在第2列,Reset放在第3列 -->
   <TableRow>
   <Button
           android:id="@+id/btnReset"
           android:layout_width="wrap_content"
           android:layout_height="wrap_content"
           android:layout_column="2"
           android:text="@string/btnReset"/>
   <Button
           android:id="@+id/btnCalculate"
           android:layout_width="wrap_content"
           android:layout_height="wrap_content"
           android:layout_column="3"
           android:text="@string/btnCalculate"/>
   </TableRow>
<!—TableLayout允许其他视图插入TableRow元素之间,
    所以可以插入一个空白视图造成分隔线。这个分隔视
    图用于隔离按钮下面的区域,该区域将显示计算结果
    的TableLayout-->
   <View
           android:layout_height="2px"
           android:background="#DDFFDD"
           android:layout_marginTop="5dip"
           android:layout_marginBottom="5dip"/>
<!-- 这一行也是用来在第2列放置结果文本视图的
    (第0列)文本视图中的结果-->
   <TableRow android:paddingBottom="10dip" android:paddingTop="5dip">
   <TextView
           android:id="@+id/txtLbl4"
           android:layout_width="wrap_content"
           android:layout_height="wrap_content"
           android:layout_column="0"
           android:text="@string/textLbl4"/>
   <TextView
           android:id="@+id/txtTipAmount"
           android:layout_width="wrap_content"
           android:layout_height="wrap_content"
           android:layout_column="2"
           android:layout_span="2"/>
   </TableRow>
   <TableRow android:paddingBottom="10dip" android:paddingTop="5dip">
   <TextView
           android:id="@+id/txtLbl5"
           android:layout_width="wrap_content"
           android:layout_height="wrap_content"
           android:layout_column="0"
           android:text="@string/textLbl5"/>
   <TextView
           android:id="@+id/txtTotalToPay"
           android:layout_width="wrap_content"
           android:layout_height="wrap_content"
           android:layout_column="2"
           android:layout_span="2"/>
   </TableRow>
   <TableRow android:paddingBottom="10dip" android:paddingTop="5dip">
   <TextView
                  android:id="@+id/txtLbl6"
           android:layout_width="wrap_content"
           android:layout_height="wrap_content"
           android:layout_column="0"
           android:text="@string/textLbl6"/>
   <TextView
           android:id="@+id/txtTipPerPerson"
           android:layout_width="wrap_content"
           android:layout_height="wrap_content"
           android:layout_column="2"
           android:layout_span="2"/>
   </TableRow>
  <!—所有行和部件结束-->
</TableLayout>

TableLayout与TableRow
研究Main.xml之后,你会发现TableLayout和TableRow的使用很简单:创建TableLayout,然后插入TableRow。现在,可以*地在TableRow中插入任何其他窗口部件,例如TextView、EditView等。
仔细观察属性,特别是android:stretchColumns、android:layout_column和 android:layout_span,可以用这些属性,按照使用常规HTML表格的方法放置窗口部件。我建议你关注这些属性的链接,研究它们对TableLayout的影响。
控制输入值
控制输入值:查看main.xml文件中①处的EditText部件。这是第一个文本输入字段,用于输入账单“总额”,只允许输入数字。可以接受小数,因为真正的饭店账单都包含元和分,而不仅仅是元。所以将android:numeric属性值类型设置为decimal。这样,该输入字段中允许输入整数值(如10)和小数值(如10.12),而不允许其他任何类型的输入。
这是简单明了的输入值控制方法,可以省去在Tipster.java文件中编写验证代码的麻烦,确保用户不会输入不正确的值。Android基于XML的约束功能相当强大而实用。你应该研究特定窗口部件的所有可能属性,从设置约束的XML快捷方法中获得最大的益处。除非我在这个版本中完全没有用到,否则我希望在未来的版本中,Android能考虑在android:numeric加入范围,便于我们定义接受的数值范围。
因为(据我所知)目前还没有范围属性,以后你将会看到,我们必须检查特定的值(如0或者空值),以确保小费计算不会失败。
分析Tipster.java
现在,我们来看看控制应用程序的Tipster.java文件。这是一个主类,完成布局、事件处理和应用程序逻辑。
Android Eclipse插件在项目中创建Tipster.java文件的默认代码如例1-7所示。
例1-7:/src/com/examples/tipcalc/Tipster.java的代码段1

package com.examples.tipcalc;
import android.app.Activity;
public class Tipster extends Activity {
    /** 在活动第一次创建时调用 */
    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.main);
    }
}

Tipster类扩展了android.app.Activity类。活动(activity)是用户所能进行的单一中心工作。Activity类负责窗口的创建和UI布局。必须调用setContentView(View view)方法将UI放入Activity中。所以可以将Activity看做空的外部框架,然后填入你的UI。
现在看看例1-8中的Tipster.java类片段。首先定义作为类的成员窗口部件。仔细研读这一例子①到②的部分,作为以后的参考。
然后,用findViewById(int id)方法定位窗口部件。当在Eclipse中清理并构建项目时,在main.xml文件中定义的每个部件ID自动在R.java文件中定义。(如果将Eclipse设置为自动构建,当更新main.xml时,R.java文件立刻更新)。
每个窗口部件都从View类继承而来,提供特殊的GUI功能。TextView类提供了在UI上放置标签的手段,而EditText则提供了一个文本字段。查看例1-8中③~⑥对应的部分,你可以看到如何使用findViewById()寻找窗口部件。
例1-8:/src/com/examples/tipcalc/Tipster.java的代码段2

public class Tipster extends Activity {
    // 应用程序中的窗口部件
    private EditText txtAmount;①
    private EditText txtPeople;
    private EditText txtTipOther;
    private RadioGroup rdoGroupTips;
    private Button btnCalculate;
    private Button btnReset;
    private TextView txtTipAmount;
    private TextView txtTotalToPay;
    private TextView txtTipPerPerson;②
    // 选中的按钮ID
    private int radioCheckedId = -1;
    /**在活动第一次创建时调用*/
    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.main);
        // Access the various widgets by their id in R.java
        txtAmount = (EditText) findViewById(R.id.txtAmount);③
        //On app load, the cursor should be in the Amount field
        txtAmount.requestFocus();④
        txtPeople = (EditText) findViewById(R.id.txtPeople);
        txtTipOther = (EditText) findViewById(R.id.txtTipOther);
        rdoGroupTips = (RadioGroup) findViewById(R.id.RadioGroupTips);
        btnCalculate = (Button) findViewById(R.id.btnCalculate);
        //On app load, the Calculate button is disabled
        btnCalculate.setEnabled(false);⑤
        btnReset = (Button) findViewById(R.id.btnReset);
        txtTipAmount = (TextView) findViewById(R.id.txtTipAmount);
        txtTotalToPay = (TextView) findViewById(R.id.txtTotalToPay);
        txtTipPerPerson = (TextView) findViewById(R.id.txtTipPerPerson);⑥
        // On app load, disable the Other Tip Percentage text field
        txtTipOther.setEnabled(false);⑦

解决易用性或者可用性问题
应用程序必须达到其他已经发布的应用程序或者网页的易用性水平。简而言之,增加易用特性能够带来好的用户体验。为此,再次查看例1-8。
关注使用View类的requestFocus()方法的地方④。因为EditText部件继承自View类,该方法适用于它。因此,当应用程序加载时,总额(Total Amount)文本字段将得到焦点,光标出现在该控件中。这与流行Web应用程序的屏幕登录相似——在登录屏幕中光标出现在用户名文本字段中。
现在再看看Calculate(计算)按钮⑤,通过调用Button部件上的setEnabled(Boolean enabled)将它禁用。这样,用户在输入必要的字段值之前,用户无法单击它。如果在总额和人数字段未输入值的情况下允许用户单击Calculate按钮,就必须编写捕捉这些情况的验证代码,从而必须向用户显示有关空值的弹出式警告,这会增加不必要的代码和用户交互。当用户看到Calculate按钮禁用时,就能很明显地看出,除非输入所有值,否则小费无法计算。
在例1-8中的⑦,这里还禁用了Other Tip Percentage(其他的小费比例)文本字段。这是因为应用程序加载时默认选中“15% tip”单选按钮,这一默认选择通过main.xml文件完成。main.xml中用如下的语句选择了“15% tip”单选按钮:

android:checkedButton="@+id/radioFifteen"

RadioGroup属性android:checkedButton允许选择组中默认的一个RadioButton部件。
使用过流行桌面和Web应用程序的大部分用户都熟悉“disabled widgets enabled on certain conditions”(在某种条件下启用已经禁用的部件)范例。增加这种小的便利功能总是能使应用更加易用,用户体验也更加丰富。
处理UI事件
和流行的Windows、Java Swing、 Flex和其他UI框架一样,Android也提供了事件模型,可以监听UI中由用户交互引起的事件。我们来看看如何在应用中使用Android事件模型。
首先关注UI中的单选按钮。我们希望知道,用户选择了哪一个单选按钮,因为这能够确定应用程序中的小费比例。使用静态接口OnCheckedChangeListener()“监听”单选按钮,当按钮选择状态变化时将会得到通知。
在应用程序中,希望在选中Other(其他)单选按钮时才启用Other Tip Percentage文本字段。当“15% tip”和“20% tip”单选按钮选中时,我们希望禁用文本字段。除此之外,还要添加一些有利于易用性的逻辑。之前已经讨论过,不应该在必要的所有字段中输入有效值之前启用Calculate按钮。对于这三个单选按钮,应该确保在如下两个条件下启用Calculate按钮:
Other单选按钮选中,且Other Tip Percentage文本字段输入了有效值。
“15% tip”或“20% tip”单选按钮选中,且Total Amount和No. of People文本字段输入了有效值。
例1-9中对单选按钮进行了处理。源代码注释已经做出了很好的解释:
例1-9:/src/com/examples/tipcalc/Tipster.java的代码片段3

/*
 * 在单选按钮组上附加一个OnCheckedChangeListener监控被用户选中的单选按钮
 */
 rdoGroupTips.setOnCheckedChangeListener(new OnCheckedChangeListener() {
 @Override
 public void onCheckedChanged(RadioGroup group, int checkedId) {
    // Enable/disable Other Tip Percentage field
    if (checkedId == R.id.radioFifteen
               || checkedId == R.id.radioTwenty) {
        txtTipOther.setEnabled(false);
        /*
         *如果Total Amount和No. of People字段的值有效,
         *启用Calculate按钮
         */
        btnCalculate.setEnabled(txtAmount.getText().length() > 0
                  && txtPeople.getText().length() > 0);
    }
    if (checkedId == R.id.radioOther) {
       // enable the Other Tip Percentage field
       txtTipOther.setEnabled(true);
       // set the focus to this field
       txtTipOther.requestFocus();
       /*
        *如果Total Amount和No. of People字段的值有效,
        *启用Calculate按钮。在此之前还要确认用户输入了Other Tip Percentage值
        */
       btnCalculate.setEnabled(txtAmount.getText().length() > 0
               && txtPeople.getText().length() > 0
               && txtTipOther.getText().length() > 0);
    }
    // 确定用户选择的小费比例
    radioCheckedId = checkedId;
   }
});

监控文本字段中的键盘活动
前面已经提到,除非文本字段输入了有效值,否则Calculate按钮不能启用。因此,必须确保Calculate按钮仅在Total Amount、No. of People和Other Tip Percentage文本字段中输入有效值之后启用。Other Tip Percentage文本字段仅在选中Other Tip Percentage单选按钮时启用。
我们不必担心输入值的类型、用户是否输入负数或者字母,因为android:numeric属性已经定义了文本字段,限制了用户所能输入的类型。只需要确保输入值存在。
使用静态接口OnKeyListener(),该接口在按下键时通知我们。通知在实际按键发送到EditText部件之前就会到达。
例1-10和例1-11处理文本字段中的按键事件。和例1-9中一样,源代码中的注释也很好地解释了代码的功能。
例1-10:/src/com/examples/tipcalc/Tipster.java的代码片段4

/*
 * 在Tip Amount、No. of People和Other Tip Percentage字段中附加一个KeyListener
 */
txtAmount.setOnKeyListener(mKeyListener);
txtPeople.setOnKeyListener(mKeyListener);
txtTipOther.setOnKeyListener(mKeyListener);

注意,只创建一个监听器,而不是为每个文本字段都创建匿名/内部监听器。我不确定自己的风格更好或者值得推荐,但是如果监听器将要执行一些公用的操作,我就总以这种风格编写代码。所有文本字段都有公共的一个关注点:它们都不应该为空,只有在它们有非空值时,才应该启用Calculate按钮。
例1-11:代码片段5,摘自KeyListener.java

/*
 *用于Total Amount、No of People和Other Tip Percentage字段的KeyListener,
 *我们需要应用这个键盘监听器检查如下的条件:
 *1)如果用户选择了Other Tip Percentage,用户应该在Other Tip Percentage字段输入有效的值
 *在用户输入有效值时启用Calculate按钮
 * 
 *2)如果用户没有在Total Amount和No. of People fields输入值,
 *我们不能进行计算。所以我们只在用户输入有效值时启用Calculate按钮
 */
private OnKeyListener mKeyListener = new OnKeyListener() {
    @Override
    public boolean onKey(View v, int keyCode, KeyEvent event) {
    switch (v.getId()) {①
    case R.id.txtAmount:②
    case R.id.txtPeople:③
        btnCalculate.setEnabled(txtAmount.getText().length() > 0
                && txtPeople.getText().length() > 0);
        break;
    case R.id.txtTipOther:④
        btnCalculate.setEnabled(txtAmount.getText().length() > 0
                && txtPeople.getText().length() > 0
                && txtTipOther.getText().length() > 0);
        break;
    }
    return false;
    }
};

在例1-11的①处,检查View类的ID。记住,因为在main.xml文件中的定义,每个窗口部件都有唯一的ID。之后,在生成的R.java类中定义了这些值。
在②和③处,如果总金额或者人数字段中发生了键盘事件,将检查字段中输入的值,确保用户对这两个字段都没有留空。
在④处,检查用户是否选择Other单选按钮,然后确定Other文本字段不为空。还要再次确定金额或者人数字段是否为空。
KeyListener的目的现在很清晰了:确保所有文本字段非空,仅在这种情况下启用Calculate按钮。
监听按钮单击
现在来看Calculate和Reset(复位)按钮。我们使用静态接口OnClickListener()来通知用户何时单击这些按钮。
和文本字段一样,只创建一个监听器,在监听器中检测被单击的按钮。根据单击的按钮,调用calculate() 或reset()方法。
例1-12说明了为按钮添加单击事件监听器的方法。
例1-12:/src/com/examples/tipcalc/Tipster.java的代码片段6

/*为Calculate 和Reset按钮附加监听器* /
btnCalculate.setOnClickListener(mClickListener);
btnReset.setOnClickListener(mClickListener);
例1-13说明了如何通过检查接受单击事件的View类的ID,检测单击的是哪一个按钮。
例1-13:/src/com/examples/tipcalc/Tipster.java的代码片段7
/**
 * Calculate和Reset按钮的ClickListener,根据单击的按钮调用对应方法
 */
private OnClickListener mClickListener = new OnClickListener() {
    @Override
    public void onClick(View v) {
        if (v.getId() == R.id.btnCalculate) {
            calculate();
        } else {
            reset();
        }
    }
};

复位应用程序
当用户单击Reset按钮时,文本字段应该清除,选中默认的“15% tip”单选按钮,应该清除所有计算结果。
例1-14展示了reset()方法。
例1-14:/src/com/examples/tipcalc/Tipster.java的代码片段8

/**
 *复位屏幕底部的结果文本视图以及文本字段和单选按钮
 */
private void reset() {
    txtTipAmount.setText("");
    txtTotalToPay.setText("");
    txtTipPerPerson.setText("");
    txtAmount.setText("");
    txtPeople.setText("");
    txtTipOther.setText("");
    rdoGroupTips.clearCheck();
    rdoGroupTips.check(R.id.radioFifteen);
    // set focus on the first field
    txtAmount.requestFocus();
}

验证计算小费的输入
前面已经提到,我们限制了文本字段中用户所能输入的值的类型。但是,用户仍然可以在总金额、人数和其他小费比例字段中输入0值,这会造成计算中除以0等错误。
如果用户输入0,必须显示一个弹出式警告框,要求用户输入非0值。用showErrorAlert(String errorMessage, final int fieldId)方法处理这一任务,稍后将对此进行更详细的讨论。
首先,看看例1-15中展示的calculate()方法。注意用户的值是如何解析为双精度值的。
现在注意①和②中对0值的检查。如果用户输入0,显示一个弹出警告框对用户加以警告。接下来看看③,因为用户选择了Other单选按钮,所以Other Tip Percentage文本字段启用,还必须检查小费率是否为0。
当应用程序加载时,默认选中“15% tip”单选按钮。如果用户改变了选择,将选中的单选按钮ID赋值给成员变量radioCheckedId,正如在例1-9的OnCheckedChangeListener中看到的那样。
但是,如果用户接受默认选择,radioCheckedId将为默认值-1。简而言之,我们永远不会知道选中的是哪一个单选按钮。当然,我们知道默认选中的是哪一个按钮,可以编写稍有不同的逻辑,如果radioCheckedId的值为-1就假定小费比例为15%。但是,如果查阅API,你就会发现:可以在RadioGroup上而不是在单独的单选按钮上调用getCheckedRadioButtonId()方法。这是因为OnCheckedChangeListener提供了选中的单选按钮的ID。
显示结果
计算小费很简单。如果没有验证错误,布尔标志isError将为false。简单的小费计算参见例1-15的④~⑤。接下来,计算出的值将在⑥~⑦中设置到TextView窗口部件。
例1-15:/src/com/examples/tipcalc/Tipster.java的代码片段9

/**
 * 按照用户输入的数据计算小费
 */
private void calculate() {
    Double billAmount = Double.parseDouble(
        txtAmount.getText().toString());
    Double totalPeople = Double.parseDouble(
        txtPeople.getText().toString());
    Double percentage = null;
    boolean isError = false;
    if (billAmount < 1.0) {①
        showErrorAlert("Enter a valid Total Amount.",
            txtAmount.getId());
        isError = true;
    }
    if (totalPeople < 1.0) {②
        showErrorAlert("Enter a valid value for No. of People.",
            txtPeople.getId());
        isError = true;
    }
    /*
     * 如果用户从未修改按钮的选择,意味着默认的15%比例有效,但是出于安全还是进行验证
     */
    if (radioCheckedId == -1) {
        radioCheckedId = rdoGroupTips.getCheckedRadioButtonId();
    }
    if (radioCheckedId == R.id.radioFifteen) {
        percentage = 15.00;
    } else if (radioCheckedId == R.id.radioTwenty) {
        percentage = 20.00;
    } else if (radioCheckedId == R.id.radioOther) {
        percentage = Double.parseDouble(
            txtTipOther.getText().toString());
        if (percentage < 1.0) {③
            showErrorAlert("Enter a valid Tip percentage",
                txtTipOther.getId());
            isError = true;
        }
    }
    /*
     * 如果所有字段都填写了有效值,则继续计算小费
     */
    if (!isError) {
        Double tipAmount = ((billAmount * percentage) / 100);④
        Double totalToPay = billAmount + tipAmount;
        Double perPersonPays = totalToPay / totalPeople;⑤
        txtTipAmount.setText(tipAmount.toString());⑥
        txtTotalToPay.setText(totalToPay.toString());
        txtTipPerPerson.setText(perPersonPays.toString());⑦
    }
}

显示警告
Android提供AlertDialog类来显示弹出式警告。可以用它显示一个具有多达3个按钮和一条信息的对话框。
例1-16展示的showErrorAlert方法使用AlertDialog显示错误消息。注意,向这个方法传递了两个参数:String error Message和int fieldId。第一个参数是我们希望向用户显示的错误消息。fieldId是导致错误的字段ID。在用户关闭警告对话框之后,这个fieldId使我们能够将焦点置于该字段之上,用户由此得知该字段发生了错误。
例1-16:/src/com/examples/tipcalc/Tipster.java的代码片段10

/**
 * S在一个警告框中显示错误消息
 *
 * @param errorMessage
 * 显示的错误消息字符串
 * @param fieldId
 * 导致错误的字段ID。需要用它在对话框消失之后设置字段焦点
 */
private void showErrorAlert(String errorMessage,
    final int fieldId) {
    new AlertDialog.Builder(this).setTitle("Error")
    .setMessage(errorMessage).setNeutralButton("Close",
            new DialogInterface.OnClickListener() {
                @Override
                public void onClick(DialogInterface dialog,
                   int which) {
                findViewById(fieldId).requestFocus();
            }
        }).show();
}

组合以上各个功能,就实现了图1-39中的效果。

《Android应用开发攻略》——1.15 程序:Android OS下的小费计算器Tipster


结语
Android OS的开发与其他UI工具包的开发(包括Microsoft Windows、 X Windows、 Java Swing或Adobe Flex)没有太大的不同。当然,Android也有自己的特点,总体上是一个非常好的设计。XML布局范例相当好,可以用简单的XML构造复杂的UI。此外,事件处理模型很简单、具有丰富的特性,在代码中使用也很直观。
1.15.4 源代码下载URL
可以从http://www.vidyut.com/sunit/android/tipster.zip下载上述例子的源代码。

1.15.5 二进制文件下载URL
可以从http://www.vidyut.com/sunit/android/tipster.zip下载上述例子的二进制文件。

上一篇:在人工智能时代,MATLAB 还是曾经的那个“高级计算器”么?


下一篇:【机器学习】图解Word2vec