Android Studio开发实战:从零基础到App上线 (移动开发丛书)
上QQ阅读APP看书,第一时间看更新

2.5 实战项目:简单计算器

到目前为止,虽然只学了一些Android的初级控件,但是也可以学以致用,即便只有这些简单的布局和控件,也能够做出实用的App。接下来我们设计并实现一个简单计算器。

2.5.1 设计思路

计算器是人们日常生活中最常用的工具之一,无论在电脑上还是手机上,都少不了计算器的身影。以Windwos上的计算器为例,界面简洁且十分实用,程序界面如图2-31所示。

图2-31 Windows的计算器

这个计算器界面主要分为两部分,一部分是上面的文本框,用于显示计算结果;另一部分是下面的几排按钮,用于输入数字与各种运算符。为了减少复杂度,我们可以精简一些功能,只保留数字与加、减、乘、除四则运算,另外补充一个开根号(求平方根)的运算。至于App的显示界面,基本与习惯的计算器界面保持一致,经过对操作按钮的适当排列,调整后的设计效果如图2-32所示。

图2-32 简单计算器的设计效果图

这个计算器虽然小巧,但是基本囊括了本章的知识点,先来看看用了哪些控件。

● 线性布局LinearLayout:计算器界面整体上是从上往下布局的,所以需要垂直方向的LinearLayout;下面部分每行都有4个按钮,又需要水平方向的LinearLayout。

● 滚动视图ScrollView:虽然计算器界面不宽也不高,但是以防万一,最好还是加个垂直方向的ScrollView。

● 文本视图TextView:很明显上方标题“简单计算器”就是TextView,下面的计算结果也需要使用TextView,而且是能够自动从下往上滚动的TextView,即聊天室效果的文本视图。

● 按钮Button:绝大多数数字与运算符按钮都采用Button控件。

● 图像视图ImageView:暂时未用到。

● 图像按钮ImageButton:开根号的运算符“√”虽然能够打出来,但是右上角少了数学课本上的一横,所以该按钮要用一张标准的开根号图片显示,这就用到了ImageButton。

● 状态列表图形:每个按钮都有按下和弹起两种状态,这里定制了按钮控件的自定义样式,因此用到了状态列表图形。

● 形状图形:运算结果用到的文本视图边框是圆角矩形,所以得给它定义一个shape文件,把shape定义的圆角矩形作为文本视图的背景。

● 九宫格图片:注意计算器界面左下角的“0”,该按钮是其他按钮的两倍宽,如果使用普通图片当背景,势必造成边缘线被拉宽、拉模糊的问题,故而要采用点九图片避免这种情况。

经过对计算器效果图的详细分析,我们初步了解了所运用的控件技术,接下来就可以对界面进行布局和排列了。

2.5.2 小知识:日志Log/提示Toast

在正式编码之前,读者有必要了解一下Android中的运行信息调试手段。例如,开发C程序时,我们常常用printf函数输出程序日志;开发Java程序时,我们常常用System.out.println函数输出程序日志。同样,App开发也有相应的函数输出提示信息。提示信息可分为两类,一类是给开发者看的,另一类是给用户看的。

1. Log

给开发者看的提示信息要调用Log类的相应方法,日志打印结果可在Android Studio界面下方的logcat小窗口查看。Log类各种方法的区别在于日志的等级,具体说明如下。

● Log.e:表示错误信息,比如可能导致程序崩溃的异常。

● Log.w:表示警告信息。

● Log.i:表示一般消息。

● Log.d:表示调试信息,可把程序运行时的变量值打印出来,方便跟踪调试。

● Log.v:表示冗余信息。

2. Toast

给用户看的提示信息要调用Toast类的相应方法,提示文字会在屏幕下方以一个小窗口临时展现。对于计算器来说,有好几种情况需要提示用户,如“被除数不能为0”“开根号的数值不能小于0”等。

Toast的简单用法只需一行代码就可以了,示例代码如下:

            Toast.makeText(MainActivity.this, "提示文字", Toast.LENGTH_SHORT).show();

另外,计算器每个按钮的展示风格基本相同,为了减少冗余代码,可将相同的样式定义写在values目录下的styles.xml文件中,然后在布局文件节点下增加style="@style/btn_cal"这样的属性定义。下面是styles.xml中计算器按钮风格定义的例子:

          <style name="btn_cal">
              <item name="android:layout_width">0dp</item>
              <item name="android:layout_height">match_parent</item>
              <item name="android:layout_weight">1</item>
              <item name="android:gravity">center</item>
              <item name="android:textColor">@color/black</item>
              <item name="android:textSize">30sp</item>
              <item name="android:background">@drawable/btn_nine_selector</item>
              </style>

2.5.3 代码示例

看到这里,估计读者对计算器App的布局和代码框架都了然于胸了,接下来介绍一些业务逻辑判断与基本的数学四则运算。只要设计充分并且合理,编码就会很快。计算器App运行后的计算效果如图2-33所示。

图2-33 简单计算器的运行效果图

编码过程主要分为3个步骤:

步骤01 先想好代码文件与布局文件的名称,比如代码文件取名CalculatorActivity.java、布局文件取名activity_calculator.xml。记得在AndroidManifest.xml中注册acitivity节点,不然App运行时会报ActivityNotFoundException异常,具体是在application节点下补充一行声明:

              <activity android:name=".CalculatorActivity" />

步骤02 在res/layout目录下创建布局文件activity_calculator.xml,按照简单计算器的效果图在里面填入各控件的布局结构,并指定相关的属性定义。

步骤03 在项目的包名目录下创建CalculatorActivity类,仿照MainActivity代码在onCreate内部的setContentView方法中填入参数R.layout.activity_calculator,表示该页面使用activity_calculator.xml中定义的界面布局。接着编写具体的控件操作与业务代码。

下面是计算器App的主要业务代码片段:

          public void onClick(View v) {
              int resid = v.getId();
              String inputText;
              if (resid == R.id.ib_sqrt) {
                inputText = "√";
              } else {
                inputText = ((TextView) v).getText().toString();
              }
              Log.d(TAG, "resid="+resid+", inputText="+inputText);
              if (resid == R.id.btn_clear) {
                clear("");
              } else if (resid == R.id.btn_cancel) {
                if (operator.equals("") == true) {
                    if (firstNum.length() == 1) {
                        firstNum = "0";
                    } else if (firstNum.length() > 0) {
                        firstNum = firstNum.substring(0, firstNum.length() -1);
                    } else {
                        Toast.makeText(this, "没有可取消的数字了", Toast.LENGTH_SHORT).show();
                        return;
                    }
                    showText=firstNum;
                    tv_result.setText(showText);
                  }else{
                    if(nextNum.length()==1){
                        nextNum="";
                    }else if(nextNum.length()>0){
                        nextNum=nextNum.substring(0, nextNum.length()-1);
                    }else{
                        Toast.makeText(this, "没有可取消的数字了", Toast.LENGTH_SHORT).show();
                        return;
                    }
                    showText=showText.substring(0, showText.length()-1);
                    tv_result.setText(showText);
                  }
              }else if(resid==R.id.btn_equal){
                  if(operator.length()==0||operator.equals("=")==true){
                    Toast.makeText(this, "请输入运算符", Toast.LENGTH_SHORT).show();
                    return;
                  }else if(nextNum.length()<=0){
                    Toast.makeText(this, "请输入数字", Toast.LENGTH_SHORT).show();
                    return;
                  }
                  if(caculate()==true){
                    operator=inputText;
                    showText=showText+"="+result;
                    tv_result.setText(showText);
                  }else{
                    return;
                  }
              }else if(resid==R.id.btn_plus||resid==R.id.btn_minus
                    ||resid==R.id.btn_multiply||resid==R.id.btn_divide){
                  if(firstNum.length()<=0){
                    Toast.makeText(this, "请输入数字", Toast.LENGTH_SHORT).show();
                    return;
                  }
                  if(operator.length()==0||operator.equals("=")==true||operator.equals("√")==true){
                    operator=inputText;  // 操作符
                    showText=showText+operator;
                    tv_result.setText(showText);
                  }else{
                    Toast.makeText(this, "请输入数字", Toast.LENGTH_SHORT).show();
                    return;
                }
              }else if(resid==R.id.ib_sqrt){
                if(firstNum.length()<=0){
                    Toast.makeText(this, "请输入数字", Toast.LENGTH_SHORT).show();
                    return;
                }else if(Double.parseDouble(firstNum)<0){
                    Toast.makeText(this, "开根号的数值不能小于0", Toast.LENGTH_SHORT).show();
                    return;
                }
                result=String.valueOf(Math.sqrt(Double.parseDouble(firstNum)));
                firstNum=result;
                nextNum="";
                operator=inputText;
                showText=showText+"√="+result;
                tv_result.setText(showText);
                Log.d(TAG, "result="+result+", firstNum="+firstNum+", operator="+operator);
              }else{
                if(operator.equals("=")==true){
                    operator="";
                    firstNum="";
                    showText="";
                }
                if(resid==R.id.btn_dot){
                    inputText=".";
                }
                if(operator.equals("")==true){
                    firstNum=firstNum+inputText;
                }else{
                    nextNum=nextNum+inputText;
                }
                showText=showText+inputText;
                tv_result.setText(showText);
              }
          }
          private String operator="";  // 操作符
          private String firstNum="";  // 前一个操作数
          private String nextNum="";  // 后一个操作数
          private String result="";  // 当前计算结果
          private String showText="";  // 显示的文本内容
          private boolean caculate(){  // 开始加减乘除四则运算
              if(operator.equals("+")==true){
                result=String.valueOf(Arith.add(firstNum, nextNum));
              } else if (operator.equals("-") == true) {
                  result = String.valueOf(Arith.sub(firstNum, nextNum));
              } else if (operator.equals("×") == true) {
                  result = String.valueOf(Arith.mul(firstNum, nextNum));
              } else if (operator.equals("÷") == true) {
                  if ("0".equals(nextNum)) {
                    Toast.makeText(this, "被除数不能为零", Toast.LENGTH_SHORT).show();
                    return false;
                  } else {
                    result = String.valueOf(Arith.div(firstNum, nextNum));
                  }
              }
              firstNum = result;
              nextNum = "";
              return true;
          }
          private void clear(String text){  // 清空并初始化
              showText = text;
              tv_result.setText(showText);
              operator = "";
              firstNum = "";
              nextNum = "";
              result = "";
          }