angularjs实现IOS8自带计算器

最近看到一则面试题目,要求使用angularjs实现一个计算器,利用放假时间实现了一个仿iOS8风格的计算器,功能基本和iOS自带的计算器是一致的。

查看demo,接着给出实现过程。

首先创建angularjs的基本项目就不说了,最好是利用yeoman这个脚手架工具直接生成,如果没有该环境的,当然也可以通过自行下载angularjs的文件引入项目。

main.js是项目的主要js文件,所有的js都写在这个文件中,初始化之后,该文件的js代码如下

angular
  .module(‘calculatorApp‘, [
    ‘ngAnimate‘,
    ‘ngCookies‘,
    ‘ngResource‘,
    ‘ngRoute‘,
    ‘ngSanitize‘,
    ‘ngTouch‘
  ])
  .controller(‘MainCtrl‘, function ($scope) {
    $scope.result="";
    $scope.data={
        "1":["AC","+/-","%","÷"],
        "2":["7","8","9","×"],
        "3":["4","5","6","-"],
        "4":["1","2","3","+"],
        "5":["0",".","="]
    };
 });

这里的result是用来双向绑定显示运算结果的,data为计算器键盘上的数字和符号。

该项目相关的所有css代码如下:

*{
    margin:0;
    padding:0;
}

body {
  padding-top: 20px;
  padding-bottom: 20px;
}
h1{
    text-align:center;
    color:#3385ff;
}
.main{
    margin:20px auto;
    border:1px solid #202020;
    border-bottom: none;
    width:60%;
    height:600px;
}

.result{
    display: block;
    width: 100%;
    height: 30%;
    background:#202020;
    box-sizing: border-box;
    border:none;
    padding: 0;
    margin: 0;
    resize: none;
    color: #fff;
    font-size: 80px;
    text-align: right;
    line-height: 270px;
    overflow: hidden;
    background-clip: border-box;

}
.row{
    height: 14%;
    background: #d7d8da;
    box-sizing: border-box;
    border-bottom: 1px solid #202020;
    overflow: hidden;
}

.col{
    height: 100%;
    box-sizing: border-box;
    border-right:1px solid #202020;
    float: left;
    color: #202020;
    font-size: 28px;
    text-align: center;
    line-height: 83px;
}
.normal{
    width: 25%;
}
.end-no{
    width: 25%;
    border-right: none;
    background: #f78e11;
    color: #fff;

}
.zero{
width: 50%;
}
.history{
    background:#3385ff ;
    color:#fff;
    font-size: 22px;
    text-align: center;
}

然后是html的布局如下:

<body ng-app="calculatorApp" >
<h1>calculator for ios8</h1>
     <hr/>
      <p class="history">{{ history.join(" ") }}</p>
<div class="main">
        <textarea ng-model="result" class="result" ></textarea>
    <div ng-repeat="item in data" class="row">
        <div class="col"  ng-repeat="a in item" ng-class="showClass($index,a)" ng-click="showResult(a)">{{ a }}</div>
    </div>
</div>
</body>

这里class为history的p标签是用来显示输入记录的,就是说你按下的所有键都会显示在上面,便于查看结果,history为当前scope下面的一个数组,后面会讲解。这里使用一个textarea来作为计算结果的显示屏幕,主要是为了使用双向绑定的特性。同时生成计算器各个按键和界面元素都是通过对data对象进行 循环遍历来生成的,showClass方法是scope下面的一个方法,用来获取不规则界面显示元素的class属性,后面会讲解,showResult方法就是对按键响应的主方法,我们所有对按键的按下响应都是通过这个方法来的,后面会详细讲解。

showClass方法代码如下:

     //显示计算器样式
   $scope.showClass=function(index,a){
       if(a==0){
           return "zero";
       }
       return index==3||a=="="?"end-no":"normal";
   };

这个方法主要是针对每行的最后一列要显示为橘黄色和对于显示0的按键要占用两个单元格来进行特殊处理。

到目前为止,已经完全实现了计算器的界面,结果如下:

下面需要实现对按键的响应,按键包括数字键,运算符键,AC键,每种按键按下都会有不同相应并且按键之间是存在联系的

为了使代码容易讲解,采用分段性给出showResult方法的代码然后进行详细解释的方法。

首先,这里要添加几个变量进行控制和存储之用。

 //计算时用的数字的栈
    $scope.num=[];
    $scope.history=[];
     //接受输入用的运算符栈
    $scope.opt=[];
        //计算器计算结果
    $scope.result="";
  //表示是否要重新开始显示,为true表示不重新显示,false表示要清空当前输出重新显示数字
    $scope.flag=true;
 //表示当前是否可以再输入运算符,如果可以为true,否则为false
    $scope.isOpt=true;

num数组实际上是一个栈,用来接收用户输入的数字,具体用法后面会讲解,history数组为用户输入的所有按键,每次按下就让该按键上的符号或数字进栈,然后使用绑定实时显示在界面上。opt数组是另外一个栈,用来接收用户输入的运算符。具体用法后面会讲解,flag是一个标志,为true的时候表示在按下数字的过程中被按下的数字是当前显示数字的一部分,需要跟在其后面显示,比如当前界面显示的是12,再按下3的时候会判断该标志,如果为true,就显示123,否则就清空界面,直接显示3.isOpt是另外一个标志,主要是为了防止用户在输入过程中对运算符的非法输入,比如说用户接连输入了1+2+,当输到这里是,下面输入的应该是一个数字,但是用户却输入了一个运算符,通过判断这个标志,会让计算器忽略这个非法的运算符,让输入依然保持1+2+。

下面的代码分段给出,完整的代码就是将它们连接起来。

$scope.init=function(){
       $scope.num=[];
       $scope.opt=[];
       $scope.history=[];
       $scope.flag = true;
       $scope.isOpt=true;

   } ;
 $scope.showResult=function(a){
       $scope.history.push(a);
       var reg=/\d/ig,regDot=/\./ig,regAbs=/\//ig;
       //如果点击的是个数字
       if(reg.test(a)) {
           //消除冻结
           if($scope.isOpt==false){
               $scope.isOpt=true;
           }

           if ($scope.result != 0 && $scope.flag && $scope.result != "error") {
               $scope.result += a;
           }
           else {
               $scope.result = a;
               $scope.flag = true;
           }

       }

init方法是用来初始化一些变量和标志,让它们回到原始状态。showResult方法是显示界面响应用户操作的主方法,上面的代码是该方法中的一个if分支,表示如果输入的是一个数字,那么如果对运算符的输入已经被冻结(当前不允许输入运算符了,输入后会被忽略),那么输入数字的时候,就解开冻结状态,以便下次输入运算符的时候会进入运算符栈。如果当前显示的结果不为空并且现在按下的数字是当前显示的数字的一部分并且没有发生错误,那么显示的结果就是当前按下的数字接在当前显示数字的末尾,否则就代表重新显示,重新显示的时候需要让下次再输入的数字接在这个数字后面显示。

js代码(接上)

 //如果点击的是AC
       else if(a=="AC"){
           $scope.result=0;
           $scope.init();
       }

如果点击的是AC,那么代表初始化,让显示结果为0,清空所有状态。

js代码(接上)

  //如果点击的是个小数点
       else if(a=="."){
           if($scope.result!=""&&!regDot.test($scope.result)){
               $scope.result+=a;
           }
       }

如果点击的是个小数点,则在当前显示不为空并且当前显示的结果里面不存在小数点的情况下让这个小数点接在当前显示的末尾。

js代码(接上)

 //如果点击的是个取反操作符
       else if(regAbs.test(a)){
           if($scope.result>0){
               $scope.result="-"+$scope.result;
           }
           else{
               $scope.result=Math.abs($scope.result);
           }
       }

如果点击的是个取反操作,则将当前显示结果取反

js代码(接上)

  //如果点击的是个百分号
       else if(a=="%"){
           $scope.result=$scope.format(Number($scope.result)/100);

       }

如果点击的是个百分号,则将当前显示结果除以100之后再显示,这里有个format函数,其代码如下:

  //格式化result输出
        $scope.format=function(num){
        var regNum=/.{10,}/ig;
         if(regNum.test(num)){
             if(/\./.test(num)){
                 return num.toExponential(3);
             }
             else{
                 return num.toExponential();
             }
         }
            else{
             return num;
         }
        }

它的作用主要是ios8自带的计算器不会无限显示很多位的数字,如果超过10位(包括小数点),则采用科学计算法来显示,这里为了简便,对于含有小数点且超过10位的显示结果采用科学计算法计算的时候,让它保留小数点之后3位显示。

js代码(showResult部分接上)

  //如果点击的是个运算符且当前显示结果不为空和error
       else if($scope.checkOperator(a)&&$scope.result!=""&&$scope.result!="error"&&$scope.isOpt){
            $scope.flag=false;
            $scope.num.push($scope.result);
           $scope.operation(a);
           //点击一次运算符之后需要将再次点击运算符的情况忽略掉
           $scope.isOpt=false;
       }

这个分支是最复杂的一个分支,它代表如果输入的是一个运算符,那么就要进行运算了。进入到这个分支,需要首先将flag置为false,作用是下次再输入数字就是重新输入数字而不是接着当前显示结果输入了。

然后要让当前显示的数字作为被运算的数字首先进入到数字栈中,operation方法就是运算方法,因为这次已经点击了一个运算符,所以下次再点击就要忽略这个运算符,将isOpt置为false。

operation代码如下

        //比较当前输入的运算符和运算符栈栈顶运算符的优先级
        //如果栈顶运算符优先级小,则将当前运算符进栈,并且不计算,
        //否则栈顶运算符出栈,且数字栈连续出栈两个元素,进行计算
        //然后将当前运算符进栈。
        $scope.operation=function(current){
            //如果运算符栈为空,直接将当前运算符入栈
           if(!$scope.opt.length){
              $scope.opt.push(current);
               return;
           }
           var  operator,right,left;
           var  lastOpt=$scope.opt[$scope.opt.length-1];
            //如果当前运算符优先级大于last运算符,仅进栈
            if($scope.isPri(current,lastOpt)){
                $scope.opt.push(current);
            }
            else{
                    operator=$scope.opt.pop();
                    right=$scope.num.pop();
                    left=$scope.num.pop();
                    $scope.calculate(left,operator,right);
                    $scope.operation(current);
                }
        };

该方法接受当前输入的运算符作为参数,其核心思想为,当前接收到了一个运算符,如果运算符栈为空,则将当前运算符入栈,然后这种情况就不用再做什么了。如果当前运算符栈不为空,那么弹出当前运算符栈的栈顶元素,让当前接收的运算符和栈顶运算符比较优先级(乘除优先级大于加减,同一优先级的情况下栈顶运算符优先级较高,因为先入栈的)。isPri方法用来判断优先级的,接收两个参数,第一个为当前接收的运算符,第二个为出栈的栈顶运算符,如果按照前面所说的规则,当前运算符的优先级较高,那么就直接将这个运算符入栈。如果当前运算符优先级小于栈顶运算符,那么就需要进行计算并更改计算器的显示了,将运算数字栈栈顶两个元素依次弹出,分别作为一次运算的两个运算数字,然后弹出运算符栈的栈顶元素,作为本次运算的运算符,调用calculate方法进行运算,该方法代码如下

  //负责计算结果函数
       $scope.calculate=function(left,operator,right) {
           switch (operator) {
               case "+":
                   $scope.result = $scope.format(Number(left) + Number(right));
                   $scope.num.push($scope.result);
                   break;
               case "-":
                   $scope.result = $scope.format(Number(left) - Number(right));
                   $scope.num.push($scope.result);
                   break;
               case "×":
                   $scope.result = $scope.format(Number(left) * Number(right));
                   $scope.num.push($scope.result);
                   break;
               case "÷":
                   if(right==0){
                       $scope.result="error";
                       $scope.init();
                   }
                   else{
                       $scope.result = $scope.format(Number(left) / Number(right));
                       $scope.num.push($scope.result);
                   }
                   break;
               default:break;
           }
       };

该方法接受三个参数,左运算数字,中间的运算符和右边的运算数字,按照加减乘除法运算后更改result显示结果并将计算结果入栈到运算数字栈中,这里需要注意如果运算的是除法并且除数是0,则发生了错误,显示错误,清空所有状态,否则正常运算。

一次运算完成之后,运算符栈和数字栈中的状态都会被更改,而目前的按键current值还没有入栈,所以又要重复上述过程进行优先级比较后在运算,实际上是一个递归的过程,直到运算符栈为空或者当前运算符的优先级高于运算符栈的栈顶运算符。isPri方法是用来判断运算符优先级的,代码如下:

 //判断当前运算符是否优先级高于last,如果是返回true
        //否则返回false
      $scope.isPri=function(current,last){
          if(current==last){
              return false;
          }
          else {
              if(current=="×"||current=="÷"){
                  if(last=="×"||last=="÷"){
                      return false;
                  }
                  else{
                      return true;
                  }
              }
              else{
                  return false;
              }
          }
      };

判断规则前面已经讲述。

此外还有一个checkOperator方法,是判断输入的符号是不是加减乘除四则运算符号,代码如下:

  //判断当前符号是否是可运算符号
       $scope.checkOperator=function(opt){
           if(opt=="+"||opt=="-"||opt=="×"||opt=="÷"){
               return true;
           }
           return false;
       }

如果是就返回true,否则返回false。

到目前为止,还有一个输入等于号的分支没有,其代码如下(接showResult方法)

 //如果点击的是等于号
       else if(a=="="&&$scope.result!=""&&$scope.result!="error"){
           $scope.flag=false;
           $scope.num.push($scope.result);
           while($scope.opt.length!=0){
               var operator=$scope.opt.pop();
               var right=$scope.num.pop();
               var left=$scope.num.pop();
               $scope.calculate(left,operator,right);
           }
       }
   };

如果输入的是等于号,则首先将flag置为false,允许下次输入数字的时候界面重新显示,并且要将当前显示的数字作为运算数字入栈到数字栈。然后就要进行不断的出栈运算直到运算符栈为空才能够停止。

上面就是实现的主要代码和过程,由于分支代码较多而一次全部给出所有分支又不能够详细讲述,所以将showResult方法分开了,可能看着不太适应,这里是完整代码以及demo查看地址。由于写的比较仓促且没有花太多时间去测试,可能存在一些bug,欢迎指出。同时由于水平有限,可能该方法不是最好,欢迎给出更好的方案一起交流学习~~

时间: 2024-10-13 14:52:07

angularjs实现IOS8自带计算器的相关文章

网页布局实例————win7自带计算器

晚上7点左右开始进行布局,9点半正好搞定.发现自己专注的时候效率真的挺高的哈.在进行计算器布局之前,做过两个简单的网页布局练练手,今晚整体感觉还好,难度不大.但是在两个浏览器之间调试有点蛋疼,真心不想搭理IE. 在进行布局之前,我先把win7自带的计算器整体进行裁剪测量了一下,然后了解到大致的尺寸,然后画了张草图(如下),真是丑的不忍心看,我自己都觉得不像我的风格,实在是太丑了.这样子,对于全局的把握起到了一个很好的作用,最起码心里有数应该怎么怎么搞了. 先定义好盒子,把整体框架弄出来,然后在一

DevExpress:带计算器功能的文本框CalcEdit

本文为我的.NET控件库DevExpress使用笔记,我的DevExpress版本为13.1 1.控件类型全称:DevExpress.XtraEditors.CalcEdit 2.控件所在程序集:DevExpress.XtraEditors.v13.1.dll 3.工具箱内分类:DX.13.1: Common Controls 4.控件样式截图 5.CalcEdit控件是一个用于显示数字的文本框,文本框的内容,可以通过点击右侧按钮弹出的计算器计算得出 6.CalcEdit的功能与Windows提

用angularJS写的第一个计算器

一直想学习angularJS, 但是不知道angularJS用在页面的好处是什么, 今天我有一个粗陋的理解,记录下来. <script src="http://apps.bdimg.com/libs/angular.js/1.2.16/angular.min.js"></script> <div ng-controller="myCalc"> 单价: <span ng-model="number1">

使用系统自带计算器进行二进制运算

int x =110; int y =10; Console.WriteLine(x|= y); Console.WriteLine(x&= ~y); 想亲自算一下这种计算的时候,打开windows自带的计算器calc.exe 调到-程序员计算器-模式即可 选择DEC(十进制),然后输入,对应的可以看到其他进制的值. ------------------------------------------------------------------------------------- x|=

创建 AngularJS 自定义过滤器,带自定义参数

Angularjs过滤器是 angularjs非常棒的特性之一.有朝一日,你可能需要使用自定义过滤器,幸运的是,你找到了这篇博文. 下面显示的是自定义过滤器长什么样子(请注意myfilter): <tr ng-repeat="friend in friends |myfilter:'param1':'param2':true:'windowScopedFilter'"> 我们的自定义过滤器叫做 "myfilter", 它有由 ':'隔开的4个参数. 这是

iOS8自带模糊效果

typedef NS_ENUM(NSInteger, UIBlurEffectStyle) {     UIBlurEffectStyleExtraLight,     UIBlurEffectStyleLight,     UIBlurEffectStyleDark } NS_ENUM_AVAILABLE_IOS(8_0); UIBlurEffect *blurEffect = [UIBlurEffect effectWithStyle:UIBlurEffectStyleDark]; UIVi

带你走近AngularJS - 创建自定义指令

为什么使用AngularJS 指令? 使用过 AngularJS 的朋友应该最感兴趣的是它的指令.现今市场上的前端框架也只有AngularJS 拥有自定义指令的功能,并且AngularJS 是目前唯一提供Web应用可复用能力的框架. 目前有很多JavaScript 产品提供插件给Web开发人员.例如, Bootstrap 就是当前比较流行的提供样式和JavaScript插件的前端开发工具包.但是开发人员在使用Booostrap中的插件时, 必须切换到JavaScript 模式来写 jQuery

带你走近AngularJS - 体验指令实例

带你走近AngularJS系列: 带你走近AngularJS - 基本功能介绍 带你走近AngularJS - 体验指令实例 带你走近AngularJS - 创建自定义指令 ------------------------------------------------------------------------------------------------ 之前我们已经介绍了所有的AngularJS 基础知识,下面让我们通过实例来加深记忆,体验自定义指令的乐趣. 手风琴指令 我们展示的第

Android 计算器制作 2.注册View 构建函数

鄙人新手 整了 快两天 终于搞定了.. 1.首先是MainActivity 中 在Oncreate函数中 注册 2.按+ 或者 - 号 来分成两大字符串 s1 和 s2 再将s2 分为更小的s1 和 s2 直到 s2不包含+ 或者 -号 思路就是这样,没用递归,递归速度太慢,也不擅长用递归. 3.就是找各种Bug 还有装饰 基本达成目标 和自己手机上安卓的自带计算器基本一样. 项目代码如下: 链接: 链接: http://pan.baidu.com/s/1sj65nop 密码: 926r 看到还