美文网首页
Flutter实现一个较好看的计算器

Flutter实现一个较好看的计算器

作者: 星星y | 来源:发表于2020-03-18 11:46 被阅读0次

    效果图

    实现原理

    第三方库

    总体设计是基于375*812的,为统一尺寸,所以这里采用了flutter_screenutil库。同时为解决浮点数精度问题,使用了decimal
    具体引入如下:

    dependencies:
        ...
      flutter_screenutil: 0.5.3
      decimal: 0.3.5
    

    总体布局

    布局方式可以使用多种方式,如ListView,Stack等。本项目采用的是Stack+Positioned(动画显示部分用到),底部Positionedc数字及操作符号部分采用Column+Row。


    布局伪代码

    Stack(
        chindren:<Widget>[
            Positioned(),
            Positioned(),
            Positioned(
                left:0,
                right:0,
                bottom:0,
                child:Container(
                    child:Colum(
                        Row(),
                        Row(),
                        Row(),
                        Row(
                            children:<Widget>[
                                buildNumberItem("00"),
                                buildNumberItem("0"),
                                buildNumberItem("."),
                                buildEqualItem()
                            ]
                        )
                    )
                )
            )
        ]
    );
    

    CalculatorItem操作符组件

    数字及操作符号的布局类似,我们统一建一个CalculatorItem组件

    class CalculatorItem extends StatefulWidget {
      final Color activeColor;
      final Color color;
      final Widget child;
      final GestureTapCallback onTap;
      final double width;
    
      const CalculatorItem({Key key, this.activeColor, this.color, this.child, this.onTap, this.width}) : super(key: key);
    
      @override
      State<StatefulWidget> createState() {
        return _CalculatorItemState(activeColor, color, child, onTap, width);
      }
    }
    
    class _CalculatorItemState extends State<CalculatorItem> {
      bool active = false;
      final Color activeColor;
      final Color color;
      final Widget child;
      final GestureTapCallback onTap;
      final double width;
    
      _CalculatorItemState(this.activeColor, this.color, this.child, this.onTap, this.width);
    
      void _active(bool flag) {
        setState(() {
          active = flag;
        });
      }
    
      @override
      Widget build(BuildContext context) {
        double dp8 = WidgetUtil.getWidth(8);
        double dp24 = WidgetUtil.getWidth(24);
        double dp60 = WidgetUtil.getWidth(58);
        return GestureDetector(
            onTapDown: (arg) => _active(true),
            onTapUp: (arg) => _active(false),
            onTapCancel: () => _active(false),
            onTap: onTap,
            child: Container(
                margin: EdgeInsets.fromLTRB(dp8, dp24, dp8, 0),
                width: width ?? dp60,
                height: dp60,
                decoration:
                    BoxDecoration(borderRadius: BorderRadius.circular(dp60 / 2), color: active ? activeColor : color),
                child: child));
      }
    }
    

    实现buildNumberItem创建数字及操作符组件

    Widget buildNumberItem(String text) {
        return CalculatorItem(
          activeColor: Color(0xffD3D3D3),
          color: Color(0xFF1D3247),
          onTap: () => addText(text),
          child: Center(
            child: Text(
              text,
              style: TextStyle(
                  fontSize: WidgetUtil.getFontSize(34),
                  color: Colors.white,
                  fontFamily: "din_medium"),
            ),
          ),
        );
      }
    

    表达式布局及计算结果布局

    我们通过改变Positioned的top值来实现计算结果的动画展示效果,当点击"="号,计算结果移至表达式,有一个字体向上平移放大效果。
    首先创建表达式布局和计算结果布局,放在Stack的前两个子view中

    Stack(
            children: <Widget>[
              Positioned(
                top: top1,
                right: 5,
                child: Container(
                    height: 50,
                    child: Center(
                      child: Text(
                        text1,
                        style: TextStyle(
                            fontSize: font1,
                            color: const Color(0xff333333),
                            fontFamily: "din_medium"),
                      ),
                    )),
              ),
              Positioned(
                  top: top2,
                  right: 5,
                  child: Offstage(
                    offstage: hideFont2,
                    child: Container(
                        height: 50,
                        child: Center(
                          child: Text(
                            text2,
                            style: TextStyle(
                                fontSize: font2,
                                color: const Color(0xffCCCCCC),
                                fontFamily: "din_medium"),
                          ),
                        )),
                  )),
              Positioned()
            ]
    )     
    

    然后使用AnimationController实现动画效果,通过调用controller.forward()开始动画,Positioned(计算结果)通过top属性上移(移至表达式布局所在位置),当动画完成时,重置计算结果布局和表达式布局的位置,并且Offstage控制隐藏结果布局,完成动画效果。

    
      String text1 = "";
      String text2 = "";
    
      AnimationController controller;
      Animation<double> animation;
      double top1 = 100;
      double top2 = 150;
      double font1 = 35;
      double font2 = 20;
      bool hideFont2 = false;
    
      @override
      void initState() {
        super.initState();
    
        controller = AnimationController(
            vsync: this, duration: const Duration(milliseconds: 300));
        CurvedAnimation easy =
            CurvedAnimation(parent: controller, curve: Curves.easeIn);
        animation = Tween(begin: 0.0, end: 1.0).animate(easy)
          ..addListener(() {
            double v = animation.value * 50;
            double level = text2.length > 15 ? fontLevel2 : fontLevel1;
            double f = animation.value * (level - fontLevel2);
            top1 = 100 - v;
            top2 = 150 - v;
            font2 = fontLevel2 + f;
            setState(() {});
          })
          ..addStatusListener((status) {
            if (status == AnimationStatus.completed) {
              top1 = 100;
              top2 = 150;
              hideFont2 = true;
              text1 = text2;
              calculator.reset(text2);
              text2 = "";
              setState(() {});
              controller.reset();
            } else if (status == AnimationStatus.forward) {
              setState(() {
                hideFont2 = false;
              });
            }
          });
      }
      void calculateAndUpdate() {
        if (text2.isEmpty) return;
        controller.forward();
      }
      ...
    }
    

    计算逻辑封装Calculator

    我们用一个数组来存放计算表达式,如2×3-4÷5在数组里表现为["2","×","3","-","4","÷","5"],通过addText来接收进来的操作符如1,2,3,+,-,×,÷等,特殊的操作如清空,和删除等操作用C,<代替,于是在addText就有了这些逻辑。

    class CalcuCalculatorlator {
      //计算器表达式项
      var expressionItems = [];
    
      String getCurrent() {
        var len = expressionItems.length;
        if (len == 0)
          return "";
        else
          return expressionItems[len - 1];
      }
    
      void addText(String text) {
        if (expressionItems.isEmpty && isOpt(text)) return;
        var current = getCurrent();
        if (current.isEmpty && !("C" == text || "<" == text || "00" == text)) {
          expressionItems.add(text);
          return;
        }
        switch (text) {
          case "1":
          case "2":
          case "3":
          case "4":
          case "5":
          case "6":
          case "7":
          case "8":
          case "9":
          case "0":
            if (isNumber(current)) {//如果当前是数字,替换当前数字
              current += text;
              replaceLast(current);
            } else {
              expressionItems.add(text);
            }
            break;
          case ".":
            if (isInteger(current)) {//如果是整数,添加小数点
              current = "$current.";
              replaceLast(current);
            }
            break;
          case "00":
            if (isNumber(current)) {
              current += "00";
              replaceLast(current);
            }
            break;
          case "+":
          case "-":
          case "×":
          case "÷":
            if (isNumber(current)) {//如果最后的操作符为数字,则新增操作符,否则替换操作符
              expressionItems.add(text);
            } else {
              replaceLast(text);
            }
            break;
          case "C":
            expressionItems.clear();//清空操作符
            break;
          case "<":
            deleteItem();           //退格操作
            break;
        }
      }
      ...
    }
    

    接下来就是进行四则运算了,我们通过两个栈实现。

    String calculate() {
        DStack<Decimal> numbers = DStack(30);
        DStack<String> opts = DStack(30);
        int i = 0;
        if (expressionItems.isEmpty) return "";
        if (expressionItems.length == 1) return expressionItems[0];
        var end = expressionItems.length;
        if (isCurrentOpt()) end -= 1;
        while (i < end || !opts.isEmpty) {
          String str;
          if (i < end) str = expressionItems[i];
          if (str != null && isNumber(str)) {
            numbers.push(Decimal.parse(str));
            i++;
          } else if (str != null &&
              (opts.isEmpty || level(str) > level(opts.top))) {
            opts.push(str);
            i++;
          } else {
            try {
              Decimal right = numbers.pop();
              Decimal left = numbers.pop();
              String opt = opts.top;
              if ("+" == opt) {
                numbers.push(left + right);
              } else if ("-" == opt) {
                numbers.push(left - right);
              } else if ("×" == opt) {
                numbers.push(left * right);
              } else if ("÷" == opt) {
                numbers.push(left / right);
              }
              opts.pop();
            } catch (e) {
              print(e);
            }
          }
        }
        Decimal v = numbers.pop();
        var result = "$v";
        if (result.length > 15)//超出15位,使用指数表示
          return v.toStringAsExponential(5).replaceAll("+", "");
        return result;
      }
    
      int level(String str) {
        if ("×÷".contains(str)) {
          return 2;
        } else if ("+-".contains(str)) {
          return 1;
        } else {
          return 0;
        }
      }
    

    项目地址

    https://github.com/iamyours/flutter_demo

    相关文章

      网友评论

          本文标题:Flutter实现一个较好看的计算器

          本文链接:https://www.haomeiwen.com/subject/aqeqyhtx.html