65.9K
CodeProject 正在变化。 阅读更多。
Home

Flutter 入门:教程 5 Grid

starIconstarIconstarIconstarIconstarIcon

5.00/5 (2投票s)

2018 年 7 月 22 日

CPOL

4分钟阅读

viewsIcon

32175

downloadIcon

511

让我们探索 Flutter Grid 视图和 MediaQuery。

引言

FlutterGridViewListView 几乎相同,只是它提供了一个二维视图,而 ListView 提供的是单向视图。在移动应用开发中,它也是一个非常受欢迎的组件。如果你不相信我,打开你手机上的任何一个电商应用——它肯定依赖于 ListView GridView 来显示数据,例如:

在这里,Amazon 手机应用以网格形式显示数据。

另一个例子,PayTM,印度流行的在线钱包服务应用之一,广泛使用网格布局来展示不同的产品。

背景

本文的最终目标是实现一个类似于此的视图。

但是,如果你注意到上图,那是在横屏模式下。所以,在这篇文章中我将要做的是:当应用程序处于竖屏模式时,MobileApp 会以 ListView 显示项目,而在横屏模式下,则每行显示 3 个项目并以网格形式显示。我还将探索创建自定义组件,将 gridview 的实现移到一个单独的类中。

Using the Code

我将基于我之前的文章 Flutter 入门教程:第 4 部分 ListView 进行开发,在那里我已经创建了一个基于 ListView 的应用程序。这是初始项目结构和初始 UI。

这是我们将要开始构建的初始代码:

class HomePage extends StatelessWidget {
  final List<City> _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: <Widget>[
        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: <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)),
              ]),
          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.builder 创建了一个简单的 ListView,它提供了创建无限 listitem 视图的灵活性,因为它只为屏幕上可见的项目调用回调函数。
  • 我显示了 City 信息,如城市地标图片、CityName(城市名称)、城市所属的 Country(国家)以及其人口。
  • 最后,点击时,它会在屏幕底部显示一个短暂消失的消息,称为 SnackBar

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

添加文件后,首先导入 material widget,通过导入 '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<City> allCities;
  MyGridView({Key key, this.allCities}) : super(key: key);

  @override
  Widget build(BuildContext context) {
    return null;
  }
}

我现在将添加一个基本的 GridView,只显示 City 名称,所以我将在重写的 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<Widget> allWidgets = new List<Widget>();
  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,以减小两个 tile 之间的边距。

UI 看起来是这样的:

现在,让我们使 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: <Widget>[
            new Image.asset(
              "assets/" + item.image,
              fit: BoxFit.fill,
              
            ),
            new Expanded(
                child: new Center(
                    child: new Column(
              children: <Widget>[
                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);
}

所以我们的最终 UI 将是这样的:

教程结束。

关注点

请阅读这些文章。它们可能会为您指明方向。

  1. https://material.io/design/components/cards.html
  2. Github: https://github.com/thatsalok/FlutterExample/tree/master/flutter5_gridlist

Flutter 教程

  1. Flutter入门:教程1 基础
  2. Flutter 入门:教程 2 – StateFulWidget
  3. Flutter 入门:教程 3 导航
  4. Flutter 入门:教程 4 ListView

Dart 教程

  1. DART2 Prima Plus - 教程 1
  2. DART2 Prima Plus - 第二课 - LIST
  3. DART2 Prima Plus - 第三课 - MAP

历史

  • 2018 年 7 月 22 日:第一个版本
© . All rights reserved.