[Flutter学徒] 3 - 基本Widget

704 阅读26分钟

本文由简悦 SimpRead转码, 原文地址www.raywenderlich.com

开始使用基本的小工具来建立你的应用程序。学习应用结构和导航,......

如您所知,Flutter 中的所有东西都是一个部件。但您怎么知道什么时候使用哪个部件呢?在这一章中,您将探索三类基本部件,您可以在以下方面使用它们。

  • 结构和导航
  • 显示信息
  • 定位小组件 在本章结束时,你将使用这些不同类型的部件来建立一个名为Fooderlich的应用程序的基础,这是一个社交食谱应用程序。你将建立该应用的结构,并学习如何创建三种不同的食谱卡:主食谱卡、作者卡和探索卡。

准备好了吗?看一下启动项目就可以开始了。

注意:在本书出版时,书中使用的一些依赖关系并不完全支持空安全。所以从这一章开始,你将看到的代码不是空安全的。我们会在未来的更新中把代码迁移到支持非空值类型。

开始学习

首先,从书中的材料库中下载github.com/raywenderli…

找到projects文件夹并打开starter。如果你的IDE出现了 "Pub get "未运行的横幅,请点击Get dependencies来解决这个问题。

从Android Studio运行该应用程序,你会看到一个应用程序栏和一些简单的文本。

main.dart是任何Flutter应用程序的起点。打开它,你会看到下面的内容。

void main() {
  // 1
  runApp(const Fooderlich());
}

class Fooderlich extends StatelessWidget {
  const Fooderlich({Key key}) : super(key: key);
  // 2
  @override
  Widget build(BuildContext context) {
    // 3
    return MaterialApp(
      title: 'Fooderlich',
      // 4
      home: Scaffold(
        // 5
        appBar: AppBar(title: const Text('Fooderlich')),
        body: const Center(child: Text('Let\'s get cooking 👩‍🍳')),
      ),
    );
  }
}



花点时间来探索代码的作用:

  1. Flutter中的一切都从一个widget开始。runApp接收了根widget Fooderlich。
  2. 每个无状态小组件都必须覆盖build()方法。
  3. Fooderlich小组件首先组成一个MaterialApp小组件,使其具有Material Design系统的外观和感觉。关于Material Design的更多细节,请参阅material.io。
  4. MaterialApp小组件包含一个Scaffold小组件,它定义了应用程序的布局和结构。稍后会有更多关于这个的内容。
  5. 该脚手架有两个属性:一个AppBar和一个body。一个Appbar的标题包含一个Text widget。主体有一个中心部件,其子属性是一个文本部件。

塑造您的应用程序

由于Flutter是跨平台的,谷歌的UI工具包自然会支持Android和iOS的视觉设计系统。

Android使用Material Design系统,您可以像这样导入。

import 'package:flutter/material.dart'

iOS使用的是Cupertino系统。你可以这样导入它。

import 'package:flutter/cupertino.dart'

为了保持简单,一个经验法则是为你的用户界面只选择一个设计系统。想象一下,仅仅为了管理这两种设计就必须创建if-else语句,更不用说支持不同的过渡和操作系统版本的兼容性了。

在本书中,你将学习如何使用Material Design系统。你会发现Material Design的外观和感觉是相当可定制的

注意:在Material和Cupertino之间的切换已经超出了本书的范围。关于这些软件包在UI组件方面提供的更多信息,请查看。

Material UI Components: flutter.dev/docs/develo… Cupertino UI组件:flutter.dev/docs/develo… 现在你已经确定了一个设计,你将在下一节为你的应用程序设置一个主题。

设置一个主题

你可能会注意到,目前的应用程序使用默认的蓝色看起来有点无聊,所以你将用一个自定义的主题来为它增添色彩 你的第一步是为你的应用程序选择要使用的字体。

使用谷歌字体

google_fonts包支持超过977种字体,以帮助你为你的文本设计风格。它已经包含了pubspec.yaml,而且你在之前点击Pub Get时已经把它添加到了应用程序中。你将使用这个包来为你的主题类应用自定义字体。

定义一个主题类

为了在你的应用程序中,共享颜色和字体样式,你将向MaterialApp提供一个ThemeData对象。在lib目录下,打开fooderlich_theme.dart,它包含了一个为你的应用程序预定义的主题。

请看一下代码。

import 'package:flutter/material.dart';
import 'package:google_fonts/google_fonts.dart';

class FooderlichTheme {

  // 1
  static TextTheme lightTextTheme = TextTheme(
    bodyText1: GoogleFonts.openSans(
      fontSize: 14.0,
      fontWeight: FontWeight.w700,
      color: Colors.black),
    headline1: GoogleFonts.openSans(
      fontSize: 32.0,
      fontWeight: FontWeight.bold,
      color: Colors.black),
    headline2: GoogleFonts.openSans(
      fontSize: 21.0,
      fontWeight: FontWeight.w700,
      color: Colors.black),
    headline3: GoogleFonts.openSans(
      fontSize: 16.0,
      fontWeight: FontWeight.w600,
      color: Colors.black),
    headline6: GoogleFonts.openSans(
      fontSize: 20.0,
      fontWeight: FontWeight.w600,
      color: Colors.black),
  );

  // 2
  static TextTheme darkTextTheme = TextTheme(
    bodyText1: GoogleFonts.openSans(
      fontSize: 14.0,
      fontWeight: FontWeight.w600,
      color: Colors.white),
    headline1: GoogleFonts.openSans(
      fontSize: 32.0,
      fontWeight: FontWeight.bold,
      color: Colors.white),
    headline2: GoogleFonts.openSans(
      fontSize: 21.0,
      fontWeight: FontWeight.w700,
      color: Colors.white),
    headline3: GoogleFonts.openSans(
      fontSize: 16.0,
      fontWeight: FontWeight.w600,
      color: Colors.white),
    headline6: GoogleFonts.openSans(
      fontSize: 20.0,
      fontWeight: FontWeight.w600,
      color: Colors.white),
  );

  // 3
  static ThemeData light() {
    return ThemeData(
        brightness: Brightness.light,
        primaryColor: Colors.white,
        accentColor: Colors.black,
        textSelectionTheme:
          const TextSelectionThemeData(selectionColor: Colors.green),
        textTheme: lightTextTheme,
    );
  }

  // 4
  static ThemeData dark() {
    return ThemeData(
        brightness: Brightness.dark,
        primaryColor: Colors.grey[900],
        accentColor: Colors.green[600],
        textTheme: darkTextTheme,
    );
  }
}


这段代码做了以下工作。

  1. 声明一个名为lightTextTheme的文本主题,它使用谷歌字体Open Sans,并有一个预定义的字体大小和重量。最重要的是,文本的颜色是黑色。
  2. 然后它定义了darkTextTheme。在这种情况下,文本是白色的。
  3. 接下来,它定义了一个静态方法,light,它使用你在步骤1中创建的lightTextTheme返回浅色主题的色调。
  4. 最后,它声明了一个静态方法,dark,它使用你在步骤2中创建的darkTextTheme返回暗色主题的色调。 你的下一步是利用这个主题。

使用该主题

在main.dart中,通过在现有的导入语句下面添加以下内容来导入你的主题。

import 'fooderlich_theme.dart'

然后在build()方法的顶部,在返回MaterialApp之前添加这个。

final theme = FooderlichTheme.dark()。

要将新的主题添加到MaterialApp小组件中,请在MaterialApp内的标题上方添加以下内容。

theme: 主题。

现在,通过将appBar的代码替换成这样来添加一些文本样式。

appBar: AppBar(title: Text('Fooderlich',
            style: theme.textTheme.headline6),)。

最后,将body替换为以下内容。

body: Center(child: Text('Let\'s get cooking 🍳',
              style: theme.textTheme.headline1),)。

在你所有的更新之后,你的代码应该看起来像这样。

// 1
import 'fooderlich_theme.dart';

void main() {
  runApp(const Fooderlich());
}

class Fooderlich extends StatelessWidget {
  const Fooderlich({Key key}) : super(key: key);
  @override
  Widget build(BuildContext context) {
    // 2
    final theme = FooderlichTheme.dark();
    return MaterialApp(
      // 3
      theme: theme,
      title: 'Fooderlich',
      home: Scaffold(
        appBar: AppBar(
          title: Text('Fooderlich',
              // 4
              style: theme.textTheme.headline6),
        ),
        body: Center(
          child: Text('Let\'s get cooking 👩‍🍳',
              // 5
              style: theme.textTheme.headline1),
        ),
      ),
    );
  }
}


回顾一下,你的更新。

  1. 导入了FooderlichTheme。
  2. 定义了一个持有该主题的变量。
  3. 添加了MaterialApp小组件的主题属性。
  4. 添加了AppBar的文本样式。
  5. 最后,添加了主体文本的样式。 保存你的修改。多亏了热重载,你几乎可以立即看到更新的主题。

要看到浅色和深色模式的区别,请在FooderlichTheme.dark()和FooderlichTheme.light()之间改变主题。这两个主题看起来像这样。

注意:一般来说,为你的应用程序建立一个共同的主题对象是个好主意--特别是当你与设计师合作时。这为你提供了一个单一的真理来源,以便在你所有的小部件中访问你的主题。

接下来,你将了解构建应用程序的一个重要方面--了解要使用哪种应用程序结构。

应用程序结构和导航

从一开始就建立你的应用程序的结构,对用户体验很重要。应用正确的导航结构可以让你的用户轻松地浏览你的应用程序中的信息。

Fooderlich使用Scaffold小组件作为其起始应用结构。Scaffold是Flutter中最常用的Material widget之一。接下来,您将学习如何在您的应用程序中实现它。

使用Scaffold

Scaffold小组件实现了您所有的基本视觉布局结构需求。它由以下部分组成。

AppBar 底部表格 底部导航栏 抽屉 浮动操作按钮 小吃栏 Scaffold有很多开箱即用的功能!

下图表示了上述的一些项目,同时也显示了左右导航选项。

欲了解更多信息,请查看Flutter关于Material Components部件的文档,包括应用结构和导航:flutter.dev/docs/develo…

现在,是时候添加更多的功能了。

设置主页小部件

当你建立大规模的应用程序时,你会开始组成一个小部件的楼梯。由其他小部件组成的小部件可能会变得非常长和混乱。为了可读性,把你的小工具分成独立的文件是个好主意。

为了避免使你的代码过于复杂,你现在将创建这些独立文件中的第一个。

Scaffold需要通过一个StatefulWidget来处理一些状态变化。你的下一步是把代码从main.dart移到一个新的名为Home的StatefulWidget中。

在lib目录下,创建一个名为home.dart的新文件并添加以下内容。

import 'package:flutter/material.dart';

// 1
class Home extends StatefulWidget {
  const Home({Key key}) : super(key: key);

  @override
  _HomeState createState() => _HomeState();
}

class _HomeState extends State<Home> {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text(
          'Fooderlich',
          // 2
          style: Theme.of(context).textTheme.headline6)),
      body: Center(
        child: Text(
          'Let\'s get cooking 👩‍🍳',
          // 3
          style: Theme.of(context).textTheme.headline1)),
    );
  }
}



Scaffold的大部分代码看起来和你在main.dart中的一样,但有一些变化。

  1. 你的新类继承了StatefulWidget。
  2. AppBar的风格现在是这样的。Theme.of(context).textTheme.headline6,而不是: theme.textTheme.headline6。Theme.of(context)返回widget树中最近的主题。如果widget有一个定义好的Theme,它就会返回那个。否则,它将返回应用程序的主题。
  3. 和AppBar一样,你也更新了Text样式以使用Theme.of(context)。 回到main.dart,你需要更新它,以便它能使用新的Home widget。在顶部,添加以下导入语句。
import 'home.dart';

在MaterialApp中,用新的Home()替换home属性的Scaffold,如下所示。

return MaterialApp(
  theme: theme,
  title: 'Fooderlich',
  home: const Home(),
);



完成这些后,你将继续处理Scaffold的底部导航。

添加一个底部导航栏

你的下一步是在scaffold上添加一个底部导航条。这将让你的用户在标签之间进行导航。

首先打开home.dart,在Scaffold widget中添加以下代码,就在body参数的后面。

// 4
bottomNavigationBar: BottomNavigationBar(
  // 5
  selectedItemColor:
    Theme.of(context).textSelectionTheme.selectionColor,
  // 6
  items: <BottomNavigationBarItem>[
    const BottomNavigationBarItem(
      icon: Icon(Icons.card_giftcard),
      label: 'Card'),
    const BottomNavigationBarItem(
      icon: Icon(Icons.card_giftcard),
      label: 'Card2'),
    const BottomNavigationBarItem(
      icon: Icon(Icons.card_giftcard),
      label: 'Card3'),
  ]
)



花点时间来回顾一下代码。在这里,你。

  1. 定义了一个BottomNavigationBar。
  2. 设置了一个项目在被点击时的选择颜色。
  3. 定义了三个底部导航标签栏项目。 完成这些后,你的应用程序看起来像这样。

现在你已经设置了底部导航条,你需要实现标签条项目之间的导航。

项目之间的导航

在你能让用户在标签栏项目之间切换之前,你需要知道他们选择了哪个索引。

要做到这一点,在_HomeState的顶部,build()方法的上方添加以下内容。

// 7
int _selectedIndex = 0;

// 8
static List<Widget> pages = <Widget>[
  Container(color: Colors.red),
  Container(color: Colors.green),
  Container(color: Colors.blue)
];

// 9
void _onItemTapped(int index) {
  setState(() {
    _selectedIndex = index;
  });
}



下面是你用这段代码添加的内容。

  1. _selectedIndex记录了当前被选中的标签。_selectedIndex中的下划线表示它是私有的。选定的索引是被_HomeState跟踪的状态。
  2. 在这里,你定义了将在每个标签上显示的小部件。现在,当你点击不同的标签栏项目时,它会显示不同颜色的容器小部件。很快,你就会用卡片小部件取代这些小部件。
  3. 这个函数处理被点击的标签栏项目。在这里,你设置了用户按下的项目的索引。 setState()通知框架这个对象的状态已经改变,然后在内部重建这个部件。

注意:在下一章中,你将了解到更多关于部件如何在引擎盖下工作的信息。请继续关注。

接下来,将支架中的 body 替换为。

body: pages[_selectedIndex],


当框架重建部件时,它会显示所选标签栏项目的容器部件。

指示选中的标签栏项目

现在,你想向用户表明他们目前选择了哪个标签栏项目。

在BottomNavigationBar中,在selectedItemColor下面添加以下参数。

// 10
currentIndex: _selectedIndex,
// 11
onTap: _onItemTapped,


下面是这段代码的内容。 10. currentIndex将告诉底部导航栏要突出哪个标签栏项目。 11. 当用户点击一个标签栏项目时,它会调用_onItemTapped处理程序,它用正确的索引更新状态。在这种情况下,它会改变颜色。

因为你已经对状态做了改变,你有两个选择来查看这些改变。你可以停止你的应用程序并重新启动它,这需要一点时间,或者你可以使用热重启,它在几秒钟内重建你的应用程序。

按下运行窗口上的热重启按钮,看看它有多快。

图片

重启后,你的应用程序的每个标签栏项目看起来都不一样,像这样。

图片

现在你已经设置了你的标签导航,是时候创建漂亮的食谱卡了

创建自定义食谱卡

在这一节中,你将通过结合显示和布局部件的混合来组成三个食谱卡。 Display小组件处理用户在屏幕上看到的东西。显示部件的例子包括。

  • Text
  • Image
  • Button

Layout小组件帮助布局小组件。布局部件的例子包括。

  • Container
  • Padding
  • Stack
  • Column
  • SizedBox
  • Row

注意:Flutter有大量的布局部件可供选择,但本章只涉及最常见的。更多的例子,请查看flutter.dev/docs/develo…

构成Card1:主要的食谱卡片

你要构思的第一个卡片看起来像这样。

图片

Card1是由以下部件组成的。

  • Container: 将所有其他的小部件组合在一起。它应用Padding并使用BoxDecoration来描述如何应用阴影和圆角。
  • Stack: 将小组件分层在彼此的上面。
  • Text: 显示菜谱内容,如标题、副标题和作者。
  • Image: 显示菜谱的艺术。 在lib目录下,创建一个名为card1.dart的新文件,并在其中添加以下代码。
import 'package:flutter/material.dart';

class Card1 extends StatelessWidget {
  const Card1({Key key}) : super(key: key);
  // 1
  final String category = 'Editor\'s Choice';
  final String title = 'The Art of Dough';
  final String description = 'Learn to make the perfect bread.';
  final String chef = 'Ray Wenderlich';

  // 2
  @override
  Widget build(BuildContext context) {
    // 3
    return Center(
      child: Container(),
    );
  }
}



花点时间浏览一下代码。

1.定义字符串变量以显示在卡片上。这只是用来帮助建立卡片的示例数据。 2.每个stateless widget都有一个build()方法,你可以重写。 3.开始时,在中间铺设一个容器。 接下来,打开home.dart。在页面列表中,用Card1()替换第一个Container,如下图所示。

static List<Widget> pages = <Widget>[
    const Card1(),
    Container(color: Colors.green),
    Container(color: Colors.blue),
];

注意,导入'card1.dart';应该出现在文件顶部的导入部分。如果没有,请手动添加它。

现在你已经设置好了Card1。热重启,应用程序目前看起来像这样。

图片

这有点平淡无奇,不是吗?在下一步,你将用一张图片为它增添色彩。

添加图片

切换到card1.dart。为了给Card1添加图片,用下面的代码替换空的Container()部件。

Container(
  // 1
  padding: const EdgeInsets.all(16),
  // 2
  constraints: const BoxConstraints.expand(width: 350, height: 450),
  // 3
  decoration: const BoxDecoration(
    // 4
    image: DecorationImage(
      // 5
      image: AssetImage('assets/mag1.png'),
      // 6
      fit: BoxFit.cover,
    ),
    // 7
    borderRadius: BorderRadius.all(Radius.circular(10.0)),
  ),
)



下面是你添加到Container的参数。

1.在盒子的所有边上应用一个16的padding。Flutter的单位是以逻辑像素指定的,就像Android上的dp。 2.将容器的尺寸限制为宽350,高450。 3.应用BoxDecoration。这描述了如何绘制一个盒子。 4.在BoxDecoration中,设置DecorationImage,它告诉盒子要画一个图像。 5.使用AssetImage,即在启动项目资产中找到的图像,来设置在盒子里画哪张图像。 6.用该图像覆盖整个盒子。 7.对容器的所有边应用一个10的角半径。

保存你的修改,以便热重新加载。你的应用程序现在看起来像这样。

图片

好多了! 但你仍然需要告诉用户他们在看什么。

添加文本

你要添加三行文字来描述卡片的作用。首先,在card1.dart文件的顶部添加以下import语句,以便你能使用你的Theme。

import 'fooderlich_theme.dart'

接下来,添加以下代码作为容器的子节点。

child: Stack(
        children: [
          Text(category, style: FooderlichTheme.darkTextTheme.bodyText1),
          Text(title, style: FooderlichTheme.darkTextTheme.headline5),
          Text(description, style: FooderlichTheme.darkTextTheme.bodyText1),
          Text(chef, style: FooderlichTheme.darkTextTheme.bodyText1),
        ],
       ),



Stack将这些新的部件放在彼此的上面--因此被称为Stack。下面是它的外观。

图片

嗯,这不太对。接下来,你要定位文本,使其可以阅读。

定位文本

用下面的语句来代替Stack。

Stack(
  children: [
    // 8
    Text(category, style: FooderlichTheme.darkTextTheme.bodyText1,),
    // 9
    Positioned(
      child: Text(
        title,
        style: FooderlichTheme.darkTextTheme.headline2,),
      top: 20,),
    // 10
    Positioned(
      child: Text(
        description,
        style: FooderlichTheme.darkTextTheme.bodyText1,),
      bottom: 30,
      right: 0,),
    // 11
    Positioned(
      child: Text(
        chef, style: FooderlichTheme.darkTextTheme.bodyText1,),
      bottom: 10,
      right: 0,)
  ],
),

),


对于相关的文本,你应用一个定位的部件。该小组件控制你在堆栈中的文本位置。这里是你使用的位置。

8.类别,编辑的选择,保持它的位置。记住,Container已经在所有的边上应用了16的padding。 9.你把标题放在离顶部20像素的地方。 10.在这里,你把描述放在离底部30像素的地方,并向右移0。 11.最后,你把厨师的名字放在离右下方10像素的地方。

经过这些更新后,card1看起来像这样。

图片

很好,现在第一张卡已经完成了。现在是时候进入下一个了!

构成card2:作者卡

现在是时候开始制作下一张卡片了,即作者卡片。下面是你完成后的样子。

图片

尽管在外观上有差异,但卡片2与卡片1相似。它是由以下部件组成的。

  • 一个带有BoxDecoration的容器,显示一个带有圆角的图像。
  • 一个自定义的作者小组件,显示作者的个人资料图片、姓名和工作职位。
  • Text部件--但这次注意到Smoothies有一个垂直旋转。
  • 右上角有一个爱心的IconButton。

在lib目录下,创建一个名为card2.dart的新文件。添加以下代码。

import 'package:flutter/material.dart';

class Card2 extends StatelessWidget {
  const Card2({Key key}) : super(key: key);
  @override
  Widget build(BuildContext context) {
    return Center(
      // 1
      child: Container(
        constraints: const BoxConstraints.expand(width: 350, height: 450),
        decoration: const BoxDecoration(
          image: DecorationImage(
            image: AssetImage('assets/mag5.png'),
            fit: BoxFit.cover,
          ),
          borderRadius: BorderRadius.all(Radius.circular(10.0)),
        ),
        // 2
        child: Column(
          children: [
            // TODO 1: add author information
            // TODO 4: add Positioned text
        ],
        ),
      ),
    );
  }
}

}


快速看一下代码,你会注意到以下情况。

  1. Center小组件有一个容器子小组件,它有三个属性,前两个是约束和装饰。
  2. 第三个属性是child,它有一个Column小组件。一个柱形部件会垂直显示它的子部件。

在这个代码片段中,需要注意的是以//TODO开头的注释。TODO注释指出了你需要处理或更新的东西。在这本书中,它们让你更容易浏览代码更新。如果你想查看你所有的TODO条目,请在Android Studio中打开TODO标签。

图片

Card2的初始设置与Card1类似。在home.dart中,用Card2()替换第二个Container,所以它看起来像下面这样。

static List<Widget> pages = <Widget>[
    const Card1(),
    const Card2(),
    Container(color: Colors.blue),
];

并记得导入card2,如下所示。

import 'card2.dart';

然后,执行一次热重启。

点击Card2标签栏项目。你的应用程序应该看起来像这样。

图片

下面是你添加了Column的子部件后,Card2的布局会是这样的。

图片

这一栏将垂直显示以下两个小部件。

  • 作者的卡片
  • 食谱的标题

你的下一步是建立这些小部件。

组成作者卡

以下小部件组成了作者卡。

图片

  • Container: 把所有的小部件放在一起。
  • Row: 水平地排列小部件,并按以下顺序排列。CircleImage, Column and IconButton.
  • Column: 垂直排列两个文本部件,作者的名字在作者的标题之上。
  • CircleImage: 一个你接下来要创建的自定义部件,显示作者的头像。
  • IconButton: 一个显示图标的按钮。

创建一个圆形头像小组件

你的第一步是创建作者的圆形头像。

图片

在lib目录下,创建一个名为circle_image.dart的新文件。添加以下代码。

import 'package:flutter/material.dart';

class CircleImage extends StatelessWidget {
  // 1
  const CircleImage({
    Key key,
    this.imageProvider,
    this.imageRadius = 20,
  }) : super(key: key);

  // 2
  final double imageRadius;
  final ImageProvider imageProvider;

  @override
  Widget build(BuildContext context) {
    // 3
    return CircleAvatar(
      backgroundColor: Colors.white,
      radius: imageRadius,
      // 4
      child: CircleAvatar(
        radius: imageRadius - 5,
        backgroundImage: imageProvider,
      ),
    );
  }
}



这里是你如何创建这个新的自定义小组件。

  1. CircleImage有两个参数:imageProvider和imageRadius。
  2. imageRadius和imageProvider的属性声明。
  3. CircleAvatar是一个由Material库提供的小部件。它被定义为一个白色的圆,半径为imageRadius。
  4. 在外圈内是另一个CircleAvatar,它是一个较小的圆,包括用户的个人资料图片。让内圈变小,就能得到白色边框的效果。

设置AuthorCard小组件

在lib目录下,创建一个名为author_card.dart的新文件。添加以下代码。

import 'package:flutter/material.dart';
import 'fooderlich_theme.dart';
import 'circle_image.dart';

class AuthorCard extends StatelessWidget {
  // 1
  final String authorName;
  final String title;
  final ImageProvider imageProvider;

  const AuthorCard({
    Key key,
    this.authorName,
    this.title,
    this.imageProvider,
  }) : super(key: key);

  // 2
  @override
  Widget build(BuildContext context) {
    return Container(
      padding: const EdgeInsets.all(16),
      child: Row(
        children: [],
      ),
    );
  }
}


下面是这段代码的工作原理。

  1. AuthorCard有三个属性:authorName、作者的职位名称和imageProvider处理的简介图片。
  2. 请记住,AuthorCard被分组在一个容器中,并使用一个Row小组件来水平排列其他小组件。

你以后会回到这个小部件。现在,你要把事情设置好,以便在你完成这个小组件时,热重载会刷新用户界面。

添加AuthorCard小组件到Card2

打开card2.dart并添加以下导入。

import 'author_card.dart';

然后,找到// TODO 1: 添加作者信息,用下面的内容代替它。

const AuthorCard(
  authorName: 'Mike Katz',
  title: 'Smoothie Connoisseur',
  imageProvider: AssetImage('assets/author_katz.jpeg')),

现在你已经添加了作者卡,现在是时候回到作者卡部件本身的构成。

组成作者卡小部件

打开author_card.dart,把build()中的return Container(...);替换为以下内容。

return Container(
      padding: const EdgeInsets.all(16),
      child: Row(
        // TODO 3: add alignment
        children: [
        // 1
        Row(children: [
          CircleImage(imageProvider: imageProvider, imageRadius: 28),
          // 2
          const SizedBox(width: 8),
          // 3
          Column(
            crossAxisAlignment: CrossAxisAlignment.start,
            children: [
              Text(
                authorName,
                style: FooderlichTheme.lightTextTheme.headline2,
              ),
              Text(
                title,
                style: FooderlichTheme.lightTextTheme.headline3,
              )
            ],
          ),
        ]),
        // TODO 2: add IconButton
       ],
     ),
    );



请注意,该容器有两个相互嵌套的行部件。下面是代码的作用。

  1. 内部的Row将CircleImage和作者的文本信息分组。
  2. 在图片和文本之间应用8像素的填充。
  3. 用一列垂直排列作者的名字和工作职位。

热重载并点击Card2的标签栏按钮。你的应用程序现在看起来就像这样。

图片

看起来不错,但有几个重要的元素你还需要添加。

添加IconButton小部件

接下来,你需要在内部行小部件之后添加心形的IconButton小部件。当用户想收藏一个食谱时,他们会点击这个图标。

首先找到//TODO 2:添加IconButton,然后用下面的代码替换它。

IconButton(
  // 4
  icon: const Icon(Icons.favorite_border),
  iconSize: 30,
  color: Colors.grey[400],
  // 5
  onPressed: () {
    const snackBar = SnackBar(content: Text('Press Favorite'));
    ScaffoldMessenger.of(context).showSnackBar(snackBar);
  }),
.of(context).showSnackBar(snackBar);
}),

下面是一个快速分解。

  1. 设置图标、图标的大小和颜色。
  2. 当用户按下该图标时,显示一个snackbar。

注意:当一个动作发生时,snackbar对于向用户简短地显示信息很有用。例如,当你删除一封电子邮件时,你可以向用户提供一个撤销操作。在这个例子中,snackbar会告诉用户,他们已经喜欢上了一个食谱。

当你按下心形图标时,你的应用程序将看起来像这样。

图片

接下来,找到//TODO 3:添加对齐方式,并将其替换为以下内容。

mainAxisAlignment: MainAxisAlignment.spaceBetween,

外围的行部件应用spaceBetween对齐方式。这在外行的子节点之间均匀地添加了额外的空间,将IconButton放在了屏幕的最右边。

只剩下一个重要的元素要添加:文本。

组成文本 回到card2.dart,并添加主题导入。

import 'fooderlich_theme.dart';

然后找到 // TODO 4: 添加Positioned文本,并将其替换为以下内容。

// 1
Expanded(
  // 2
  child: Stack(
    children: [
      // 3
      Positioned(
        bottom: 16,
        right: 16,
        child: Text(
          'Recipe',
          style: FooderlichTheme.lightTextTheme.headline1,
          ),
      ),
      // 4
      Positioned(
        bottom: 70,
        left: 16,
        child: RotatedBox(
          quarterTurns: 3,
          child: Text(
            'Smoothies',
            style: FooderlichTheme.lightTextTheme.headline1,
          ),
        ),
      ),
      ],
    ),
  ),



注意到使用FooderlichTheme来应用文本样式是多么方便。

现在,看一下代码。

  1. 使用 "Expanded",你填入剩余的可用空间。
  2. 应用Stack widget,将文本定位在彼此的顶部。
  3. 将第一个文本放在离底部16像素、离右侧16像素的位置。
  4. 最后,将第二个文本放在离底部70像素、离左边16像素的位置。同时应用一个RotatedBox部件,将文本顺时针旋转四分之三圈。这使它看起来是垂直的。

保存和热重载后,Card2将看起来像这样。

图片

这就是你需要为第二张卡片做的所有事情。接下来,你将进入最后一张卡片。

构成card3:探索卡

Card3是本章中你要创建的最后一张卡片。这个卡片让用户探索趋势,找到他们想尝试的食谱。

图片

以下是组成Card3的小部件。

  • Container和BoxDecoration显示图像和圆角,类似于上面的卡片。
  • 你用第二个Container使图像变暗和半透明,这样白色的文字就更容易阅读。
  • 显示一个图标和标题。
  • 显示一个Chip widgets的集合,显示食谱属性,如健康或素食。

在lib目录下,创建一个名为card3.dart的新文件。添加以下代码。

import 'package:flutter/material.dart';
import 'fooderlich_theme.dart';

class Card3 extends StatelessWidget {
  const Card3({Key key}) : super(key: key);
  @override
  Widget build(BuildContext context) {
    return Center(
      child: Container(
        constraints: const BoxConstraints.expand(width: 350, height: 450),
        decoration: const BoxDecoration(
          image: DecorationImage(image: AssetImage('assets/mag2.png'),
                                 fit: BoxFit.cover),
          borderRadius: BorderRadius.all(Radius.circular(10.0)),
        ),
        child: Stack(
          children: [
            // TODO 5: add dark overlay BoxDecoration
            // TODO 6: Add Container, Column, Icon and Text
            // TODO 7: Add Center widget with Chip widget children
          ],
        ),
      ),
    );
  }
}

}


与之前的卡片类似,这为你的卡片设置了基本的容器和盒子的装饰。

Card3的初始设置就像Card1和Card2一样。在home.dart中,用Card3()替换最后的Container,这样它看起来就像下面的代码。

static List<Widget> pages = <Widget>[
    const Card1(),
    const Card2(),
    const Card3(),
];

然后在文件的顶部添加需要的导入。

import 'card3.dart';

通过点击运行面板上的按钮执行热重启。

点击Card3标签栏项目。你的应用程序将看起来像这样。

图片

到目前为止,卡片只是有典型的卡片主题和图片。接下来你将添加其他元素。

构成深色覆盖层

为了使白色文本从图像中突出,你将给图像一个深色的覆盖层。就像你之前做的那样,你将使用Stack在图片上叠加其他部件。

在card3.dart中,找到//TODO 5:添加深色覆盖的BoxDecoration,在Stack.Container()中用下面的代码替换它。

Container(
  decoration: BoxDecoration(
    // 1
    color: Colors.black.withOpacity(0.6),
    // 2
    borderRadius: const BorderRadius.all(Radius.circular(10.0)),
  ),
),



添加这段代码的作用如下。

  1. 你添加一个带有60%半透明背景的颜色覆盖的容器,使图像看起来更暗。
  2. 这使图像的边角呈现出圆润的外观。

你的应用程序现在看起来像这样。

图片

很好! 现在是一些文字。

构成标题

你要做的下一件事是添加Recipe Trends文本和图标。要做到这一点,请将//TODO 6:添加容器、列、图标和文本替换为。

Container(
  // 3
  padding: const EdgeInsets.all(16),
  // 4
  child: Column(
    // 5
    crossAxisAlignment: CrossAxisAlignment.start,
    children: [
      // 6
      const Icon(Icons.book, color: Colors.white, size: 40),
      // 7
      const SizedBox(height: 8),
      // 8
      Text(
        'Recipe Trends',
        style: FooderlichTheme.darkTextTheme.headline2),
      // 9
      const SizedBox(height: 30),
    ],
  ),
),



下面是你用这段代码做的事情。

  1. 在所有边上应用16像素的填充。
  2. 设置一个子列来垂直布置小部件。
  3. 将所有的小部件放在列的左边。
  4. 添加一个图书图标。
  5. 在垂直方向上应用一个8像素的空间。
  6. 添加文本小组件。
  7. 在垂直方向上应用一个30像素的空间。

保存文件,你的卡片现在看起来像这样。

图片

很好,接下来你将添加带有菜谱类别的筹码。

chips的构成

找到 // TODO 7: 添加中心小部件与芯片小部件的孩子,并将其替换为以下内容。

// 10
Center(
  // 11
  child: Wrap(
    // 12
    alignment: WrapAlignment.start,
    // 13
    spacing: 12,
    // 14
    children: [
      Chip(
        label: Text('Healthy',
            style: FooderlichTheme.darkTextTheme.bodyText1),
        backgroundColor: Colors.black.withOpacity(0.7),
        onDeleted: () {
          print('delete');
          },
      ),
      Chip(
        label: Text('Vegan',
            style: FooderlichTheme.darkTextTheme.bodyText1),
        backgroundColor:Colors.black.withOpacity(0.7),
        onDeleted: () {
          print('delete');
          },
      ),
      Chip(
        label: Text('Carrots',
            style: FooderlichTheme.darkTextTheme.bodyText1),
        backgroundColor:Colors.black.withOpacity(0.7),
      ),
    ],
  ),
),



下面是这段代码的分解。

  1. 你添加了一个中心小组件。
  2. Wrap是一个布局部件,它试图将它的每一个子项都布置在与前一个子项相邻的地方。如果没有足够的空间,它会包到下一行。
  3. 尽可能地将子项放在左边,即开始的位置。
  4. 在每个子项之间应用一个12像素的空间。
  5. 添加Chip小组件的列表。

注:Chip widget是一个显示元素,可以显示文本和图像头像,也可以执行用户操作,如点击和删除。关于芯片小部件的更多信息,请查看Pinkesh Darji的这个很棒的教程:medium.com/aubergine-s…

保存你的修改并热重启。现在,你的卡片看起来像这样。

图片

通过重复上面的Chip()代码,添加更多的筹码。这让你有机会看到Wrap布局小部件的运作,如下图所示。

图片

你成功了! 你已经完成了这一章。一路走来,你已经应用了三种不同类别的部件。你学会了如何使用结构部件来组织不同的屏幕,你还创建了三个自定义的食谱卡片,并在每个卡片上应用了不同的部件布局。

干得好!

关键点

小工具的三个主要类别是:结构和导航;显示信息;以及,定位小工具。 在Flutter中有两个主要的视觉设计系统,Material和Cupertino。它们分别帮助您建立在安卓和iOS上看起来是原生的应用程序。 使用Material主题,你可以建立相当多的用户界面元素,给你的应用程序一个自定义的外观和感觉。 一般来说,为你的应用程序建立一个共同的主题对象是个好主意,让你的应用程序的风格有一个单一的真理来源。 Scaffold小组件实现了你所有的基本视觉布局结构需求。 容器小部件可以用来将其他小部件组合在一起。 堆栈部件将子部件分层在彼此的上面。

接下来该怎么做呢?

有大量的Material Design小组件可以玩,更不用说其他类型的小组件了--太多了,在一个章节中无法涵盖。

幸运的是,Flutter团队创建了一个Widget UI组件库,展示了每个widget的工作原理。请看这里:gallery.flutter.dev/

在本章中,你已经开始使用widget来构建一个漂亮的用户界面。在下一章中,你将深入研究widget的理论,以帮助你更好地理解如何使用它们。


www.deepl.com 翻译