手把手教你,用flutter实现车牌号输入键盘组件

效果图

读完你将学到

  • 如何实现一个自定义弹窗,并且封装成复用组件
  • 简单的父子组件间通讯
  • 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实现代码:

image.png


获取widget宽度值:_bodyKey.currentContext.size.width;

widget布局

计算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('震动一次');
 }

GitHub源代码地址

请点这里

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 215,384评论 6 497
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 91,845评论 3 391
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 161,148评论 0 351
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 57,640评论 1 290
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 66,731评论 6 388
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,712评论 1 294
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,703评论 3 415
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,473评论 0 270
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,915评论 1 307
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,227评论 2 331
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,384评论 1 345
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,063评论 5 340
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,706评论 3 324
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,302评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,531评论 1 268
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,321评论 2 368
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,248评论 2 352

推荐阅读更多精彩内容