likes
comments
collection
share

flutter-仿微信项目实战二(发现、我、组件状态保存)

作者站长头像
站长
· 阅读数 2

前言

这篇文章主要是编写仿微信的发现页、我的页面,由于这两个功能模块比较少,就放到一起了

这里主要介绍界面搭建,小组件封装等效果

源码地址

小组件cell

介绍发现页和我的页面之前,先介绍一下他们都再用的图文混排的 cell,就像下面这样,我们顺道把 朋友圈后面的东西也做了

flutter-仿微信项目实战二(发现、我、组件状态保存)

//由于我们封装的小组件,我们要设置一些属性,在外面调用时,可以通过构造方法传递进来
//如下所示,由于状态会更新,选择的StatefulWidget
class ItemCell extends StatefulWidget {
  final String imageUrl;
  final String text;
  final String? subImageUrl;
  final String? subText;
  final bool hasLine;
    
  //require表示的必须传入,直接赋值的可以不传,为默认值
  const ItemCell({Key? key, required this.imageUrl, required this.text, 
      this.subImageUrl, this.subText, this.hasLine = false}) : super(key: key);

  @override
  State<ItemCell> createState() => _ItemCellState();
}

class _ItemCellState extends State<ItemCell> {
}

下面则是 build 中的代码实现,将关键内容标出来

//GestureDetector为点击手势,我们可以通过点击按下效果和cancel等效果,配合container的color
//实现类似的按下变色效果,当需要特殊的自定义时,可以这样做
//直接换成TextButton也可以,注意button有个外边
return GestureDetector(
  //点击生效后,恢复颜色
  onTap: () {
    setState(() {
      backColor = Colors.white;
    });
  },
  //按下时,背景变色,仿ios效果
  onTapDown: (TapDownDetails details) {
    setState(() {
      backColor = Colors.grey;
    });
  },
  //取消点击时恢复白色效果
  onTapCancel: () {
    setState(() {
      backColor = Colors.white;
    });
  },
  //设置children Column,垂直布局,则是将内部分为上下两块
  //上面是内容,下面是下划线
  child: Column(
    children: [
       //设置一个container,可以设置背景色高度等
      Container(
        color: backColor,
        height: 46,
        //设置内边距padding,外边距为margin
        padding: const EdgeInsets.only(left: 16, right: 10),
        //第一排为横向布局,将左边的图片文字,和右边的文案箭头分为两部分
        child: Row(
            //左右两头对齐,中间内容等间距
            mainAxisAlignment: MainAxisAlignment.spaceBetween,
            children: [
              //通过水平布局row,来设置左侧图片和文字
              Row(
                children: [
                  Image.asset(
                    widget.imageUrl,
                    width: 16,
                    height: 16,
                    fit: BoxFit.fitWidth,
                  ),
                  Container(
                    width: 14,
                  ),
                  Text(
                      widget.text,
                      style: const TextStyle(
                          fontSize: 12,
                          color: Colors.black
                      )
                  ),
                ],
              ),
                
              //设置右侧文案,左侧提醒,右侧箭头
              Row(
                children: [
                    //设置提醒文案
                    Row(
                      children: [
                        //也可以使用三目运算符
                          //剪裁图片,如果设置border,请使用 container的
                          //右上角红圈的话,Stack嵌套Position即可
                        widget.subImageUrl != null ?
                        SizedBox(
                          width: 24,
                          height: 24,
                          //注意图片右上角有个红点,除了设置圆角,还得使用stack布局设置红点位置
                          child: Stack(
                            alignment: Alignment.center,
                            children: [
                              //头像,这里采用其中一种方式,剪裁,另外一种在下面
                              ClipRRect(
                                borderRadius: BorderRadius.circular(4),
                                child: Image.asset(
                                  widget.subImageUrl!,
                                  width: 20,
                                  height: 20,
                                  fit: BoxFit.fitWidth,
                                )
                              ),

                              // 红点,可以一张图片处理,默认使用圆角背景
                              Positioned(
                                right: 0,
                                top: 0,
                                child: Container(
                                  width: 6,
                                  height: 6,
                                  //设置了 decoration 不能设置Container的color属性
                                  decoration: BoxDecoration(
                                    //也可以设置image属性,给图片加圆角
                                    color: Colors.red,
                                    border: Border.all(color: Colors.red, width: 1),
                                    borderRadius: 
                                        const BorderRadius.all(Radius.circular(3))
                                  ),
                                )
                              )
                            ],
                          )
                        ) : Container(),
                        //设置间距
                        const SizedBox(width: 2),
                        //设置活动子标题,如果有的话
                        widget.subText != null ?
                          Text(
                              widget.subText!,
                              style: const TextStyle(
                                  fontSize: 12,
                                  color: Colors.black
                              )
                          ) : Container(),
                      ],
                  ),
                  //间距
                  const SizedBox(width: 2),
                  //右侧箭头
                  Image.asset(
                    "images/icon_right.png",
                    width: 12,
                    height: 12,
                    fit: BoxFit.fitWidth,
                  )
                ],
              )
            ]
        ),
      ),

      //通过三目运算符来动态控制下划线的显示,Container完全可以当占位符
      widget.hasLine ?
      Row(
        children: [
          //两个Container都可以
          Container(width: 46, height: 1, color: Colors.white),
          Expanded(child: Container(height: 1, color: 
              const Color.fromRGBO(0xe1, 0xe1, 0xe1, 1))),],
      ) :
      Container(height: 1, color: Colors.white)
    ],
  )
);

就这样一个小组件完成了

如果小组件点击后有回调,那么可以定义一个Function属性,和 name 等一样,构造方法传入即可,例如:

//不带参回调
final Function() onClick;
//带参回调
final Function(int index) onClick;

发现页

发现页没有多少特殊的地方,直接ListView搭配多个cell即可,ListView 默认垂直垂直方向布局,可设置水平

//跳转新页面必备
Scaffold(
  //顶部导航
  appBar: AppBar(
    title: const Text("发现"),
    foregroundColor: Colors.black,
    backgroundColor: const Color.fromRGBO(0xe1, 0xe1, 0xe1, 1),
    elevation: 0 //去掉阴影
  ),
  body: Container(
    //设置ListView的背景,ListView组件默认不能设置颜色,只能用这个了
    color: const Color.fromRGBO(0xe1, 0xe1, 0xe1, 1),
    child: ListView(
        children: const <Widget>[
          ItemCell(imageUrl: "images/朋友圈.png", text: "朋友圈", 
              subImageUrl: 'images/head.jpg', subText: "2个朋友赞过"),
          //也可以使用Container,如果不设置颜色或者边距,那么直接SizeBox更合适
          SizedBox(height: 6),
          ItemCell(imageUrl: "images/扫一扫2.png", text: "扫一扫", hasLine: true),
          ItemCell(imageUrl: "images/摇一摇.png", text: "摇一摇"),

          SizedBox(height: 6),
          ItemCell(imageUrl: "images/看一看.png", text: "看一看", hasLine: true),
          ItemCell(imageUrl: "images/搜一搜.png", text: "搜一搜"),

          SizedBox(height: 6),
          ItemCell(imageUrl: "images/附近.png", text: "附近"),

          SizedBox(height: 6),
          ItemCell(imageUrl: "images/购物.png", text: "购物", hasLine: true),
          ItemCell(imageUrl: "images/游戏.png", text: "游戏"),

          SizedBox(height: 6),
          ItemCell(imageUrl: "images/小程序.png", text: "小程序"),
      ]
    ),
  )
);

效果如下所示

flutter-仿微信项目实战二(发现、我、组件状态保存)

我的

我的页面如下所示,我们从顶部开始

flutter-仿微信项目实战二(发现、我、组件状态保存)

//先放置一个ListView,保证能滚动,避免一些小手机无法看到底部 item
ListView(
  children: [
    //为了保证滑动底部使用默认的白色,而里面默认背景灰色,给内容包括一个Container设置为灰色
    //这样空出来的间隔颜色都是灰色
    //当然实现手段有多中
    Container(
      color: const Color.fromRGBO(0xe1, 0xe1, 0xe1, 1),
      //由于方式的内容为上下布局,采用Column
      child: Column(
        children: [
          //顶部个人信息
          Container(
            //默认背景白色
            color: Colors.white,
            height: 140,
            //将顶部分为左侧内容和右侧箭头
            //这样左侧内容也可以随意调整,后面也可以根据内容限制长度
            child: Row(
              mainAxisAlignment: MainAxisAlignment.spaceBetween,
              children: [
                //将左侧图片和右侧文字分为两个挨着的部分
                Row(
                    crossAxisAlignment: CrossAxisAlignment.center,
                    children: [
                      //左侧图片
                      Container(
                          margin: const EdgeInsets.only(left: 12),
                          width: 60,
                          height: 60,
                          decoration: const BoxDecoration(
                              image: DecorationImage(
                                  image: AssetImage("images/head.jpg")
                              ),
                              borderRadius: BorderRadius.all(Radius.circular(6))
                          )
                      ),
                      //间距
                      const SizedBox(width: 12),
                      //右侧文字上下布局
                      Column(
                          mainAxisAlignment: MainAxisAlignment.center,
                          crossAxisAlignment: CrossAxisAlignment.start,
                          children: const [
                            Text("剪刀石头布",
                              style: TextStyle(
                                  fontSize: 18,
                                  fontWeight: FontWeight.bold
                              ),
                            ),
                            SizedBox(height: 4),
                            Text("微信号: rock666")
                          ]
                      )
                    ]
                ),
                //右侧箭头
                Container(
                    margin: const EdgeInsets.only(right: ),
                    child: Image.asset(
                      "images/icon_right.png",
                      width: 12,
                      height: 12,
                      fit: BoxFit.fitWidth,
                    )
                )
              ],
            ),
          ),
          //下面的功能item
          const SizedBox(height: 6),
          const ItemCell(imageUrl: "images/微信卡包.png", text: "卡包"),
          //也可以使用Container
          const SizedBox(height: 6),
          const ItemCell(imageUrl: "images/微信收藏.png", text: "shoucang ", hasLine: true),
          const ItemCell(imageUrl: "images/微信相册.png", text: "摇一摇", hasLine: true),
          const ItemCell(imageUrl: "images/微信表情.png", text: "看一看"),

          const SizedBox(height: 6),
          const ItemCell(imageUrl: "images/微信设置.png", text: "附近"),
          
          //底部灰色部分,看起来好看一些,可以根据全屏高度计算剩余 
          //例如:MediaQuery.of(context).size.height-占用空间 来计算
          //const SizedBox(height: 150),
        ],
      ),
    ),
  ],
)

保存状态 AutomaticKeepAliveClientMixin

保存状态时,需要进行三步,继承重写调用父类build方法,如下所示

注意:组件状态保存仅仅适用于 StatefulWidget 类型组件

//1、继承 AutomaticKeepAliveClientMixin
class _DiscoverState extends State<Discover> with AutomaticKeepAliveClientMixin {

  //2、重写 wantKeepAlive 为 true
  //重写该方法,保持住状态,且只有StatefulWidget才可以
  @override
  bool get wantKeepAlive => true;

  @override
  Widget build(BuildContext context) {
    //3、调用父类的 build方 法
    super.build(context);
    return Scaffold(
      appBar: AppBar(
        title: const Text("发现"),
        foregroundColor: Colors.black,
        backgroundColor: const Color.fromRGBO(0xe1, 0xe1, 0xe1, 1),
        elevation: 0 //去掉阴影
      ),
      body: Container(
        color: const Color.fromRGBO(0xe1, 0xe1, 0xe1, 1),
        child: const Text("发现页"),
      ),
    );
}

最后

功能看起来很简单,介绍的也比价简单,这里没有多介绍原理,没有一步步告诉思路,毕竟代码也不多,个人直接贴出啦的方式比较好,主要步骤注释出来,方便理解