# Flutter ListView / ListTile /Card 介紹
## 靜態 ListView
適用在內容不太變動的地方
### ListTile.divideTiles
在每一個項目間新增分割線
```dart
Widget _myListView(BuildContext context) {
return ListView(
children: ListTile.divideTiles(
context: context,
tiles: [
ListTile(
title: Text('Sun'),
),
ListTile(
title: Text('Moon'),
),
ListTile(
title: Text('Star'),
),
],
).toList(),
);
}
```

## 動態 ListView
### ListView.builder()
這個方法只會處理要在螢幕上顯示的資料
```dart
Widget _myListView(BuildContext context) {
// backing data
final europeanCountries = ['Albania', 'Andorra', 'Armenia', 'Austria',
'Azerbaijan', 'Belarus', 'Belgium', 'Bosnia and Herzegovina', 'Bulgaria',
'Croatia', 'Cyprus', 'Czech Republic', 'Denmark', 'Estonia', 'Finland',
'France', 'Georgia', 'Germany', 'Greece', 'Hungary', 'Iceland', 'Ireland',
'Italy', 'Kazakhstan', 'Kosovo', 'Latvia', 'Liechtenstein', 'Lithuania',
'Luxembourg', 'Macedonia', 'Malta', 'Moldova', 'Monaco', 'Montenegro',
'Netherlands', 'Norway', 'Poland', 'Portugal', 'Romania', 'Russia',
'San Marino', 'Serbia', 'Slovakia', 'Slovenia', 'Spain', 'Sweden',
'Switzerland', 'Turkey', 'Ukraine', 'United Kingdom', 'Vatican City'];
return ListView.builder(
itemCount: europeanCountries.length, //告訴ListView有多少資料要顯示
itemBuilder: (context, index) { //動態的處理每一個要顯示在ListView上的資料。
return ListTile(
title: Text(europeanCountries[index]),
);
},
);
}
```
### 無限ListView
希望有類似無限滾輪可以一直滾動,移除`itemCount`即可
```dart
Widget _myListView(BuildContext context) {
return ListView.builder(
itemBuilder: (context, index) {
return ListTile(
title: Text('row $index'),
);
},
);
}
```
### ListView.separated
在各item之間插入分割(Sizedbox、Divider()等)
* primary false 表示如果內容不足則無法滾動
* physics 滾動原理
* AlwaysScrollableScrollPhysics: 始終響應用戶的滾動
* BouncingScrollPhysics: 允許滾動超出邊界,但之後內容會反彈回來,例如: IOS手機橡皮筋特效出現的空白區塊。
* ClampingScrollPhysics: 防止滾動超出邊界,例如: 安卓手機卷軸拉升至頂/尾有微光效果。
* FixedExtentScrollPhysics: 搭配`FixedExtendScrollControllers`使用,不會有任何偏移量。
```dart
FixedExtentScrollController fixedExtentScrollController =
new FixedExtentScrollController();
ListWheelScrollView(
controller: fixedExtentScrollController,
physics: FixedExtentScrollPhysics(),
children: monthsOfTheYear.map((month) {
return Card(
child: Row(
children: <Widget>[
Expanded(
child: Padding(
padding: const EdgeInsets.all(8.0),
child: Text(
month,
style: TextStyle(fontSize: 18.0),
),
)),
],
));
}).toList(),
itemExtent: 60.0,
),
```
* NeverScrollableScrollPhysics: 不允許用戶進行滾動
* shrinkWrap 這個屬性會讓其內容只占據畫面上所需要的大小。

```dart
Widget _myListView(BuildContext context) {
return ListView.separated(
itemCount: 1000,
itemBuilder: (context, index) {
return ListTile(
title: Text('row $index'),
);
},
separatorBuilder: (context, index) {
return const SizedBox(width: 16);
},
);
}
```
### 橫向ListView
橫向滾動的ListView。只需要給定scrollDirection是橫向的(`Axis.horizontal`)。
```dart
Widget _myListView(BuildContext context) {
return ListView.builder(
scrollDirection: Axis.horizontal,
itemBuilder: (context, index) {
return Container(
margin: const EdgeInsets.symmetric(horizontal: 1.0),
color: Colors.tealAccent,
child: Text('$index'),
);
},
);
}
```

## AnimatedList
AnimatedList 和ListView 的功能大體相似,不同的是, AnimatedList 可以在列表中插入或刪除節點時執行一個動畫,在需要添加或刪除列表項的場景中會提高用戶體驗。
* GlobalKey 每次動畫的時候都需要更新AnimatedList用到的資料和GlobalKey。
* 添加
```dart
void insertItem(int index, { Duration duration = _kDuration });
```
```dart
Widget buildAddBtn() {
return Positioned(
child: FloatingActionButton(
child: Icon(Icons.add),
onPressed: () {
// 添加一个列表项
data.add('${++counter}');
// 告诉列表项有新添加的列表项
globalKey.currentState!.insertItem(data.length - 1);
print('添加 $counter');
},
),
bottom: 30,
left: 0,
right: 0,
);
}
```
* 移除
```dart
void removeItem(int index, AnimatedListRemovedItemBuilder builder, { Duration duration = _kDuration }) ;
```
```dart
setState(() {
globalKey.currentState!.removeItem(
index,
(context, animation) {
// 删除过程执行的是反向动画,animation.value 会从1变为0
var item = buildItem(context, index);
print('删除 ${data[index]}');
data.removeAt(index);
// 删除动画是一个合成动画:渐隐 + 缩小列表项告诉
return FadeTransition(
opacity: CurvedAnimation(
parent: animation,
//让透明度变化的更快一些
curve: const Interval(0.5, 1.0),
),
// 不断缩小列表项的高度
child: SizeTransition(
sizeFactor: animation,
axisAlignment: 0.0,
child: item,
),
);
},
duration: Duration(milliseconds: 200), // 动画时间为 200 ms
);
});
```
### 官方動畫參考
https://docs.flutter.dev/development/ui/widgets/animation
## 其他常用搭配使用的樣式
### ListTile
* leading是用來在ListTile的**開始**,例如:標題圖片
* tailing是用來在ListTile的**最後**,例如:箭頭 icon
* title標題
* subtitle子標題
* onTap點擊事件
```dart
ListTile(
leading: CircleAvatar(
backgroundImage: AssetImage('assets/sun.jpg'),
),
title: Text('Sun'),
subtitle: Text('93 million miles away'),
trailing: Icon(Icons.keyboard_arrow_right),
onTap: () {
print('Sun');
},
),
```
### Card
* elevation 陰影
* shape 圓角樣式

```dart
class StarShapeBorder extends ShapeBorder {
@override
EdgeInsetsGeometry get dimensions => null;
@override
Path getInnerPath(Rect rect, {TextDirection textDirection}) {
return null;
}
@override
Path getOuterPath(Rect rect, {TextDirection textDirection}) =>
nStarPath(9, 50, 40, dx: 50, dy: 50);
@override
void paint(Canvas canvas, Rect rect, {TextDirection textDirection}) {
}
@override
ShapeBorder scale(double t) {
return null;
}
Path nStarPath(int num, double R, double r, {dx = 0, dy = 0}) {
Path _path = Path();
_path.reset(); //重置路径
double perRad = 2 * pi / num; //每份的角度
double radA = perRad / 2 / 2; //a角
double radB = 2 * pi / (num - 1) / 2 - radA / 2 + radA; //起始b角
_path.moveTo(cos(radA) * R + dx, -sin(radA) * R + dy); //移动到起点
for (int i = 0; i < num; i++) { //循环生成点,路径连至
_path.lineTo(
cos(radA + perRad * i) * R + dx, -sin(radA + perRad * i) * R + dy);
_path.lineTo(
cos(radB + perRad * i) * r + dx, -sin(radB + perRad * i) * r + dy);
}
_path.close();
return _path;
}
}
作者:张风捷特烈
链接:https://juejin.cn/post/6998323631839248421
来源:稀土掘金
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
```
* margin 邊距
* clipBehavior 內容溢出的剪裁方式
* Clip.none 不裁剪(預設)
* Clip.hardEdge
* Clip.antiAlias
* Clip.antiAliasWithSaveLayer
```dart
Card(
clipBehavior: Clip.antiAlias, //裁切邊緣
color: const Color(0xffB3FE65),
shape: const RoundedRectangleBorder(
side: BorderSide.none,
borderRadius: BorderRadius.all(Radius.circular(10))),
elevation: 3,
shadowColor: Colors.blueAccent,
child: buildContent(),
),
```
* InkWell/Ink,點擊Card後產生的回饋水波紋
```dart
@override
Widget build(BuildContext context) {
return Scaffold(
body: Center(
child: Card(
clipBehavior: Clip.antiAlias,
color: const Color(0xffB3FE65),
elevation: 3,
shadowColor: Colors.blueAccent,
child: InkWell(
splashColor: Colors.blue.withAlpha(30),
onTap: (){
},
child:buildContent()),
),
),
);
}
```
有些時候,使用 Image 或為 Container 設置顏色之後,水波紋就會無法觸發,可改採Ink。
```dart
Widget buildContent() {
return Ink(
width: 200,
height: 0.618 * 200,
decoration: const BoxDecoration(
image: DecorationImage(
fit: BoxFit.cover,
image: AssetImage('assets/images/anim_draw.webp'))),
child: Padding(
padding: const EdgeInsets.all(20),
child: Text("Card: 卡片",
style: TextStyle(fontSize: 20,color: Colors.white)),
));
}
```
## 參考資料
1. https://iter01.com/509244.html
2. https://book.flutterchina.club/chapter6/animatedlist.html
3. https://juejin.cn/post/6998323631839248421
4. https://zhuanlan.zhihu.com/p/84716922
5. https://classesover.github.io/2019/01/06/%E6%B7%B1%E5%85%A5%E5%AD%A6%E4%B9%A0Flutter%E4%B9%8BListView%E5%92%8CScrollPhysics/
###### tags: `flutter`