为前端工程师准备的 Flutter 入门指南

如果你恰好是一名前端工程师,且对 Flutter 抱有兴趣,那么真的是太好了,这篇文章完全就是为你准备的。写惯了 HTML、CSS 与 JavaScript,要不要来是试试 Dart?如果你不熟悉 Flutter 但仍对其感兴趣,可以先看看「让我们在2019年重新认识 Flutter」一文了解些 Flutter 基础。

在接下来的章节中,我们仔细来对比下平时用 HTML/CSS 代码所实现的效果,如果替换为等价的 Flutter/Dart 代码,会是什么样子。

本文结构如下:

  1. 基础布局
  2. 位置与大小
  3. 图形/形状
  4. 文本

注:本文只摘录 Web 到 Flutter 中一些特性的转换介绍,详细及完整的使用方法与语法请查阅 Flutter/Dart 官网 https://flutter.io, https://flutter.cnhttps://www.dartlang.org.

本文示例中默认已包含如下假设:

  • HTML 文件均会以 <!DOCTYPE html> 开头,且为了与 Flutter 模型保持一致,所有 HTML 元素的 CSS 盒模型被设置为 border-box
    {
      box-sizing: border-box;
    }
    
  • 在 Flutter 中,为了保持语法简洁,示例中所用的 “Lorem ipsum” 文本的默认样式由如下 bold24Roboto 变量定义:
    TextStyle bold24Roboto = TextStyle(
      color: Colors.white,
      fontSize: 24.0,
      fontWeight: FontWeight.w900,
    );
    

Flutter UI 采用声明式编程,欲了解其与传统命令式风格的不同,请查阅声明式 UI 介绍

一、基础布局

先来看看最常见的一些 UI 布局操作。

1.1 文本样式与对齐

我们在 CSS 中设置的字体样式、大小以及其他文本属性,都是 Flutter 中一个 Text widget 子元素 TextStyle 中单独的属性。

不论是 HTML 还是 Flutter,子元素或者 widget 都默认锚定在左上方。

  • Web
<div class="greybox">
    Lorem ipsum
</div>

.greybox {
  background-color: #e0e0e0; /* grey 300 */
  width: 320px;
  height: 240px;
  font: 900 24px Georgia;
}
  • Dart
var container = Container( // grey box
  child: Text(
    "Lorem ipsum",
    style: TextStyle(
      fontSize: 24.0,
      fontWeight: FontWeight.w900,
      fontFamily: "Georgia",
    ),
  ),
  width: 320.0,
  height: 240.0,
  color: Colors.grey[300],
);

1.2 背景颜色

在 Flutter 中,你可以通过 Containerdecoration 属性来设置背景颜色。

CSS 示例中我们使用等价的十六进制颜色表示。

  • Web
<div class="greybox">
  Lorem ipsum
</div>

.greybox {
  background-color: #e0e0e0;  /* grey 300 */
  width: 320px;
  height: 240px;
  font: 900 24px Roboto;
}
  • Dart
var container = Container( // grey box
    child: Text(
      "Lorem ipsum",
      style: bold24Roboto,
    ),
    width: 320.0,
    height: 240.0,
    color: Colors.grey[300],
  );

1.3 居中元素

在 Flutter 中,Center widget 可以将它的子元素水平和垂直居中。

要用 CSS 实现相似的效果,其父元素则需要使用一个 flex 或者 table-cell 显示布局。本节示例使用的是 flex 布局。

  • Web
<div class="greybox">
  Lorem ipsum
</div>

.greybox {
  background-color: #e0e0e0; /* grey 300 */
  width: 320px;
  height: 240px;
  font: 900 24px Roboto;
  display: flex;
  align-items: center;
  justify-content: center; 
}
  • Dart
var container = Container( // grey box
  child:  Center(
    child:  Text(
      "Lorem ipsum",
      style: bold24Roboto,
    ),
  ),
  width: 320.0,
  height: 240.0,
  color: Colors.grey[300],
);

1.4 设置容器宽度

Container widget 的宽度可以用它的 width 属性指定,但需要注意的是,和 CSS 中的 max-width 属性用于指定容器可调整的最大宽度值不同的是,这里指定的是一个固定宽度。要在 Flutter 中模拟 max-width 的效果,可以使用 Container 的 constraints 属性。新建一个带有 minWidthmaxWidth 属性的 BoxConstraints widget。 而对嵌套的 Container 来说,如果其父元素宽度小于子元素宽度,则子元素实际尺寸以父元素大小为准。

  • Web
<div class="greybox">
  <div class="redbox">
    Lorem ipsum
  </div>
</div>

.greybox {
  background-color: #e0e0e0; /* grey 300 */
  width: 320px; 
  height: 240px;
  font: 900 24px Roboto;
  display: flex;
  align-items: center;
  justify-content: center;
}
.redbox {
  background-color: #ef5350; /* red 400 */
  padding: 16px;
  color: #ffffff;
  width: 100%;
  max-width: 240px; 
}
  • Dart
var container = Container( // grey box
  child: Center(
    child: Container( // red box
      child: Text(
        "Lorem ipsum",
        style: bold24Roboto,
      ),
      decoration: BoxDecoration(
        color: Colors.red[400],
      ),
      padding: EdgeInsets.all(16.0),
      width: 240.0, //max-width is 240.0
    ),
  ),
  width: 320.0, 
  height: 240.0,
  color: Colors.grey[300],
);

二、位置与大小

以下示例将展示如何对 widget 的位置、大小以及背景进行更复杂的操作。

2.1 绝对定位

默认情况下, widget 是相对于其父元素定位的。要通过 x-y 坐标指定一个 widget 的绝对位置,请把它嵌套在一个 Positioned widget 中,而该 widget 则需被嵌套在一个 Stack widget 中。

  • Web
<div class="greybox">
  <div class="redbox">
    Lorem ipsum
  </div>
</div>

.greybox {
  background-color: #e0e0e0; /* grey 300 */
  width: 320px;
  height: 240px;
  font: 900 24px Roboto;
  position: relative; 
}
.redbox {
  background-color: #ef5350; /* red 400 */
  padding: 16px;
  color: #ffffff;
  position: absolute;
  top: 24px;
  left: 24px; 
}
  • Dart
var container = Container( // grey box
  child: Stack(
    children: [
      Positioned( // red box
        child:  Container(
          child: Text(
            "Lorem ipsum",
            style: bold24Roboto,
          ),
          decoration: BoxDecoration(
            color: Colors.red[400],
          ),
          padding: EdgeInsets.all(16.0),
        ),
        left: 24.0,
        top: 24.0,
      ),
    ],
  ), 
  width: 320.0,
  height: 240.0,
  color: Colors.grey[300],
);

2.2 旋转

要旋转一个 widget,请将它嵌套在 Transform widget 中。其中,使用 Transform widget 的 alignmentorigin 属性分别来指定转换原点的具体位置信息。

对于简单的 2D 旋转,widget 是依据弧度在 Z 轴上旋转的。(角度 × π / 180)

  • Web
<div class="greybox">
  <div class="redbox">
    Lorem ipsum
  </div>
</div>

.greybox {
  background-color: #e0e0e0; /* grey 300 */
  width: 320px;
  height: 240px;
  font: 900 24px Roboto;
  display: flex;
  align-items: center;
  justify-content: center;
}
.redbox {
  background-color: #ef5350; /* red 400 */
  padding: 16px;
  color: #ffffff;
  transform: rotate(15deg); 
}
  • Dart
var container = Container( // gray box
  child: Center(
    child:  Transform(
      child:  Container( // red box
        child: Text(
          "Lorem ipsum",
          style: bold24Roboto,
          textAlign: TextAlign.center,
        ),
        decoration: BoxDecoration(
          color: Colors.red[400],
        ),
        padding: EdgeInsets.all(16.0),
      ),
      alignment: Alignment.center,
      transform: Matrix4.identity()
        ..rotateZ(15 * 3.1415927 / 180),
    ), 
  ),
  width: 320.0,
  height: 240.0,
  color: Colors.grey[300],
);

2.3 缩放元素

将元素嵌套在一个 Transform widget 中,可以实现缩放。使用 Transform widget 的 alignmentorigin 属性分别来指定缩放原点的具体位置信息。

对于沿 x 轴的简单缩放操作,新建一个 Matrix4 标识对象并用它的 scale() 方法来指定缩放因系数。

当你缩放一个父 widget 时,它的子 widget 也会相应被缩放。

  • Web
<div class="greybox">
  <div class="redbox">
    Lorem ipsum
  </div>
</div>

.greybox {
  background-color: #e0e0e0; /* grey 300 */
  width: 320px;
  height: 240px;
  font: 900 24px Roboto;
  display: flex;
  align-items: center;
  justify-content: center;
}
.redbox {
  background-color: #ef5350; /* red 400 */
  padding: 16px;
  color: #ffffff;
  transform: scale(1.5); 
}
  • Dart
var container = Container( // gray box
  child: Center(
    child:  Transform(
      child:  Container( // red box
        child: Text(
          "Lorem ipsum",
          style: bold24Roboto,
          textAlign: TextAlign.center,
        ),
        decoration: BoxDecoration(
          color: Colors.red[400],
        ),
        padding: EdgeInsets.all(16.0),
      ),
      alignment: Alignment.center,
      transform: Matrix4.identity()
        ..scale(1.5),
     ), 
  width: 320.0,
  height: 240.0,
  color: Colors.grey[300],
);

2.4 线性变换

将元素嵌套在一个 Container widget 中,可以将线性变换应用在 widget 的背景上。之后,再用 Container widget 的 decoration 属性生成一个 BoxDecoration 对象,然后使用 BoxDecoration 的 gradient 属性来变换背景填充内容。

变换“角度”基于 Alignment (x, y) 取值来定:

  • 如果开始和结束的 x 值相同,变换将是垂直的(0° 180°)。
  • 如果开始和结束的 y 值相同,变换将是水平的(90° 270°)。

这里,只展示垂直变换的代码差异:

  • Web
<div class="greybox">
  <div class="redbox">
    Lorem ipsum
  </div>
</div>

.greybox {
  background-color: #e0e0e0; /* grey 300 */
  width: 320px;
  height: 240px;
  font: 900 24px Roboto;
  display: flex;
  align-items: center;
  justify-content: center;
}
.redbox {
  padding: 16px;
  color: #ffffff;
  background: linear-gradient(180deg, #ef5350, rgba(0, 0, 0, 0) 80%); 
}
  • Dart
var container = Container( // grey box
  child: Center(
    child: Container( // red box
      child: Text(
        "Lorem ipsum",
        style: bold24Roboto,
      ),
      decoration: BoxDecoration(
        gradient: LinearGradient(
          begin: const Alignment(0.0, -1.0),
          end: const Alignment(0.0, 0.6),
          colors: <Color>[
            const Color(0xffef5350),
            const Color(0x00ef5350)
          ],
        ),
      ), 
      padding: EdgeInsets.all(16.0),
    ),
  ),
  width: 320.0,
  height: 240.0,
  color: Colors.grey[300],
);

三、图形/形状

以下示例将展示如何新建和自定义图形。

3.1 圆角

在矩形上实现圆角,可以用 BoxDecoration 对象的 borderRadius 属性。新建一个 BorderRadius 对象来指定每个圆角的半径大小。

  • Web
<div class="greybox">
  <div class="redbox">
    Lorem ipsum
  </div>
</div>

.greybox {
  background-color: #e0e0e0; /* gray 300 */
  width: 320px;
  height: 240px;
  font: 900 24px Roboto;
  display: flex;
  align-items: center;
  justify-content: center;
}
.redbox {
  background-color: #ef5350; /* red 400 */
  padding: 16px;
  color: #ffffff;
  border-radius: 8px; 
}
  • Dart
var container = Container( // grey box
  child: Center(
    child: Container( // red circle
      child: Text(
        "Lorem ipsum",
        style: bold24Roboto,
      ),
      decoration: BoxDecoration(
        color: Colors.red[400],
        borderRadius: BorderRadius.all(
          const Radius.circular(8.0),
        ), 
      ),
      padding: EdgeInsets.all(16.0),
    ),
  ),
  width: 320.0,
  height: 240.0,
  color: Colors.grey[300],
);

3.2 阴影

在 CSS 中你可以通过 box-shadow 属性快速指定阴影偏移与模糊范围。比如如下两个盒阴影的属性设置:

  • xOffset: 0px, yOffset: 2px, blur: 4px, color: black @80% alpha
  • xOffset: 0px, yOffset: 06x, blur: 20px, color: black @50% alpha

在 Flutter 中,每个属性与其取值都是单独指定的。请使用 BoxDecoration 的 boxShadow 属性来生成一系列 BoxShadow widget。你可以定义一个或多个 BoxShadow widget,这些 widget 共同用于设置阴影深度、颜色等等。

  • Web
<div class="greybox">
  <div class="redbox">
    Lorem ipsum
  </div>
</div>

.greybox {
  background-color: #e0e0e0; /* grey 300 */
  width: 320px;
  height: 240px;
  font: 900 24px Roboto;
  display: flex;
  align-items: center;
  justify-content: center;
}
.redbox {
  background-color: #ef5350; /* red 400 */
  padding: 16px;
  color: #ffffff;
  box-shadow: 0 2px 4px rgba(0, 0, 0, 0.8),
              0 6px 20px rgba(0, 0, 0, 0.5);
}
  • Dart
var container = Container( // grey box
  child: Center(
    child: Container( // red box
      child: Text(
        "Lorem ipsum",
        style: bold24Roboto,
      ),
      decoration: BoxDecoration(
        color: Colors.red[400],
        boxShadow: <BoxShadow>[
          BoxShadow (
            color: const Color(0xcc000000),
            offset: Offset(0.0, 2.0),
            blurRadius: 4.0,
          ),
          BoxShadow (
            color: const Color(0x80000000),
            offset: Offset(0.0, 6.0),
            blurRadius: 20.0,
          ),
        ], 
      ),
      padding: EdgeInsets.all(16.0),
    ),
  ),
  width: 320.0,
  height: 240.0,
  decoration: BoxDecoration(
    color: Colors.grey[300],
  ),
  margin: EdgeInsets.only(bottom: 16.0),
);

3.3 圆与椭圆

尽管 CSS 中有基础图形,CSS 中一个生成圆的变通方案是:将矩形的四边 border-radius 均设成50%。

虽然 BoxDecorationborderRadius 属性支持这样设置,Flutter 为 BoxShape enum 提供一个 shape 属性也用于实现同样的目的。

  • Web
<div class="greybox">
  <div class="redcircle">
    Lorem ipsum
  </div>
</div>

.greybox {
  background-color: #e0e0e0; /* gray 300 */
  width: 320px;
  height: 240px;
  font: 900 24px Roboto;
  display: flex;
  align-items: center;
  justify-content: center;
}
.redcircle {
  background-color: #ef5350; /* red 400 */
  padding: 16px;
  color: #ffffff;
  text-align: center;
  width: 160px;
  height: 160px;
  border-radius: 50%; 
}
  • Dart
var container = Container( // grey box
  child: Center(
    child: Container( // red circle
      child: Text(
        "Lorem ipsum",
        style: bold24Roboto,
        textAlign: TextAlign.center, 
      ),
      decoration: BoxDecoration(
        color: Colors.red[400],
        shape: BoxShape.circle, 
      ),
      padding: EdgeInsets.all(16.0),
      width: 160.0,
      height: 160.0, 
    ),
  ),
  width: 320.0,
  height: 240.0,
  color: Colors.grey[300],
);

四、文本

以下示例展示了如何设置字体和其他文本属性,除此外还包括一些特性比如如何变换文本字符、自定义间距以及生成摘录。

4.1 文字间距

在 CSS 中你可以通过分别给 letter-spacing 和 word-spacing 属性的长度赋值来指定每个字母以及每个单词间的空白距离。距离的单位可以是 px, pt, cm, em 等等。

在 Flutter 中,你可以在 Text widget 子元素 TextStyleletterSpacingwordSpacing 属性中将间距设置为逻辑像素(允许负值)。

  • Web
<div class="greybox">
  <div class="redbox">
    Lorem ipsum
  </div>
</div>

.greybox {
  background-color: #e0e0e0; /* grey 300 */
  width: 320px;
  height: 240px;
  font: 900 24px Roboto;
  display: flex;
  align-items: center;
  justify-content: center;
}
.redbox {
  background-color: #ef5350; /* red 400 */
  padding: 16px;
  color: #ffffff;
  letter-spacing: 4px; 
}
  • Dart
var container = Container( // grey box
  child: Center(
    child: Container( // red box
      child: Text(
        "Lorem ipsum",
        style: TextStyle(
          color: Colors.white,
          fontSize: 24.0,
          fontWeight: FontWeight.w900,
          letterSpacing: 4.0, 
        ),
      ),
      decoration: BoxDecoration(
        color: Colors.red[400],
      ),
      padding: EdgeInsets.all(16.0),
    ),
  ),
  width: 320.0,
  height: 240.0,
  color: Colors.grey[300],
);

4.2 内联样式

一个 Text widget 可以展示同一类样式的文本。为了展现具有多种样式的文本,需要改用 RichText widget。它的 text 属性可以指定一个或多个可以单独设置样式的 TextSpan widget。

在下例中,”Lorem” 位于 TextSpan widget 中,具有默认(继承自其父元素)文本样式,”ipsum” 位于具有自定义样式、单独的一个 TextSpan 中。

  • Web
<div class="greybox">
  <div class="redbox">
    Lorem <em>ipsum</em> 
  </div>
</div>

.greybox {
  background-color: #e0e0e0; /* grey 300 */
  width: 320px;
  height: 240px;
  font: 900 24px Roboto; 
  display: flex;
  align-items: center;
  justify-content: center;
}
.redbox {
  background-color: #ef5350; /* red 400 */
  padding: 16px;
  color: #ffffff;
}
 .redbox em {
  font: 300 48px Roboto;
  font-style: italic;
} 
  • Dart
var container = Container( // grey box
  child: Center(
    child: Container( // red box
      child:  RichText(
        text: TextSpan(
          style: bold24Roboto,
          children: <TextSpan>[
            TextSpan(text: "Lorem "),
            TextSpan(
              text: "ipsum",
              style: TextStyle(
                fontWeight: FontWeight.w300,
                fontStyle: FontStyle.italic,
                fontSize: 48.0,
              ),
            ),
          ],
        ),
      ), 
      decoration: BoxDecoration(
        backgroundColor: Colors.red[400],
      ),
      padding: EdgeInsets.all(16.0),
    ),
  ),
  width: 320.0,
  height: 240.0,
  color: Colors.grey[300],
);

4.3 文本摘要

在 Web 中,我们常用省略号处理溢出的文本内容,且在 HTML/CSS 中,摘要不能超过一行。 如果要在多行之后进行截断,那么就需要 JavaScript 的帮助了。

在 Flutter 中,使用 Text widget 的 maxLines 属性来指定包含在摘要中的行数,以及 overflow 属性来处理溢出文本。

  • Web
<div class="greybox">
  <div class="redbox">
    Lorem ipsum dolor sit amet, consec etur
  </div>
</div>

.greybox {
  background-color: #e0e0e0; /* grey 300 */
  width: 320px;
  height: 240px;
  font: 900 24px Roboto;
  display: flex;
  align-items: center;
  justify-content: center;
}
.redbox {
  background-color: #ef5350; /* red 400 */
  padding: 16px;
  color: #ffffff;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap; 
}
  • Dart
var container = Container( // grey box
  child: Center(
    child: Container( // red box
      child: Text(
        "Lorem ipsum dolor sit amet, consec etur",
        style: bold24Roboto,
        overflow: TextOverflow.ellipsis,
        maxLines: 1, 
      ),
      decoration: BoxDecoration(
        backgroundColor: Colors.red[400],
      ),
      padding: EdgeInsets.all(16.0),
    ),
  ),
  width: 320.0,
  height: 240.0,
  color: Colors.grey[300],
);

Teaser 截图自 flutter.io 官网。

Leave a Comment