Flutter 仿写微信发现、我的页面

12,915 阅读4分钟

这是我参与11月更文挑战的第5天,活动详情查看:2021最后一次更文挑战

发现页面实现

image.png

这里我们仿照微信的发现页面用 Flutter 类实现这页面的简单布局及每个 cell 的点击效果及点击每条 cell 之后跳转到一个新的页面。这里我们分几步分别来实现这些功能。

自定义 cell

image.png

针对 cell 的布局我们可以分为两部分,左边跟右边,左边是主图片加标题,右边是子标题、 子图片加箭头。主图片名称、主标题、子标题、子图片名称这些都可以由于 cell 初始化的时候由外部传进来。这里主图片、标题跟箭头是固定的每个 cell 都有的,子标题、 子图片是可选的。针对 cell 布局的代码如下。

  String title;
  String imageName;
  String subTitle;
  String subImageName;
  DiscoverCell(this.title, this.imageName, this.subTitle, this.subImageName);
Container(
        color: _currentColor,
        height: 55,
        child: Row(
          mainAxisAlignment: MainAxisAlignment.spaceBetween,
          children: [
            // left
            Container(
              padding: EdgeInsets.all(10),
              child: Row(
                children: [
                  // 图标
                  Image(image: AssetImage(widget.imageName), width: 20,),
                  // 间隙
                  SizedBox(width: 15,),
                  // Title
                  Text(widget.title),
                ],
              ),
            ),
            // right
            Container(
              padding: EdgeInsets.all(10),
              child: Row(
                children: [
                  // subTitle
                  widget.subTitle != null ? Text(widget.subTitle) : Text(''),
                  // subImage
                  widget.subImageName.length > 0 ? Image.asset(widget.subImageName, width: 15,) : Container(),
                  // 箭头
                  Image(image: AssetImage('images/icon_right.png'), width: 15,)
                ],
              ),
            ),
          ],
        ),
      )

列表布局

class _DiscoverPageState extends State<DiscoverPage> {
  Color _themColor = Color.fromRGBO(230, 230, 230, 1.0);
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        backgroundColor: _themColor,
        // 安卓里面用到,切 app 的时候显示
        centerTitle: true,
        title: Text('发现'),
        elevation: 0.0,
      ),
      body: Container(
        height: 800,
        color: _themColor,
        child: ListView(
          children: [
            DiscoverCell('朋友圈', 'images/朋友圈.png', '', ''),
            SizedBox(height: 10,),
            DiscoverCell('扫一扫', 'images/扫一扫2.png', '', ''),
            //分割线
            Row(
              children: [
                // 左边线条
                Container(width: 50, height: 0.5, color: Colors.white,),
                // 右边线条
                Container(height: 0.5, color: Colors.grey,)
              ],
            ),
            DiscoverCell('摇一摇', 'images/摇一摇.png', '', ''),
            SizedBox(height: 10,),
            DiscoverCell('看一看', 'images/看一看icon.png', '', ''),
            //分割线
            Row(
              children: [
                // 左边线条
                Container(width: 50, height: 0.5, color: Colors.white,),
                // 右边线条
                Container(height: 0.5, color: Colors.grey,)
              ],
            ),
            DiscoverCell('搜一搜', 'images/搜一搜.png', '', ''),
            SizedBox(height: 10,),
            DiscoverCell('附近的人', 'images/附近的人icon.png', '', ''),
            SizedBox(height: 10,),
            DiscoverCell('购物', 'images/购物.png', '双十一限时特价', 'images/badge.png'),
            Row(
              children: [
                // 左边线条
                Container(width: 50, height: 0.5, color: Colors.white,),
                // 右边线条
                Container(height: 0.5, color: Colors.grey,)
              ],
            ),
            DiscoverCell('游戏', 'images/游戏.png', '', ''),
            SizedBox(height: 10,),
            DiscoverCell('小程序', 'images/小程序.png', '', ''),
          ],
        ),
      )
    );
  }
}

针对列表的布局我们用的是 ListView,在 children 里面按顺序添加每条 cell 数据,这里每组之间的间隔我们用 SizedBoxcell 上的下划线我们用 Row 来实现,分为左边线条跟右边线条。

cell 点击跳转

image.png

return GestureDetector(
      // cell 手势点击
      onTap: (){
        Navigator.of(context).push(
            MaterialPageRoute(builder:
                (BuildContext context) => DiscoverChildPage(widget.title)
            )
        );

这里我们定义一个新的页面 DiscoverChildPage 标题由 cell 点击的时候传入。在 cell 中我们添加点击方法,然后 push 到一个新的页面。

cell 添加点击状态

GestureDetector(
      // cell 手势点击
      onTap: (){
        Navigator.of(context).push(
            MaterialPageRoute(builder:
                (BuildContext context) => DiscoverChildPage(widget.title)
            )
        );
        setState(() {
          _currentColor = Colors.white;
        });
      },
      // cell 手势点击下去
      onTapDown: (TapDownDetails details){
        setState(() {
          _currentColor = Colors.grey;
        });
      },
      // cell 手势点击取消
      onTapCancel: (){
        setState(() {
          _currentColor = Colors.white;
        });
      },
      child: Container(
        color: _currentColor,
      ),
    );

cell 加上点击状态的话就需要继承于有状态的 Widget,然后在不同的点击状态下设置不同的颜色,然后调用 setState 方法。因为调用 setState 方法的时候会重新构建 widget,所以针对复杂的控件的时候我们只让需要改变状态的子控件继承于 StatefulWidget,把需要改变的部分抽取出来。我们这里因为 cell 整体的子控件也不多,所以我们就直接让 cell 继承于 StatefulWidget 。也是为了偷下懒🐶。

我的页面实现

image.png

我的页面首先我们可以分为两大块,列表跟相机,这里我们采用 Stack 部件来布局。其中列表部分又可以分为两大块头部跟底部 cell 部分。按照这种布局思路我们的代码如下。

class _MinePageState extends State<MinePage> {
  Widget headerWidget() {
    return Container(
      height: 200,
      color: Colors.white,
      child: Container(
        margin: EdgeInsets.only(top: 90, bottom: 20, left: 16, right: 10),
        child: Row(
          children: [
            // 头像
            Container(
              width: 70,
              height: 70,
              // 设置圆角属性
              decoration: BoxDecoration(
                borderRadius: BorderRadius.circular(10),
                image: DecorationImage(
                    image: AssetImage('images/ChenXi.JPG')
                )
              ),
            ),
            // 右边部分
            Expanded(child: Container(
              padding: EdgeInsets.only(left: 10, top: 8, right: 10),
              child: Column(
                crossAxisAlignment: CrossAxisAlignment.start,
                children: [
                  // 昵称
                  Container(
                    height: 35,
                    child: Text('Chenxi', style: TextStyle(fontSize: 25, color: Colors.black87),)
                  ),
                  // 微信号加箭头
                  Container(
                    height: 35,
                    child: Row(
                      mainAxisAlignment: MainAxisAlignment.spaceBetween,
                      children: [
                        Text('微信号:CX123', style: TextStyle(fontSize: 17, color: Colors.grey),),
                        Image(image: AssetImage('images/icon_right.png'), width: 15,)
                      ],
                    ),
                  ),
                ],
              ),
            )),
          ],
        ),
      ),
    );
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: Container(
        color: Color.fromRGBO(230, 230, 230, 1.0),
        child: Stack(
          children: [
            // 列表
            Container(
              child: MediaQuery.removePadding(
                  removeTop: true,
                  context: context,
                  child: ListView(
                children: [
                  // 头部
                  headerWidget(),

                  // 列表
                  SizedBox(height: 10,),
                  DiscoverCell('支付', 'images/微信 支付.png', '', ''),
                  SizedBox(height: 10,),
                  DiscoverCell('收藏', 'images/微信收藏.png', '', ''),
                  //分割线
                  Row(
                    children: [
                      // 左边线条
                      Container(width: 50, height: 0.5, color: Colors.white,),
                      // 右边线条
                      Container(height: 0.5, color: Colors.grey,)
                    ],
                  ),
                  DiscoverCell('朋友圈', 'images/微信相册.png', '', ''),
                  //分割线
                  Row(
                    children: [
                      // 左边线条
                      Container(width: 50, height: 0.5, color: Colors.white,),
                      // 右边线条
                      Container(height: 0.5, color: Colors.grey,)
                    ],
                  ),
                  DiscoverCell('卡包', 'images/微信卡包.png', '', ''),
                  //分割线
                  Row(
                    children: [
                      // 左边线条
                      Container(width: 50, height: 0.5, color: Colors.white,),
                      // 右边线条
                      Container(height: 0.5, color: Colors.grey,)
                    ],
                  ),
                  DiscoverCell('表情', 'images/微信表情.png', '', ''),
                  SizedBox(height: 10,),
                  DiscoverCell('设置', 'images/微信设置.png', '', ''),
                ],
              )
              ),
            ),
            // 相机
            Container(
              margin: EdgeInsets.only(right: 10, top: 25),
              height: 25,
              child: Row(
                mainAxisAlignment: MainAxisAlignment.end,
                children: [
                  Image(image: AssetImage('images/相机.png')),
                ],
              ),
            ),
          ],
        ),
      ),
    );
  }
}

总结:其实实现这些布局的方式有很多,如果需要评定哪种布局方式更好的话,我们遵循页面复杂度最低的布局方式总归是没有错的。