如何在Flutter中实现任何UI

7,635 阅读7分钟

在这篇文章中,您将学习如何将任何用户界面图像、作品或屏幕转换为Flutter代码。

这不是一个关于建立一个应用程序的教程。相反,它是一个指南,将帮助您将遇到的任何用户界面实现到您已经拥有的应用程序中。本教程还解释了Flutter中各种各样的UI概念。

什么是Flutter?

Flutter是谷歌的一个开源框架,用于从一个代码库中构建漂亮的、本地编译的、多平台的应用程序。- (来源:Flutter.dev)

在Flutter中,与大多数框架相反,Dart是你用来编码的唯一编程语言。这是Flutter的一个未被充分强调的好处。特别是对于一个可以构建桌面、移动和网络应用的工具。

大多数UI平台使用不止一种语言。例如,在前端Web开发中,你必须写HTMLCSSJavaScript。对于Android,你必须写Kotlin(或Java)和XML。但在Flutter中,只有一种语言。Dart。

再加上只有一种编程语言的好处,Flutter很简单,因为Flutter中的所有东西都是一个widget。比如说 [AnimatedWidget](https://api.flutter.dev/flutter/widgets/AnimatedWidget-class.html), [BottomNavigationBar](https://api.flutter.dev/flutter/material/BottomNavigationBar-class.html), [Container](https://api.flutter.dev/flutter/widgets/Container-class.html), [Drawer](https://api.flutter.dev/flutter/material/Drawer-class.html), [ElevatedButton](https://api.flutter.dev/flutter/material/ElevatedButton-class.html), [FormField](https://api.flutter.dev/flutter/widgets/FormField-class.html), [Image](https://api.flutter.dev/flutter/widgets/Image-class.html), [Opacity](https://api.flutter.dev/flutter/widgets/Opacity-class.html), [Padding](https://api.flutter.dev/flutter/widgets/Padding-class.html), ...

这也是Flutter易于使用的部分原因--它基本上是纯英语。小工具的名字反映了它们是什么,它们的属性很容易理解。

Flutter中的小部件

widget是一个Dart,它扩展了StatefulWidgetStatelessWidget

您的本地Flutter安装中带有几个widget。要查看默认情况下可用的部件,请在您喜欢的编辑器中打开您的Flutter安装包的文件夹。然后在所有文件中搜索 "extends StatefulWidget "和 "extends StatelessWidget",并记下结果的数量。

flutter-packages-widget-count

Flutter 2.10开始,你将得到408个有状态的部件和272个无状态的部件。那就是总共有680个widget可供你使用和实现UI。

这些部件通常拥有你所需要的一切。pub.dev,Dart和Flutter的包管理器,有更多的widget,你可以用来实现UI。

要数清pub.dev中的部件是很困难的。但是搜索一个空字符串(不要在搜索栏中输入任何东西,然后按搜索图标),并将SDK设置为Flutter,就会返回当前发布的包的总数。

pub.dev-widget-count

在写这篇文章的时候,pub.dev里有超过23000个Flutter包。每个包都至少有一个widget。这意味着,除了可用的680个,你还有超过23000个来自pub.dev的widget可以实现。这意味着你真的可以在Flutter中轻松实现你想要的任何UI。

除了许多可用的部件外,您还可以在实现UI时创建自己的部件。

小部件树

以下是您在创建一个新的Flutter项目并删除注释后得到的部分代码。

 @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text(widget.title),
      ),
      body: Center(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: <Widget>[
            const Text(
              'You have pushed the button this many times:',
            ),
            Text(
              '$_counter',
              style: Theme.of(context).textTheme.headline4,
            ),
          ],
        ),
      ),
      floatingActionButton: FloatingActionButton(
        onPressed: _incrementCounter,
        tooltip: 'Increment',
        child: const Icon(Icons.add),
      ),
    );
  }

新Flutter项目的部分占位符代码

父级Scaffold ,采取appBar,body, 和floatingActionButton 参数。反过来,该 [AppBar](https://api.flutter.dev/flutter/material/AppBar-class.html)也接受一个title 参数,该参数有一个Text 的值。

body 取一个 ,该值有一个 。 又有两个 s作为 。该 采取 回调,'增量' ,和一个 ,以获得一个 。Center Column child Column Text children FloatingActionButton onPressed tooltip Icon child

Screenshot--142--2

Flutter部件树分解

这是一个简单的widget树。它有父辈和子辈。childchildren 是大多数Flutter部件的共同属性。随着widget不断吸收更多的widget子代,你的应用程序逐渐成长为一个大的widget树。

当您在Flutter中实现UI时,请记住,您正在构建一个widget树。你会注意到,你的代码从左边缘向内缩进。它似乎在左边形成了某种虚拟的大于号(空的空间)。

**注意:**巨大的缩进程度是你需要重构你的代码的一个标志。这意味着您需要将一些小部件的层次结构提取到一个单独的小部件中。

如何在Flutter中实现任何UI

1.从左上角开始写你的代码,然后向下移动到右下角

您将根据每个元素在UI中的位置来实现一个又一个的UI widget。因此,您将首先为出现在UI顶部的东西编写代码。然后你继续为在页面上向下移动的其他项目写代码,直到你到达该UI的底部。

Screenshot--143-

这是很直观的。

在横轴上,从左到右。如果有必要,或者如果是一个从右到左的用户界面,那么就从右到左来实现它。

2.选择一个小部件

接下来,你需要从逻辑上确定你要为一个给定的UI元素使用的小部件。最低限度,对于一个给定的UI元素,你将使用你所熟悉的简单的小部件,根据它们的名字来决定它们的作用。

有可能UI组件的名称就是小部件的名称。如果你觉得很难做出选择,快速的在线搜索会给你答案。Flutter有一个很好的在线社区。

3.使用widget组

如果一组UI项目是垂直排列的,一个接一个,则使用一个 [Column](https://api.flutter.dev/flutter/widgets/Column-class.html).如果它们是水平排列的,一个接一个,使用a [Row](https://api.flutter.dev/flutter/widgets/Row-class.html).如果它们被放置在彼此的顶部,使用a [Stack](https://api.flutter.dev/flutter/widgets/Stack-class.html),浮动的widgets被包裹在 [Positioned](https://api.flutter.dev/flutter/widgets/Positioned-class.html)小部件。

a.列/行

ColumnRow ,你可以改变或调整小部件如何在主轴或横轴上对齐。使用它们的 [CrossAxisAlignment](https://api.flutter.dev/flutter/rendering/CrossAxisAlignment.html)[MainAxisAlignment](https://api.flutter.dev/flutter/rendering/MainAxisAlignment.html)属性来进行这种调整。

对于横轴,你可以对准中心,结束,开始,和伸展。对于主轴,你可以对准中心、末端、周围的空间、中间的空间、均匀的空间和末端。

Column ,垂直轴是主轴,而水平轴是交叉轴。在Row ,水平轴是主轴,而垂直轴是交叉轴。

1Untitled-1-1

改编自arzerin.com/2019/11/20/…

Untitled-1

改编自arzerin.com/2019/11/20/…

ColumnRow中,如果你想让一个特定的子部件尽可能多地占用可用空间,可以将该部件包裹在一个 [Expanded](https://api.flutter.dev/flutter/widgets/Expanded-class.html)小组件内。如果你熟悉web前端,你会注意到Columns和Rows就像 [display: flex;](https://developer.mozilla.org/en-US/docs/Web/CSS/flex)在CSS中。

b.堆栈小部件

通过Stackchildren's list中的最后一个widget(s)出现在前面的子项之上。

你可能需要编辑Stack的 [alignment](https://api.flutter.dev/flutter/widgets/Stack/alignment.html)来表示小部件的相对位置。比如 [topCenter](https://api.flutter.dev/flutter/painting/AlignmentDirectional/topCenter-constant.html), [center](https://api.flutter.dev/flutter/painting/AlignmentDirectional/center-constant.html), [bottomEnd](https://api.flutter.dev/flutter/painting/AlignmentDirectional/bottomEnd-constant.html),等等。

Stack'的大小是基于非定位的小部件计算的(子列表中的小部件没有被包裹在一个 [Positioned](https://api.flutter.dev/flutter/widgets/Positioned-class.html)父级)。在编码时,记住你的Stack 应该至少有一个非定位的widget,或者它应该被包裹在一个明确设置了Stack'size的父widget中。

Positioned 采取任何或全部的 [bottom](https://api.flutter.dev/flutter/widgets/Positioned/bottom.html), [top](https://api.flutter.dev/flutter/widgets/Positioned/top.html), [left](https://api.flutter.dev/flutter/widgets/Positioned/left.html), [right](https://api.flutter.dev/flutter/widgets/Positioned/right.html).它们设定子代相对于Stack 的位置。负值使子代向相反方向移动。然而,负值会把孩子的一部分剪掉。使用 [clipBehavior: Clip.none](https://api.flutter.dev/flutter/widgets/Stack/clipBehavior.html)Stack ,以显示定位小组件的所有部分。

Screenshot--148-

完整的代码在这里

4.创建自定义小部件

当你建立小部件树时,你会注意到两件事。

  1. 要么树上的一个块增长得太大了,它是一个独立的逻辑单元。
  2. 或者一些小工具的块或集可能会在轻微的变化下重复。

这两个迹象表明你应该重构你的代码。这意味着你应该把这些部件提取出来,在另一个Dart文件中定义它们。

你的代码编辑器会帮助你进行重构。不管有没有编辑器,你所需要做的就是。

  1. 创建一个新的Dart文件。文件名应该反映新的部件的名称。
  2. 创建一个扩展StatefulWidget或StatelessWidget的新类,这取决于新widget是否有状态
  3. 然后从一个方法中返回widget chunk [build](https://api.flutter.dev/flutter/widgets/StatelessWidget/build.html)方法返回widget chunk。
  4. (可选)如果需要的话,你的新Dart类可以在其构造函数中加入位置参数或命名参数,以自定义widget的外观。
// in counter_display.dart
import 'package:flutter/material.dart';

class CounterDisplay extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Column(
  	  mainAxisAlignment: MainAxisAlignment.center,
      children: [
        Text('You have pushed the button this many times:'),
        Text('$counter', style: TextStyle(fontSize: 24)),
      ],
    );
  }
}

// in main.dart
//
// ... 
  body: Center(child: CounterDisplay()),
// ...

你会建立许多自定义部件,它们又会成为更多自定义部件的后代,这很好。小组件树是为了在需要时持续增长。

5.增加更多的自定义功能

你不会仅仅因为重构和重复(DRY代码)而定制widget。你会因为你正在实现的用户界面而创建自定义widget。

你将创建自定义widget,因为许多可用的widget并不总是能满足特定UI的确切需求。你需要以某种特殊的方式组合它们来实现一个特定的用户界面。

a.容器小部件

[Container](https://api.flutter.dev/flutter/widgets/Container-class.html)是一个强大的小组件。你可以用不同的方式设计它。如果你习惯于Web前端,你会发现它就像一个 [div](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/div)在HTML中。

Container 是一个基础部件。你可以用它来创建任何UI作品。

一些Container 参数是 [constraints](https://api.flutter.dev/flutter/widgets/Container/constraints.html), [decoration](https://api.flutter.dev/flutter/widgets/Container/decoration.html), [margin](https://api.flutter.dev/flutter/widgets/Container/margin.html), [padding](https://api.flutter.dev/flutter/widgets/Container/padding.html), [transform](https://api.flutter.dev/flutter/widgets/Container/transform.html),以及其他一些参数。当然,Container 需要一个 [child](https://api.flutter.dev/flutter/widgets/Container/child.html)它可以是任何 widget。

decoration 属性可以接受一个 [BoxDecoration](https://api.flutter.dev/flutter/painting/BoxDecoration-class.html),而后者又可以接受其他几个属性。这就是Container 的灵活性的核心。BoxDecoration 需要的参数包括 [border](https://api.flutter.dev/flutter/painting/BoxDecoration/border.html), [borderRadius](https://api.flutter.dev/flutter/painting/BoxDecoration/borderRadius.html), [boxShadow](https://api.flutter.dev/flutter/painting/BoxDecoration/boxShadow.html), [color](https://api.flutter.dev/flutter/painting/BoxDecoration/color.html), [gradient](https://api.flutter.dev/flutter/painting/BoxDecoration/gradient.html), [image](https://api.flutter.dev/flutter/painting/BoxDecoration/image.html), [shape](https://api.flutter.dev/flutter/painting/BoxDecoration/shape.html),等等。

通过这些参数和它们的值,你可以按照你的口味实现任何UI。您可以使用Container ,而不是Flutter自带的许多材料部件。这样你的应用程序就能符合你的口味。

b.GestureDetector / InkWell

[GestureDetector](https://api.flutter.dev/flutter/widgets/GestureDetector-class.html)顾名思义就是检测用户的互动。不是每一个用户界面都是一个按钮。而在实现UI时,你将需要一些小部件来对用户的行为做出反应。在这种情况下,使用GestureDetector

GestureDetector 可以检测不同类型的手势:轻拍、双击、轻扫...... 当然需要一个GestureDetector [child](https://api.flutter.dev/flutter/widgets/GestureDetector/child.html)(可以是任何widget),以及针对不同手势的不同回调,如 [onTap](https://api.flutter.dev/flutter/widgets/GestureDetector/onTap.html), [onDoubleTap](https://api.flutter.dev/flutter/widgets/GestureDetector/onDoubleTap.html), [onPanUpdate](https://api.flutter.dev/flutter/widgets/GestureDetector/onDoubleTap.html)(用于滑动), ...

注意: child 默认情况下,当用户与GestureDetectors的空位互动时,回调不会被调用。如果你想让你的GestureDetector 对空位上的手势做出反应(在它的child ),那么就把 [behavior](https://api.flutter.dev/flutter/widgets/GestureDetector/behavior.html)``GestureDetector 的属性为 [HitTestBehavior.translucent](https://api.flutter.dev/flutter/rendering/HitTestBehavior.html).

GestureDetector(
  // set behavior to detect taps on empty spaces
  behavior: HitTestBehavior.translucent,
  child: Column(
    children: [
      Text('I have space after me ...'),
      SizedBox(height: 32),
      Text('... that can detect taps.'),
    ],
  ),
  onTap: () => print('Tapped on empty space.'),
)

GestureDetector的用法,表明HitTestBehavior.translucent行为。

[InkWell](https://api.flutter.dev/flutter/material/InkWell-class.html)它类似于GestureDetector 。它对一些 GestureDetector 响应的手势做出反应。然而,当与之互动时,它显示出波纹效应(GestureDetector's不会)。

QqEZ3

From stackoverflow.com/q/58285012/…

InkWell 必须有一个 [Material](https://api.flutter.dev/flutter/material/Material-class.html)祖先。所以,如果你最上面的部件是 [MaterialApp](https://api.flutter.dev/flutter/material/MaterialApp-class.html)你不需要担心。否则,把InkWell 包在一个Material

如果你要改变InkWell's parent orchild 的颜色,你也应该做这个包装。如果你不这样做,波纹就不会显示。你还必须设置 [color](https://api.flutter.dev/flutter/material/Material/color.html)``Material widget的颜色来显示波纹。你可以将color 设置为 [Colors.transparent](https://api.flutter.dev/flutter/material/Colors/transparent-constant.html)然后Flutter会处理剩下的事情。

如何实现滚动界面

滚动是一个有点微妙的话题。默认情况下,小组件在Flutter中不会滚动。如果您的ColumnRow 将会是可滚动的,请使用 [ListView](https://api.flutter.dev/flutter/widgets/ListView-class.html)代替。ListView ,也需要children 参数。

ListView 也有工厂构造函数,如 [ListView.builder](https://api.flutter.dev/flutter/widgets/ListView/ListView.builder.html)[ListView.separated](https://api.flutter.dev/flutter/widgets/ListView/ListView.separated.html).builder 可以让你更多地控制子节点的构建过程,而separated 则考虑到了分离器 (如 [Divider](https://api.flutter.dev/flutter/material/Divider-class.html)为例)。

默认情况下,ListView,垂直滚动其子代。然而,你可以改变 [scrollDirection](https://api.flutter.dev/flutter/widgets/ScrollView/scrollDirection.html)改为ListView[Axis.horizontal](https://api.flutter.dev/flutter/painting/Axis.html)来水平滚动其子代。

有时,你可能想使用 [SingleChildScrollView](https://api.flutter.dev/flutter/widgets/SingleChildScrollView-class.html)而不是ListView 。顾名思义,它只需要一个 [child](https://api.flutter.dev/flutter/widgets/SingleChildScrollView/child.html)并且它可以滚动。你可以传递widget组作为它的child

还有其他的滚动小工具

但要特别注意的是 [CustomScrollView](https://api.flutter.dev/flutter/widgets/CustomScrollView-class.html).它给了你巨大的滚动控制权,与其他的不同。它需要 [slivers](https://api.flutter.dev/flutter/widgets/CustomScrollView/slivers.html),这又是具有强大滚动机制的滚动小部件。

[SliverFillRemaining](https://api.flutter.dev/flutter/widgets/SliverFillRemaining-class.html), [SliverFillViewport](https://api.flutter.dev/flutter/widgets/SliverFillViewport-class.html), [SliverGrid](https://api.flutter.dev/flutter/widgets/SliverGrid-class.html), [SliverList](https://api.flutter.dev/flutter/widgets/SliverList-class.html), [SliverPersistentHeader](https://api.flutter.dev/flutter/widgets/SliverPersistentHeader-class.html)以及其他一些,都是你在 slivers列表中包含的小部件的例子 这些小部件大多数都有一个委托,用来处理如何发生滚动。

一个使用CustomScrollView 的好例子是 [SliverAppBar](https://api.flutter.dev/flutter/material/SliverAppBar-class.html),在那里你希望AppBar在默认情况下被展开,在滚动时被缩小。

ap

另一个例子是用一个 [DraggableScrollableSheet](https://api.flutter.dev/flutter/widgets/DraggableScrollableSheet-class.html)的情况下,你会把一些动作按钮贴在底部。

bs

关于CustomPaint

这是Flutter给UI世界带来终极灵活性的地方。

[CustomPaint](https://api.flutter.dev/flutter/widgets/CustomPaint-class.html)是Flutter中的*Canvas API*对HTML或SVG对图像的作用。

CustomPaint 是Flutter中的一个小部件,它让你能够不受限制地设计和绘画。它为您提供了一个画布,您可以在上面用 [painter](https://api.flutter.dev/flutter/widgets/CustomPaint/painter.html).

visualizer

来自blog.codemagic.io/flutter-cus…

你很少会用到CustomPaint。但要知道它的存在。因为可能会有非常复杂的UI,widget组合可能无法实现它们,你除了用CustomPaint

当那个时候到来的时候,对你来说并不困难,因为你已经熟悉了其他部件。

总结

对于一个给定的UI作品,选择一个widget,编写它的代码,用其他widget构建这个widget,看看你用Flutter实现了什么伟大的UI。

实现UI是移动、网络和桌面应用开发的一个主要部分。Flutter是一个UI工具包,为这些平台构建跨平台的UI。Flutter的声明性和其丰富的部件使UI实现变得简单。

继续在Flutter中实现UI。当您这样做时,它将成为您的第二天性。而您将能够在Flutter中实现任何UI。