「Flutter 實戰」簡約而不簡單的計算器


「Flutter 實戰」簡約而不簡單的計算器


先看一下效果:

「Flutter 實戰」簡約而不簡單的計算器

大家學習UI編程語言時喜歡用哪個 App 當作第一個練手的項目呢?,我喜歡使用 計算器 ,可能是習慣了吧,學習 Android 和 React Native 都用此 App 當作練手的項目。


下面我會一步一步的教大家如何實現此項目。


整個項目的 UI 分為兩大部分,一部分是頂部顯示數字和計算結果,另一部分是底部的輸入按鈕。

「Flutter 實戰」簡約而不簡單的計算器


所以整體佈局使用 Column,在不同分辨率的手機上,規定底部固定大小,剩餘空間都由頂部組件填充,所以頂部組件使用 Expanded 擴充,代碼如下:

<code>Container(
 padding: EdgeInsets.symmetric(horizontal: 18),
 child: Column(
   children: <Widget>[
     Expanded(
       child: Container(
         alignment: Alignment.bottomRight,
         padding: EdgeInsets.only(right: 10),
         child: Text(
           '$_text',
           maxLines: 1,
           style: TextStyle(
               color: Colors.white,
               fontSize: 48,
               fontWeight: FontWeight.w400),
        ),
      ),
    ),
     SizedBox(
       height: 20,
    ),
     _CalculatorKeyboard(
       onValueChange: _onValueChange,
    ),
     SizedBox(
       height: 80,
    )
  ],
),
)/<code>
<code>Ink(
 decoration: BoxDecoration(

     color: Color(0xFF363636),
     borderRadius: BorderRadius.all(Radius.circular(200))),
 child: InkWell(
   borderRadius: BorderRadius.all(Radius.circular(200)),
   highlightColor: Color(0xFF363636),
   child: Container(
     width: 70,
     height: 70,
     alignment: Alignment.center,
     child: Text(
       '1',
       style: TextStyle(color: Colors.white, fontSize: 24),
    ),
  ),
),
)/<code>
<code>Ink(
 decoration: BoxDecoration(
     color: Color(0xFF363636),
     borderRadius: BorderRadius.all(Radius.circular(200))),
 child: InkWell(
   borderRadius: BorderRadius.all(Radius.circular(200)),
   highlightColor: Color(0xFF363636),
   child: Container(
     width: 158,
     height: 70,
     alignment: Alignment.center,
     child: Text(
       '0',
       style: TextStyle(color: Colors.white, fontSize: 24),
    ),
  ),
),
)/<code>
<code>class _CalculatorItem extends StatelessWidget {
 final String text;
 final Color textColor;
 final Color color;
 final Color highlightColor;
 final double width;
 final ValueChanged<String> onValueChange;

 _CalculatorItem(
    {this.text,
     this.textColor,
     this.color,
     this.highlightColor,
     this.width,

     this.onValueChange});

 @override
 Widget build(BuildContext context) {
   return Ink(
     decoration: BoxDecoration(
         color: color, borderRadius: BorderRadius.all(Radius.circular(200))),
     child: InkWell(
       onTap: () {
         onValueChange('$text');
      },
       borderRadius: BorderRadius.all(Radius.circular(200)),
       highlightColor: highlightColor ?? color,
       child: Container(
         width: width ?? 70,
         height: 70,
         padding: EdgeInsets.only(left: width == null ? 0 : 25),
         alignment: width == null ? Alignment.center : Alignment.centerLeft,
         child: Text(
           '$text',
           style: TextStyle(color: textColor ?? Colors.white, fontSize: 24),
        ),
      ),
    ),
  );
}
}/<code>
<code>final List<Map> _keyboardList = [
{
   'text': 'AC',
   'textColor': Colors.black,
   'color': Color(0xFFA5A5A5),
   'highlightColor': Color(0xFFD8D8D8)
},
{
   'text': '+/-',
   'textColor': Colors.black,
   'color': Color(0xFFA5A5A5),
   'highlightColor': Color(0xFFD8D8D8)
},
{
   'text': '%',
   'textColor': Colors.black,
   'color': Color(0xFFA5A5A5),
   'highlightColor': Color(0xFFD8D8D8)
},
{
   'text': '÷',
   'color': Color(0xFFE89E28),

   'highlightColor': Color(0xFFEDC68F)
},
{'text': '7', 'color': Color(0xFF363636)},
{'text': '8', 'color': Color(0xFF363636)},
{'text': '9', 'color': Color(0xFF363636)},
{
   'text': 'x',
   'color': Color(0xFFE89E28),
   'highlightColor': Color(0xFFEDC68F)
},
{'text': '4', 'color': Color(0xFF363636)},
{'text': '5', 'color': Color(0xFF363636)},
{'text': '6', 'color': Color(0xFF363636)},
{
   'text': '-',
   'color': Color(0xFFE89E28),
   'highlightColor': Color(0xFFEDC68F)
},
{'text': '1', 'color': Color(0xFF363636)},
{'text': '2', 'color': Color(0xFF363636)},
{'text': '3', 'color': Color(0xFF363636)},
{
   'text': '+',
   'color': Color(0xFFE89E28),
   'highlightColor': Color(0xFFEDC68F)
},
{'text': '0', 'color': Color(0xFF363636), 'width': 158.0},
{'text': '.', 'color': Color(0xFF363636)},
{
   'text': '=',
   'color': Color(0xFFE89E28),
   'highlightColor': Color(0xFFEDC68F)
},
];/<code>
<code>class _CalculatorKeyboard extends StatelessWidget {
 final ValueChanged<String> onValueChange;

 const _CalculatorKeyboard({Key key, this.onValueChange}) : super(key: key);

 @override
 Widget build(BuildContext context) {
   return Wrap(
     runSpacing: 18,
     spacing: 18,
     children: List.generate(_keyboardList.length, (index) {
       return _CalculatorItem(
         text: _keyboardList[index]['text'],
         textColor: _keyboardList[index]['textColor'],
         color: _keyboardList[index]['color'],

         highlightColor: _keyboardList[index]['highlightColor'],
         width: _keyboardList[index]['width'],
         onValueChange: onValueChange,
      );
    }),
  );
}
}/<code>
<code>case 'AC':
 _text = '0';
 _beforeText = '0';
 _isResult = false;
 break;/<code>
<code>case '+/-':
 if (_text.startsWith('-')) {
   _text = _text.substring(1);
} else {
   _text = '-$_text';
}
 break;/<code>


  1. 不足之一:計算結果邏輯,上面計算結果的邏輯是不完美的,當增加一個操作符(比如 取餘),計算邏輯複雜度將會以指數級方式增加,那為什麼還要用此方式?最重要的原因是計算結果邏輯不是此項目的重點,作為一個Flutter的入門項目重點是熟悉組件的使用,計算器的計算邏輯有一個比較著名的方式:後綴表達式的計算過程,然而此方式偏向於算法,對初學者非常不友好,因此,我採用了一種不完美但適合初學者的邏輯。
  2. 不足之二:此App沒有考慮橫屏的情況,為什麼?因為橫屏很可能導致整體佈局發生變化,橫屏時按鈕是變大還是拉伸,或者拉伸間隙?不同的方式使用的佈局會發生變化,因此,目前只考慮了豎屏的佈局,實際項目中要考慮橫屏情況嗎?其實這是一個用戶體驗的問題,首先問問自己,為什麼要橫屏?橫屏可以顯著的提升用戶體驗嗎?如果不能,為什麼要花費大力氣適配橫屏呢?

回過頭來,發現代碼僅僅只有250多行,當然App也是有不足的地方:


<code>case '=':
double d = _value2Double(_beforeText);
double d1 = _value2Double(_text);
switch (_operateText) {
case '+':
_text = '${d + d1}';
break;
case '-':
_text = '${d - d1}';
break;
case 'x':
_text = '${d * d1}';
break;
case '÷':
_text = '${d / d1}';
break;
}
_beforeText = '';
_isResult = true;
_operateText = '';

break;


double _value2Double(String value) {
if (_text.startsWith('-')) {
String s = value.substring(1);
return double.parse(s) * -1;
} else {
return double.parse(value);
}
}/<code>

= 按鈕計算結果:


<code>case '0':
case '1':
case '2':
case '3':
case '4':
case '5':
case '6':
case '7':
case '8':
case '9':
case '.':
if (_isResult) {
_text = value;
}
if (_operateText.isNotEmpty && _beforeText.isEmpty) {
_beforeText = _text;
_text = '';
}
_text += value;
if (_text.startsWith('0')) {
_text = _text.substring(1);
}
break;/<code>

0-9 和 . 按鈕根據是否是計算結果和是否有操作符號進行顯示:


<code>case '+':
case '-':
case 'x':
case '÷':
_isResult = false;
_operateText = value;/<code>

+、-、x、÷ 按鈕,保存當前 操作符號:


<code>case '%':
double d = _value2Double(_text);
_isResult = true;
_text = '${d / 100.0}';
break;/<code>

% 按鈕表示當前數除以100:


+/- 按鈕表示對當前數字取反,比如 5->-5:


AC 按鈕表示清空當前輸入,顯示 0,同時初始化其他變量:


  • _text:顯示當前輸入的數字和計算結果。
  • _beforeText:用於保存被加數,比如輸入 5+1,保存 5 ,用於後面的計算。
  • _isResult:表示當前值是否為計算的結果,true:新輸入數字直接顯示,false:新輸入數字和當前字符串相加,比如當前顯示 5,如果是計算的結果,點擊 1 時,直接顯示1,否則顯示 51。
  • _operateText:保存加減乘除。

這裡有4個變量:

onValueChange 是點擊按鈕的回調,參數是當前按鈕的文本,用於計算,下面說下計算邏輯:


「Flutter 實戰」簡約而不簡單的計算器

整個輸入按鈕組件:

輸入按鈕的佈局使用 Wrap 佈局組件,如果沒有 0 這個組件也可以使用 GridView組件,按鈕的數據:

輸入按鈕


將按鈕組件進行封裝,其中高亮顏色(按住時顏色)、背景顏色、按鈕文本、文本顏色屬性作為參數,封裝如下:

「Flutter 實戰」簡約而不簡單的計算器

0 這個按鈕的寬度是兩個按鈕的寬度 + 兩個按鈕的間隙,所以 0 按鈕代碼如下:

「Flutter 實戰」簡約而不簡單的計算器

_CalculatorKeyboard 是底部的輸入按鈕組件,也是此項目的重點,除了 0 這個按鈕外,其餘都是圓形按鈕,不同之處是 高亮顏色(按住時顏色)、背景顏色、按鈕文本、文本顏色不同,因此先實現一個按鈕組件,代碼如下:

SizedBox 組件用於兩個組件之間的間隔。


## 交流

老孟Flutter博客地址(330個控件用法):http://laomengit.com

歡迎加入Flutter交流群(微信:laomengit)、關注公眾號【老孟Flutter】


分享到:


相關文章: