flutter-仿微信项目实战二(发现、我、组件状态保存)
站长
· 阅读数 2
前言
这篇文章主要是编写仿微信的发现页、我的页面,由于这两个功能模块比较少,就放到一起了
这里主要介绍界面搭建,小组件封装等效果
小组件cell
介绍发现页和我的页面之前,先介绍一下他们都再用的图文混排的 cell
,就像下面这样,我们顺道把 朋友圈后面的东西也做了
//由于我们封装的小组件,我们要设置一些属性,在外面调用时,可以通过构造方法传递进来
//如下所示,由于状态会更新,选择的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: "小程序"),
]
),
)
);
效果如下所示
我的
我的页面如下所示,我们从顶部开始
//先放置一个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("发现页"),
),
);
}
最后
功能看起来很简单,介绍的也比价简单,这里没有多介绍原理,没有一步步告诉思路,毕竟代码也不多,个人直接贴出啦的方式比较好,主要步骤注释出来,方便理解