ListView and GridView in Flutter

在移动应用开发中,ListViewGridView是非常流行的控件。如果不相信,打开手机中的任何电子商务应用,肯定会发现它们依赖于 ListView 或 GridView 来展示数据。例如,亚马逊移动应用以网格形式展示数据,而印度流行的在线钱包服务应用 PayTM 也广泛使用网格布局来展示不同的产品。

本文的目标是实现一个类似于下面图片的视图:但是,如果注意到上面的图片,它是在横屏模式下。所以这篇文章中将要做的是,当应用处于竖屏模式时,MobileApp 会以 ListView 的形式展示项目,而当它处于横屏模式时,以每行3个项目的网格形式展示。还将探索通过将 gridview 实现在一个单独的类中来创建自定义控件。

将在前一篇文章 "FlutterGetting Started: Tutorial 4ListView" 的基础上构建,在那里已经创建了一个基于 ListView 的应用程序,这里是初始的项目结构和初始 UI。

以下是开始构建的初始代码:

class HomePage extends StatelessWidget { final List _allCities = City.allCities(); HomePage() {} final GlobalKey scaffoldKey = new GlobalKey(); @override Widget build(BuildContext context) { return new Scaffold( key: scaffoldKey, appBar: new AppBar( title: new Text("Cites around world", style: new TextStyle(fontSize: 18.0, fontWeight: FontWeight.bold, color: Colors.black87)), ), body: new Padding( padding: EdgeInsets.fromLTRB(0.0, 10.0, 0.0, 0.0), child: getHomePageBody(context) ) ); } getHomePageBody(BuildContext context) { return ListView.builder( itemCount: _allCities.length, itemBuilder: _getListItemUI, padding: EdgeInsets.all(0.0), ); } Widget _getListItemUI(BuildContext context, int index, {double imgwidth: 100.0}) { return new Card( child: new Column( children: [ new ListTile( leading: new Image.asset("assets/" + _allCities[index].image, fit: BoxFit.fitHeight, width: imgwidth), title: new Text(_allCities[index].name, style: new TextStyle(fontSize: 14.0, fontWeight: FontWeight.bold)), subtitle: new Column( mainAxisAlignment: MainAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start, children: [ new Text(_allCities[index].country, style: new TextStyle(fontSize: 13.0, fontWeight: FontWeight.normal)), new Text('Population: ${_allCities[index].population}', style: new TextStyle(fontSize: 11.0, fontWeight: FontWeight.normal)), ]), onTap: () { _showSnackBar(context, _allCities[index]); }, ) ], ) ); } _showSnackBar(BuildContext context, City item) { final SnackBar objSnackbar = new SnackBar( content: new Text("${item.name} is a city in ${item.country}"), backgroundColor: Colors.amber, ); Scaffold.of(context).showSnackBar(objSnackbar); } }

在上面的代码中,创建了一个简单的 ListView,使用 ListView.builder,它提供了创建无限列表项视图的灵活性,因为它只调用那些可能显示在屏幕上的项目。展示了城市信息,如城市地标图片,随后是城市名称、所属国家和人口。最后,在点击时,它会在屏幕底部显示一个小型的自动消失的消息,称为 SnackBar。

现在,工作开始了,正如之前提到的,将重构一个新的控件到不同的类中,以保持代码模块化并提高代码的清晰度。所以,在 lib 文件夹下创建一个新的文件夹 widget,并添加一个新的 DART 文件 mygridview.dart。

一旦添加了文件,首先通过导入 'package:flutter/material.dart' 导入材料控件,然后添加 MyGridView类,它扩展了最喜欢的 StatelessWidget并覆盖了 Build 函数,所以基本代码看起来像这样:

import 'package:flutter/material.dart'; import 'package:flutter5_gridlist/model/city.dart'; class MyGridView extends StatelessWidget { final List allCities; MyGridView({Key key, this.allCities}) : super(key: key); @override Widget build(BuildContext context) { return null; } }

将添加一个基本的 GridView,只显示城市名称,所以将在覆盖的 Build 函数中添加以下代码:

@override Widget build(BuildContext context) { return GridView.count( crossAxisCount: 3, padding: EdgeInsets.all(16.0), childAspectRatio: 8.0, children: _getGridViewItems(context), ); } _getGridViewItems(BuildContext context){ List allWidgets = new List(); for (int i = 0; i < allCities.length; i++) { var widget = new Text(allCities[i].name); allWidgets.add(widget); }; return allWidgets; }

GridView.count 方法将为应用程序提供 GridView 控件。crossAxisCount 属性用于让移动应用程序知道想要在每一行显示多少个项目。children 属性将包含所有希望在页面加载时显示的控件。childAspectRatio 是交叉轴到主轴长度的比率,因为正在显示名称,所以将保持在 8.0,以便减少两个瓦片之间的边距。

现在让使 UI 与在 ListView 中看到的类似。在这里,创建了一个新的函数,它将以 Card 的形式发送 City 类。

// Create individual item _getGridItemUI(BuildContext context, City item) { return new InkWell( onTap: () { _showSnackBar(context, item); }, child: new Card( child: new Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ new Image.asset( "assets/" + item.image, fit: BoxFit.fill, ), new Expanded( child: new Center( child: new Column( children: [ new SizedBox(height: 8.0), new Text( item.name, style: new TextStyle( fontSize: 20.0, fontWeight: FontWeight.bold, ), ), new Text(item.country), new Text('Population: ${item.population}') ], )) ) ], ), elevation: 2.0, margin: EdgeInsets.all(5.0), )); }

使用了 InkWell 类,因为 Card 类不支持直接的手势,所以将其包装在 InkWell 类中,以利用其 onTap 事件来显示 SnackBar。其余的代码与 ListView 中的卡片类似,只是没有指定宽度。

另外,由于正在显示一个完整的卡片,不要忘记将 childAspectRatio 从 8.0 改为 8.0/9.0,因为需要更多的高度。

在文章开始时,提到将在竖屏方向显示ListView,在横屏方向显示 GridView,所以为了实现这一点,将使用 MediaQuery 类来识别方向。每当方向改变时,即倾斜手机,控件将被重新绘制,所以 Build 函数被调用,可以在那里决定应该调用什么代码。所以在 homepage.dart 类中,将使用以下函数来处理方向变化。

getHomePageBody(BuildContext context) { if (MediaQuery.of(context).orientation == Orientation.portrait) return ListView.builder( itemCount: _allCities.length, itemBuilder: _getListItemUI, padding: EdgeInsets.all(0.0), ); else return new MyGridView(allCities: _allCities); }
沪ICP备2024098111号-1
上海秋旦网络科技中心:上海市奉贤区金大公路8218号1幢 联系电话:17898875485