前言
大家都知道,在Flutter框架中,渲染并不像ReactNative或者Veex等通过JSCore来映射成原生组件,而是有自己的一套渲染引擎,这也是Flutter的强大之处。来上一张Flutter架构图:
从图上可以看出,我们是在Framework层通过Dart语法进行开发,再往下层就是基于C++的一些引擎依赖。因此,如果我们想在Flutter中使用像iOS-Grouped风格的TableView就需要使用Widget来自己构建了。
今天我们就以微信通讯录页面UI来做实现一个Grouped风格的ListView。先看下效果图:
开始
-
布局分析
因为有section的存在,在iOS下是很好实现的,但是在Flutter中并没有相应的API或者回调来配置这部分,所有我把这部分归类到Cell中,然后通过数据来控制Section的显示。如图:
整个cell外层通过一个Column()
布局拆分成Section+Content两部分,然后Content部分用Row()
布局拆分成leftIcon和nickName部分,因为nickName 部分底部还有一跟分割线,所有nickName部分可以通过一个一个Column()
布局分为上下两个部分。(还是那句话,UI布局思路非常多且灵活,以上只是个人的思路)。
- 开始造
这里还是基于之前创建的一个Tabbar项目,这里我们使用ListView.builder()
,这个是可是实现Cell复用的ListView,内部需要一个itemCount
(就是numberOfRowsInSection:
)和itemBuilder
(是一个callback函数,类似于cellForRowAtIndexPath:
);我这里把这个callback的实现写在了外面的_cellForRowAtIndex
方法中,这个方法在滚动列表时,也会实时回调,跟cellForRowAtIndexPath
一模一样,一个需要返回一个UITableViewCell
,一个需要返回一个Widget
.
以下是build()
部分的代码:
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('泽泽2'),
),
body: Container(
child: ListView.builder( //具备复用能力的ListView()
itemCount: _headerData.length+_listData.length, // 数组个数
itemBuilder: _cellForRowAtIndex, //复用回调函数
),
),
);
}
_itemForRow()
代码块:
//cell复用回调
Widget _cellForRowAtIndex(BuildContext context, int index){
//头部cell
if(index < _headerData.length){
FriendModel model = _headerData[index];
return CustomCell(
imageAssets: model.imageUrl,
name: model.name,
);
}
//其他cell
return CustomCell(
imageUrl: _listData[index - 4].imageUrl,
name: _listData[index - 4].name,
groupTitle: _listData[index - 4].indexLetter,
);
}
自定义Cell custom_cell.dart
部分:
import 'package:flutter/material.dart';
class CustomCell extends StatefulWidget {
final String imageUrl;
final String name;
final String groupTitle;
final String imageAssets;
const CustomCell({
Key key,
this.imageUrl,
this.name,
this.groupTitle,
this.imageAssets,
}) : super(key: key);
@override
_CustomCellState createState() => _CustomCellState();
}
class _CustomCellState extends State<CustomCell> {
@override
Widget build(BuildContext context) {
return Container(
child: Column(
children: [
Container(
alignment: Alignment.centerLeft,
padding: EdgeInsets.only(left: 10),
height: widget.groupTitle != null ? 30 : 0,
color: Color.fromRGBO(236, 237, 237, 1.0),
// child: Text(widget.groupTitle,style: TextStyle(color: Colors.white),),
child: widget.groupTitle != null ? Text(widget.groupTitle,style: TextStyle(color: Color.fromRGBO(76, 75, 75, 1.0),),):null,
),//SectionHeader
Container(
height: 54,
color: Colors.white,
child: Row(
children: [
Container(
width:34,
height: 34,
margin: EdgeInsets.all(10),
//设置圆角
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(6.0),
image: DecorationImage(
image:widget.imageUrl != null ? NetworkImage(widget.imageUrl) : AssetImage(widget.imageAssets),
),
),
), //左图
Container(
// color: Colors.blue,
child: Column(
mainAxisAlignment: MainAxisAlignment.spaceBetween, //上下对齐
children: [
Container( //昵称
// color: Colors.red,
height:53.5,
width: MediaQuery.of(context).size.width - 10 - 10 - 34,
alignment: Alignment.centerLeft, //文本对齐
child: Text(
widget.name,
style: TextStyle(fontSize: 18),
),
),
Container( //线
height: 0.5,
width: MediaQuery.of(context).size.width - 10 - 10 - 34,
color: Color.fromRGBO(246, 246, 246, 1.0),
)
],
),
),
],
),
),//Contents
],
),
);
}
}
目前看到的效果:
现在看到的效果就是我们定制的这个完整Cell的样子了(SectionHeader+Content),但是我们不需要每个cell都来显示这个SectionHeader,怎么弄呐?我们通过model数据来做控制SectionHeader的显示。这里在model中定义了一个
groupTitle
的字段,根据groupTitle
是否为 null 来控制SectionHeader的显示与隐藏;第二个问题就是mode中包含相同groupTitle
的Cell,只让第一个显示SectionHeader显示,之后就不显示,直到不同的groupTitle出现。思路还是数据驱动UI的方式。首先把列表分成了4+n,因为上面的4个cell是固定不变的,分别将model数据添加到
_headerData
和_listData
中,然后来到_cellForRowAtIndex
中做一些判断:
//cell复用回调
Widget _cellForRowAtIndex(BuildContext context, int index){
//头部cell
if(index < _headerData.length){
FriendModel model = _headerData[index];
return CustomCell(
imageAssets: model.imageUrl,
name: model.name,
);
}
//其他cell
//如果当前model.indexLetter(对应model中的indexLetter字段)与上一个model.indexLetter相同则不显示
//否则就显示
if(index > 4 && _listData[index - 4].indexLetter == _listData[index - 5].indexLetter){
//groupTitle = null
return CustomCell(
imageUrl: _listData[index - 4].imageUrl,
name: _listData[index - 4].name,
);
}
return CustomCell(
imageUrl: _listData[index - 4].imageUrl,
name: _listData[index - 4].name,
groupTitle: _listData[index - 4].indexLetter,
);
}
效果图:
这样我们就将一个Group风格的tableView就构造完成了。
补充
- 常用的一些变量或者常亮或者一些方法可以单独的抽离到一个dart文件中,比如获取屏幕的宽高,一些主题颜色等,比如我这里的
app_config.dart
文件
import 'package:flutter/material.dart';
//主题色调
final Color APP_ThemeColor = Color.fromRGBO(223, 223, 223, 1.0);
//屏幕的宽 等同于 [UIScreen mainScreen].bounds.size.width 因为这里需要一个context 所以需要定义成方法
double ScreenWidth(BuildContext context) => MediaQuery.of(context).size.width;
//屏幕的高
double ScreenHeight(BuildContext context) => MediaQuery.of(context).size.height;
使用app_config.dart
Container( //线
height: 0.5,
width: ScreenWidth(context) - 10 - 10 - 34,
color: Color.fromRGBO(246, 246, 246, 1.0),
)
- 关于导航条(
AppBar
) 上如何配置左右视图(比如iOS下的UINavigationItem
),这里需要用到AppBar
下的actions
属性,接收一个Widget数组,可以添加多个Widget,这里举例一下
我们添加一个Icon()
,然后通过GestureDetector()
给Icon()
一个点击事件,然后通过Navigator
实现页面跳转并传参。
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('泽泽2'),
actions: <Widget>[
GestureDetector(
child: Container(
margin: EdgeInsets.only(right: 10),
child: Icon(Icons.add),
),
onTap: (){
print('我被点击了');
Navigator.of(context).push(MaterialPageRoute(builder: (BuildContext context){
return AddFriend(
cellName: '泽泽伐木类',
);
}));
},
),
],
),
body: Container(
child: ListView.builder( //具备复用能力的ListView()
itemCount: _headerData.length+_listData.length,
itemBuilder: _cellForRowAtIndex,
),
),
);
}
}
- flutter 中的
..
级联调用语法,可以对同一个对象进行一系列操作,比如在构造数据多次的调用addAll()的时候:
@override
void initState() { //这个方法需要在State类重写 同iOS下的viewWillAppear()
super.initState();
//构造下数据 .. 语法糖
//为了多加点测试数据,对_listData进行了两次addAll(),然后根据字母进行了排序
_listData..addAll(datas)..addAll(datas)..sort((FriendModel a,FriendModel b){
return a.indexLetter.compareTo(b.indexLetter);
});
}
这里等同于
@override
void initState() { //这个方法需要在State类重写 同iOS下的viewWillAppear()
super.initState();
//构造下数据 .. 语法
_listData.addAll(datas);
_listData.addAll(datas);
//按照字母进行排序 跟NSArray类似
_listData.sort((FriendModel a,FriendModel b){
return a.indexLetter.compareTo(b.indexLetter);
});
}
更多Dart语法相关的内容,会在后面的文章中介绍
总结
本篇内容大部分的内容还是关于UI相关的东西,主要就是拓展一下在iOS中常见的一些UI布局,迁移到Flutter中的一些思路;同时也简单的提到了一些Dart语法相关的东西,这个后续我们详细聊;