前言

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

这儿首要介绍界面搭建,小组件封装等作用

源码地址

小组件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("发现页"),
      ),
    );
}

最后

功用看起来很简略,介绍的也比价简略,这儿没有多介绍原理,没有一步步告知思路,究竟代码也不多,个人直接贴出啦的方法比较好,首要过程注释出来,方便了解