基于AngularJS实现iOS8自带的计算器


Posted in Javascript onSeptember 12, 2016

前言

首先创建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的按键要占用两个单元格来进行特殊处理。

到目前为止,已经完全实现了计算器的界面

效果图如下:

基于AngularJS实现iOS8自带的计算器

下面需要实现对按键的响应,按键包括数字键,运算符键,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方法分开了,可能看着不太适应。由于写的比较仓促且没有花太多时间去测试,可能存在一些bug,欢迎指出。同时由于水平有限,可能该方法不是最好,欢迎给出更好的方案一起交流学习~~以上就是这篇文章的全部内容了,希望对的大家的学习或者工作带来一定的帮助。

Javascript 相关文章推荐
定时器(setTimeout/setInterval)调用带参函数失效解决方法
Mar 26 Javascript
用JavaScript实现类似于ListBox功能示例代码
Mar 09 Javascript
iScroll中事件点击触发两次解决方案
Mar 11 Javascript
javascript实现图片自动和可控的轮播切换特效
Apr 13 Javascript
AngularJS实现Input格式化的方法
Nov 07 Javascript
基于Vue的SPA动态修改页面title的方法(推荐)
Jan 02 Javascript
vue获取当前点击的元素并传值的实例
Mar 09 Javascript
详解webpack打包第三方类库的正确姿势
Oct 20 Javascript
vue框架制作购物车小球动画效果实例代码
Sep 26 Javascript
Vue插槽_特殊特性slot,slot-scope与指令v-slot说明
Sep 04 Javascript
vue 动态组件(component :is) 和 dom元素限制(is)用法说明
Sep 04 Javascript
js重写alert事件(避免alert弹框标题出现网址)
Dec 04 Javascript
Javascript6中字符串的四个新用法分享
Sep 11 #Javascript
JavaScript制作简单分页插件
Sep 11 #Javascript
关于vue.js弹窗组件的知识点总结
Sep 11 #Javascript
JavaScript常用代码书写规范的超全面总结
Sep 11 #Javascript
Javascript实现前端简单的路由实例
Sep 11 #Javascript
JavaScript中Array的实用操作技巧分享
Sep 11 #Javascript
JS实现六边形3D拖拽翻转效果的方法
Sep 11 #Javascript
You might like
PHP中一个控制字符串输出的函数
2006/10/09 PHP
php获取当前网址url并替换参数或网址的方法
2010/06/06 PHP
thinkphp查询,3.X 5.0方法(亲试可行)
2017/06/17 PHP
PHP实现的自定义图像居中裁剪函数示例【测试可用】
2017/08/11 PHP
爆炸式的JS圆形浮动菜单特效代码
2010/03/03 Javascript
js+css使DIV始终居于屏幕中间 左下 左上 右上 右下的代码集合
2011/03/10 Javascript
EXTJS FORM HIDDEN TEXTFIELD 赋值 使用value不好用的问题
2011/04/16 Javascript
Javascript数组的排序 sort()方法和reverse()方法
2012/06/04 Javascript
jQuery点击弹出下拉菜单的小例子
2013/08/01 Javascript
js中将String转换为number以便比较
2014/07/08 Javascript
jquery实现拖动效果
2016/08/10 Javascript
简单实现JS倒计时效果
2016/12/23 Javascript
js正则表达式验证表单【完整版】
2017/03/06 Javascript
Bootstrap实现的标签页内容切换显示效果示例
2017/05/25 Javascript
AngularJS 仿微信图片手势缩放的实例
2017/09/28 Javascript
360doc网站不登录就无法复制内容的解决方法
2018/01/27 Javascript
layer弹出层父子页面事件相互调用方法
2018/08/17 Javascript
Vue.js点击切换按钮改变内容的实例讲解
2018/08/22 Javascript
vue计算属性无法监听到数组内部变化的解决方案
2019/11/06 Javascript
[01:29]Ti4循环赛第三日精彩回顾
2014/07/13 DOTA
老生常谈python之鸭子类和多态
2017/06/13 Python
Python3.7+tkinter实现查询界面功能
2019/12/24 Python
python+Selenium自动化测试——输入,点击操作
2020/03/06 Python
Python模块zipfile原理及使用方法详解
2020/08/04 Python
Scrapy基于scrapy_redis实现分布式爬虫部署的示例
2020/09/29 Python
使用css3 属性如何丰富图片样式(圆角 阴影 渐变)
2012/11/22 HTML / CSS
商务专员岗位职责
2013/11/23 职场文书
软件项目实施计划书
2014/05/02 职场文书
法制教育演讲稿
2014/09/10 职场文书
四风问题民主生活会对照检查材料思想汇报
2014/09/27 职场文书
信访工作汇报材料
2014/10/27 职场文书
2014年客服工作总结范文
2014/11/13 职场文书
2014年保卫工作总结
2014/12/05 职场文书
2015企业年终工作总结范文
2015/05/27 职场文书
2016年暑期社会实践活动总结报告
2016/04/06 职场文书
全家福照片寄语怎么写?
2019/04/02 职场文书