基于AngularJS實(shí)現(xiàn)iOS8自帶的計(jì)算器
前言
首先創(chuàng)建angularjs的基本項(xiàng)目就不說(shuō)了,最好是利用yeoman這個(gè)腳手架工具直接生成,如果沒(méi)有該環(huán)境的,當(dāng)然也可以通過(guò)自行下載angularjs的文件引入項(xiàng)目。
實(shí)例詳解
main.js是項(xiàng)目的主要js文件,所有的js都寫(xiě)在這個(gè)文件中,初始化之后,該文件的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是用來(lái)雙向綁定顯示運(yùn)算結(jié)果的,data為計(jì)算器鍵盤上的數(shù)字和符號(hào)。
該項(xiàng)目相關(guān)的所有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標(biāo)簽是用來(lái)顯示輸入記錄的,就是說(shuō)你按下的所有鍵都會(huì)顯示在上面,便于查看結(jié)果,history為當(dāng)前scope下面的一個(gè)數(shù)組,后面會(huì)講解。這里使用一個(gè)textarea來(lái)作為計(jì)算結(jié)果的顯示屏幕,主要是為了使用雙向綁定的特性。同時(shí)生成計(jì)算器各個(gè)按鍵和界面元素都是通過(guò)對(duì)data對(duì)象進(jìn)行 循環(huán)遍歷來(lái)生成的,showClass方法是scope下面的一個(gè)方法,用來(lái)獲取不規(guī)則界面顯示元素的class屬性,后面會(huì)講解,showResult方法就是對(duì)按鍵響應(yīng)的主方法,我們所有對(duì)按鍵的按下響應(yīng)都是通過(guò)這個(gè)方法來(lái)的,后面會(huì)詳細(xì)講解。
showClass方法代碼如下:
//顯示計(jì)算器樣式
$scope.showClass=function(index,a){
if(a==0){
return "zero";
}
return index==3||a=="="?"end-no":"normal";
};
這個(gè)方法主要是針對(duì)每行的最后一列要顯示為橘黃色和對(duì)于顯示0的按鍵要占用兩個(gè)單元格來(lái)進(jìn)行特殊處理。
到目前為止,已經(jīng)完全實(shí)現(xiàn)了計(jì)算器的界面
效果圖如下:

下面需要實(shí)現(xiàn)對(duì)按鍵的響應(yīng),按鍵包括數(shù)字鍵,運(yùn)算符鍵,AC鍵,每種按鍵按下都會(huì)有不同相應(yīng)并且按鍵之間是存在聯(lián)系的
為了使代碼容易講解,采用分段性給出showResult方法的代碼然后進(jìn)行詳細(xì)解釋的方法。
首先,這里要添加幾個(gè)變量進(jìn)行控制和存儲(chǔ)之用。
//計(jì)算時(shí)用的數(shù)字的棧 $scope.num=[]; $scope.history=[]; //接受輸入用的運(yùn)算符棧 $scope.opt=[]; //計(jì)算器計(jì)算結(jié)果 $scope.result=""; //表示是否要重新開(kāi)始顯示,為true表示不重新顯示,false表示要清空當(dāng)前輸出重新顯示數(shù)字 $scope.flag=true; //表示當(dāng)前是否可以再輸入運(yùn)算符,如果可以為true,否則為false $scope.isOpt=true;
num數(shù)組實(shí)際上是一個(gè)棧,用來(lái)接收用戶輸入的數(shù)字,具體用法后面會(huì)講解,history數(shù)組為用戶輸入的所有按鍵,每次按下就讓該按鍵上的符號(hào)或數(shù)字進(jìn)棧,然后使用綁定實(shí)時(shí)顯示在界面上。opt數(shù)組是另外一個(gè)棧,用來(lái)接收用戶輸入的運(yùn)算符。具體用法后面會(huì)講解,flag是一個(gè)標(biāo)志,為true的時(shí)候表示在按下數(shù)字的過(guò)程中被按下的數(shù)字是當(dāng)前顯示數(shù)字的一部分,需要跟在其后面顯示,比如當(dāng)前界面顯示的是12,再按下3的時(shí)候會(huì)判斷該標(biāo)志,如果為true,就顯示123,否則就清空界面,直接顯示3.isOpt是另外一個(gè)標(biāo)志,主要是為了防止用戶在輸入過(guò)程中對(duì)運(yùn)算符的非法輸入,比如說(shuō)用戶接連輸入了1+2+,當(dāng)輸?shù)竭@里是,下面輸入的應(yīng)該是一個(gè)數(shù)字,但是用戶卻輸入了一個(gè)運(yùn)算符,通過(guò)判斷這個(gè)標(biāo)志,會(huì)讓計(jì)算器忽略這個(gè)非法的運(yùn)算符,讓輸入依然保持1+2+。
下面的代碼分段給出,完整的代碼就是將它們連接起來(lái)。
$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;
//如果點(diǎn)擊的是個(gè)數(shù)字
if(reg.test(a)) {
//消除凍結(jié)
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方法是用來(lái)初始化一些變量和標(biāo)志,讓它們回到原始狀態(tài)。showResult方法是顯示界面響應(yīng)用戶操作的主方法,上面的代碼是該方法中的一個(gè)if分支,表示如果輸入的是一個(gè)數(shù)字,那么如果對(duì)運(yùn)算符的輸入已經(jīng)被凍結(jié)(當(dāng)前不允許輸入運(yùn)算符了,輸入后會(huì)被忽略),那么輸入數(shù)字的時(shí)候,就解開(kāi)凍結(jié)狀態(tài),以便下次輸入運(yùn)算符的時(shí)候會(huì)進(jìn)入運(yùn)算符棧。如果當(dāng)前顯示的結(jié)果不為空并且現(xiàn)在按下的數(shù)字是當(dāng)前顯示的數(shù)字的一部分并且沒(méi)有發(fā)生錯(cuò)誤,那么顯示的結(jié)果就是當(dāng)前按下的數(shù)字接在當(dāng)前顯示數(shù)字的末尾,否則就代表重新顯示,重新顯示的時(shí)候需要讓下次再輸入的數(shù)字接在這個(gè)數(shù)字后面顯示。
js代碼(接上)
//如果點(diǎn)擊的是AC
else if(a=="AC"){
$scope.result=0;
$scope.init();
}
如果點(diǎn)擊的是AC,那么代表初始化,讓顯示結(jié)果為0,清空所有狀態(tài)。
js代碼(接上)
//如果點(diǎn)擊的是個(gè)小數(shù)點(diǎn)
else if(a=="."){
if($scope.result!=""&&!regDot.test($scope.result)){
$scope.result+=a;
}
}
如果點(diǎn)擊的是個(gè)小數(shù)點(diǎn),則在當(dāng)前顯示不為空并且當(dāng)前顯示的結(jié)果里面不存在小數(shù)點(diǎn)的情況下讓這個(gè)小數(shù)點(diǎn)接在當(dāng)前顯示的末尾。
js代碼(接上)
//如果點(diǎn)擊的是個(gè)取反操作符
else if(regAbs.test(a)){
if($scope.result>0){
$scope.result="-"+$scope.result;
}
else{
$scope.result=Math.abs($scope.result);
}
}
如果點(diǎn)擊的是個(gè)取反操作,則將當(dāng)前顯示結(jié)果取反
js代碼(接上)
//如果點(diǎn)擊的是個(gè)百分號(hào)
else if(a=="%"){
$scope.result=$scope.format(Number($scope.result)/100);
}
如果點(diǎn)擊的是個(gè)百分號(hào),則將當(dāng)前顯示結(jié)果除以100之后再顯示,這里有個(gè)format函數(shù)
其代碼如下:
//格式化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自帶的計(jì)算器不會(huì)無(wú)限顯示很多位的數(shù)字,如果超過(guò)10位(包括小數(shù)點(diǎn)),則采用科學(xué)計(jì)算法來(lái)顯示,這里為了簡(jiǎn)便,對(duì)于含有小數(shù)點(diǎn)且超過(guò)10位的顯示結(jié)果采用科學(xué)計(jì)算法計(jì)算的時(shí)候,讓它保留小數(shù)點(diǎn)之后3位顯示。
js代碼(showResult部分接上)
//如果點(diǎn)擊的是個(gè)運(yùn)算符且當(dāng)前顯示結(jié)果不為空和error
else if($scope.checkOperator(a)&&$scope.result!=""&&$scope.result!="error"&&$scope.isOpt){
$scope.flag=false;
$scope.num.push($scope.result);
$scope.operation(a);
//點(diǎn)擊一次運(yùn)算符之后需要將再次點(diǎn)擊運(yùn)算符的情況忽略掉
$scope.isOpt=false;
}
這個(gè)分支是最復(fù)雜的一個(gè)分支,它代表如果輸入的是一個(gè)運(yùn)算符,那么就要進(jìn)行運(yùn)算了。進(jìn)入到這個(gè)分支,需要首先將flag置為false,作用是下次再輸入數(shù)字就是重新輸入數(shù)字而不是接著當(dāng)前顯示結(jié)果輸入了。
然后要讓當(dāng)前顯示的數(shù)字作為被運(yùn)算的數(shù)字首先進(jìn)入到數(shù)字棧中,operation方法就是運(yùn)算方法,因?yàn)檫@次已經(jīng)點(diǎn)擊了一個(gè)運(yùn)算符,所以下次再點(diǎn)擊就要忽略這個(gè)運(yùn)算符,將isOpt置為false。
operation代碼如下
//比較當(dāng)前輸入的運(yùn)算符和運(yùn)算符棧棧頂運(yùn)算符的優(yōu)先級(jí)
//如果棧頂運(yùn)算符優(yōu)先級(jí)小,則將當(dāng)前運(yùn)算符進(jìn)棧,并且不計(jì)算,
//否則棧頂運(yùn)算符出棧,且數(shù)字棧連續(xù)出棧兩個(gè)元素,進(jìn)行計(jì)算
//然后將當(dāng)前運(yùn)算符進(jìn)棧。
$scope.operation=function(current){
//如果運(yùn)算符棧為空,直接將當(dāng)前運(yùn)算符入棧
if(!$scope.opt.length){
$scope.opt.push(current);
return;
}
var operator,right,left;
var lastOpt=$scope.opt[$scope.opt.length-1];
//如果當(dāng)前運(yùn)算符優(yōu)先級(jí)大于last運(yùn)算符,僅進(jìn)棧
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);
}
};
該方法接受當(dāng)前輸入的運(yùn)算符作為參數(shù),其核心思想為,當(dāng)前接收到了一個(gè)運(yùn)算符,如果運(yùn)算符棧為空,則將當(dāng)前運(yùn)算符入棧,然后這種情況就不用再做什么了。如果當(dāng)前運(yùn)算符棧不為空,那么彈出當(dāng)前運(yùn)算符棧的棧頂元素,讓當(dāng)前接收的運(yùn)算符和棧頂運(yùn)算符比較優(yōu)先級(jí)(乘除優(yōu)先級(jí)大于加減,同一優(yōu)先級(jí)的情況下棧頂運(yùn)算符優(yōu)先級(jí)較高,因?yàn)橄热霔5模?。isPri方法用來(lái)判斷優(yōu)先級(jí)的,接收兩個(gè)參數(shù),第一個(gè)為當(dāng)前接收的運(yùn)算符,第二個(gè)為出棧的棧頂運(yùn)算符,如果按照前面所說(shuō)的規(guī)則,當(dāng)前運(yùn)算符的優(yōu)先級(jí)較高,那么就直接將這個(gè)運(yùn)算符入棧。如果當(dāng)前運(yùn)算符優(yōu)先級(jí)小于棧頂運(yùn)算符,那么就需要進(jìn)行計(jì)算并更改計(jì)算器的顯示了,將運(yùn)算數(shù)字棧棧頂兩個(gè)元素依次彈出,分別作為一次運(yùn)算的兩個(gè)運(yùn)算數(shù)字,然后彈出運(yùn)算符棧的棧頂元素,作為本次運(yùn)算的運(yùn)算符,調(diào)用calculate方法進(jìn)行運(yùn)算
該方法代碼如下
//負(fù)責(zé)計(jì)算結(jié)果函數(shù)
$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;
}
};
該方法接受三個(gè)參數(shù),左運(yùn)算數(shù)字,中間的運(yùn)算符和右邊的運(yùn)算數(shù)字,按照加減乘除法運(yùn)算后更改result顯示結(jié)果并將計(jì)算結(jié)果入棧到運(yùn)算數(shù)字棧中,這里需要注意如果運(yùn)算的是除法并且除數(shù)是0,則發(fā)生了錯(cuò)誤,顯示錯(cuò)誤,清空所有狀態(tài),否則正常運(yùn)算。
一次運(yùn)算完成之后,運(yùn)算符棧和數(shù)字棧中的狀態(tài)都會(huì)被更改,而目前的按鍵current值還沒(méi)有入棧,所以又要重復(fù)上述過(guò)程進(jìn)行優(yōu)先級(jí)比較后在運(yùn)算,實(shí)際上是一個(gè)遞歸的過(guò)程,直到運(yùn)算符棧為空或者當(dāng)前運(yùn)算符的優(yōu)先級(jí)高于運(yùn)算符棧的棧頂運(yùn)算符。isPri方法是用來(lái)判斷運(yùn)算符優(yōu)先級(jí)的
代碼如下:
//判斷當(dāng)前運(yùn)算符是否優(yōu)先級(jí)高于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;
}
}
};
判斷規(guī)則前面已經(jīng)講述。
此外還有一個(gè)checkOperator方法,是判斷輸入的符號(hào)是不是加減乘除四則運(yùn)算符號(hào)
代碼如下:
//判斷當(dāng)前符號(hào)是否是可運(yùn)算符號(hào)
$scope.checkOperator=function(opt){
if(opt=="+"||opt=="-"||opt=="×"||opt=="÷"){
return true;
}
return false;
}
如果是就返回true,否則返回false。
到目前為止,還有一個(gè)輸入等于號(hào)的分支沒(méi)有
其代碼如下(接showResult方法)
//如果點(diǎn)擊的是等于號(hào)
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);
}
}
};
如果輸入的是等于號(hào),則首先將flag置為false,允許下次輸入數(shù)字的時(shí)候界面重新顯示,并且要將當(dāng)前顯示的數(shù)字作為運(yùn)算數(shù)字入棧到數(shù)字棧。然后就要進(jìn)行不斷的出棧運(yùn)算直到運(yùn)算符棧為空才能夠停止。
總結(jié)
上面就是實(shí)現(xiàn)的主要代碼和過(guò)程,由于分支代碼較多而一次全部給出所有分支又不能夠詳細(xì)講述,所以將showResult方法分開(kāi)了,可能看著不太適應(yīng)。由于寫(xiě)的比較倉(cāng)促且沒(méi)有花太多時(shí)間去測(cè)試,可能存在一些bug,歡迎指出。同時(shí)由于水平有限,可能該方法不是最好,歡迎給出更好的方案一起交流學(xué)習(xí)~~以上就是這篇文章的全部?jī)?nèi)容了,希望對(duì)的大家的學(xué)習(xí)或者工作帶來(lái)一定的幫助。
相關(guān)文章
AngularJS實(shí)現(xiàn)與Java Web服務(wù)器交互操作示例【附demo源碼下載】
這篇文章主要介紹了AngularJS實(shí)現(xiàn)與Java Web服務(wù)器交互操作的方法,結(jié)合實(shí)例形式較為詳細(xì)的分析了AngularJS前臺(tái)ajax提交與javascript后臺(tái)處理的完整流程與實(shí)現(xiàn)技巧,并附帶demo源碼供讀者下載參考,需要的朋友可以參考下2016-11-11
Angular4實(shí)現(xiàn)鼠標(biāo)懸停3d傾斜效果
這篇文章主要介紹了Angular4實(shí)現(xiàn)鼠標(biāo)懸停3d傾斜效果,非常不錯(cuò),具有參考借鑒價(jià)值,需要的朋友可以參考下2017-10-10
angular 實(shí)現(xiàn)的輸入框數(shù)字千分位及保留幾位小數(shù)點(diǎn)功能示例
這篇文章主要介紹了angular 實(shí)現(xiàn)的輸入框數(shù)字千分位及保留幾位小數(shù)點(diǎn)功能,涉及AngularJS數(shù)值運(yùn)算、正則匹配等相關(guān)操作技巧,需要的朋友可以參考下2018-06-06
AngularJS使用ng-Cloak阻止初始化閃爍問(wèn)題的方法
這篇文章主要介紹了AngularJS使用ng-Cloak阻止初始化閃爍問(wèn)題的方法,結(jié)合實(shí)例形式分析了AngularJS使用ng-Cloak來(lái)解決初始化時(shí)出現(xiàn)閃爍問(wèn)題的相關(guān)技巧,需要的朋友可以參考下2016-11-11
前后端如何實(shí)現(xiàn)登錄token攔截校驗(yàn)詳解
這篇文章主要給大家介紹了關(guān)于前后端如何實(shí)現(xiàn)登錄token攔截校驗(yàn)的相關(guān)資料,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2018-09-09
angularJS開(kāi)發(fā)注意事項(xiàng)
本篇文章給大家分享了angularJS開(kāi)發(fā)注意事項(xiàng)以及相關(guān)知識(shí)點(diǎn),對(duì)此有興趣的朋友參考學(xué)習(xí)下。2018-05-05
angularjs實(shí)現(xiàn)多張圖片上傳并預(yù)覽功能
這篇文章主要為大家詳細(xì)介紹了angularjs實(shí)現(xiàn)多張圖片上傳并預(yù)覽功能,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2017-02-02
angular 動(dòng)態(tài)組件類型詳解(四種組件類型)
這篇文章給大家講解四種組件類型,非常不錯(cuò),具有參考借鑒價(jià)值,對(duì)angular 動(dòng)態(tài)組件類型感興趣的朋友參考下吧2017-02-02

