Flutter 仿微信价值几个亿的页面

简介: 网传微信支付页面的第三方链接一个格子需要广告费1一个亿,微信支付页非常适合做功能导航,本篇使用 ListView和 GridView 模仿微信支付页面
网传微信支付页面的第三方链接一个格子需要广告费1一个亿,微信支付页非常适合做功能导航,本篇使用 ListView和 GridView 模仿了微信支付的页面,同时介绍了如何装饰一个组件的背景和边缘样式。

支付.png
左侧是微信支付的界面,右侧是开发完成后的效果,图标是从 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 代表列布局(即子元素按一列排布)。

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 里面的,两个组件都是纵向滚动,这样会引起冲突导致布局无法满足约束。因此,在这里设置了 shrinkWraptruephysicsNeverScrollableScrollPhysics,以禁止 GridView 的滚动,从而满足约束。

代码实现

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

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

  1. 其次来看顶部区域,顶部区域只有两个按钮,使用带装饰的容器实现背景的装饰和圆角。再采用行布局将两个菜单按钮在横向均匀排布。同时,使用 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()),
      ),
    );
  }
  1. 其他菜单布局都是一样,只是区域标题,菜单数量、菜单内容不同,因此可以统一封装一个通用的方法来构建任意形式的菜单,以及设置区域标题的字体样式、圆角背景等属性。菜单均使用 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(),
    );
  }
}
  1. ListView 构建完整页面:实际的整个页面很简单,只需要将各个区域放入到 ListViewchildren 属性即可,从这里也可以看出,将子组件尽可能细化,不但能够提高代码复用性,还可以降低嵌套层级,提高代码的可读性和可维护性。
@override
  Widget build(BuildContext context) {
    return Scaffold(
      body: ListView(
        children: [
          _headerGridButtons(),
          _dynamicGridButtons(GridMockData.financeGrids(), '金融理财'),
          _dynamicGridButtons(GridMockData.serviceGrids(), '生活服务'),
          _dynamicGridButtons(GridMockData.thirdpartyGrids(), '购物消费'),
        ],
      ),
    );
  }
  1. 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'},
    ];
  }
  1. 其他待改进的地方:从代码中可以看出,访问按钮的时候是使用 Map 对象的键来访问的,需要使用['name']['icon']来访问,这种方式非常不利于编码,而且很容易拼写错误。因此,实际使用中应当将 Json 对象(即 Map)转换为实体类,这样就可以通过访问实体类的属性来设置菜单的参数,实际维护起来更为方便。
结语:Flutter 提供的基础 UI 组件库能够满足绝大部分复杂页面布局,通过各种布局组件的组合即可完成。因此,熟悉基础的布局组件的特性十分重要。同时,需要注意组件的拆分和抽离完成子组件的封装,提高复用性的同时也避免了嵌套层级过深的问题。

欢迎关注个人公众号:岛上码农

相关文章
|
6月前
|
缓存 小程序 UED
微信小程序如何在切换页面后原页面状态不变
微信小程序如何在切换页面后原页面状态不变
336 0
|
2月前
|
JSON 小程序 前端开发
微信小程序的目录结构及页面结构的说明
本文详细介绍了微信小程序的目录结构、页面组成部分以及项目的全局配置文件,阐述了小程序的宿主环境和运行机制,包括小程序启动和页面渲染的过程。
微信小程序的目录结构及页面结构的说明
|
3月前
|
小程序
微信小程序多种跳转页面方式
微信小程序多种跳转页面方式
|
2月前
|
小程序 JavaScript API
微信小程序开发学习之页面导航(声明式导航和编程式导航)
这篇文章介绍了微信小程序中页面导航的两种方式:声明式导航和编程式导航,包括如何导航到tabBar页面、非tabBar页面、后退导航,以及如何在导航过程中传递参数和获取传递的参数。
微信小程序开发学习之页面导航(声明式导航和编程式导航)
|
23天前
|
Dart 开发者
flutter:页面整合 (十)
本文档介绍了如何在 Flutter 应用中使用底部导航栏(`BottomNavigationBar`)和侧边栏(`Drawer`),包括简单的使用方法、属性配置、与按钮结合使用、使用图片作为图标、以及如何实现顶部导航。示例代码展示了如何创建包含多个页面的导航栏,并通过 `TabController` 控制器实现页面切换。同时,文档还提供了如何自定义导航栏样式和添加浮动操作按钮(`FloatingActionButton`)的方法。最后,整合了底部导航栏、侧边栏和顶部导航栏的完整示例代码,帮助开发者快速构建具有导航功能的 Flutter 应用界面。
|
2月前
|
小程序 前端开发 生物认证
微信小程序如何将一个按钮放到页面的最底下?
微信小程序如何将一个按钮放到页面的最底下?
179 5
|
2月前
|
JSON 小程序 JavaScript
微信小程序页面事件,下拉刷新事件和上拉触底事件
这篇文章介绍了微信小程序中如何实现下拉刷新和上拉触底事件,包括开启下拉刷新、配置下拉刷新样式、监听下拉刷新事件,以及监听上拉触底事件和配置上拉触底的距离。
|
3月前
|
小程序 开发者
万能的微信小程序个人主页:商城系统个人主页、外卖系统个人主页、购票系统个人主页等等【全部源代码分享+页面效果展示+直接复制粘贴编译即可】
这篇文章分享了四个不同应用场景下的微信小程序个人主页的源代码和页面效果展示,包括商城系统、外卖系统、医疗挂号和电影购票系统的个人主页。提供了完整的页面布局和样式代码,允许开发者直接复制粘贴并根据自己的项目需求进行简单的改造使用。
万能的微信小程序个人主页:商城系统个人主页、外卖系统个人主页、购票系统个人主页等等【全部源代码分享+页面效果展示+直接复制粘贴编译即可】
|
4月前
|
小程序 安全 搜索推荐
【微信小程序开发实战项目】——个人中心页面的制作
本文介绍了如何设计和实现一个网上花店的微信小程序,包括个人中心、我的订单和我的地址等功能模块。个人中心让用户能够查看订单历史、管理地址和与客服互动。代码示例展示了`own.wxml`、`own.wxss`和`own.js`文件,用于构建个人中心界面,包括用户信息、订单链接、收藏、地址、客服和版本信息。我的订单部分展示了订单详情,包括商品图片、名称、销量、价格和订单状态,用户可以查看和管理订单。我的地址功能允许用户输入和编辑收货信息,包括联系人、性别、电话、城市和详细地址。每个功能模块都附有相应的WXML和WXSS代码,以及简洁的样式设计。
198 0
【微信小程序开发实战项目】——个人中心页面的制作
|
4月前
|
前端开发
css动画(仿微信聊天页面)
css动画(仿微信聊天页面)