给 Web 开发者的 Flutter 指南

本文是为那些熟悉用 HTML 与 CSS 语法来管理应用页面中元素的开发者准备的。本文会将 HTML/CSS 代码片段替换为等价的 Flutter/Dart 代码。

这些示例包含如下假设:

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

React-style 或 声明式 编程与传统的命令式风格有何不同?为了对比,请查阅 声明式 UI 介绍

执行基础布局操作

以下示例将向你展示如何执行最常见的 UI 布局操作。

文本样式与对齐

CSS 所处理的字体样式、大小以及其他文本属性,都是一个 Text widget 子元素 TextStyle 中单独的属性。

在 HTML 和 Flutter 中,子元素或者 widget 都默认锚定在左上方。

  1. <div class="greybox">
  2. Lorem ipsum
  3. </div>
  4.  
  5. .greybox {
  6. background-color: #e0e0e0; /* grey 300 */
  7. width: 320px;
  8. height: 240px;
  9. font: 900 24px Georgia;
  10. }
  1. var container = Container( // grey box
  2. child: Text(
  3. "Lorem ipsum",
  4. style: TextStyle(
  5. fontSize: 24,
  6. fontWeight: FontWeight.w900,
  7. fontFamily: "Georgia",
  8. ),
  9. ),
  10. width: 320,
  11. height: 240,
  12. color: Colors.grey[300],
  13. );

设置背景颜色

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

CSS 示例使用十六进制颜色,这等价于材质调色板。

  1. <div class="greybox">
  2. Lorem ipsum
  3. </div>
  4.  
  5. .greybox {
  6. background-color: #e0e0e0; /* grey 300 */
  7. width: 320px;
  8. height: 240px;
  9. font: 900 24px Roboto;
  10. }
  1. var container = Container( // grey box
  2. child: Text(
  3. "Lorem ipsum",
  4. style: bold24Roboto,
  5. ),
  6. width: 320,
  7. height: 240,
  8. decoration: BoxDecoration(
  9. color: Colors.grey[300],
  10. ),
  11. );

居中元素

一个 Center widget 可以将它的子元素水平和垂直居中。

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

  1. <div class="greybox">
  2. Lorem ipsum
  3. </div>
  4.  
  5. .greybox {
  6. background-color: #e0e0e0; /* grey 300 */
  7. width: 320px;
  8. height: 240px;
  9. font: 900 24px Roboto;
  10. display: flex;
  11. align-items: center;
  12. justify-content: center;
  13. }
  1. var container = Container( // grey box
  2. child: Center(
  3. child: Text(
  4. "Lorem ipsum",
  5. style: bold24Roboto,
  6. ),
  7. ),
  8. width: 320,
  9. height: 240,
  10. color: Colors.grey[300],
  11. );

设置容器宽度

要指定一个 Containerwidget 的宽度,请使用它的 width 属性。和 CSS 中的 max-width 属性用于指定容器可调整的宽度最大值不同的是,这里指定的是一个固定宽度。要在 Flutter 中模拟该效果,可以使用 Container 的 constraints 属性。新建一个带有 minWidthmaxWidth 属性的BoxConstraints widget。

对嵌套的 Container 来说,如果其父元素宽度小于子元素宽度,则子元素会调整尺寸以匹配父元素大小。

  1. <div class="greybox">
  2. <div class="redbox">
  3. Lorem ipsum
  4. </div>
  5. </div>
  6.  
  7. .greybox {
  8. background-color: #e0e0e0; /* grey 300 */
  9. width: 320px;
  10. height: 240px;
  11. font: 900 24px Roboto;
  12. display: flex;
  13. align-items: center;
  14. justify-content: center;
  15. }
  16. .redbox {
  17. background-color: #ef5350; /* red 400 */
  18. padding: 16px;
  19. color: #ffffff;
  20. width: 100%;
  21. max-width: 240px;
  22. }
  1. var container = Container( // grey box
  2. child: Center(
  3. child: Container( // red box
  4. child: Text(
  5. "Lorem ipsum",
  6. style: bold24Roboto,
  7. ),
  8. decoration: BoxDecoration(
  9. color: Colors.red[400],
  10. ),
  11. padding: EdgeInsets.all(16),
  12. width: 240, //max-width is 240
  13. ),
  14. ),
  15. width: 320,
  16. height: 240,
  17. color: Colors.grey[300],
  18. );

操控位置与大小

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

设置绝对位置

默认情况下, widget 相对于其父元素定位。

要通过 x-y 坐标指定一个 widget 的绝对位置,把它嵌套在一个 Positionedwidget 中,而该 widget 则需被嵌套在一个Stack widget 中。

  1. <div class="greybox">
  2. <div class="redbox">
  3. Lorem ipsum
  4. </div>
  5. </div>
  6.  
  7. .greybox {
  8. background-color: #e0e0e0; /* grey 300 */
  9. width: 320px;
  10. height: 240px;
  11. font: 900 24px Roboto;
  12. position: relative;
  13. }
  14. .redbox {
  15. background-color: #ef5350; /* red 400 */
  16. padding: 16px;
  17. color: #ffffff;
  18. position: absolute;
  19. top: 24px;
  20. left: 24px;
  21. }
  1. var container = Container( // grey box
  2. child: Stack(
  3. children: [
  4. Positioned( // red box
  5. child: Container(
  6. child: Text(
  7. "Lorem ipsum",
  8. style: bold24Roboto,
  9. ),
  10. decoration: BoxDecoration(
  11. color: Colors.red[400],
  12. ),
  13. padding: EdgeInsets.all(16),
  14. ),
  15. left: 24,
  16. top: 24,
  17. ),
  18. ],
  19. ),
  20. width: 320,
  21. height: 240,
  22. color: Colors.grey[300],
  23. );

旋转元素

要旋转一个 widget,请将它嵌套在Transformwidget 中。使用 Transform widget 的 alignmentorigin属性分别来指定转换原点(支点)的相对和绝对位置信息。

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

  1. <div class="greybox">
  2. <div class="redbox">
  3. Lorem ipsum
  4. </div>
  5. </div>
  6.  
  7. .greybox {
  8. background-color: #e0e0e0; /* grey 300 */
  9. width: 320px;
  10. height: 240px;
  11. font: 900 24px Roboto;
  12. display: flex;
  13. align-items: center;
  14. justify-content: center;
  15. }
  16. .redbox {
  17. background-color: #ef5350; /* red 400 */
  18. padding: 16px;
  19. color: #ffffff;
  20. transform: rotate(15deg);
  21. }
  1. var container = Container( // gray box
  2. child: Center(
  3. child: Transform(
  4. child: Container( // red box
  5. child: Text(
  6. "Lorem ipsum",
  7. style: bold24Roboto,
  8. textAlign: TextAlign.center,
  9. ),
  10. decoration: BoxDecoration(
  11. color: Colors.red[400],
  12. ),
  13. padding: EdgeInsets.all(16),
  14. ),
  15. alignment: Alignment.center,
  16. transform: Matrix4.identity()
  17. ..rotateZ(15 * 3.1415927 / 180),
  18. ),
  19. ),
  20. width: 320,
  21. height: 240,
  22. color: Colors.grey[300],
  23. );

缩放元素

要缩放或放大一个 widget,请将它嵌套在一个Transformwidget 中。使用 Transform widget 的 alignmentorigin 属性分别来指定缩放原点(支点)的相对和绝对信息。

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

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

  1. <div class="greybox">
  2. <div class="redbox">
  3. Lorem ipsum
  4. </div>
  5. </div>
  6.  
  7. .greybox {
  8. background-color: #e0e0e0; /* grey 300 */
  9. width: 320px;
  10. height: 240px;
  11. font: 900 24px Roboto;
  12. display: flex;
  13. align-items: center;
  14. justify-content: center;
  15. }
  16. .redbox {
  17. background-color: #ef5350; /* red 400 */
  18. padding: 16px;
  19. color: #ffffff;
  20. transform: scale(1.5);
  21. }
  1. var container = Container( // gray box
  2. child: Center(
  3. child: Transform(
  4. child: Container( // red box
  5. child: Text(
  6. "Lorem ipsum",
  7. style: bold24Roboto,
  8. textAlign: TextAlign.center,
  9. ),
  10. decoration: BoxDecoration(
  11. color: Colors.red[400],
  12. ),
  13. padding: EdgeInsets.all(16),
  14. ),
  15. alignment: Alignment.center,
  16. transform: Matrix4.identity()
  17. ..scale(1.5),
  18. ),
  19. width: 320,
  20. height: 240,
  21. color: Colors.grey[300],
  22. );

应用线性变换

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

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

  • If the beginning and ending x values are equal, the gradient is vertical(0° | 180°).

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

  • If the beginning and ending y values are equal, the gradient is horizontal(90° | 270°).

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

垂直变换

  1. <div class="greybox">
  2. <div class="redbox">
  3. Lorem ipsum
  4. </div>
  5. </div>
  6.  
  7. .greybox {
  8. background-color: #e0e0e0; /* grey 300 */
  9. width: 320px;
  10. height: 240px;
  11. font: 900 24px Roboto;
  12. display: flex;
  13. align-items: center;
  14. justify-content: center;
  15. }
  16. .redbox {
  17. padding: 16px;
  18. color: #ffffff;
  19. background: linear-gradient(180deg, #ef5350, rgba(0, 0, 0, 0) 80%);
  20. }
  1. var container = Container( // grey box
  2. child: Center(
  3. child: Container( // red box
  4. child: Text(
  5. "Lorem ipsum",
  6. style: bold24Roboto,
  7. ),
  8. decoration: BoxDecoration(
  9. gradient: LinearGradient(
  10. begin: const Alignment(0.0, -1.0),
  11. end: const Alignment(0.0, 0.6),
  12. colors: <Color>[
  13. const Color(0xffef5350),
  14. const Color(0x00ef5350)
  15. ],
  16. ),
  17. ),
  18. padding: EdgeInsets.all(16),
  19. ),
  20. ),
  21. width: 320,
  22. height: 240,
  23. color: Colors.grey[300],
  24. );

水平变换

  1. <div class="greybox">
  2. <div class="redbox">
  3. Lorem ipsum
  4. </div>
  5. </div>
  6.  
  7. .greybox {
  8. background-color: #e0e0e0; /* grey 300 */
  9. width: 320px;
  10. height: 240px;
  11. font: 900 24px Roboto;
  12. display: flex;
  13. align-items: center;
  14. justify-content: center;
  15. }
  16. .redbox {
  17. padding: 16px;
  18. color: #ffffff;
  19. background: linear-gradient(90deg, #ef5350, rgba(0, 0, 0, 0) 80%);
  20. }
  1. var container = Container( // grey box
  2. child: Center(
  3. child: Container( // red box
  4. child: Text(
  5. "Lorem ipsum",
  6. style: bold24Roboto,
  7. ),
  8. decoration: BoxDecoration(
  9. gradient: LinearGradient(
  10. begin: const Alignment(-1.0, 0.0),
  11. end: const Alignment(0.6, 0.0),
  12. colors: <Color>[
  13. const Color(0xffef5350),
  14. const Color(0x00ef5350)
  15. ],
  16. ),
  17. ),
  18. padding: EdgeInsets.all(16),
  19. ),
  20. ),
  21. width: 320,
  22. height: 240,
  23. color: Colors.grey[300],
  24. );

操控图形

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

圆角

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

  1. <div class="greybox">
  2. <div class="redbox">
  3. Lorem ipsum
  4. </div>
  5. </div>
  6.  
  7. .greybox {
  8. background-color: #e0e0e0; /* gray 300 */
  9. width: 320px;
  10. height: 240px;
  11. font: 900 24px Roboto;
  12. display: flex;
  13. align-items: center;
  14. justify-content: center;
  15. }
  16. .redbox {
  17. background-color: #ef5350; /* red 400 */
  18. padding: 16px;
  19. color: #ffffff;
  20. border-radius: 8px;
  21. }
  1. var container = Container( // grey box
  2. child: Center(
  3. child: Container( // red circle
  4. child: Text(
  5. "Lorem ipsum",
  6. style: bold24Roboto,
  7. ),
  8. decoration: BoxDecoration(
  9. color: Colors.red[400],
  10. borderRadius: BorderRadius.all(
  11. const Radius.circular(8),
  12. ),
  13. ),
  14. padding: EdgeInsets.all(16),
  15. ),
  16. ),
  17. width: 320,
  18. height: 240,
  19. color: Colors.grey[300],
  20. );

添加盒阴影 (box shadows)

在 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 属性来生成一系列BoxShadowwidget。你可以定义一个或多个BoxShadow widget,这些 widget 共同用于设置阴影深度、颜色等等。

  1. <div class="greybox">
  2. <div class="redbox">
  3. Lorem ipsum
  4. </div>
  5. </div>
  6.  
  7. .greybox {
  8. background-color: #e0e0e0; /* grey 300 */
  9. width: 320px;
  10. height: 240px;
  11. font: 900 24px Roboto;
  12. display: flex;
  13. align-items: center;
  14. justify-content: center;
  15. }
  16. .redbox {
  17. background-color: #ef5350; /* red 400 */
  18. padding: 16px;
  19. color: #ffffff;
  20. box-shadow: 0 2px 4px rgba(0, 0, 0, 0.8),
  21. 0 6px 20px rgba(0, 0, 0, 0.5);
  22. }
  1. var container = Container( // grey box
  2. child: Center(
  3. child: Container( // red box
  4. child: Text(
  5. "Lorem ipsum",
  6. style: bold24Roboto,
  7. ),
  8. decoration: BoxDecoration(
  9. color: Colors.red[400],
  10. boxShadow: [
  11. BoxShadow (
  12. color: const Color(0xcc000000),
  13. offset: Offset(0, 2),
  14. blurRadius: 4,
  15. ),
  16. BoxShadow (
  17. color: const Color(0x80000000),
  18. offset: Offset(0, 6),
  19. blurRadius: 20,
  20. ),
  21. ],
  22. ),
  23. padding: EdgeInsets.all(16),
  24. ),
  25. ),
  26. width: 320,
  27. height: 240,
  28. decoration: BoxDecoration(
  29. color: Colors.grey[300],
  30. ),
  31. margin: EdgeInsets.only(bottom: 16),
  32. );

生成圆与椭圆

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

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

  1. <div class="greybox">
  2. <div class="redcircle">
  3. Lorem ipsum
  4. </div>
  5. </div>
  6.  
  7. .greybox {
  8. background-color: #e0e0e0; /* gray 300 */
  9. width: 320px;
  10. height: 240px;
  11. font: 900 24px Roboto;
  12. display: flex;
  13. align-items: center;
  14. justify-content: center;
  15. }
  16. .redcircle {
  17. background-color: #ef5350; /* red 400 */
  18. padding: 16px;
  19. color: #ffffff;
  20. text-align: center;
  21. width: 160px;
  22. height: 160px;
  23. border-radius: 50%;
  24. }
  1. var container = Container( // grey box
  2. child: Center(
  3. child: Container( // red circle
  4. child: Text(
  5. "Lorem ipsum",
  6. style: bold24Roboto,
  7. textAlign: TextAlign.center,
  8. ),
  9. decoration: BoxDecoration(
  10. color: Colors.red[400],
  11. shape: BoxShape.circle,
  12. ),
  13. padding: EdgeInsets.all(16),
  14. width: 160,
  15. height: 160,
  16. ),
  17. ),
  18. width: 320,
  19. height: 240,
  20. color: Colors.grey[300],
  21. );

操控文本

以下示例展示了如何设置字体和其他文本属性。他们同时还展示了如何变换文本字符、自定义间距以及生成摘要。

文字间距调整

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

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

  1. <div class="greybox">
  2. <div class="redbox">
  3. Lorem ipsum
  4. </div>
  5. </div>
  6.  
  7. .greybox {
  8. background-color: #e0e0e0; /* grey 300 */
  9. width: 320px;
  10. height: 240px;
  11. font: 900 24px Roboto;
  12. display: flex;
  13. align-items: center;
  14. justify-content: center;
  15. }
  16. .redbox {
  17. background-color: #ef5350; /* red 400 */
  18. padding: 16px;
  19. color: #ffffff;
  20. letter-spacing: 4px;
  21. }
  1. var container = Container( // grey box
  2. child: Center(
  3. child: Container( // red box
  4. child: Text(
  5. "Lorem ipsum",
  6. style: TextStyle(
  7. color: Colors.white,
  8. fontSize: 24,
  9. fontWeight: FontWeight.w900,
  10. letterSpacing: 4,
  11. ),
  12. ),
  13. decoration: BoxDecoration(
  14. color: Colors.red[400],
  15. ),
  16. padding: EdgeInsets.all(16),
  17. ),
  18. ),
  19. width: 320,
  20. height: 240,
  21. color: Colors.grey[300],
  22. );

内联样式更改

一个 Text widget允许你展示同一类样式的文本。为了展现具有多种样式(本例中,是一个带重音的单词)的文本,需要改用 RichText widget。它的 text 属性可以指定一个或多个可以单独设置样式的TextSpan widget。

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

  1. <div class="greybox">
  2. <div class="redbox">
  3. Lorem <em>ipsum</em>
  4. </div>
  5. </div>
  6.  
  7. .greybox {
  8. background-color: #e0e0e0; /* grey 300 */
  9. width: 320px;
  10. height: 240px;
  11. font: 900 24px Roboto;
  12. display: flex;
  13. align-items: center;
  14. justify-content: center;
  15. }
  16. .redbox {
  17. background-color: #ef5350; /* red 400 */
  18. padding: 16px;
  19. color: #ffffff;
  20. }
  21. .redbox em {
  22. font: 300 48px Roboto;
  23. font-style: italic;
  24. }
  1. var container = Container( // grey box
  2. child: Center(
  3. child: Container( // red box
  4. child: RichText(
  5. text: TextSpan(
  6. style: bold24Roboto,
  7. children: <TextSpan>[
  8. TextSpan(text: "Lorem "),
  9. TextSpan(
  10. text: "ipsum",
  11. style: TextStyle(
  12. fontWeight: FontWeight.w300,
  13. fontStyle: FontStyle.italic,
  14. fontSize: 48,
  15. ),
  16. ),
  17. ],
  18. ),
  19. ),
  20. decoration: BoxDecoration(
  21. color: Colors.red[400],
  22. ),
  23. padding: EdgeInsets.all(16),
  24. ),
  25. ),
  26. width: 320,
  27. height: 240,
  28. color: Colors.grey[300],
  29. );

生成文本摘要

一个摘要会展示一个段落中文本的初始行内容,并常用省略号处理溢出的文本内容。在 HTML/CSS 中,摘录不能超过一行。在多行之后进行截断需要运行一些 JavaScript 代码。

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

  1. <div class="greybox">
  2. <div class="redbox">
  3. Lorem ipsum dolor sit amet, consec etur
  4. </div>
  5. </div>
  6.  
  7. .greybox {
  8. background-color: #e0e0e0; /* grey 300 */
  9. width: 320px;
  10. height: 240px;
  11. font: 900 24px Roboto;
  12. display: flex;
  13. align-items: center;
  14. justify-content: center;
  15. }
  16. .redbox {
  17. background-color: #ef5350; /* red 400 */
  18. padding: 16px;
  19. color: #ffffff;
  20. overflow: hidden;
  21. text-overflow: ellipsis;
  22. white-space: nowrap;
  23. }
  1. var container = Container( // grey box
  2. child: Center(
  3. child: Container( // red box
  4. child: Text(
  5. "Lorem ipsum dolor sit amet, consec etur",
  6. style: bold24Roboto,
  7. overflow: TextOverflow.ellipsis,
  8. maxLines: 1,
  9. ),
  10. decoration: BoxDecoration(
  11. color: Colors.red[400],
  12. ),
  13. padding: EdgeInsets.all(16),
  14. ),
  15. ),
  16. width: 320,
  17. height: 240,
  18. color: Colors.grey[300],
  19. );