Flutter 入门:教程 4 ListView
Flutter ListView:
引言
欢迎来到 Flutter 入门系列教程的另一篇文章。在这里,我将探讨 ListView
小部件。根据 Flutter 官网的说法:
引用ListView 是最常用的滚动小部件。它在其滚动方向上一个接一个地显示其子项。
让我们更深入地了解 Flutter,这个有前景的未来框架。
背景
本文的最终目标是创建这样的 UI:
在这里,我们希望创建一个可滚动的列表,该列表显示城市受欢迎的地标作为其展示图片,附加信息包括城市人口以及它所在的国家。
教程目标
在本文中,我们将创建一个基于 ListView
的应用程序。首先,我们将展示一个非常基础的城市列表;然后,我们将在此基础上构建,以显示城市的 名称
、人口
、所属的 国家
。第三,我们将展示其受欢迎的地标作为图片;最后,我们将进一步增强 UI
并显示用户点击 ListItem
时的 SnackBar
(一种小型消息通知)。
Using the Code
任务 #1:让我们从应用程序的第一个阶段开始,在该阶段中,我们将创建显示仅城市名称的基础 ListView
。
首先,在 model 文件夹下创建一个名为 City
的类(你需要在 lib 文件夹内创建该文件夹),它将作为显示的基础模型。如果你不知道如何创建 Flutter 项目,我在这篇文章中已详细解释,请参考以获取一般知识。
class City {
//--- Name Of City
final String name;
//-- image
final String image;
//--- population
final String population;
//--- country
final String country;
City({this.name,this.country,this.population,this.image});
}
在这个类中,我提供了一个基础的骨架类,它将为图块提供显示模型。为了简单起见,我在这里也添加了代码到 City
类以提供城市列表,尽管这不是一种好的做法。
static List<City> allCities()
{
var lstOfCities = new List<City>();
lstOfCities.add(new City(name:"Delhi",country: "India",population: "19 mill",image: "delhi.png"));
lstOfCities.add(new City(name:"London",
country: "Britain",population: "8 mill",image: "london.png"));
lstOfCities.add(new City(name:"Vancouver",
country: "Canada",population: "2.4 mill",image: "vancouver.png"));
lstOfCities.add(new City(name:"New York",
country: "USA",population: "8.1 mill",image: "newyork.png"));
lstOfCities.add(new City(name:"Paris",
country: "France",population: "2.2 mill",image: "paris.png"));
lstOfCities.add(new City(name:"Berlin",
country: "Germany",population: "3.7 mill",image: "berlin.png"));
return lstOfCities;
}
至此,我们的 City
类就完成了,将所有代码保存在 city.dart 文件中。现在,在 lib 文件夹内添加另一个名为 pages
的文件夹,然后添加我们唯一的页面 homepage.dart。由于我们不需要在这里处理状态,因此我们可以简单地构建一个继承自 StatelessWidget
的 HomePage
类。这是初始代码:
import 'package:flutter/material.dart';
import 'package:flutter4_listview/model/city.dart';
class HomePage extends StatelessWidget {
final List<City> _allCities = City.allCities();
HomePage() {}
@override
Widget build(BuildContext context) {
return new Scaffold(
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: _getItemUI,
padding: EdgeInsets.all(0.0),
);
}
// First Attempt
Widget _getItemUI(BuildContext context, int index) {
return new Text(_allCities[index].name);
}
}
在上面的代码中,我们做了以下事情:
- 我包含了类变量
_allCities
,它将包含所有城市数据。虽然上述提供数据给变量的方法不对,但使用依赖注入
会是更好的方式。 - 我创建了一个简单的 material app
scaffold
,它提供了应用程序的基本骨架,通过提供基本的AppBar
和用于放置小部件的 body。 - 我使用
ListView.builder
方法创建了 scaffold 的 body,因为它为创建无限列表提供了更好的基础架构,它仅动态回调那些在屏幕上可见的索引,从而提高了性能。 - 我使用普通的文本小部件构建了每个图块,目前仅显示城市名称。在本教程中,我们将进一步扩展它。
最后但同样重要的是,对于任务 #1,请删除 main.dart 文件中的所有内容,并粘贴以下代码,将 HomePage
类设置为应用程序的启动页。此文件将不再进行任何更改。
import 'package:flutter/material.dart';
import 'package:flutter4_listview/pages/homepage.dart';
void main() => runApp(new MyApp());
class MyApp extends StatelessWidget {
// This widget is the root of your application.
@override
Widget build(BuildContext context) {
return new MaterialApp(
title: 'Flutter Demo',
theme: new ThemeData(
primarySwatch: Colors.amber,
),
home: new HomePage(),
);
}
}
现在在模拟器中运行应用程序并查看结果。
至此,我完成了任务 #1。
任务 #2:在此任务中,我想显示所有内容,即 City
类对象公开的所有属性。
如上代码所示,显示文本属性相对容易。你可以使用 Text
小部件并放置任何你想要的文本,只需将其放入构造函数中即可。但是,显示图片需要对 pubspec.yaml(主配置文件)进行一些配置。在这个演示中,我从 Google 图片搜索中收集了不同城市的著名地标。我将它们保存在根位置的 assets 文件夹中。添加图片后,我的文件夹结构如下:
现在,由于所有这些图片都已静态包含在项目中,我们需要在 pubspec.yaml 中像这样包含它们:
assets:
- assets/berlin.png
- assets/delhi.png
- assets/london.png
- assets/newyork.png
- assets/paris.png
- assets/vancouver.png
请记住,上述代码应放在 flutter
部分下,并且要小心空格,否则图片可能无法加载或出现编译错误。最安全的做法是为 assets:
使用一个制表符,为以 dash (-)
开头的图片使用两个制表符。
要加载图片到页面,我们必须使用 Image.asset
工厂构造函数,并提供图片的相对路径。所以基本上,我们将使用 Card 小部件来显示图片以及其他公开的属性。现在,修改后的 _getItemUi()
代码如下:
Widget _getItemUI(BuildContext context, int index) {
return new Card(
child: new Column(
children: <Widget>[
new ListTile(
leading: new Image.asset(
"assets/" + _allCities[index].image,
fit: BoxFit.cover,
width: 100.0,
),
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: <Widget>[
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)),
]),
//trailing: ,
onTap: () {
_showSnackBar(context, _allCities[index]);
},
)
],
));
}
在这里,我在上面的代码中做了以下事情:
- 在 Card 的 leading 位置,我使用
Image.asset
显示图片,并设置固定宽度为100.0
。 - Card 的标题将是城市名称。
- 对于 Card 的 Subtitle,我使用 Column 小部件,它提供了垂直对齐放置小部件的灵活性。我将国家放在前面,然后是人口。
- 最后但也是最重要的,我在点击任何卡片项时显示 snackbar。snackbar 的代码如下:
_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); }
所以最后,我们得到这个:
教程结束!
关注点
请阅读这些文章。它们可能会为您指明方向。
- Flutter — 你可能喜欢它的 5 个理由
- Flutter 的革命性之处
- 为什么 Flutter 使用 Dart
- https://material.io/design/components/cards.html
- Github: https://github.com/thatsalok/FlutterExample/tree/master/flutter4_listview
Flutter 教程
Dart 教程
历史
- 2018年7月18日:初版