老孟导读:这是 【Flutter 实战】组件系列文章的最后一篇,其他组件地址:http://laomengit.com/guide/widgets/Text.html,接下来将会讲解动画系列,关注老孟,精彩不断。

先看一下效果:

大家学习UI编程语言时喜欢用哪个 App 当作第一个练手的项目呢?,我喜欢使用 计算器 ,可能是习惯了吧,学习 Android 和 React Native 都用此 App 当作练手的项目。

下面我会一步一步的教大家如何实现此项目。

整个项目的 UI 分为两大部分,一部分是顶部显示数字和计算结果,另一部分是底部的输入按钮。

所以整体布局使用 Column,在不同分辨率的手机上,规定底部固定大小,剩余空间都由顶部组件填充,所以顶部组件使用 Expanded 扩充,代码如下:

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,
)
],
),
)

SizedBox 组件用于两个组件之间的间隔。

_CalculatorKeyboard 是底部的输入按钮组件,也是此项目的重点,除了 0 这个按钮外,其余都是圆形按钮,不同之处是 高亮颜色(按住时颜色)、背景颜色、按钮文本、文本颜色不同,因此先实现一个按钮组件,代码如下:

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),
),
),
),
)

0 这个按钮的宽度是两个按钮的宽度 + 两个按钮的间隙,所以 0 按钮代码如下:

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),
),
),
),
)

将按钮组件进行封装,其中高亮颜色(按住时颜色)、背景颜色、按钮文本、文本颜色属性作为参数,封装如下:

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),
),
),
),
);
}
}

输入按钮

输入按钮的布局使用 Wrap 布局组件,如果没有 0 这个组件也可以使用 GridView组件,按钮的数据:

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)
},
];

整个输入按钮组件:

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,
);
}),
);
}
}

onValueChange 是点击按钮的回调,参数是当前按钮的文本,用于计算,下面说下计算逻辑:

这里有4个变量:

  • _text:显示当前输入的数字和计算结果。
  • _beforeText:用于保存被加数,比如输入 5+1,保存 5 ,用于后面的计算。
  • _isResult:表示当前值是否为计算的结果,true:新输入数字直接显示,false:新输入数字和当前字符串相加,比如当前显示 5,如果是计算的结果,点击 1 时,直接显示1,否则显示 51。
  • _operateText:保存加减乘除。

AC 按钮表示清空当前输入,显示 0,同时初始化其他变量:

case 'AC':
_text = '0';
_beforeText = '0';
_isResult = false;
break;

+/- 按钮表示对当前数字取反,比如 5->-5:

case '+/-':
if (_text.startsWith('-')) {
_text = _text.substring(1);
} else {
_text = '-$_text';
}
break;

% 按钮表示当前数除以100:

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

+、-、x、÷ 按钮,保存当前 操作符号:

case '+':
case '-':
case 'x':
case '÷':
_isResult = false;
_operateText = value;

0-9 和 . 按钮根据是否是计算结果和是否有操作符号进行显示:

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;

= 按钮计算结果:

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);
}
}

回过头来,发现代码仅仅只有250多行,当然App也是有不足的地方:

  1. 不足之一:计算结果逻辑,上面计算结果的逻辑是不完美的,当增加一个操作符(比如 取余),计算逻辑复杂度将会以指数级方式增加,那为什么还要用此方式?最重要的原因是计算结果逻辑不是此项目的重点,作为一个Flutter的入门项目重点是熟悉组件的使用,计算器的计算逻辑有一个比较著名的方式:后缀表达式的计算过程,然而此方式偏向于算法,对初学者非常不友好,因此,我采用了一种不完美但适合初学者的逻辑。
  2. 不足之二:此App没有考虑横屏的情况,为什么?因为横屏很可能导致整体布局发生变化,横屏时按钮是变大还是拉伸,或者拉伸间隙?不同的方式使用的布局会发生变化,因此,目前只考虑了竖屏的布局,实际项目中要考虑横屏情况吗?其实这是一个用户体验的问题,首先问问自己,为什么要横屏?横屏可以显著的提升用户体验吗?如果不能,为什么要花费大力气适配横屏呢?

交流

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

欢迎加入Flutter交流群(微信:laomengit)、关注公众号【老孟Flutter】:

【Flutter 实战】简约而不简单的计算器的更多相关文章

  1. 《Flutter实战》开源电子书

    <Flutter实战>开源电子书 <Flutter实战> 开源了,本书为 Flutter中文网开源电子书项目,本书系统介绍了Flutter技术的各个方面,本书属于原创书籍(并非 ...

  2. 【Flutter实战】定位装饰权重组件及柱状图案例

    老孟导读:Flutter中有这么一类组件,用于定位.装饰.控制子组件,比如 Container (定位.装饰).Expanded (扩展).SizedBox (固定尺寸).AspectRatio (宽 ...

  3. Flutter实战视频-移动电商-02.Flutter实战建立项目和编写入口文件

    02.Flutter实战建立项目和编写入口文件 创建项目: flutter create flutter_shop 创建完成之后呢,它会提示我们, 进入flutter_shop的目录,然后执行flut ...

  4. 通通玩blend美工(7)——简约而不简单的块

    原文:通通玩blend美工(7)--简约而不简单的块 最近在研发一个WPF快速开发框架,满脑子都是各种逻辑各种模式,写一篇比较休闲娱乐的博客,宣泄下我对美工的热爱. 我一直以来有意无意在手机应用或者各 ...

  5. 《Flutter 实战》开源电子书

    <Flutter 实战>开源电子书 转 https://blog.csdn.net/OQjya206rsQ71/article/details/86619630   关于 Flutter ...

  6. 【Flutter实战】移动技术发展史

    老孟导读:大家好,这是[Flutter实战]系列文章的第一篇,这并不是一篇Flutter技术文章,而是介绍智能手机操作系统.跨平台技术的演进以及我对各种跨平台技术看法的文章. 智能手机操作系统 塞班( ...

  7. Flutter实战】文本组件及五大案例

    老孟导读:大家好,这是[Flutter实战]系列文章的第二篇,这一篇讲解文本组件,文本组件包括文本展示组件(Text和RichText)和文本输入组件(TextField),基础用法和五个案例助你快速 ...

  8. 【Flutter实战】图片组件及四大案例

    老孟导读:大家好,这是[Flutter实战]系列文章的第三篇,这一篇讲解图片组件,Image有很多高级用法,希望对您有所帮助. 图片组件是Flutter基础组件之一,和文本组件一样必不可少.图片组件包 ...

  9. 【Flutter实战】自定义滚动条

    老孟导读:[Flutter实战]系列文章地址:http://laomengit.com/guide/introduction/mobile_system.html 默认情况下,Flutter 的滚动组 ...

随机推荐

  1. Java实现 LeetCode 764 最大加号标志(暴力递推)

    764. 最大加号标志 在一个大小在 (0, 0) 到 (N-1, N-1) 的2D网格 grid 中,除了在 mines 中给出的单元为 0,其他每个单元都是 1.网格中包含 1 的最大的轴对齐加号 ...

  2. Java实现 蓝桥杯 算法训练 区间k大数

    算法训练 区间k大数查询 时间限制:1.0s 内存限制:256.0MB 问题描述 给定一个序列,每次询问序列中第l个数到第r个数中第K大的数是哪个. 输入格式 第一行包含一个数n,表示序列长度. 第二 ...

  3. Java实现 蓝桥杯 算法训练 约数个数

    算法提高 约数个数 时间限制:1.0s 内存限制:512.0MB 输入一个正整数N (1 样例输入 12 样例输出 6 样例说明 12的约数包括:1,2,3,4,6,12.共6个 import jav ...

  4. Java实现 LeetCode 382 链表随机节点

    382. 链表随机节点 给定一个单链表,随机选择链表的一个节点,并返回相应的节点值.保证每个节点被选的概率一样. 进阶: 如果链表十分大且长度未知,如何解决这个问题?你能否使用常数级空间复杂度实现? ...

  5. Java中Iterator类的详细介绍

    迭代器模式:就是提供一种方法对一个容器对象中的各个元素进行访问,而又不暴露该对象容器的内部细节. 概述 Java集合框架的集合类,我们有时候称之为容器.容器的种类有很多种,比如ArrayList.Li ...

  6. Java实现字符串的旋转

    1 问题描述 给定一个字符串,要求将字符串前面的若干个字符移到字符串的尾部.例如,将字符串"abcdef"的前3个字符'a'.'b'和'c'移到字符串的尾部,那么原字符串将变成&q ...

  7. java实现矩阵变换加密法

    一种Playfair密码变种加密方法如下:首先选择一个密钥单词(称为pair)(字母不重复,且都为小写字母),然后与字母表中其他字母一起填入至一个5x5的方阵中,填入方法如下: 1.首先按行填入密钥串 ...

  8. java实现第三届蓝桥杯数据压缩

    数据压缩 某工业监控设备不断发回采样数据.每个数据是一个整数(0到1000之间).各个数据间用空白字符(空格,TAB或回车换行)分隔.这些数据以文本形式被存储在文件中. 因为大多数时候,相邻的采样间隔 ...

  9. java实现第七届蓝桥杯方格填数

    方格填数 题目描述 如下的10个格子 +--+--+--+ | | | | +--+--+--+--+ | | | | | +--+--+--+--+ | | | | +--+--+--+ (如果显示 ...

  10. java实现第八届蓝桥杯树型显示

    树型显示 题目描述 对于分类结构可以用树形来形象地表示.比如:文件系统就是典型的例子. 树中的结点具有父子关系.我们在显示的时候,把子项向右缩进(用空格,不是tab),并添加必要的连接线,以使其层次关 ...