Flutter 布局相关知识点
  xx2YH4ad7R0N 2023年11月02日 52 0


Flutter 中几乎所有的东西都是一个小部件,当你编写小部件时,你可以构建一个布局。例如,您可以在列小部件中添加多个小部件以创建垂直布局。随着您继续添加更多小部件,您的 Flutter 应用程序布局将变得越复杂。

在本文中,我将介绍一些在布局 Flutter 应用程序时要实施的最佳实践。

在 Flutter 中使用 SizedBox 代替 Container

有许多使用情况下,你需要使用占位符。让我们看一下下面的例子:

return _isLoaded ? Container() : YourAwesomeWidget();

​Container​​​ 是一个很棒的小部件,您将在 Flutter 中广泛使用它。 ​​Container()​​​ 扩展以适应父级提供的约束,并且不是 ​​const​​ 构造函数。

另一方面,​​SizedBox​​ 是一个构造函数,创建一个固定尺寸的盒子。宽度和高度参数可以为空,以表示盒子的尺寸不应受到相应维度的限制。

因此,当我们实现占位符时,应该使用 ​​SizedBox​​​ 而不是 ​​Container​​。

return _isLoaded ? SizedBox() : YourAwesomeWidget();

使用 if 条件而不是三元运算符语法

在布局 Flutter 应用程序时,通常需要有条件地渲染不同的小部件。您可能需要根据平台生成一个小部件,例如:

Row(
children: [
Text("Majid"),
Platform.isAndroid ? Text("Android") : SizeBox(),
Platform.isIOS ? Text("iOS") : SizeBox(),
]
);

在这种情况下,您可以删除三元运算符并利用 Dart 的内置语法在数组中添加 ​​if​​ 语句。

Row(
children: [
Text("Majid"),
if (Platform.isAndroid) Text("Android"),
if (Platform.isIOS) Text("iOS"),
]
);

您还可以使用扩展运算符扩展此功能,并根据需要加载多个小部件。

Row(
children: [
Text("Majid"),
if (Platform.isAndroid) Text("Android"),
if (Platform.isIOS) ...[
Text("iOS_1")
Text("iOS_2")
],
]
);

考虑 Flutter 中 build() 方法的成本

Flutter 小部件中的 ​​build​​​ 方法可能会在祖先小部件重建小部件时被频繁调用。避免在 ​​build()​​ 方法中重复和昂贵的工作很重要。

例如,当您使用方法而不是在应用程序中创建小部件时。让我详细说明:

class MyAwesomeWidget extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Scaffold(
body: Column(
mainAxisSize: MainAxisSize.min,
children: [
_buildHeaderWidget(),
_buildBodyWidget(context),
_buildFooterWidget(),
],
),
);
}

Widget _buildHeaderWidget() {
return Padding(
padding: const EdgeInsets.all(10.0),
child: FlutterLogo(
size: 50.0,
),
);
}

Widget _buildBodyWidget(BuildContext context) {
return Expanded(
child: Container(
child: Center(
child: Text(
'Majid Hajian, Flutter GDE',
),
),
),
);
}

Widget _buildFooterWidget() {
return Padding(
padding: const EdgeInsets.all(10.0),
child: Text('Footer'),
);
}
}

这种方法的缺点是,当 ​​MyAwesomeWidget​​ 需要再次重建时--这可能会经常发生--所有在方法中创建的widget也会被重建,导致浪费CPU周期,也可能浪费内存。

因此,最好通过以下方式将这些方法转换为 ​​StatelessWidgets​​:

class MyAwesomeWidget extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Scaffold(
body: Column(
mainAxisSize: MainAxisSize.min,
children: [
HeaderWidget(),
BodyWidget(),
FooterWidget(),
],
),
);
}
}

class HeaderWidget extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Padding(
padding: const EdgeInsets.all(10.0),
child: FlutterLogo(
size: 50.0,
),
);
}
}

class BodyWidget extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Expanded(
child: Container(
child: Center(
child: Text(
'Majid Hajian, Flutter GDE',
),
),
),
);
}
}

class FooterWidget extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Padding(
padding: const EdgeInsets.all(10.0),
child: Text('Footer'),
);
}
}

所有 ​​StatefulWidgets​​​ 或 ​​StatelessWidgets​​​,基于 key、widget 类型和属性,都有一个特殊的缓存机制,只在必要时重建。我们甚至可以通过添加 ​​const​​ 来优化这些小部件,这将导致我们进入本文的下一部分。

尽可能使用 const 小部件

在Dart中,尽可能使用 ​​const​​ 构造函数是很好的做法,记住编译器会优化你的代码。现在,让我们回顾一下上面的例子。通过一个直接的步骤,我们可以使 build 方法更有效地工作。

class MyAwesomeWidget extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Scaffold(
body: Column(
mainAxisSize: MainAxisSize.min,
children: [
const HeaderWidget(),
const BodyWidget(),
const FooterWidget(),
],
),
);
}
}

class HeaderWidget extends StatelessWidget {
const HeaderWidget();
@override
Widget build(BuildContext context) {
return Padding(
padding: const EdgeInsets.all(10.0),
child: FlutterLogo(
size: 50.0,
),
);
}
}

class BodyWidget extends StatelessWidget {
const BodyWidget();
@override
Widget build(BuildContext context) {
return Expanded(
child: Container(
child: Center(
child: Text(
'Majid Hajian, Flutter GDE',
),
),
),
);
}
}

class FooterWidget extends StatelessWidget {
const FooterWidget();
@override
Widget build(BuildContext context) {
return Padding(
padding: const EdgeInsets.all(10.0),
child: Text('Footer'),
);
}
}

此更改可能看起来很简单,但它可以帮助我们避免重新构建 ​​const​​ 小部件。

在 ListView 中为长列表编码 itemExtent

为了理解如何最好地使用 ​​itemExtent​​​,假设我们有一个有几千个元素的列表,当一个动作被触发时,例如点击一个按钮,我们需要跳到最后一个元素。这时 ​​itemExtent​​​ 可以极大地提高 ​​ListView​​ 的布局性能。

指定 ​​itemExtent​​ 比让 children元素 确定他们的范围更有效,因为滚动机器可以使用对children元素范围的预知来节省工作,如下所示:

class LongListView extends StatelessWidget {
final _scrollController = ScrollController();
@override
Widget build(BuildContext context) {
return Scaffold(
floatingActionButton: FloatingActionButton(onPressed:() {
_scrollController.jumpTo(
_scrollController.position.maxScrollExtent,
);
}),
body: ListView(
controller: _scrollController,
children: List.generate(10000, (index) => Text('Index: $index')),
itemExtent: 400,
),
);
}
}

避免使用大Tree

对于何时将小部件拆分为较小的小部件,没有硬性规定。但是,最好避免使用大树,因为它有以下好处:

  • 促进可重用性
  • 提供更简洁的代码
  • 增强可读性
  • 启用封装
  • 提供缓存机制

因此,你应该尽可能地将你的代码分割成不同的小部件。

了解 Flutter 中的约束

每个 Flutter 开发人员都必须知道的 Flutter 布局的黄金法则是:约束下降,尺寸上升,父级设置位置。

让我们来分析一下。

小部件从其父级获得自己的约束。一个约束只是一组四个双精度:最小和最大宽度,以及最小和最大高度。

然后,小部件会遍历其自己的子级列表。小部件一个接一个地告诉它的孩子他们的约束是什么(每个孩子可能不同),然后询问每个孩子它想要的大小。

接下来,小部件将其子项(水平在 ​​x​​​ 轴上,垂直在 ​​y​​ 轴上)一一定位。最后,小部件告诉它的父级它自己的大小(当然是在原始约束范围内)。

在Flutter中,所有的小部件都是基于父级或它们的盒子约束来渲染自己。这带来了一些限制。例如,想象一下,你在一个父部件内有一个子部件,你想决定它的大小。这个小组件不能有任何尺寸! 它的尺寸必须在它的父对象所设定的约束范围内。

与第一个示例类似,小部件无法知道自己在屏幕中的位置,因为这是父小部件的决定。

也就是说,如果一个子部件决定了与它的父部件不同的尺寸,而父部件没有足够的信息来对齐它,那么子部件的尺寸可能被忽略。

好的,让我们看看这个。

void main() {
runApp(MyApp());
}

class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MyWidget();
}
}

class MyWidget extends StatelessWidget {
@override
Widget build(BuildContext context) {
return ConstrainedBox(
constraints: const BoxConstraints(
maxHeight: 400,
minHeight: 100,
minWidth: 100,
maxWidth: 400,
),
child: Container(
color: Colors.green,
),
);
}
}

如果您愿意,您可以忽略 ​​ConstrainedBox​​​ 并将高度和小部件添加到 ​​Container​​ 中。

class MyWidget extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Container(
height: 400,
width: 400,
color: Colors.green,
);
}
}

您可能希望上面的代码渲染一个最大高度和宽度为 ​​400​​​ 的绿色 ​​Container​​。但是,当您运行此代码时,您会感到惊讶。

Flutter 布局相关知识点_android

整个屏幕将是纯绿色!我不会在这里深入探讨细节,但在构建 Flutter 布局时,您可能会遇到几个与此类似的问题。

让我们看看这里发生了什么。在上面的示例中,树如下所示:

- `MyApp`
- `MyWidget`
- `ConstrainedBox`
- `Container`

约束规则将从父控件传递给子控件,因此子控件可以在其父控件的给定约束范围内决定其大小。因此,约束适用。

因此,Flutter 将严格约束传递给 ​​MyApp()​​​,然后 ​​MyApp()​​​ 将其严格约束传递给 ​​ConstrainedBox​​​。然后,​​ConstrainedBox​​ 被迫忽略它自己的约束并使用它的父级,在这种情况下,它是全屏大小,这就是为什么你会看到一个全屏的绿色框。

通常,你会发现添加一个 ​​Center​​小部件可能会解决这个问题。让我们试一试吧。

class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Center(
child: MyWidget()
);
}
}

Flutter 布局相关知识点_flutter_02

瞧!这就对了。它已经修好了!

​Center​​​ 小部件从 ​​MyApp()​​​ 中获取一个严格的约束,并将其转换为对其子项的松散约束,即 ​​ConstrainedBox​​​。因此,​​Container​​​ 遵循 ​​ConstrainedBox​​​ 给出的约束,以便 ​​Container​​ 应用最小和最大大小。

在我们完成本节之前,让我快速解释一下什么是紧约束和松约束。

一个严格的约束提供了一种可能性——一个精确的尺寸,这意味着它的最大宽度等于它的最小宽度,它的最大高度等于它的最小高度。

如果你转到 Flutter 的 ​​box.dart​​​ 文件并搜索 ​​BoxConstraints​​ 构造函数,你会发现以下内容:

BoxConstraints.tight(Size size)
: minWidth = size.width,
maxWidth = size.width,
minHeight = size.height,
maxHeight = size.height;

另一方面,松散约束设置最大宽度和高度,但允许小部件尽可能小。它的最小宽度和高度都等于 ​​0​​:

BoxConstraints.loose(Size size)
: minWidth = 0.0,
maxWidth = size.width,
minHeight = 0.0,
maxHeight = size.height;

如果你再看上面的例子,它告诉我们 ​​Center​​​ 允许绿色 ​​Container​​​比屏幕更小,但不能更大。当然,​​Center​​​通过将松散的约束传递给 ​​Container​​来做到这一点。

【版权声明】本文内容来自摩杜云社区用户原创、第三方投稿、转载,内容版权归原作者所有。本网站的目的在于传递更多信息,不拥有版权,亦不承担相应法律责任。如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱: cloudbbs@moduyun.com

  1. 分享:
最后一次编辑于 2023年11月08日 0

暂无评论

推荐阅读
  b1UHV4WKBb2S   2023年11月13日   39   0   0 ide抗锯齿
  iD7FikcuyaVi   2023年11月30日   23   0   0 MacWindowsandroid
  b1UHV4WKBb2S   2023年11月13日   33   0   0 裁剪ideflutter
  b1UHV4WKBb2S   2023年11月13日   26   0   0 flutterDart
  zSWNgACtCQuP   2023年11月13日   29   0   0 ide
xx2YH4ad7R0N