# 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(), ); } ``` ![](https://i.imgur.com/9UAAqHQ.png) ## 動態 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 這個屬性會讓其內容只占據畫面上所需要的大小。 ![](https://i.imgur.com/ifz6oKD.png) ```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'), ); }, ); } ``` ![](https://i.imgur.com/oAFOhKi.png) ## 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 圓角樣式 ![](https://i.imgur.com/UZMkJLz.png) ```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`