前言
今天我们来看一下在Flutter中如何自定义Widget,同时针对前几篇文章中涉及到的知识在这里做下练习。我们以在iOS下创建自定义Cell的思路来创建Widget,并将它应用到ListView 中,点击cell 实现一个页面跳转的效果。
先看下效果图:
主要就是参考微信的一个简单的List页面
自定义Widget(自定义Cell)
-
布局分析
我这里用到的是Container+Row 的布局方式,其实也可以通过Stack布局,方式非常多,大家灵活应用。这里主要目的是对Flutter-UI的练习。
-
自定义Widget
在iOS中,定制这样一个这样的cell 是不是分分钟的事情,算是非常简单的一种了;这个cell上可变的部分包括4部分:最左边的icon(UIImageView),标题title(UILabel),右边的灰色子标题subTitle(UILabel),小红点状态icon(UIImageView),还有不可变的小箭头(UIImageView);然后我们将可变的部分通过对外暴露一个方法来动态获取这些参数,并显示在屏幕。
在flutter中,我们通过自定义Widget来实现,这里我们自定义一个StatefulWidget(内部有状态改变,所以这里使用有状态的Widget),直接上代码:
import 'package:flutter/material.dart';
class ExamCell extends StatefulWidget {
//这里定义了4个变量,用来外界传入
final String title; // 成员变量
final String subTitle;
final String imgName;
final String subImgName;
// ExamCell 的构造方法 给外界暴露的方法 就像使用Text() 一样
//写法的规则就是这样
//快速生成:当我们只定义了上面的成员变量时,会提示报错,
// 此时选中报错的成员变量然后 option+回车 就自动生成以下构造方法
const ExamCell({
Key key,
@required this.title, //@required 类似于ios Protocol @required 表示必传参数
this.subTitle, //未标记@required为可选参数
@required this.imgName,
this.subImgName
}) : super(key: key);
@override
_ExamCellState createState() => _ExamCellState();
}
class _ExamCellState extends State<ExamCell> {
@override
Widget build(BuildContext context) {
return Container(
padding: EdgeInsets.all(10), //给整个容器添加一个内边框10,避免贴着屏幕
height: 54, // heightForRow
color: Colors.white, //cell.backgroundColor
child: Row( //整体一个横向布局
mainAxisAlignment: MainAxisAlignment.spaceBetween, //主轴约束
children: <Widget>[
Container( // left部分:image+label
child: Row(
children: [
Image( // UIImageView
image: AssetImage(widget.imgName), //注意:通过widget.ivar 访问内部的成员变量(self.ivar)
width:20,
),
SizedBox( //这里元素间的间隔先用SizeBox
width: 15,
),
Text(widget.title), //UILabel
],
),
),
Container( // right部分:label+image+image
child: Row(
children: <Widget>[
Text(
widget.subTitle != null ? widget.subTitle : '', //注意:给属性赋值是可以写逻辑判断的
style: TextStyle(color: Colors.grey),
),
widget.subImgName != null ? Container( //不为null给一个有内容的Container()
child: Image(
image: AssetImage(widget.subImgName),
width: 15,
),
margin: EdgeInsets.only(left: 10,right: 10),
) : Container(),
Image(
image: AssetImage('images/icon_right.png'),
width: 15,
)
],
),
),
],
),
);
}
}
1.在State
类中,通过widget.
来访问widget
类中的成员变量;
2.在渲染widget时可以添加逻辑判断;
-
使用ExamCell
import 'package:flutter/material.dart';
import 'package:flutterappzeze/pages/cells/exam_cell.dart';
class ExamPage extends StatefulWidget {
@override
_ExamPageState createState() => _ExamPageState();
}
class _ExamPageState extends State<ExamPage> {
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('水平测试'),
),
body: Container(
child: ListView(
children: <Widget>[ // Cells 因为现在是学习UI 我们就直接Cell往里生怼
ExamCell( // 这里就是我们自定义的cell 构造方法接受4个参数
imgName: 'images/pyq.png',
subImgName: '',
title: '泽泽圈',
subTitle: '',
),
SizedBox(height: 10), //暂时使用SizeBox 来达到类似iOS GroupStyle的Cell分组效果
ExamCell( // 这里就是我们自定义的cell 构造方法接受4个参数
imgName: 'images/pyq.png',
subImgName: '',
title: '泽泽的二圈',
subTitle: '',
),
SizedBox(height: 10),
ExamCell( // 这里就是我们自定义的cell 构造方法接受4个参数
imgName: 'images/shop.png',
subImgName: '',
title: 'zeze',
subTitle: '',
),
SizedBox(height: 10),
ExamCell( // 这里就是我们自定义的cell 构造方法接受4个参数
imgName: 'images/sys.png',
subImgName: '',
title: 'family伐木类',
subTitle: '',
),
SizedBox(height: 10),
ExamCell( // 这里就是我们自定义的cell 构造方法接受4个参数
imgName: 'images/icon_game.png',
subImgName: 'images/badge.png',
title: '泽泽的游戏',
subTitle: '玩游戏赢大奖',
),
],
),
)
);
}
}
在ExamPage
中使用我们自定义的widget,使用也非常简单,通过我们的构造方法ExamCell(imageName: subImageName: title: subTitle:)
,so easy!这里有个问题就是我们直接在ListView()中这样添加Cell是不合理的,如果有100行岂不是要累死😆,这里只是演示用法。其实在ListView()中也是有复用机制的,这里先不做介绍。
-
实现点击ExamCell并跳转页面+正向传参
这里有两个问题:
1.在ios开发中celld点击,我们通常是实现tableView:didSelectAtIndexPath:
就可以实现了,但在flutter中默认Widget是不具备事件响应能力的,它如果想具备事件响应需要用GestureDetector()
将widget包裹起来,GestureDetector()
能响应onTap()
,onTapDown()
,onTapCancel()
等事件;这就又类似于在iOS下的CALayer
和UIView
的关系了,有意思!
这里看下_DiscoverCellState
类的代码
class _ExamCellState extends State<ExamCell> {
@override
Widget build(BuildContext context) {
return GestureDetector(
onTap: (){
print("onTap");
},
onTapDown: (TapDownDetails details){
print("onTapDown");
},
onTapCancel: (){
print("onTapCancel");
},
child: Container(
//这里省略一些代码...就是cell内部的具体UI元素定义
//现在将这部分赋值给了GestureDetector的child
)
);
}
当点击屏幕时,控制台输出:
flutter: onTapDown
flutter: onTap
flutter: onTapDown
flutter: onTap
flutter: onTapDown
flutter: onTap
flutter: onTapDown
flutter: onTapCancel
2.页面跳转:在ios下我们可以通过ViewController的present
或者push
的方式实现页面跳转,并直接给controller实例赋值实现正向传参,在flutter中又是怎么实现呐是不是也有类似的方式?他就是Navigator
,这篇文章先不对这个玩意儿做详细的介绍,我会在后面的文章中涉及,到时候再详细说,这里就先简单的使用一下;
class _ExamCellState extends State<ExamCell> {
@override
Widget build(BuildContext context) {
return GestureDetector(
onTap: (){
print("onTap");
Navigator.of(context).push(MaterialPageRoute(
builder: (BuildContext context) => TestPage(
title: widget.title,
)));//跳转
//下面的写法也是一个意思,在flutter中如果方法内部只有一行代码
// 默认使用=>的方式 ,如上。
// Navigator.of(context).push(MaterialPageRoute(
// builder: (BuildContext context){
// return TestPage(
// title: widget.title,
// );
// }
// ));
},
child: Container(
//这里省略一些代码...就是cell内部的具体UI元素定义
//现在将这部分赋值给了GestureDetector的child
)
);
}
这里就是通过点击我的ExamCell
跳转到一个TestPage
的一个Widget页面,同时将当前cell的title带入。
总结
熟能生巧,在Flutter中UI布局非常灵活,当我们掌握了它们之后就可以在具体的业务上应用自如了,到目前为止并没有什么技术含量或者难以理解的东西,完全就是在熟悉Flutter下的语法规则。