效果图
读完你将学到
- 如何实现一个自定义弹窗,并且封装成复用组件
- 简单的父子组件间通讯
- stack、grid网格布局
- 简单的自定义toast提示
- 如何获取一个widget的宽高
- 简单的入场动画
开始
如何实现一个自定义弹窗
翻阅文档可得:showGeneralDialog方法,具体参数含义请自行查阅文档,
由上面的效果图看到,点击输入信息,当输入正确的7位车牌号就自动关闭键盘,同时页面更新用户输入的车牌号。那么现在可以确定的是组件要有车牌号,键盘关闭事件两个参数。直接上代码:
class PlateNoKeyboard {
final BuildContext context;
final String plateNo;
final Function onClose;
PlateNoKeyboard({
@required this.context,
@required this.plateNo,
@required this.onClose,
});
Future<bool> show() async {
return await showGeneralDialog(
context: context,
pageBuilder: (BuildContext buildContext, Animation<double> animation,
Animation<double> secondaryAnimation) {
return _buildContent;
},
barrierDismissible: false,
barrierLabel: MaterialLocalizations.of(context).modalBarrierDismissLabel,
barrierColor: Colors.black.withOpacity(0.5), // 设置背景颜色
transitionDuration: Duration(milliseconds: 400), // 设置弹窗进场时间
transitionBuilder: (
BuildContext context,
Animation<double> animation,
Animation<double> secondaryAnimation,
Widget child,
) =>
fromBottom(animation, secondaryAnimation, child),
);
}
Widget get _buildContent {
// 改变车牌号需要刷新页面,所以这里必须返回一个StatefulWidget包裹的内容
return KeyboardContent(plateNo: plateNo, onClose: onClose, itemClick: itemClick,);
}
// 从下往上弹出动画效果
fromBottom(Animation<double> animation,
Animation<double> secondaryAnimation, Widget child) {
return SlideTransition(
position: Tween<Offset>(
begin: const Offset(0.0, 1.0),
end: Offset.zero,
).animate(animation),
child: child,
);
}
}
上面的代码就是该组件的整体代码架构,接下来讲下_buildContent内容。 上面代码注释有写到,由于在点击按钮的时候上面有个面板显示输入的结果,所以这里必须要用到StatefulWidget。
class KeyboardContent extends StatefulWidget {
final String plateNo;
final Function onClose;
const KeyboardContent({
Key key,
@required this.plateNo,
@required this.onClose,
}): super(key: key);
@override
_KeyboardContentState createState() => _KeyboardContentState();
}
class _KeyboardContentState extends State<KeyboardContent> {
String _plate;
void initState() {
super.initState();
_plate = widget.plateNo; //初始化下传进来的车牌号
}
}
简单的父子组件间通讯
传进来的onClose回调事件监听_plate车牌位数变为7位时,调用即可
if (_plate.length == 7) {
// 车牌号达到七位数自动关闭键盘
widget.onClose(_plate); //事件回调
Navigator.pop(context); //关闭弹窗
}
grid网格布局
键盘的按钮布局用到了GridView.count,借助它可以很方便的实现一个像键盘这样的大小一致的标准网格布局
GridView.count(
crossAxisSpacing: 7, // 纵向间隔
mainAxisSpacing: 6, // 横向间隔
crossAxisCount: 6, // 设置每行个数
childAspectRatio: 1.24, // 元素大小比例,该组件child元素设置宽高无效,而是由该参数比例进行设定
children: []
)
这里的渲染chidren时有个小技巧,如果只是渲染数组里面的数据不需要获取下标,推荐用
data.map((item) {
return widget
}).toList();
如果需要获取下标,可以用
List.generate(data.length, (index) {
return widget;
})
额,这里用网格布局其实遇到了一个问题:这里按钮用的是FlatButton,但是在网格布局中发现没有了点击墨水散开效果(InkWell),移除到别的widget却正常,由文章开头效果图可以看到按钮点击时没有一个正常的点击效果的,基于此只好给它加个toast提示当前点击的是哪个。
实现Toast提示
这个提示位置不是固定的,所以要用stack配合position实现。为了较方便的实现按钮居中定位(长度)在每个按钮的上方,toast提示框宽度设为和按钮一样的长度。上面的网格布局有说到每个child的长宽其实不是固定的宽高而是固定一个比例,所以这就要借助GlobalKey获取widget的长度了
这里又遇到一个问题,直接将key放置在键盘按钮的FlatButton中,又会报奇怪的错误,只好又折中取其父容器的宽度计算出按钮的长度,至于toast提示的高度这里就设置了一个大概合理定值,这个高度多少影响不大
相关toast实现代码:
获取widget宽度值:
_bodyKey.currentContext.size.width;
计算toast提示显示位置函数处理:
最后
也许仅仅增加一个toast提示反馈还不够?还要增加一个震动反馈效果?那就增加个点击事件回调吧!这个简单
class PlateNoKeyboard {
final BuildContext context;
final String plateNo;
final Function onClose;
final Function itemClick; // 新增
PlateNoKeyboard({
@required this.context,
@required this.plateNo,
@required this.onClose,
this.itemClick, // 新增
});
Widget get _buildContent {
// 改变车牌号需要刷新页面,所以这里必须返回一个StatefulWidget包裹的内容
return KeyboardContent(plateNo: plateNo, onClose: onClose, itemClick: itemClick //新增,);
}
在点击事件中新增:
widget.itemClick != null ? widget.itemClick() : '';
// 这里不能使用widget?.itemClick()及widget.itemClick != null && widget.itemClick()写法,会报错
至此,一个组件就完成了!
使用
在页面中引入
import 'package:app_flutter/widgets/plate_no_keyboard.dart';
在点击事件中调用
PlateNoKeyboard(context: context, plateNo: plateNo, onClose: keyboardClose).show();
参数 | 含义 |
---|---|
context | Buildcontext |
plateNo | 车牌号 |
onClose | 键盘关闭事件回调 |
itemClick | 按钮点击事件回调(可选,用于需要给点击按钮加震动反馈效果处理) |
函数处理例子
keyboardClose(String plate) {
setState(() {
plateNo = plate;
});
}
itemClick() {
print('震动一次');
}