本文微信公衆號「AndroidTraveler」首發。git
本篇主要講述如何快速在 Flutter 中實現 ListView。github
先上效果圖感覺一下: json
首先咱們要先肯定咱們列表項的佈局,咱們按照咱們效果圖上面所顯示的,能夠寫出以下代碼:bash
import 'package:flutter/material.dart';
class ItemWidget extends StatefulWidget {
@override
_ItemWidgetState createState() => _ItemWidgetState();
}
class _ItemWidgetState extends State<ItemWidget> {
@override
Widget build(BuildContext context) {
return Column(
mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[
Text('title'),
SizedBox(height: 6,),
Text('description')
],
);
}
}
複製代碼
顯示效果以下: 微信
固然這裏的 title 和 description 目前是 hard code,咱們第二步肯定 Bean 以後會作相應的處理。網絡
咱們根據列表項的顯示狀況能夠獲得以下 Bean:less
class ItemBean {
final String title;
final String description;
ItemBean(this.title, this.description);
}
複製代碼
能夠看到就是標題和描述而已。ide
同時咱們第一步的列表項能夠更新以下:佈局
import 'package:flutter/material.dart';
import 'package:my_flutter/item_bean.dart';
class ItemWidget extends StatefulWidget {
final ItemBean itemBean;
ItemWidget(this.itemBean);
@override
_ItemWidgetState createState() => _ItemWidgetState();
}
class _ItemWidgetState extends State<ItemWidget> {
@override
Widget build(BuildContext context) {
return Column(
mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[
Text(widget?.itemBean?.title ?? ''),
SizedBox(height: 6,),
Text(widget?.itemBean?.description ?? '')
],
);
}
}
複製代碼
再也不 hard code 了。post
另外若是你對於 ?. 和 ?? 不熟悉,能夠看下我以前的文章 Dart 如何優雅的避空。
有了數據源和顯示的 Widget,那麼顯示也就水到渠成了。
以下:
import 'package:flutter/material.dart';
import 'package:my_flutter/item_bean.dart';
import 'package:my_flutter/item_widget.dart';
class ListViewWidget extends StatefulWidget {
@override
_ListViewWidgetState createState() => _ListViewWidgetState();
}
class _ListViewWidgetState extends State<ListViewWidget> {
final List<ItemBean> itemBeans = [];
@override
void initState() {
super.initState();
_initData();
}
/// 實際場景多是從網絡拉取,這裏演示就直接填充數據源了
void _initData() {
itemBeans.add(ItemBean('第一句', '關注微信公衆號「AndroidTraveler」'));
itemBeans.add(ItemBean('第二句', '星河滾燙,你是人間理想'));
itemBeans.add(ItemBean('第三句', '我明白你會來,因此我等。'));
itemBeans.add(ItemBean('第四句', '家人閒坐,燈火可親。'));
}
@override
Widget build(BuildContext context) {
return Container(
color: Colors.white,
child: ListView.builder(
itemCount: itemBeans.length,
itemBuilder: (context, index) {
return ItemWidget(itemBeans[index]);
},
),
);
}
}
複製代碼
列表的關鍵代碼在於:
ListView.builder(
itemCount: itemBeans.length,
itemBuilder: (context, index) {
return ItemWidget(itemBeans[index]);
},
)
複製代碼
仍是比較固定的。
最後咱們把這個 ListViewWidget 加載到主頁面,主頁面代碼以下:
import 'package:flutter/material.dart';
import 'package:my_flutter/listview_widget.dart';
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(
home: Scaffold(
body: Center(
child: _buildWidget(),
),
),
);
}
Widget _buildWidget() {
return ListViewWidget();
}
}
複製代碼
運行效果以下:
看起來仍是怪怪的,咱們增長下分隔線看看效果。
Flutter 官方 sdk 裏面自帶了分隔線 Widget,爲 Divider。
具體每一個屬性能夠在代碼裏面看到詳細註釋,這裏就不展開了。
咱們的 Divider 代碼以下:
Divider(color: Colors.grey,),
複製代碼
很簡單,就是指定分隔線的顏色。
由於咱們的 Item 自己就是一個 Column,咱們直接追加就能夠了。
ItemWidget 修改後以下:
···
class _ItemWidgetState extends State<ItemWidget> {
@override
Widget build(BuildContext context) {
return Container(
padding: EdgeInsets.only(left: 16.0),
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[
Text(widget?.itemBean?.title ?? ''),
SizedBox(
height: 6,
),
Text(widget?.itemBean?.description ?? ''),
Divider(color: Colors.grey),
],
),
);
}
}
複製代碼
效果以下:
可能有小夥伴會說,你這個是恰好 item 佈局是 Column,若是不是 Column 的話呢?
方法多種多樣,這裏就說其中的一種方法吧,好比你能夠利用 Stack 來實現。
咱們知道,列表成功顯示只是第一步而已,點擊可以實現咱們指望的效果纔是常規操做。
所以,點擊回調是必不可少的。
那麼如何實現呢?
其實也很簡單,就是跟普通 Widget 同樣包裹一層 GestureDetector 就能夠了。
修改後的 ItemWidget 以下:
···
class _ItemWidgetState extends State<ItemWidget> {
@override
Widget build(BuildContext context) {
Widget container = Container(
padding: EdgeInsets.only(left: 16.0),
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[
Text(widget?.itemBean?.title ?? ''),
SizedBox(
height: 6,
),
Text(widget?.itemBean?.description ?? ''),
Divider(color: Colors.grey),
],
),
);
return GestureDetector(
child: container,
onTap: (){
print('onTap');
},
);
}
}
複製代碼
點擊 Item 時控制檯確實輸出了打印日誌:
flutter: onTap
flutter: onTap
複製代碼
可是存在兩個問題。
第一個就是不知道點擊的是哪個 item,第二個就是通常回調應該是在外層而不該該直接寫在裏面。
所以咱們須要對 ItemWidget 作修改,傳入 index 和監聽回調。
咱們定義的回調接口以下:
/// 定義一個回調接口
typedef OnItemClickListener = void Function(int position, ItemBean itemBean);
複製代碼
ItemWidget 修改後代碼以下:
import 'package:flutter/material.dart';
import 'package:my_flutter/item_bean.dart';
/// 定義一個回調接口
typedef OnItemClickListener = void Function(int position, ItemBean itemBean);
class ItemWidget extends StatefulWidget {
final int position;
final ItemBean itemBean;
final OnItemClickListener listener;
ItemWidget(this.position, this.itemBean, this.listener);
@override
_ItemWidgetState createState() => _ItemWidgetState();
}
class _ItemWidgetState extends State<ItemWidget> {
@override
Widget build(BuildContext context) {
Widget container = Container(
padding: EdgeInsets.only(left: 16.0),
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[
Text(widget?.itemBean?.title ?? ''),
SizedBox(
height: 6,
),
Text(widget?.itemBean?.description ?? ''),
Divider(color: Colors.grey),
],
),
);
return GestureDetector(
child: container,
onTap: () => widget.listener(widget.position, widget.itemBean),
);
}
}
複製代碼
能夠看到咱們增長了 position 和 listener。
所以咱們的 ListViewWidget 也須要作相應修改:
class ListViewWidget extends StatefulWidget {
final OnItemClickListener listener;
ListViewWidget(this.listener);
@override
_ListViewWidgetState createState() => _ListViewWidgetState();
}
class _ListViewWidgetState extends State<ListViewWidget> {
···
@override
Widget build(BuildContext context) {
return Container(
color: Colors.white,
child: ListView.builder(
itemCount: itemBeans.length,
itemBuilder: (context, index) {
return ItemWidget(index, itemBeans[index], widget.listener);
},
),
);
}
}
複製代碼
能夠看到改動項就是傳入了 listener 而且在 itemBuilder 返回的時候對應傳入參數給 ItemWidget。
而後咱們在 main.dart 修改以下:
···
class MyApp extends StatelessWidget {
···
Widget _buildWidget() {
return ListViewWidget((position, itemBean){
print('pos=$position, title='+itemBean.title+",description="+itemBean.description);
});
}
}
複製代碼
點擊列表,控制檯輸出指望效果以下:
flutter: pos=0, title=第一句,description=關注微信公衆號「AndroidTraveler」
flutter: pos=1, title=第二句,description=星河滾燙,你是人間理想
複製代碼
點擊是實現了,可是點擊以後沒有一點點反饋,用戶怎麼知道本身是否是點擊了呢?
所以點擊後的視覺反饋也是必不可少的。
那麼這個點擊後的反饋怎麼處理呢?
其實仍是離不開 GestureDetector 的回調監聽。
當按下時,咱們更新顏色值,當擡起或取消時咱們恢復顏色值。
所以咱們能夠修改 ItemWidget 以下:
···
class _ItemWidgetState extends State<ItemWidget> {
Color _color;
@override
void initState() {
super.initState();
_color = Colors.white;
}
@override
Widget build(BuildContext context) {
Widget container = Container(
color: _color,
padding: EdgeInsets.only(left: 16.0),
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[
Text(widget?.itemBean?.title ?? ''),
SizedBox(
height: 6,
),
Text(widget?.itemBean?.description ?? ''),
Divider(color: Colors.grey),
],
),
);
return GestureDetector(
child: container,
onTap: () => widget.listener(widget.position, widget.itemBean),
onTapDown: (_) => _updatePressedColor(),
onTapUp: (_) => _updateNormalColor(),
onTapCancel: () => _updateNormalColor(),
);
}
void _updateNormalColor() {
setState(() {
_color = Colors.white;
});
}
void _updatePressedColor() {
setState(() {
_color = Color(0xFFF0F1F2);
});
}
}
複製代碼
效果以下:
能夠看到分隔線有點問題,主要緣由是 Divider 默認高度是 16.0,因此咱們調整下,同時改下 item 的上下間隔。
修改以下:
···
class _ItemWidgetState extends State<ItemWidget> {
···
@override
Widget build(BuildContext context) {
Widget container = Container(
color: _color,
padding: EdgeInsets.only(left: 16.0),
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[
SizedBox(
height: 8,
),
Text(widget?.itemBean?.title ?? ''),
SizedBox(
height: 6,
),
Text(widget?.itemBean?.description ?? ''),
SizedBox(
height: 8,
),
Divider(color: Colors.grey, height: 0.5,),
],
),
);
return GestureDetector(
child: container,
onTap: () => widget.listener(widget.position, widget.itemBean),
onTapDown: (_) => _updatePressedColor(),
onTapUp: (_) => _updateNormalColor(),
onTapCancel: () => _updateNormalColor(),
);
}
void _updateNormalColor() {
setState(() {
_color = Colors.white;
});
}
void _updatePressedColor() {
setState(() {
_color = Colors.grey;
});
}
}
複製代碼
效果以下:
可是若是你不是長按,而是快速點擊,會發現沒有效果。
因此咱們須要給擡起恢復來個延時,修改以下:
···
void _updateNormalColor() {
Future.delayed(Duration(milliseconds: 100), () {
setState(() {
_color = Colors.white;
});
});
}
···
複製代碼
效果以下:
這個其實也不難。
咱們知道 ListView 的核心代碼是:
ListView.builder(
itemCount: itemBeans.length,
itemBuilder: (context, index) {
return ItemWidget(itemBeans[index]);
},
)
複製代碼
所以只須要在 itemBuilder 這裏作文章。
舉個例子,假設我要求要顯示一個純色塊在頂部。
那麼咱們能夠以下修改
···
class _ListViewWidgetState extends State<ListViewWidget> {
···
/// 實際場景多是從網絡拉取,這裏演示就直接填充數據源了
void _initData() {
itemBeans.add(ItemBean('', ''));
itemBeans.add(ItemBean('第一句', '關注微信公衆號「AndroidTraveler」'));
itemBeans.add(ItemBean('第二句', '星河滾燙,你是人間理想'));
itemBeans.add(ItemBean('第三句', '我明白你會來,因此我等。'));
itemBeans.add(ItemBean('第四句', '家人閒坐,燈火可親。'));
}
@override
Widget build(BuildContext context) {
return Container(
color: Colors.white,
child: ListView.builder(
itemCount: itemBeans.length,
itemBuilder: (context, index) {
if (index == 0) {
return Container(
color: Colors.blue,
height: 66,
);
} else {
return ItemWidget(index, itemBeans[index], widget.listener);
}
},
),
);
}
}
複製代碼
這裏經過在一開始添加一個空 Bean,而後在 itemBuilder 作判斷返回對應佈局來實現。
固然你也能夠不在集合添加,可是 index 須要更改,而且列表長度也要修改,等價代碼以下:
···
class _ListViewWidgetState extends State<ListViewWidget> {
···
@override
Widget build(BuildContext context) {
return Container(
color: Colors.white,
child: ListView.builder(
itemCount: itemBeans.length + 1,
itemBuilder: (context, index) {
if (index == 0) {
return Container(
color: Colors.blue,
height: 66,
);
} else {
return ItemWidget(index, itemBeans[index - 1], widget.listener);
}
},
),
);
}
}
複製代碼
能夠看到 itemCount 和 itemBuilder 都變化了。
效果圖以下:
從這個小演示,咱們也能夠看到關鍵在於 itemCount 和 itemBuilder 的處理。
只要處理得當,能夠實現各類各樣的佈局。
通常的方式都是經過在 Bean 添加一個 viewType 來區分加載不一樣的佈局。
也能夠考慮繼承和多態等方式,這裏就不展開講了。
相信小夥伴們都可以自行處理的。
咱們一開始的效果圖就是這個代碼,不過度隔線和視覺反饋的顏色值不同而已。
因爲只是演示,所以有一些地方並無作額外處理,實際使用須要注意。