Flutter 仿微信支付界面

作者:岛上码农 时间:2023-08-30 01:31:53 

目录
  • 带装饰效果的 Container

  • Row 行布局和 Column列布局

  • ListView列表组件

  • GridView网格组件

  • 代码实现

  • 结语:

Flutter 仿微信支付界面

左侧是微信支付的界面,右侧是开发完成后的效果,图标是从 iconfont 上下载的。首先介绍一下本篇涉及到的组件。

带装饰效果的 Container

实际过程中我们经常会遇到一个容器需要额外的样式,例如圆角,背景色等。在 Flutter 中,对于各种容器都有一个 decoration 的属性,可以用于装饰容器。典型的用法有设置背景色、圆角、边框和阴影等,其中背景色可以使用渐变色。decoration 是一个 Decoration 对象,最常用的是 BoxDecoration,BoxDecoration 的属性如下所示:


const BoxDecoration({
   this.color,
   this.image,
   this.border,
   this.borderRadius,
   this.boxShadow,
   this.gradient,
   this.backgroundBlendMode,
   this.shape = BoxShape.rectangle,
 })

其中color为使用颜色填充容器,image 为 使用图片作为背景,border 为边框,borderRadius 为边框圆角,boxShadow 为容器阴影,gradient 使用渐变色作为背景,backgroundBlendMode 是指与容器的混合模型,默认是覆盖,shape 是背景形状,默认是矩形。其中背景部分我们一般只会选择一种。这里以上面的绿色圆弧背景为例,还加上了一点点渐变(渐变色支持多个,可以根据需要调节),示例代码如下:


return Container(
     //......
     decoration: BoxDecoration(
       borderRadius: BorderRadius.circular(4.0),
       gradient: LinearGradient(
           begin: Alignment.topCenter,
           end: Alignment.bottomCenter,
           colors: [
             Color(0xFF56AF6D),
             Color(0xFF56AA6D),
           ]),
     ),
 //...
   );

这里设置了边角为圆弧,半径为4,使用渐变色填充,渐变方向为从顶部中央到底部中央,渐变色有两个。

Row 行布局和 Column列布局

这个在之前的第五篇列表篇介绍过,其中 Row 代表行布局(即子元素按一行排布),Column 代表列布局(即子元素按一列排布)。具体可以参考Flutter 入门与实战(五):来一个图文并茂的列表。

ListView列表组件

列表视图,和之前的一篇一样,只是本篇的用法不同,用于实现整个页面可以按列表的方式进行滚动,直接将各个部分组件放入到列表的 children属性中,而不是使用数组构建列表元素,有点类似滚动视图的用法。

GridView网格组件

GridView 用于将一个容器按行列划分,可以指定主轴的元素个数(根据滚动方向定),之后自动按总元素的个数分别填充到网格,例如按纵向滚动时,则可以指定行方向一行有多少个网格,每个网格一个元素。超出一行数量后会自动换另一行。最简单的用法是使用 GridView.count 方法构建 GridView,用法如下:


GridView.count(
  crossAxisSpacing: gridSpace,
  mainAxisSpacing: gridSpace,
  crossAxisCount: crossAxisCount,
  //设置以下两个参数,禁止GridView的滚动,防止与 ListView 冲突
  shrinkWrap: true,
  physics: NeverScrollableScrollPhysics(),
  children: buttons.map((item) {
     return _getMenus(item['icon'], item['name'], color: textColor);
   }).toList(),
);

这里 crossAxisSpacing 是与滚动方向垂直的元素的间距,如按纵向(默认值)滚动,则是横向行元素之间的间距。mainAxisSpacing 是与滚动方向相同的元素的间距。children 即网格中的元素。这里需要注意的是,由于 本例中GridView是嵌套在 ListView 里面的,两个组件都是纵向滚动,这样会引起冲突导致布局无法满足约束。因此,在这里设置了 shrinkWrap 为 true 和 physics 为NeverScrollableScrollPhysics,以禁止 GridView 的滚动,从而满足约束。

代码实现

首先来分析布局,所有菜单按钮其实都是一样的布局,可以使用统一的列布局完成菜单按钮,提高复用性。菜单按钮从上到下一次为图标、间距(图标与文字之间)和菜单名称。实现代码如下:


Column _getMenus(String icon, String name, {Color color = Colors.black}) {
 return Column(
   mainAxisAlignment: MainAxisAlignment.center,
   children: <Widget>[
     SizedBox(
       child: Image.asset(icon),
       width: 50,
       height: 50,
     ),
     SizedBox(
       height: 5,
     ),
     Text(name, style: TextStyle(fontSize: 14.0, color: color, height: 2)),
   ],
 );

通过传输一个图标名称,菜单名称和可选的字体颜色(顶部区域和其他的文字颜色不同)来实现单个菜单。

其次来看顶部区域,顶部区域只有两个按钮,使用带装饰的容器实现背景的装饰和圆角。再采用行布局将两个菜单按钮在横向均匀排布。同时,使用 Center 布局将两个菜单保持中部居中。这里指定了容器的高度,这是因为从美观上看太矮了不太协调,实际开发要根据 UI 设计稿定。


Widget _headerGridButtons() {
   double height = 144;
   List<Map<String, String>> buttons = GridMockData.headerGrids();
   return Container(
     height: height,
     margin: EdgeInsets.fromLTRB(MARGIN, MARGIN, MARGIN, MARGIN / 2),
     decoration: BoxDecoration(
       borderRadius: BorderRadius.circular(4.0),
       gradient: LinearGradient(
           begin: Alignment.topCenter,
           end: Alignment.bottomCenter,
           colors: [
             Color(0xFF56AF6D),
             Color(0xFF56AA6D),
           ]),
     ),
     child: Center(
       child: Row(
           mainAxisAlignment: MainAxisAlignment.spaceEvenly,
           children: buttons
               .map((item) =>
                   _getMenus(item['icon'], item['name'], color: Colors.white))
               .toList()),
     ),
   );
 }

其他菜单布局都是一样,只是区域标题,菜单数量、菜单内容不同,因此可以统一封装一个通用的方法来构建任意形式的菜单,以及设置区域标题的字体样式、圆角背景等属性。菜单均使用 GridView 实现网格式布局,同时由于菜单布局相同,可以封装一个通用的方法来指定网格一行按钮的数量,按钮字体颜色等属性,实现代码的复用。


Widget _dynamicGridButtons(List<Map<String, String>> buttons, String title,
     {int crossAxisCount = 4}) {
   return Container(
     margin: EdgeInsets.fromLTRB(MARGIN, MARGIN, MARGIN, MARGIN / 2),
     padding: EdgeInsets.all(MARGIN),
     decoration: BoxDecoration(
       borderRadius: BorderRadius.circular(4.0),
       color: Colors.white,
     ),
     child: Column(
       mainAxisAlignment: MainAxisAlignment.center,
       crossAxisAlignment: CrossAxisAlignment.start,
       children: [
         Text(
           title,
           style: TextStyle(color: Colors.grey[700]),
         ),
         SizedBox(height: 20),
         _gridButtons(buttons, crossAxisCount, textColor: Colors.black),
       ],
     ),
   );
 }

GridView _gridButtons(List<Map<String, String>> buttons, int crossAxisCount,
     {Color textColor = Colors.white}) {
   double gridSpace = 5.0;
   return GridView.count(
     crossAxisSpacing: gridSpace,
     mainAxisSpacing: gridSpace,
     crossAxisCount: crossAxisCount,
     //设置以下两个参数,禁止GridView的滚动,防止与 ListView 冲突
     shrinkWrap: true,
     physics: NeverScrollableScrollPhysics(),
     children: buttons.map((item) {
       return _getMenus(item['icon'], item['name'], color: textColor);
     }).toList(),
   );
 }
}

ListView 构建完整页面:实际的整个页面很简单,只需要将各个区域放入到 ListView 的 children 属性即可,从这里也可以看出,将子组件尽可能细化,不但能够提高代码复用性,还可以降低嵌套层级,提高代码的可读性和可维护性。


@override
 Widget build(BuildContext context) {
   return Scaffold(
     body: ListView(
       children: [
         _headerGridButtons(),
         _dynamicGridButtons(GridMockData.financeGrids(), '金融理财'),
         _dynamicGridButtons(GridMockData.serviceGrids(), '生活服务'),
         _dynamicGridButtons(GridMockData.thirdpartyGrids(), '购物消费'),
       ],
     ),
   );
 }

Mock 数据准备
按钮数据均使用 Mock 数据,这里只是返回一个 List<Map<String, String>>数组对象,对象里是每个菜单的图标文件名称和菜单名称,下面是金融服务区域的菜单 Mock方法。


static List<Map<String, String>> financeGrids() {
   return [
     {'name': '信用卡还款', 'icon': 'images/grid-buttons/grid-1-1.png'},
     {'name': '借钱', 'icon': 'images/grid-buttons/grid-1-2.png'},
     {'name': '理财', 'icon': 'images/grid-buttons/grid-1-3.png'},
     {'name': '保险', 'icon': 'images/grid-buttons/grid-1-4.png'},
   ];
 }

其他待改进的地方:从代码中可以看出,访问按钮的时候是使用 Map 对象的键来访问的,需要使用['name']或['icon']来访问,这种方式非常不利于编码,而且很容易拼写错误。因此,实际使用中应当将 Json 对象(即 Map)转换为实体类,这样就可以通过访问实体类的属性来设置菜单的参数,实际维护起来更为方便。

结语:

Flutter 提供的基础 UI 组件库能够满足绝大部分复杂页面布局,通过各种布局组件的组合即可完成。因此,熟悉基础的布局组件的特性十分重要。同时,需要注意组件的拆分和抽离完成子组件的封装,提高复用性的同时也避免了嵌套层级过深的问题。

来源:https://juejin.cn/post/6967704627227656223

标签:Flutter,微信,支付
0
投稿

猜你喜欢

  • c++异常处理机制示例及详细讲解

    2022-04-27 11:31:27
  • 一篇文章带你了解Maven的生命周期

    2022-04-22 10:40:22
  • C#关于System.Collections空间详解

    2022-04-12 16:04:06
  • Netty分布式pipeline管道传播outBound事件源码解析

    2022-10-17 23:43:06
  • C#中out与ref的区别实例解析

    2022-01-27 13:29:09
  • SpringBoot如何优雅地处理全局异常详解

    2023-01-06 04:30:19
  • Java简单从文件读取和输出的实例

    2023-04-04 20:16:45
  • Android 打开相册选择单张图片实现代码

    2023-05-29 17:06:38
  • 十种JAVA排序算法实例

    2022-11-11 00:19:54
  • android studio3.0以上如何通过navicat访问SQLite数据库文件

    2022-03-26 14:28:55
  • Android实现ListView数据动态加载的方法

    2021-11-02 23:01:02
  • SpringCloud+RocketMQ实现分布式事务的实践

    2022-04-06 16:33:04
  • mybatis-generator生成文件覆盖问题的解决

    2023-03-11 15:35:01
  • Android 判断SIM卡是中国移动\\中国联通\\中国电信(移动运营商)

    2023-01-22 04:20:27
  • C# PDF转图片(JPG,Png)的项目实践

    2021-11-10 17:59:24
  • C#邮件定时群发工具Atilia用法实例

    2022-06-13 11:50:24
  • Android activity和view判断滑动

    2021-09-18 17:25:17
  • Android编程实现的一键锁屏程序详解

    2022-09-03 16:44:27
  • C#实现Word和ODT文档相互转换详解

    2022-12-17 21:36:47
  • 使用Java实现三种等级的扫雷游戏(完整版)

    2023-05-10 07:34:17
  • asp之家 软件编程 m.aspxhome.com