前言
大家都知道混巧,在Flutter框架中糕殉,渲染并不像ReactNative或者Veex等通過(guò)JSCore來(lái)映射成原生組件奕锌,而是有自己的一套渲染引擎挤茄,這也是Flutter的強(qiáng)大之處山害。來(lái)上一張F(tuán)lutter架構(gòu)圖:
從圖上可以看出纠俭,我們是在Framework層通過(guò)Dart語(yǔ)法進(jìn)行開發(fā),再往下層就是基于C++的一些引擎依賴浪慌。因此,如果我們想在Flutter中使用像iOS-Grouped風(fēng)格的TableView就需要使用Widget來(lái)自己構(gòu)建了冤荆。
今天我們就以微信通訊錄頁(yè)面UI來(lái)做實(shí)現(xiàn)一個(gè)Grouped風(fēng)格的ListView。先看下效果圖:
開始
-
布局分析
因?yàn)橛衧ection的存在权纤,在iOS下是很好實(shí)現(xiàn)的钓简,但是在Flutter中并沒(méi)有相應(yīng)的API或者回調(diào)來(lái)配置這部分,所有我把這部分歸類到Cell中汹想,然后通過(guò)數(shù)據(jù)來(lái)控制Section的顯示外邓。如圖:
整個(gè)cell外層通過(guò)一個(gè)Column()
布局拆分成Section+Content兩部分,然后Content部分用Row()
布局拆分成leftIcon和nickName部分,因?yàn)閚ickName 部分底部還有一跟分割線,所有nickName部分可以通過(guò)一個(gè)一個(gè)Column()
布局分為上下兩個(gè)部分古掏。(還是那句話,UI布局思路非常多且靈活损话,以上只是個(gè)人的思路)。
- 開始造
這里還是基于之前創(chuàng)建的一個(gè)Tabbar項(xiàng)目槽唾,這里我們使用ListView.builder()
,這個(gè)是可是實(shí)現(xiàn)Cell復(fù)用的ListView,內(nèi)部需要一個(gè)itemCount
(就是numberOfRowsInSection:
)和itemBuilder
(是一個(gè)callback函數(shù),類似于cellForRowAtIndexPath:
)丧枪;我這里把這個(gè)callback的實(shí)現(xiàn)寫在了外面的_cellForRowAtIndex
方法中,這個(gè)方法在滾動(dòng)列表時(shí)光涂,也會(huì)實(shí)時(shí)回調(diào),跟cellForRowAtIndexPath
一模一樣豪诲,一個(gè)需要返回一個(gè)UITableViewCell
,一個(gè)需要返回一個(gè)Widget
.
以下是build()
部分的代碼:
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('澤澤2'),
),
body: Container(
child: ListView.builder( //具備復(fù)用能力的ListView()
itemCount: _headerData.length+_listData.length, // 數(shù)組個(gè)數(shù)
itemBuilder: _cellForRowAtIndex, //復(fù)用回調(diào)函數(shù)
),
),
);
}
_itemForRow()
代碼塊:
//cell復(fù)用回調(diào)
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),
//設(shè)置圓角
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, //上下對(duì)齊
children: [
Container( //昵稱
// color: Colors.red,
height:53.5,
width: MediaQuery.of(context).size.width - 10 - 10 - 34,
alignment: Alignment.centerLeft, //文本對(duì)齊
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
],
),
);
}
}
目前看到的效果:
現(xiàn)在看到的效果就是我們定制的這個(gè)完整Cell的樣子了(SectionHeader+Content),但是我們不需要每個(gè)cell都來(lái)顯示這個(gè)SectionHeader,怎么弄吶挂绰?我們通過(guò)model數(shù)據(jù)來(lái)做控制SectionHeader的顯示屎篱。這里在model中定義了一個(gè)
groupTitle
的字段,根據(jù)groupTitle
是否為 null 來(lái)控制SectionHeader的顯示與隱藏葵蒂;第二個(gè)問(wèn)題就是mode中包含相同groupTitle
的Cell,只讓第一個(gè)顯示SectionHeader顯示交播,之后就不顯示,直到不同的groupTitle出現(xiàn)践付。思路還是數(shù)據(jù)驅(qū)動(dòng)UI的方式秦士。首先把列表分成了4+n,因?yàn)樯厦娴?個(gè)cell是固定不變的,分別將model數(shù)據(jù)添加到
_headerData
和_listData
中永高,然后來(lái)到_cellForRowAtIndex
中做一些判斷:
//cell復(fù)用回調(diào)
Widget _cellForRowAtIndex(BuildContext context, int index){
//頭部cell
if(index < _headerData.length){
FriendModel model = _headerData[index];
return CustomCell(
imageAssets: model.imageUrl,
name: model.name,
);
}
//其他cell
//如果當(dāng)前model.indexLetter(對(duì)應(yīng)model中的indexLetter字段)與上一個(gè)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,
);
}
效果圖:
這樣我們就將一個(gè)Group風(fēng)格的tableView就構(gòu)造完成了隧土。
補(bǔ)充
- 常用的一些變量或者常亮或者一些方法可以單獨(dú)的抽離到一個(gè)dart文件中,比如獲取屏幕的寬高命爬,一些主題顏色等曹傀,比如我這里的
app_config.dart
文件
import 'package:flutter/material.dart';
//主題色調(diào)
final Color APP_ThemeColor = Color.fromRGBO(223, 223, 223, 1.0);
//屏幕的寬 等同于 [UIScreen mainScreen].bounds.size.width 因?yàn)檫@里需要一個(gè)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),
)
- 關(guān)于導(dǎo)航條(
AppBar
) 上如何配置左右視圖(比如iOS下的UINavigationItem
),這里需要用到AppBar
下的actions
屬性饲宛,接收一個(gè)Widget數(shù)組皆愉,可以添加多個(gè)Widget,這里舉例一下
我們添加一個(gè)Icon()
艇抠,然后通過(guò)GestureDetector()
給Icon()
一個(gè)點(diǎn)擊事件幕庐,然后通過(guò)Navigator
實(shí)現(xiàn)頁(yè)面跳轉(zhuǎn)并傳參。
@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('我被點(diǎn)擊了');
Navigator.of(context).push(MaterialPageRoute(builder: (BuildContext context){
return AddFriend(
cellName: '澤澤伐木類',
);
}));
},
),
],
),
body: Container(
child: ListView.builder( //具備復(fù)用能力的ListView()
itemCount: _headerData.length+_listData.length,
itemBuilder: _cellForRowAtIndex,
),
),
);
}
}
- flutter 中的
..
級(jí)聯(lián)調(diào)用語(yǔ)法家淤,可以對(duì)同一個(gè)對(duì)象進(jìn)行一系列操作异剥,比如在構(gòu)造數(shù)據(jù)多次的調(diào)用addAll()的時(shí)候:
@override
void initState() { //這個(gè)方法需要在State類重寫 同iOS下的viewWillAppear()
super.initState();
//構(gòu)造下數(shù)據(jù) .. 語(yǔ)法糖
//為了多加點(diǎn)測(cè)試數(shù)據(jù),對(duì)_listData進(jìn)行了兩次addAll(),然后根據(jù)字母進(jìn)行了排序
_listData..addAll(datas)..addAll(datas)..sort((FriendModel a,FriendModel b){
return a.indexLetter.compareTo(b.indexLetter);
});
}
這里等同于
@override
void initState() { //這個(gè)方法需要在State類重寫 同iOS下的viewWillAppear()
super.initState();
//構(gòu)造下數(shù)據(jù) .. 語(yǔ)法
_listData.addAll(datas)絮重;
_listData.addAll(datas);
//按照字母進(jìn)行排序 跟NSArray類似
_listData.sort((FriendModel a,FriendModel b){
return a.indexLetter.compareTo(b.indexLetter);
});
}
更多Dart語(yǔ)法相關(guān)的內(nèi)容届吁,會(huì)在后面的文章中介紹
總結(jié)
本篇內(nèi)容大部分的內(nèi)容還是關(guān)于UI相關(guān)的東西,主要就是拓展一下在iOS中常見的一些UI布局绿鸣,遷移到Flutter中的一些思路疚沐;同時(shí)也簡(jiǎn)單的提到了一些Dart語(yǔ)法相關(guān)的東西,這個(gè)后續(xù)我們?cè)敿?xì)聊潮模;