效果图
实现原理
第三方库
总体设计是基于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;
}
}
网友评论