Flutter一切皆widget但是不要将所有东西放入一个widget

简介: 当我们在widgets目录中,我们可以看到很多小部件,如Padding,Align,SizedBox,等。我们通过组合它们来创建其他小部件,我发现这种方法可扩展、强大且易于理解。但是当我阅读 一些我在互联网上找到的或由新采用者编写的源代码时,有一件让我震惊的事情:拥有大量build ` 方法的趋势,实例化很多小部件!我发现这很难阅读、理解和维护。

当我们在widgets目录中,我们可以看到很多小部件,如PaddingAlignSizedBox,等。我们通过组合它们来创建其他小部件,我发现这种方法可扩展、强大且易于理解。

但是当我阅读 一些我在互联网上找到的或由新采用者编写的源代码时,有一件让我震惊的事情:拥有大量build ` 方法的趋势,实例化很多小部件!我发现这很难阅读、理解和维护。

作为软件开发人员,我们必须记住,软件的真实生活从第一次发布给用户开始。该软件的源代码将由其他人(包括您未来的您)阅读和维护,这就是为什么保持我们的代码简单、易于阅读和理解非常重要。

“小部件中的一切”的示例可以在Flutter 文档本身中找到。本教程的目标是展示如何构建此布局:

网络异常,图片无法展示
|

最终代码达到了它的目的:展示如何简单地创建上述布局。正如我们所见,甚至还有一些变量和方法可以为布局的各个部分提供语义。这是一个很好的观点,因为它使代码更容易理解。

import 'package:flutter/material.dart';
void main() {
  runApp(MyApp());
}
class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    Widget titleSection = Container(
      padding: const EdgeInsets.all(32),
      child: Row(
        children: [
          Expanded(
            child: Column(
              crossAxisAlignment: CrossAxisAlignment.start,
              children: [
                Container(
                  padding: const EdgeInsets.only(bottom: 8),
                  child: Text(
                    'Oeschinen Lake Campground',
                    style: TextStyle(
                      fontWeight: FontWeight.bold,
                    ),
                  ),
                ),
                Text(
                  'Kandersteg, Switzerland',
                  style: TextStyle(
                    color: Colors.grey[500],
                  ),
                ),
              ],
            ),
          ),
          Icon(
            Icons.star,
            color: Colors.red[500],
          ),
          Text('41'),
        ],
      ),
    );
    Color color = Theme.of(context).primaryColor;
    Widget buttonSection = Container(
      child: Row(
        mainAxisAlignment: MainAxisAlignment.spaceEvenly,
        children: [
          _buildButtonColumn(color, Icons.call, 'CALL'),
          _buildButtonColumn(color, Icons.near_me, 'ROUTE'),
          _buildButtonColumn(color, Icons.share, 'SHARE'),
        ],
      ),
    );
    Widget textSection = Container(
      padding: const EdgeInsets.all(32),
      child: Text(
        'Lake Oeschinen lies at the foot of the Blüemlisalp in the Bernese '
        'Alps. Situated 1,578 meters above sea level, it is one of the '
        'larger Alpine Lakes. A gondola ride from Kandersteg, followed by a '
        'half-hour walk through pastures and pine forest, leads you to the '
        'lake, which warms to 20 degrees Celsius in the summer. Activities '
        'enjoyed here include rowing, and riding the summer toboggan run.',
        softWrap: true,
      ),
    );
    return MaterialApp(
      title: 'Flutter layout demo',
      home: Scaffold(
        appBar: AppBar(
          title: Text('Flutter layout demo'),
        ),
        body: ListView(
          children: [
            Image.asset(
              'images/lake.jpg',
              width: 600,
              height: 240,
              fit: BoxFit.cover,
            ),
            titleSection,
            buttonSection,
            textSection,
          ],
        ),
      ),
    );
  }
  Column _buildButtonColumn(Color color, IconData icon, String label) {
    return Column(
      mainAxisSize: MainAxisSize.min,
      mainAxisAlignment: MainAxisAlignment.center,
      children: [
        Icon(icon, color: color),
        Container(
          margin: const EdgeInsets.only(top: 8),
          child: Text(
            label,
            style: TextStyle(
              fontSize: 12,
              fontWeight: FontWeight.w400,
              color: color,
            ),
          ),
        ),
      ],
    );
  }
}
复制代码

事实上,情况可能更糟。这是我不喜欢的这段代码的典型多合一小部件版本:。

import 'package:flutter/material.dart';
void main() {
  runApp(MyApp());
}
class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    Color color = Theme.of(context).primaryColor;
    return MaterialApp(
      title: 'Flutter layout demo',
      home: Scaffold(
        appBar: AppBar(
          title: Text('Flutter layout demo'),
        ),
        body: ListView(
          children: [
            Image.asset(
              'images/lake.jpg',
              width: 600,
              height: 240,
              fit: BoxFit.cover,
            ),
            Container(
              padding: const EdgeInsets.all(32),
              child: Row(
                children: [
                  Expanded(
                    child: Column(
                      crossAxisAlignment: CrossAxisAlignment.start,
                      children: [
                        Container(
                          padding: const EdgeInsets.only(bottom: 8),
                          child: Text(
                            'Oeschinen Lake Campground',
                            style: TextStyle(
                              fontWeight: FontWeight.bold,
                            ),
                          ),
                        ),
                        Text(
                          'Kandersteg, Switzerland',
                          style: TextStyle(
                            color: Colors.grey[500],
                          ),
                        ),
                      ],
                    ),
                  ),
                  Icon(
                    Icons.star,
                    color: Colors.red[500],
                  ),
                  Text('41'),
                ],
              ),
            ),
            Container(
              child: Row(
                mainAxisAlignment: MainAxisAlignment.spaceEvenly,
                children: [
                  Column(
                    mainAxisSize: MainAxisSize.min,
                    mainAxisAlignment: MainAxisAlignment.center,
                    children: [
                      Icon(Icons.call, color: color),
                      Container(
                        margin: const EdgeInsets.only(top: 8),
                        child: Text(
                          'CALL',
                          style: TextStyle(
                            fontSize: 12,
                            fontWeight: FontWeight.w400,
                            color: color,
                          ),
                        ),
                      ),
                    ],
                  ),
                  Column(
                    mainAxisSize: MainAxisSize.min,
                    mainAxisAlignment: MainAxisAlignment.center,
                    children: [
                      Icon(Icons.near_me, color: color),
                      Container(
                        margin: const EdgeInsets.only(top: 8),
                        child: Text(
                          'ROUTE',
                          style: TextStyle(
                            fontSize: 12,
                            fontWeight: FontWeight.w400,
                            color: color,
                          ),
                        ),
                      ),
                    ],
                  ),
                  Column(
                    mainAxisSize: MainAxisSize.min,
                    mainAxisAlignment: MainAxisAlignment.center,
                    children: [
                      Icon(Icons.share, color: color),
                      Container(
                        margin: const EdgeInsets.only(top: 8),
                        child: Text(
                          'SHARE',
                          style: TextStyle(
                            fontSize: 12,
                            fontWeight: FontWeight.w400,
                            color: color,
                          ),
                        ),
                      ),
                    ],
                  ),
                ],
              ),
            ),
            Container(
              padding: const EdgeInsets.all(32),
              child: Text(
                'Lake Oeschinen lies at the foot of the Blüemlisalp in the Bernese '
                'Alps. Situated 1,578 meters above sea level, it is one of the '
                'larger Alpine Lakes. A gondola ride from Kandersteg, followed by a '
                'half-hour walk through pastures and pine forest, leads you to the '
                'lake, which warms to 20 degrees Celsius in the summer. Activities '
                'enjoyed here include rowing, and riding the summer toboggan run.',
                softWrap: true,
              ),
            ),
          ],
        ),
      ),
    );
  }
}
复制代码

在第二个版本中,我们有一个大build方法的小部件,它很难阅读、理解和维护。

现在让我们看看我将如何重写它:

import 'package:flutter/material.dart';
void main() {
  runApp(MyApp());
}
class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Flutter layout demo',
      home: const HomePage(),
    );
  }
}
class HomePage extends StatelessWidget {
  const HomePage({
    Key key,
  }) : super(key: key);
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text('Flutter layout demo'),
      ),
      body: ListView(
        children: [
          const _Header(),
          const _SubHeader(),
          const _Buttons(),
          const _Description(),
        ],
      ),
    );
  }
}
class _Header extends StatelessWidget {
  const _Header({
    Key key,
  }) : super(key: key);
  @override
  Widget build(BuildContext context) {
    return Image.asset(
      'images/lake.jpg',
      width: 600,
      height: 240,
      fit: BoxFit.cover,
    );
  }
}
class _SubHeader extends StatelessWidget {
  const _SubHeader({
    Key key,
  }) : super(key: key);
  @override
  Widget build(BuildContext context) {
    return Container(
      padding: const EdgeInsets.all(32),
      child: Row(
        children: [
          Expanded(
            child: Column(
              crossAxisAlignment: CrossAxisAlignment.start,
              children: [
                const _Title(),
                const _SubTitle(),
              ],
            ),
          ),
          const _Likes(),
        ],
      ),
    );
  }
}
class _Title extends StatelessWidget {
  const _Title({
    Key key,
  }) : super(key: key);
  @override
  Widget build(BuildContext context) {
    return Container(
      padding: const EdgeInsets.only(bottom: 8),
      child: Text(
        'Oeschinen Lake Campground',
        style: TextStyle(
          fontWeight: FontWeight.bold,
        ),
      ),
    );
  }
}
class _SubTitle extends StatelessWidget {
  const _SubTitle({
    Key key,
  }) : super(key: key);
  @override
  Widget build(BuildContext context) {
    return Text(
      'Kandersteg, Switzerland',
      style: TextStyle(
        color: Colors.grey[500],
      ),
    );
  }
}
class _Likes extends StatelessWidget {
  const _Likes({
    Key key,
  }) : super(key: key);
  @override
  Widget build(BuildContext context) {
    return Row(
      children: <Widget>[
        Icon(
          Icons.star,
          color: Colors.red[500],
        ),
        Text('41'),
      ],
    );
  }
}
class _Buttons extends StatelessWidget {
  const _Buttons({
    Key key,
  }) : super(key: key);
  @override
  Widget build(BuildContext context) {
    return Container(
      child: Row(
        mainAxisAlignment: MainAxisAlignment.spaceEvenly,
        children: [
          const _Button(icon: Icons.call, text: 'CALL'),
          const _Button(icon: Icons.share, text: 'ROUTE'),
          const _Button(icon: Icons.share, text: 'SHARE'),
        ],
      ),
    );
  }
}
class _Button extends StatelessWidget {
  const _Button({
    Key key,
    @required this.icon,
    @required this.text,
  })  : assert(icon != null),
        assert(text != null),
        super(key: key);
  final IconData icon;
  final String text;
  @override
  Widget build(BuildContext context) {
    Color color = Theme.of(context).primaryColor;
    return Column(
      mainAxisSize: MainAxisSize.min,
      mainAxisAlignment: MainAxisAlignment.center,
      children: [
        Icon(icon, color: color),
        Container(
          margin: const EdgeInsets.only(top: 8),
          child: Text(
            text,
            style: TextStyle(
              fontSize: 12,
              fontWeight: FontWeight.w400,
              color: color,
            ),
          ),
        ),
      ],
    );
  }
}
class _Description extends StatelessWidget {
  const _Description({
    Key key,
  }) : super(key: key);
  @override
  Widget build(BuildContext context) {
    return Container(
      padding: const EdgeInsets.all(32),
      child: Text(
        'Lake Oeschinen lies at the foot of the Blüemlisalp in the Bernese '
        'Alps. Situated 1,578 meters above sea level, it is one of the '
        'larger Alpine Lakes. A gondola ride from Kandersteg, followed by a '
        'half-hour walk through pastures and pine forest, leads you to the '
        'lake, which warms to 20 degrees Celsius in the summer. Activities '
        'enjoyed here include rowing, and riding the summer toboggan run.',
        softWrap: true,
      ),
    );
  }
}
复制代码

你不觉得这更易读吗?

🤔 有什么好处?

我理解为什么教程不经常这样做:它需要更多行(在我的示例中为 100 行),人们可能想知道为什么我们要创建这么多其他小部件。由于教程旨在专注于一个概念,因此这样编写它们可能会适得其反。但结果是,新采用者可能倾向于在他们的build方法中放置一个大的小部件树。 让我们看看为布局的每个部分都有一个独特的小部件有什么好处:

可读性

我们为布局的每个语义部分创建一个小部件。因此,每个小部件都有一个较小的build方法。它更易于阅读,因为您无需滚动即可到达小部件的末尾。

可理解性

每个小部件都有一个与其角色匹配的名称,这称为语义命名。通过这样做,当我们阅读代码时,更容易在我们的脑海中映射代码的哪一部分与我们在应用程序上看到的内容相匹配。我在这里看到了可理解性方面的两个改进: \1. 当我们阅读其他地方引用的此类小部件时,我们几乎知道它的作用,而无需查看其实现。 2.在阅读带有语义命名的小部件的构建方法之前,我们已经对其内容有一个大致的了解。

可维护性

如果您必须更换一个组件或更改一个部件,它只会在一个地方,与其他小部件的其余部分分开。多亏了这种做法,它更不容易出错,因为每个小部件的角色都得到了很好的定义。在您的应用程序甚至另一个应用程序中的另一个页面中共享布局的一部分也将更加容易。

Performances

前面的所有原因应该足以让您采用这种方式来创建 Flutter 应用程序,但是这样做还有一个好处:我们提高了应用程序的性能,因为每个小部件都可以与其他小部件分开重建(事实并非如此如果我们使用方法来分隔我们的布局部分)。例如,假设我们必须在单击它时增加红星旁边的数字。在这个版本中,我们可以制作_Likes一个StatefulWidget并处理这里的增量。当用户点击星星时,只有_Likes小部件会被重建。在第一个版本中,MyApp如果我们将其设为StatefulWidget.

Flutter 文档中也解释了这种最佳实践:

setState()在状态上调用时,所有后代小部件都将重建。因此,将setState()调用本地化到 UI 实际需要更改的子树部分。如果更改包含在树的一小部分,请避免在树的高处调用 setState()。

另一个优点是能够const更频繁地使用关键字。然后可以缓存和重新使用小部件。正如Flutter 文档所述:

重用小部件比创建新的(但配置相同的)小部件要高效得多。

⚡️ 如何提高工作效率?

如您所见,通过为布局的每个语义部分创建一个小部件,我们编写了更多代码。我们可以在 Visual Studio Code 中使用Dart扩展提供的stlessstful片段,

为了我自己的需要,我创建了新的片段,称为slesssful,这样我的工作效率比以往任何时候都高。如果您希望在 Visual Studio Code 中使用它们,则必须遵循此文档并添加以下内容:

{
  "Flutter stateless widget": {
        "scope": "dart",
        "prefix": "sless",
        "description": "Insert a StatelessWidget",
        "body": [
            "class $1 extends StatelessWidget {",
            "  const $1({",
            "    Key key,",
            "  }) : super(key: key);",
            "",
            "  @override",
            "  Widget build(BuildContext context) {",
            "    return Container(",
            "      $2",
            "    );",
            "  }",
            "}"
        ]
    },
    "Flutter stateful widget": {
        "scope": "dart",
        "prefix": "sful",
        "description": "Insert a StatefulWidget",
        "body": [
            "class $1 extends StatefulWidget {",
            "  const $1({",
            "    Key key,",
            "  }) : super(key: key);",
            "",
            "  @override",
            "  _$1State createState() => _$1State();",
            "}",
            "",
            "class _$1State extends State<$1> {",
            "  @override",
            "  Widget build(BuildContext context) {",
            "    return Container(",
            "      $2",
            "    );",
            "  }",
            "}"
        ]
    },
}
复制代码

📕 结论

我相信这是编写 Flutter 应用程序的好方法,我希望你也相信。如果不是这样,我对你的意见很感兴趣😉!

从现在开始,记住这句话:“Everything’s a widget but don’t put everything in one widget! ”。

.markdown-body pre,.markdown-body pre>code.hljs{color:#333;background:#f8f8f8}.hljs-comment,.hljs-quote{color:#998;font-style:italic}.hljs-keyword,.hljs-selector-tag,.hljs-subst{color:#333;font-weight:700}.hljs-literal,.hljs-number,.hljs-tag .hljs-attr,.hljs-template-variable,.hljs-variable{color:teal}.hljs-doctag,.hljs-string{color:#d14}.hljs-section,.hljs-selector-id,.hljs-title{color:#900;font-weight:700}.hljs-subst{font-weight:400}.hljs-class .hljs-title,.hljs-type{color:#458;font-weight:700}.hljs-attribute,.hljs-name,.hljs-tag{color:navy;font-weight:400}.hljs-link,.hljs-regexp{color:#009926}.hljs-bullet,.hljs-symbol{color:#990073}.hljs-built_in,.hljs-builtin-name{color:#0086b3}.hljs-meta{color:#999;font-weight:700}.hljs-deletion{background:#fdd}.hljs-addition{background:#dfd}.hljs-emphasis{font-style:italic}.hljs-strong{font-weight:700}


相关文章
|
11天前
|
容器
Flutter Widget 解析
Flutter Widget 解析
|
11天前
|
存储 容器
Flutter 有状态Widget 和 无状态Widget
Flutter 有状态Widget 和 无状态Widget
|
1月前
深入理解Flutter鸿蒙next版本 中的Widget继承:使用extends获取数据与父类约束
本文详细介绍了Flutter中如何通过继承其他Widget来创建自定义组件。首先解释了Widget继承的基本概念,包括StatelessWidget和StatefulWidget的区别。接着通过具体示例展示了如何继承StatelessWidget和StatefulWidget,并在子类中访问父类的build方法和状态。最后,结合多个自定义Widget展示了如何在实际应用中灵活使用继承和组合来构建复杂的UI。
83 8
|
1月前
|
容器
flutter&鸿蒙next 使用 InheritedWidget 实现跨 Widget 传递状态
在 Flutter 中,状态管理至关重要。本文详细介绍了如何使用 InheritedWidget 实现跨 Widget 的状态传递。InheritedWidget 允许数据在 Widget 树中向下传递,适用于多层嵌套的场景。通过一个简单的计数器示例,展示了如何创建和使用 InheritedWidget,包括其基础概念、工作原理及代码实现。虽然 InheritedWidget 较底层,但它是许多高级状态管理解决方案的基础。
108 2
|
2月前
|
容器
flutter:第一个flutter&Widget的使用 (二)
本文介绍了Flutter框架下的基本组件及其用法,包括简单的 Stateless Widget 如文本和按钮,以及更复杂的 StatefulWidget 示例。详细解释了如何使用 `context` 获取祖先小部件的信息,并展示了 `MaterialApp` 的属性及用途。此外,还探讨了 `StatefulWidget` 与 `StatelessWidget` 的区别,以及 `AppBar` 的常见属性配置方法。适合Flutter初学者参考学习。
|
1月前
|
Dart JavaScript 前端开发
Flutter 的 Widget 概述与常用 Widgets 与鸿蒙 Next 的对比
Flutter 是 Google 开发的开源 UI 框架,用于快速构建高性能的移动、Web 和桌面应用。Flutter 通过 Widget 构建 UI,每个 UI 元素都是 Widget,包括文本、按钮、图片等。Widget 不仅描述外观,还描述行为,是不可变的。常见的 Widget 包括结构型(Container、Column、Row)、呈现型(Text、Image)、交互型(ElevatedButton)和状态管理型(StatefulWidget)。Flutter 与鸿蒙 Next 在组件化架构、开发语言、布局系统、性能和跨平台支持方面各有优势
82 0
|
5月前
Flutter-底部弹出框(Widget层级)
文章描述了如何在Flutter中使用DraggableScrollableSheet创建一个底部弹出框,同时保持其可手势滑动关闭。作者遇到问题并提出对原控件进行扩展,以支持头部和列表布局的滑动关闭功能。
201 0
|
6月前
Flutter StreamBuilder 实现局部刷新 Widget
Flutter StreamBuilder 实现局部刷新 Widget
54 0
|
7月前
|
Android开发
Flutter完整开发实战详解(六、 深入Widget原理),2024百度Android岗面试真题收录解析
Flutter完整开发实战详解(六、 深入Widget原理),2024百度Android岗面试真题收录解析
|
7月前
|
开发框架 前端开发 搜索推荐
【Flutter前端技术开发专栏】Flutter中的自定义Widget与渲染流程
【4月更文挑战第30天】探索Flutter的自定义Widget与渲染流程。自定义Widget是实现复杂UI设计的关键,优点在于个性化设计、功能扩展和代码复用,但也面临性能优化和复杂性管理的挑战。创建步骤包括设计结构、定义Widget类、实现构建逻辑和处理交互。Flutter渲染流程涉及渲染对象树、布局、绘制和合成阶段。实践案例展示如何创建带渐变背景和阴影的自定义按钮。了解这些知识能提升应用体验并应对开发挑战。查阅官方文档以深入学习。
88 0
【Flutter前端技术开发专栏】Flutter中的自定义Widget与渲染流程