Flutter实战-通过拆解UI实现薄荷APP首页

652 阅读4分钟

如何使用Flutter搭建一个薄荷APP

基础组件属性图解

1.1 Row

mainAxisAlignment(主轴对齐方式)

mainAxisAlignment用于控制主轴方向的子Widget的排列方式,默认的属性都是start

MainAxisAlignment.start // 主轴顶部
MainAxisAlignment.end // 主轴底部对齐
MainAxisAlignment.center // 中间对齐
MainAxisAlignment.spaceBetween // 首尾组件贴边,中间的Widget平分空间
MainAxisAlignment.spaceAround // 中间平分空间,首尾Widget距离边的距离是它与中间的距离的一半
MainAxisAlignment.spaceEvenly // 完全平分

以薄荷我的页面,关注布局为例

  • MainAxisAlignment.start(主轴顶部)

  • MainAxisAlignment.end(主轴底部)

  • MainAxisAlignment.center(中间对齐)

  • MainAxisAlignment.spaceBetween(首尾组件贴边,中间的Widget平分空间)

  • MainAxisAlignment.spaceAround(中间平分空间,首尾Widget距离边的距离是它与中间的距离的一半)

crossAxisAlignment(副轴对齐方式)

该属性的含义是次轴排列方式,根据上述构造函数可以知道RowColumn组件在次轴方向上默认都是居中。

crossAxisAlignment.start 
crossAxisAlignment.end
crossAxisAlignment.center
crossAxisAlignment.stretch // 让子Widget填满次轴方向
crossAxisAlignment.baseline// 使得子Widget的baseline对齐
1.1.3 mainAxisSize

表示在主轴方向占用的空间,默认是MainAxisSize.max,表示尽可能多的占用水平方向的空间,此时无论子widgets实际占用多少水平空间,Row/Column的宽度始终等于主轴方向的最大宽度;

  • MainAxisSize.min

  • MainAxisSize.max

1.2 Container(容器组件)

Container是一个组合类容器,它是DecoratedBoxConstrainedBoxTransformPaddingAlign等组件组合的一个多功能容器,所以我们只需通过一个Container组件可以实现同时需要装饰、变换、限制的场景:

Container({
  this.alignment, // 内部widget对齐
  this.padding, //容器内补白,属于decoration的装饰范围
  Color color, // 背景色
  Decoration decoration, // 背景装饰
  Decoration foregroundDecoration, //前景装饰
  double width,//容器的宽度
  double height, //容器的高度
  BoxConstraints constraints, //容器大小的限制条件
  this.margin,//容器外补白,不属于decoration的装饰范围
  this.transform, //变换
  this.child,
})

1.3 Stack(层叠布局)

层叠布局和Web中的绝对定位、Android中的Frame布局是相似的,子组件可以根据距父容器四个角的位置来确定自身的位置。绝对定位允许子组件堆叠起来(按照代码中声明的顺序)。Flutter中使用Stack和Positioned这两个组件来配合实现绝对定位。Stack允许子组件堆叠,而Positioned用于根据Stack的四个角来确定子组件的位置。

首先先来看下默认的构造函数:

Stack({
  this.alignment = AlignmentDirectional.topStart,// 此参数决定如何组件去对齐(没有使用Positioned)
  this.fit = StackFit.loose,
  this.overflow = Overflow.clip,
  List<Widget> children = const <Widget>[],
})
  • fit:此参数用于确定没有定位的子组件如何去适应Stack的大小。StackFit.loose表示使用子组件的大小,StackFit.expand表示扩伸到Stack的大小。
  • overflow:此属性决定如何显示超出Stack显示空间的子组件;值为Overflow.clip时,超出部分会被剪裁(隐藏),值为Overflow.visible 时则不会。

2. 利用Container+Row实现薄荷首页的搜索框

最终实现效果

组件拆解

代码实现

Container(
      color: Color.fromARGB(_controller.value.alpha, 0, 205, 162),
      padding: EdgeInsets.only( // 内边距
          left: 17,
          right: 17,
          top: ScreenUtil.getStatusBarH(context) + 17,
          bottom: 17),
      child: Row( // 包含搜索图标和文字
        children: <Widget>[
          Expanded( // 为了让搜索框占满横向空间,除去购物车Icon、消息Icon
              child: Container(
            child: DecoratedBox( // 圆角和背景
              decoration: BoxDecoration(
                color: _controller.value.searchBarBg,
                borderRadius: BorderRadius.circular(100),
              ),
              child: Padding( // 上下边距,撑高内容
                padding: EdgeInsets.only(top: 7, bottom: 7),
                child: Row( // 左右排列搜索框和文字
                  children: <Widget>[
                    PaddingStyles.getPadding(14),
                    Image.asset(
                      Utils.getImgPath(_controller.value.appbarLeftIcon),
                      width: 24,
                      height: 24,
                    ),
                    PaddingStyles.getPadding(7),
                    Text(
                      widget.text,
                      style: TextStyle(
                          fontSize: 14,
                          color: _controller.value.appbarTitleColor),
                    )
                  ],
                ),
              ),
            ),
          )),
          Offstage(
            child: SizeBoxFactory.getHorizontalSizeBox(14),
            offstage: !widget.isShowCartIcon,
          ),
          Offstage( // 购物车图标,Offstage是可以控制widget显示隐藏
            offstage: !widget.isShowCartIcon,
            child: Image.asset(
              Utils.getImgPath("ic_shop_cart_white"),
              width: 24,
              height: 24,
            ),
          ),
          SizeBoxFactory.getHorizontalSizeBox(14),
          Image.asset( 
            Utils.getImgPath(_controller.value.appbarRightIcon),
            width: 24,
            height: 24,
          )
        ],
      ),
    )

最后可以通过封装成一个组件来实现其他页面的复用效果,具体代码如下:

/// 通用的搜索头部Widget
class SearchBar extends StatelessWidget {
  final String text;
    
  SearchBar(
      {this.text});

  @override
  Widget build(BuildContext context) {
    return Container(
        .....
    );
  }
}

3. 复杂页面的拆解及实现(薄荷首页)

代码实现

Stack(
      children: <Widget>[
        CustomScrollView( // 统一滑动
          controller: _controller, // 监听CustomScrollView滑动距离,搜索框颜色渐变
          slivers: <Widget>[
            SliverToBoxAdapter( 
                child: Stack(children: <Widget>[//  壁纸+减肥进度条
                  ExtendedImage.network( // 壁纸
                    wallImg,
                    height: 181,
                    width: double.infinity,
                    fit: BoxFit.fitWidth,
                    enableLoadState: false,
                  ),
                  CardView( // 减肥进度条
                    margin: EdgeInsets.only(left: 17, right: 17, top: 104),
                    child: ....,
                  )
                ])),
            SliverList( // 底部卡片ListView
                ......
                ......
               ),
          ],
        ),
        SearchBar( // 顶部搜索框
          text: "搜索食物和热量",
          controller: _searchBarController,
          key: _mTitleKey,
        )
      ],
    );

最后实现各部分组件对应的UI,首页就搭建出来了,源码在👇的GitHub链接

薄荷首页

github项目地址

下载地址

参考资料

缘起 · 《Flutter实战》

用Flutter构建漂亮的UI界面 - 基础组件篇