flutter自学笔记3- 常用 Widget 整理

938 阅读26分钟

笔记1:介绍一下flutter

笔记2-了解Flutter UI 页面和跳转

笔记3- 常用 Widget 整理

笔记4- dart 语法快速学习

笔记5- dart 编码规范

笔记6- 网络请求、序列化、平台通道介绍

笔记7- 状态管理、数据持久化

笔记8- package、插件、主题、国际化

笔记9 - 架构、调试、打包部署

笔记10- Widget 构建、渲染流程和原理、布局算法优化

笔记11- 性能、渲染、包体积、懒加载、线程、并发隔离

通过上一篇《flutter自学笔记2-了解Flutter UI 页面和跳转》, 从整体上初步了解了一点页面构造和跳转。

笔记3:本篇我记录常用的一些Widget,以此为后面开发复杂界面做准备,也方便快速查找和回忆,后续也会不断完善

注:

示例代码在线运行平台:dartpad

一、基础组件

1.1 Text

Text 组件支持很多样式设置,比如颜色(color)、字体粗细(fontWeight)、字体样式(fontStyle)、文字装饰(decoration,比如下划线)、文字对齐方式(textAlign)等等。这些都可以通过 TextStyle 类来设置。

maxLinesoverflow:指定文本显示的最大行数

例如,如果你想让文本显示为红色,你可以这样做:

import 'package:flutter/material.dart';

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

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Flutter Text Example',
      theme: ThemeData(
        primarySwatch: Colors.blue,
      ),
      home: Scaffold(
        appBar: AppBar(
          title: Text('Flutter Text Demo'),
        ),
        body: Center(
          child: Text(
            '你好,Flutter!',
            style: TextStyle(
              fontSize: 24,
              color: Colors.red, // 设置文本颜色为红色
              height: 2.0,       // 行高通常是字体大小的倍数,这里设置为2.0倍
              fontFamily: "Courier", // 设置字体为Courier
              background: Paint()..color = Colors.yellow, // 设置文本背景颜色为黄色
              decoration: TextDecoration.underline, // 设置文本下划线
              decorationStyle: TextDecorationStyle.dashed, // 设置下划线为虚线
            ),
          ),
        ),
      ),
    );
  }
}

image.png

1.2 TextSpan

TextSpan,它代表文本的一个“片段”

使用 TextSpanText.rich() 方法构建富文本的示例:


import 'package:flutter/material.dart';

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

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Flutter RichText Example',
      theme: ThemeData(
        primarySwatch: Colors.blue,
      ),
      home: Scaffold(
        appBar: AppBar(
          title: Text('Flutter RichText Demo'),
        ),
        body: Center(
          child: RichText(
            text: TextSpan(
              text: '这是一个',
              style: TextStyle(fontSize: 20),
              children: [
                TextSpan(
                  text: '富文本',
                  style: TextStyle(fontSize: 24, color: Colors.blue),
                ),
                TextSpan(
                  text: '示例。',
                  style: TextStyle(fontSize: 18, fontStyle: FontStyle.italic),
                ),
              ],
            ),
          ),
        ),
      ),
    );
  }
}

image.png

1.3 字体

系统字体 :设置 fontFamily

Text(
'Hello, Flutter!',
style: TextStyle(
  fontFamily: 'Roboto', // 使用系统字体Roboto
  fontSize: 18.0,
),
)

1.4 自定义字体

将字体文件(如 .ttf.otf 文件)添加到 Flutter 项目的 assets 目录中,并在 pubspec.yaml 文件中声明这些字体文件。然后,他们就可以在 fontFamily 属性中引用这些自定义字体的名称了:

  1. MyCustomFont.ttf 文件添加到 Flutter 项目的 assets/fonts/ 目录中(如果目录不存在,可以手动创建)。
  2. pubspec.yaml 文件中声明这个字体文件:
flutter:
fonts:
  - family: MyCustomFont
    fonts:
      - asset: fonts/MyCustomFont.ttf

3.在 TextStyle 中使用 fontFamily 属性来引用这个自定义字体:

Text(
 'Hello, Custom Font!',
 style: TextStyle(
   fontFamily: 'MyCustomFont'// 使用自定义字体MyCustomFont
   fontSize: 18.0,
),
)

1.5 按钮

  1. ElevatedButton(凸起按钮): 这是一个具有明显凸起效果的按钮,通常用于主要操作。
ElevatedButton(
  onPressed: () {
    // 处理点击事件
  },
  child: Text('Elevated Button'),
)

image.png

  1. TextButton(文本按钮): 这是一个简单的文本按钮,通常用于不太重要的操作或需要节省空间的地方。
TextButton(
 onPressed: () {
   // 处理点击事件
},
 child: Text('Text Button'),
)

image.png

  1. OutlinedButton(轮廓按钮): 这是一个带有轮廓线的按钮,通常用于需要强调边界但不希望按钮过于显眼的情况。
OutlinedButton(
 onPressed: () {
   // 处理点击事件
},
 child: Text('Outlined Button'),
)

image.png

  1. IconButton(图标按钮): 这是一个仅包含图标的按钮,通常用于工具栏或需要节省空间的地方。
IconButton(
 icon: Icon(Icons.add),
 onPressed: () {
   // 处理点击事件
},
)

image.png

  1. FloatingActionButton(浮动操作按钮): 这是一个通常位于屏幕底部或角落的圆形按钮,用于执行主要操作,如添加、编辑等。
FloatingActionButton(
 onPressed: () {
   // 处理点击事件
},
 tooltip: 'Floating Action Button',
 child: Icon(Icons.add),
)

image.png

  1. ButtonStyleButton(自定义样式按钮): 虽然这不是一个具体的按钮类,但你可以通过ButtonStyle来高度自定义按钮的外观和行为。
Button(
 onPressed: () {
   // 处理点击事件
},
 styleButtonStyle(
   backgroundColor: MaterialStateProperty.all(Colors.blue),
   overlayColor: MaterialStateProperty.resolveWith<Color?>(
    (Set<MaterialState> states) {
       if (states.contains(MaterialState.pressed)) {
         return Colors.blue.withAlpha(128);
      }
       return null;
    },
  ),
   shape: MaterialStateProperty.all<RoundedRectangleBorder>(
     RoundedRectangleBorder(
       borderRadius: BorderRadius.circular(18.0),
    ),
  ),
),
 childText('Custom Style Button'),
)

1.6 图标

Icon 组件

Icon(
 Icons.add, // 这是一个Material Design图标集中的“添加”图标
 size24,  // 设置图标的大小
 color: Colors.blue, // 设置图标的颜色
)

IconButton 显示图标的按钮

IconButton(
 iconIcon(Icons.add), // 设置按钮的图标
 onPressed: () {
   // 处理按钮点击事件
},
 tooltip'Add item'// 设置按钮的提示文本,当用户长按按钮时显示
 color: Colors.blue, // 设置按钮(和图标)的颜色
)

自定义图标

Image.asset(
 'assets/icons/my_custom_icon.png'// 图标文件的路径
 width24// 设置图标的宽度
 height24// 设置图标的高度
 color: Colors.blue, // 设置图标的颜色(如果图标是透明的,这个属性将有效)
)

1.7 单选开关和复选框

单选开关(Switch)
  1. 基本属性

    • value:表示开关的当前状态,true表示打开,false表示关闭。
    • onChanged:当开关状态改变时触发的回调函数,用于更新UI或执行其他逻辑。
    • activeColor:开关打开时的颜色。
    • inactiveThumbColor:开关关闭时滑块(拇指)的颜色。
    • inactiveTrackColor:开关关闭时轨道的颜色。
    • activeThumbImageinactiveThumbImage:分别用于设置开关打开和关闭时滑块上的图像。

image.png

  1. 使用示例
import 'package:flutter/material.dart';

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

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Flutter Switch Example',
      theme: ThemeData(
        primarySwatch: Colors.blue,
      ),
      home: MyHomePage(),
    );
  }
}

class MyHomePage extends StatefulWidget {
  @override
  _MyHomePageState createState() => _MyHomePageState();
}

class _MyHomePageState extends State<MyHomePage> {
  bool _isSwitched = false;

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text('Flutter Switch Demo'),
      ),
      body: Center(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: <Widget>[
            Text(
              'Switch is: $_isSwitched',
              style: TextStyle(fontSize: 20),
            ),
            SizedBox(height: 20),
            Switch(
              value: _isSwitched,
              onChanged: (value) {
                setState(() {
                  _isSwitched = value;
                });
              },
              activeColor: Colors.green,
              inactiveThumbColor: Colors.red,
              inactiveTrackColor: Colors.grey,
            ),
          ],
        ),
      ),
    );
  }
}

  1. iOS风格的单选开关

    Flutter还提供了CupertinoSwitch组件,用于创建iOS风格的单选开关。

    CupertinoSwitch(
     value: _isSwitched,
     onChanged: (value) {
       // 处理开关状态改变
    },
    )
    
复选框(Checkbox)
  1. 基本属性

    • value:表示复选框的当前状态,true表示选中,false表示未选中。
    • onChanged:当复选框状态改变时触发的回调函数,用于更新UI或执行其他逻辑。
    • tristate:一个可选属性,如果设置为true,则复选框可以处于三种状态:选中(true)、未选中(false)和不确定(null)。这在全选/全不选操作中非常有用。
    • side:用于设置复选框的边线颜色、宽度等属性。
    • shape:用于设置复选框的形状,如圆角矩形、圆形等。
  2. 使用示例

    Checkbox(
     value: _isChecked,
     onChanged: (value) {
       setState(() {
         _isChecked = value!;
      });
    },
    )
    

image.png

  1. CupertinoCheckbox

    Flutter中的Checkbox组件属于Material风格,对于需要macOS风格的复选框,可以使用CupertinoCheckbox组件。

CupertinoCheckbox(
     value: _isChecked,
     onChanged: (value) {
       setState(() {
         _isChecked = value!;
      });
    },
     tristate: false// 是否启用三态
     side: BorderSide(color: Colors.black12), // 边线属性
     shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(6)), // 形状属性
    )

1.8 输入框(TextField)

TextField是Flutter中的一个基础组件,它允许用户在应用中输入文本。TextField提供了多种属性来定制其外观和行为,包括但不限于:

  • controller:编辑框的控制器,通过它可以设置/获取编辑框的内容、选择编辑内容、监听编辑文本改变事件。大多数情况下,开发者需要显式提供一个controller来与文本框进行交互。
  • focusNode:用于控制TextField是否占有当前键盘的输入焦点。它是我们和键盘交互的一个句柄。
  • decoration:用于控制TextField的外观显示,如提示文本、背景颜色、边框等。
  • keyboardType:用于设置该输入框默认的键盘输入类型,如文本输入、数字输入、电话号码输入等。
  • textInputAction:键盘动作按钮图标(即回车键位图标),它是一个枚举值,有多个可选值,如完成、下一步、搜索等。
  • style:正在编辑的文本样式。
  • textAlign:输入框内编辑文本在水平方向的对齐方式。
  • autofocus:是否自动获取焦点。
  • obscureText:是否隐藏正在编辑的文本,如用于输入密码的场景等,文本内容会用“•”替换。
  • maxLines:输入框的最大行数,默认为1;如果为null,则无行数限制。
  • maxLengthmaxLengthEnforcement:maxLength代表输入框文本的最大长度,设置后输入框右下角会显示输入的文本计数。maxLengthEnforcement决定当输入文本长度超过maxLength时如何处理,如截断、超出等。

此外,TextField还提供了多种回调函数,如onChange(内容改变时的回调)、onEditingComplete和onSubmitted(输入完成时触发)等,用于处理用户输入事件。

1.9 表单(Form)

Flutter中的Form组件可以对输入框进行分组,并进行一些统一操作,如输入内容校验、输入框重置以及输入内容保存。Form继承自StatefulWidget对象,它对应的状态类为FormState。

Form组件的主要属性包括:

  • autovalidate:是否自动校验输入内容。当为true时,每一个子FormField内容发生变化时都会自动校验合法性,并直接显示错误信息。否则,需要通过调用FormState.validate()来手动校验。
  • onWillPop:决定Form所在的路由是否可以直接返回(如点击返回按钮)。该回调返回一个Future对象,如果Future的最终结果是false,则当前路由不会返回;如果为true,则会返回到上一个路由。此属性通常用于拦截返回按钮。
  • onChanged:Form的任意一个子FormField内容发生变化时会触发此回调。

1.10 进度指示器

1、线性进度指示器(LinearProgressIndicator)

线性进度指示器是水平显示的进度条,通常用于表示任务的确定进度或不确定进度(即任务正在进行但具体进度未知)。

LinearProgressIndicator(
 value0.5// 设置进度值,范围为0.0到1.0
 backgroundColor: Colors.grey[200], // 设置背景颜色
 valueColor: AlwaysStoppedAnimation<Color>(Colors.blue), // 设置进度条颜色
)

循环模式

valuenull时,进度指示器将进入不确定模式,显示一个循环动画。

LinearProgressIndicator(
 backgroundColor: Colors.grey[200],
 valueColor: AlwaysStoppedAnimation<Color>(Colors.blue),
)
2、圆形进度指示器(CircularProgressIndicator)

圆形进度指示器是类似于加载旋转轮的圆形进度条,适用于任何方向的空间。

CircularProgressIndicator(
 value0.5// 设置进度值,范围为0.0到1.0
 backgroundColor: Colors.grey[200], // 设置背景颜色
 valueColor: AlwaysStoppedAnimation<Color>(Colors.blue), // 设置进度条颜色
 strokeWidth4.0// 设置圆形进度条的粗细
)

同样地,当valuenull时,圆形进度指示器也将进入不确定模式。

3、Step Progress Indicator( 步骤进度指示器)

Step Progress Indicator是一个轻量级且高度定制化的Flutter包,它以一系列可选和未选中的步骤构成条形或圆形进度指示器。

pubspec.yaml文件中添加以下依赖:

dependencies:
flutter:
  sdk: flutter
step_progress_indicator: ^1.0.2

然后运行flutter pub get来安装依赖。

使用

Step Progress Indicator提供了高度自定义的API,允许开发者调整颜色、大小、形状以及动画效果等。

import 'package:step_progress_indicator/step_progress_indicator.dart';

// ...

StepProgressIndicator(
 totalSteps5// 设置总步骤数
 currentStep3// 设置当前步骤
 size24.0// 设置步骤图标的大小
 completedColor: Colors.blue, // 设置已完成步骤的颜色
 uncompletedColor: Colors.grey, // 设置未完成步骤的颜色
 // 其他自定义属性...
)
4、液态进度指示器(Liquid Progress Indicator)

液态进度指示器提供了三种类型的进度指示器:液态圆形进度指示器、液态线性进度指示器和液态自定义进度指示器。

pubspec.yaml文件中添加以下依赖:

dependencies:
flutter:
  sdk: flutter
liquid_progress_indicator: ^0.4.0

然后运行flutter pub get来安装依赖。

使用

import 'package:liquid_progress_indicator/liquid_progress_indicator.dart';

// ...

LiquidCircularProgressIndicator(
 value0.25// 设置进度值,范围为0.0到1.0
 valueColor: AlwaysStoppedAnimation<Color>(Colors.pink), // 设置进度颜色
 backgroundColor: Colors.white, // 设置背景颜色
 borderColor: Colors.red, // 设置边框颜色
 borderWidth5.0// 设置边框宽度
 direction: Axis.vertical, // 设置进度方向
 centerText("加载中..."), // 设置中心文本
)

1.11 弹窗

AlertDialog: AlertDialog 是一种最常见的对话框,用于显示信息或接收用户确认。

showDialog(
 context: context,
 builder: (BuildContext context) {
   return AlertDialog(
     titleText('标题'),
     contentText('这是对话框的内容'),
     actions: <Widget>[
       TextButton(
         onPressed: () {
           Navigator.of(context).pop();
        },
         childText('取消'),
      ),
       TextButton(
         onPressed: () {
           // 执行确认操作
           Navigator.of(context).pop();
        },
         childText('确定'),
      ),
    ],
  );
},
);

SimpleDialog: SimpleDialog 提供一个带有选项列表的对话框,用户可以从中选择一个选项。

showDialog(
 context: context,
 builder: (BuildContext context) {
   return SimpleDialog(
     titleText('选择选项'),
     children: <Widget>[
       SimpleDialogOption(
         childText('选项1'),
         onPressed: () {
           Navigator.of(context).pop('选项1');
        },
      ),
       SimpleDialogOption(
         childText('选项2'),
         onPressed: () {
           Navigator.of(context).pop('选项2');
        },
      ),
    ],
  );
},
).then((value) {
 if (value != null) {
   // 处理用户选择的选项
   print('用户选择了: $value');
}
});

ConfirmationDialog(自定义): Flutter 并没有直接提供一个名为 ConfirmationDialog 的组件,但你可以通过 AlertDialog 实现一个确认对话框。

showDialog(
 context: context,
 builder: (BuildContext context) {
   return AlertDialog(
     titleText('确认'),
     contentText('您确定要继续吗?'),
     actions: <Widget>[
       TextButton(
         onPressed: () {
           Navigator.of(context).pop(false);
        },
         childText('取消'),
      ),
       TextButton(
         onPressed: () {
           Navigator.of(context).pop(true);
        },
         childText('确定'),
      ),
    ],
  );
},
).then((value) {
 if (value == true) {
   // 用户点击了确定
   print('用户确认了操作');
} else {
   // 用户点击了取消
   print('用户取消了操作');
}
});

BottomSheet: BottomSheet 是一种从屏幕底部滑出的对话框,常用于展示更多选项或详细信息。

showBottomSheet(
 context: context,
 builder: (BuildContext context) {
   return Container(
     height200,
     color: Colors.white,
     childColumn(
       mainAxisAlignment: MainAxisAlignment.center,
       children: <Widget>[
         Text('这是一个 BottomSheet'),
         ElevatedButton(
           onPressed: () {
             Navigator.of(context).pop();
          },
           childText('关闭'),
        ),
      ],
    ),
  );
},
);

FullScreenDialog(自定义): 虽然 Flutter 没有直接提供全屏对话框的组件,但你可以通过调整 Dialog 的样式和布局来实现一个全屏对话框。

showDialog(
 context: context,
 builder: (BuildContext context) {
   return Dialog(
     shapeRoundedRectangleBorder(
       borderRadius: BorderRadius.circular(0.0)
    ),
     backgroundColor: Colors.transparent,
     childContainer(
       decorationBoxDecoration(
         color: Colors.white,
         borderRadius: BorderRadius.circular(0.0)
      ),
       constraintsBoxConstraints(
         maxWidth: MediaQuery.of(context).size.width,
         maxHeight: MediaQuery.of(context).size.height
      ),
       childColumn(
         mainAxisAlignment: MainAxisAlignment.center,
         children: <Widget>[
           Text('这是一个全屏对话框'),
           ElevatedButton(
             onPressed: () {
               Navigator.of(context).pop();
            },
             childText('关闭'),
          ),
        ],
      ),
    ),
  );
},
);

1.12 应用:登录页

创建一个包含用户名和密码输入框的表单,并在用户点击提交按钮时进行验证:

image.png

代码示例:

import 'package:flutter/material.dart';

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

class MyApp extends StatelessWidget {
 @override
 Widget build(BuildContext context) {
   return MaterialApp(
     title'Flutter Form Example',
     homeScaffold(
       appBarAppBar(
         titleText('Flutter Form Example'),
      ),
       bodyMyForm(),
    ),
  );
}
}

class MyForm extends StatefulWidget {
 @override
 _MyFormState createState() => _MyFormState();
}

class _MyFormState extends State<MyForm> {
 final _formKey = GlobalKey<FormState>();
 String _username = '';
 String _password = '';

 @override
 Widget build(BuildContext context) {
   return Form(
     key: _formKey,
     childColumn(
       crossAxisAlignment: CrossAxisAlignment.start,
       children: <Widget>[
         TextFormField(
           decorationInputDecoration(
             labelText'用户名',
             hintText'请输入用户名',
             borderOutlineInputBorder(
               borderRadius: BorderRadius.all(Radius.circular(10)),
               borderSideBorderSide(width1color: Colors.grey),
            ),
          ),
           validator: (value) {
             if (value == null || value.isEmpty) {
               return '用户名不能为空';
            }
             return null;
          },
           onSaved: (value) {
             _username = value!;
          },
        ),
         Padding(
           padding: const EdgeInsets.symmetric(vertical16.0),
           childTextFormField(
             decorationInputDecoration(
               labelText'密码',
               hintText'请输入密码',
               borderOutlineInputBorder(
                 borderRadius: BorderRadius.all(Radius.circular(10)),
                 borderSideBorderSide(width1color: Colors.grey),
              ),
               suffixIconIconButton(
                 iconIcon(Icons.visibility),
                 onPressed: () {
                   // 实现密码可见性切换的逻辑
                },
              ),
            ),
             obscureText: true,
             validator: (value) {
               if (value == null || value.isEmpty) {
                 return '密码不能为空';
              }
               return null;
            },
             onSaved: (value) {
               _password = value!;
            },
          ),
        ),
         Padding(
           padding: const EdgeInsets.symmetric(vertical16.0),
           childElevatedButton(
             onPressed: () {
               if (_formKey.currentState!.validate()) {
                 _formKey.currentState!.save();
                 ScaffoldMessenger.of(context).showSnackBar(
                   SnackBar(
                     contentText('用户名: $_username, 密码: $_password'),
                  ),
                );
              }
            },
             childText('提交'),
          ),
        ),
      ],
    ),
  );
}
}

二、布局类组件

布局类组件都会包含一个或多个子组件,不同的布局类组件对子组件排列(layout)方式不同:

Widget说明用途
LeafRenderObjectWidget非容器类组件基类Widget树的叶子节点,用于没有子节点的widget,通常基础组件都属于这一类,如Image。
SingleChildRenderObjectWidget单子组件基类包含一个子Widget,如:ConstrainedBox、DecoratedBox等
MultiChildRenderObjectWidget多子组件基类包含多个子Widget,一般都有一个children参数,接受一个Widget数组。如Row、Column、Stack等

2.1 RenderBox

RenderBox 是一个基础渲染类,通常不直接使用,而是作为其他渲染类的基类。它代表了一个具有位置和大小的矩形框。以下是一个简单的自定义 RenderBox 子类示例,但请注意,在实际应用中,您通常会使用 SingleChildRenderObjectWidget 来包装它:

// 通常不直接这样使用,而是作为自定义渲染类的基类
class MyRenderBox extends RenderBox {
 // 构造函数和其他必要的方法需要实现,这里仅作为示例
 // ...
}

由于 RenderBox 是底层渲染类,通常不需要在Flutter的Dart代码中直接使用它,而是使用更高层次的Widget。

2.2 Sliver (RenderSliver)

Sliver 是一个用于滚动视图中表示一个可滚动片段的抽象类。RenderSliver 是其渲染类。Sliver 通常与 CustomScrollViewSliverListSliverGrid 等一起使用。以下是一个使用 SliverList 的简单示例:

CustomScrollView(
 slivers: <Widget>[
   SliverList(
     delegateSliverChildListDelegate(
      [
         ListTile(titleText('Item 1')),
         ListTile(titleText('Item 2')),
         // ...更多项
      ],
    ),
  ),
],
)

2.3 BoxConstraints

BoxConstraints 用于描述一个盒子(Widget)可以接受的布局约束。以下是一个在自定义布局或Widget中使用 BoxConstraints 的简单示例:

class MyCustomWidget extends SingleChildRenderObjectWidget {
 @override
 RenderObject createRenderObject(BuildContext context) {
   return MyCustomRenderObject();
}
}

class MyCustomRenderObject extends RenderBox {
 @override
 void performLayout() {
   // 获取传递给此RenderBox的BoxConstraints
   final BoxConstraints constraints = this.constraints;
   
   // 假设子Widget将填满可用空间(假设有子Widget)
   if (child != null) {
     child!.layout(constraints.loosen()); // 可以稍微放宽约束,但通常直接使用constraints
  }
   
   // 设置此RenderBox的大小(这里简单地设置为与约束相同)
   size = constraints.constrain(Size(
     double.infinity, // 通常会根据子Widget或特定逻辑设置宽度和高度
     double.infinity,
  ));
}
}

请注意,上面的 MyCustomRenderObject 示例是为了说明如何使用 BoxConstraints,并不是一个完整的、可运行的自定义布局实现。

2.4 SizedBox 固定大小的盒子

SizedBox 用于提供一个具有固定大小的盒子,通常用于在布局中添加间距或调整Widget的大小。

Column(
 children: <Widget>[
   SizedBox(width50height50childPlaceholder()),
   // ...其他Widget
],
)

2.5 UnconstrainedBox 移除约束影响

UnconstrainedBox 用于移除父Widget对子Widget的布局约束,允许子Widget自由布局(尽管最终的大小仍然受限于父Widget的可见区域)。

UnconstrainedBox(
 childContainer(
   width: double.infinity, // 这将不再受限于父Widget的约束
   height: double.infinity,
   color: Colors.blue,
),
)

请注意,UnconstrainedBox 内部的Widget大小仍然受到其父Widget(通常是屏幕或滚动容器)的限制。

2.6 AspectRatio

AspectRatio 用于保持子Widget的宽高比。

AspectRatio(
 aspectRatio16 / 9// 宽高比
 childPlaceholder(),
)

2.7 LimitedBox

LimitedBox 用于限制子Widget的最大和/或最小尺寸。

LimitedBox(
 maxWidth200,
 maxHeight100,
 childPlaceholder(),
)

2.8 FractionallySizedBox

FractionallySizedBox 根据其父Widget的大小和指定的宽度和高度比例来设置子Widget的大小。

FractionallySizedBox(
 widthFactor0.5// 宽度占父Widget宽度的50%
 heightFactor0.3// 高度占父Widget高度的30%
 childPlaceholder(),
)

这些代码片段展示了如何在Flutter中使用这些类和Widget。请注意,在实际应用中,您可能需要根据具体需求调整这些示例。

2.9 层叠、绝对布局

顾名思义,层叠布局就是可以覆在Flutter中,StackPositioned 通常一起使用来创建一个重叠的子Widget布局。Stack 是一个允许其子Widget在彼此的顶部进行堆叠的Widget,而 Positioned 则用于在 Stack 内部定位子Widget。

2.10 Stack 和 Positioned


import 'package:flutter/material.dart';

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

class MyApp extends StatelessWidget {
 @override
 Widget build(BuildContext context) {
   return MaterialApp(
     home: Scaffold(
       appBar: AppBar(
         title: Text('Stack and Positioned Example'),
      ),
       body: Center(
         child: Stack(
           alignment: Alignment.center, // 可选:设置子Widget的对齐方式(如果未使用Positioned)
           children: <Widget>[
             // 底层Widget:一个蓝色的圆形
             Container(
               width: 200,
               height: 200,
               decoration: BoxDecoration(
                 shape: BoxShape.circle,
                 color: Colors.blue,
              ),
            ),
             
             // 使用Positioned定位的Widget:一个红色的矩形
             Positioned(
               top: 50, // 距离Stack顶部的距离
               left: 50, // 距离Stack左侧的距离
               child: Container(
                 width: 100,
                 height: 50,
                 color: Colors.red,
              ),
            ),
             
             // 另一个使用Positioned定位的Widget:一个绿色的矩形
             Positioned(
               bottom: 20, // 距离Stack底部的距离
               right: 20, // 距离Stack右侧的距离
               child: Container(
                 width: 75,
                 height: 75,
                 color: Colors.green,
              ),
            ),
          ],
        ),
      ),
    ),
  );
}
}

在这个示例中,我们创建了一个 Stack,它包含三个子Widget:

image.png

  1. 一个底层的蓝色圆形 Container
  2. 一个使用 Positioned 定位的红色矩形 Container,它位于蓝色圆形的上方和左侧。
  3. 另一个使用 Positioned 定位的绿色矩形 Container,它位于蓝色圆形的下方和右侧。

Positioned Widget 通过 topbottomleftright 属性来指定它相对于 Stack 容器的边界的距离。这些属性可以是固定的数值,也可以是相对于 Stack 尺寸的百分比(如果使用了 FractionalOffset 或者在布局构建函数中进行了相应的计算)。

注意,虽然 Stack 有一个 alignment 属性,但它通常只在没有使用 Positioned 的情况下才有效。当使用 Positioned 时,每个子Widget的位置都是独立指定的,因此 Stackalignment 属性对这些子Widget没有影响。

2.11 Align 对齐方式

在Flutter中,Align是一个用于根据指定的对齐方式调整其子组件位置的布局组件。它允许开发者在父组件中精确地定位子组件,并支持多种预设的对齐方式,如顶部、底部、左侧、右侧、居中等。以下是关于Align的详细介绍和使用示例:

Align组件的属性

  • alignment:指定子组件在父组件中的对齐方式。该属性接受一个Alignment对象,该对象有两个属性xy,取值范围为-1.0到1.0,分别表示相对于父组件宽度和高度的比例。例如,Alignment(-1.0, -1.0)表示左上角对齐,Alignment(1.0, 1.0)表示右下角对齐。Align组件也支持直接使用预设的对齐方式,如Alignment.topLeftAlignment.center等。
  • widthFactor:指定子组件的宽度相对于父组件宽度的比例因子。取值范围为0.0到1.0。例如,widthFactor: 0.5表示子组件宽度为父组件宽度的一半。
  • heightFactor:指定子组件的高度相对于父组件高度的比例因子。取值范围为0.0到1.0。例如,heightFactor: 0.8表示子组件高度为父组件高度的80%。
  • child:要放置在父组件中的子组件。

示例

以下是一个简单的示例,展示了如何使用Align组件将子组件对齐到父组件的右下角:

import 'package:flutter/material.dart';

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

class MyApp extends StatelessWidget {
 @override
 Widget build(BuildContext context) {
   return MaterialApp(
     home: Scaffold(
       appBar: AppBar(
         title: Text('Align Example'),
      ),
       body: Container(
         color: Colors.grey[200],
         width: double.infinity,
         height: double.infinity,
         child: Align(
           alignment: Alignment.bottomRight,
           child: Container(
             color: Colors.blue,
             width: 100,
             height: 100,
          ),
        ),
      ),
    ),
  );
}
}

image.png

在这个示例中,我们创建了一个Container作为父组件,并设置了一个灰色的背景色。然后,我们使用Align组件将一个蓝色的Container子组件对齐到父组件的右下角。通过设置alignment属性为Alignment.bottomRight,我们实现了这一对齐效果。

2.12 Align 自定义对齐

除了使用预设的对齐方式外,Align组件还支持自定义对齐。通过调整Alignment对象的xy属性,我们可以将子组件对齐到父组件中的任何位置。例如,以下代码将子组件对齐到父组件的中心偏上位置:

Align(
 alignmentAlignment(0, -0.5), // 中心偏上位置
 childContainer(
   color: Colors.red,
   width50,
   height50,
),
)

在这个示例中,我们设置alignment属性为Alignment(0, -0.5),这表示子组件在水平方向上居中对齐,在垂直方向上距离父组件顶部50%的位置(因为y值为-0.5,所以子组件会向上偏移父组件高度的一半)。

综上所述,Align组件是Flutter中一个非常有用的布局组件,它提供了一种简单而强大的方法来对齐和定位子组件。通过合理使用Align组件,我们可以创建出更加灵活和响应式的布局效果。

2.13 Flex(弹性布局)

在Flutter中,Flex 是一个用于在主轴(通常是水平方向)和交叉轴(通常是垂直方向)上排列子Widget的容器。它类似于 RowColumn,但提供了更多的灵活性,因为您可以自定义主轴的方向、子Widget的对齐方式、主轴和交叉轴上的间距等。

Flex 的主要属性包括:

  • direction:主轴的方向,可以是水平(Axis.horizontal)或垂直(Axis.vertical)。
  • mainAxisAlignment:子Widget在主轴上的对齐方式。
  • crossAxisAlignment:子Widget在交叉轴上的对齐方式。
  • mainAxisSpacingcrossAxisSpacing:分别控制子Widget在主轴和交叉轴上的间距(注意:这些属性在Flutter的当前版本中可能不是直接可用的,通常使用 spacingchildren之间的空白Widget来实现间距)。
  • children:要排列的子Widget列表。

以下是一个使用 Flex 的简单代码片段:

​
child: Flex(
 direction: Axis.horizontal, // 主轴方向为水平
 mainAxisAlignment: MainAxisAlignment.spaceBetween, // 子Widget在主轴上均匀分布
 crossAxisAlignment: CrossAxisAlignment.center, // 子Widget在交叉轴上居中对齐
 children: <Widget>[
   Flexible(
     flex1// 第一个子Widget占据1份空间
     childContainer(
       color: Colors.red,
       width50// 注意:这里的宽度设置可能不起作用,因为Flexible会根据flex值分配空间
       height50,
    ),
  ),
   Flexible(
     flex2// 第二个子Widget占据2份空间
     childContainer(
       color: Colors.green,
       width50// 同样,这里的宽度可能不起作用
       height50,
    ),
  )
],
),
​

1、在上面的代码中,Flexible widget被用于包裹每个子Container,因为Flex通常与Flexible一起使用来指定子Widget应该如何根据其flex值分配空间。如果您直接使用Container或其他没有flex属性的Widget作为Flex的子Widget,那么这些子Widget将不会根据flex值进行大小调整,而是会尽可能小(通常是其内容的大小)。

2、width属性在FlexibleFlex的直接子Widget中可能不起作用,因为大小是由Flex容器根据其子Widget的flex值和可用空间来决定的。如果您需要为子Widget设置特定的宽度或高度,您可能需要使用SizedBoxAspectRatio或其他布局Widget来进一步控制大小。

3、使用RowColumn可能更为方便:在实际应用中,如果您只是想要一个简单的水平或垂直布局,并且不需要太多的自定义,那么使用RowColumn可能更为方便。RowColumn实际上是Flex的特殊情况,其中direction属性已经被固定为水平或垂直。

2.14 Row(水平、线性布局)

Rowdirection属性被固定为Axis.horizontal,是Flex的特殊情况

import 'package:flutter/material.dart';

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

class MyApp extends StatelessWidget {
 @override
 Widget build(BuildContext context) {
   return MaterialApp(
     home: Scaffold(
       appBar: AppBar(
         title: Text('Row Example'),
      ),
       body: Center(
         child: Row(
           mainAxisAlignment: MainAxisAlignment.spaceBetween, // 子Widget在主轴上均匀分布
           crossAxisAlignment: CrossAxisAlignment.center, // 子Widget在交叉轴上居中对齐
           children: <Widget>[
             Icon(Icons.star, color: Colors.red),
             Text('Flutter', style: TextStyle(fontSize: 24)),
             Icon(Icons.arrow_forward, color: Colors.blue),
          ],
        ),
      ),
    ),
  );
}
}

image.png

2.15 Column(垂直、线性布局)

Columndirection属性被固定为Axis.vertical,是Flex的特殊情况

import 'package:flutter/material.dart';

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

class MyApp extends StatelessWidget {
 @override
 Widget build(BuildContext context) {
   return MaterialApp(
     home: Scaffold(
       appBar: AppBar(
         title: Text('Column Example'),
      ),
       body: Center(
         child: Column(
           mainAxisAlignment: MainAxisAlignment.center, // 子Widget在主轴上居中对齐
           crossAxisAlignment: CrossAxisAlignment.start, // 子Widget在交叉轴上靠左对齐
           children: <Widget>[
             Text('Flutter', style: TextStyle(fontSize: 32, fontWeight: FontWeight.bold)),
             Text('一个现代的UI工具包,用于从单一代码库构建美观的、原生编译的移动、Web和桌面应用程序。', style: TextStyle(fontSize: 16)),
             Row(
  mainAxisAlignment: MainAxisAlignment.center,
  children: <Widget>[
    TextButton(
      onPressed: () {},
      child: Text('了解更多'),
    ),
    // 你可以在这里添加更多的按钮,并使用 Spacer widget 来添加间距
  ],
),
          ],
        ),
      ),
    ),
  );
}
}

image.png

mainAxisAlignment

Row中,主轴是水平方向,因此mainAxisAlignment控制水平对齐,而在Column中,主轴是垂直方向,因此mainAxisAlignment控制垂直对齐。

crossAxisAlignment

crossAxisAlignmentRow中控制垂直对齐,在Column中控制水平对齐。

2.16 Wrap (流式布局)

前面介绍的 Row 和 Column 都是单行和单列的,不可折行,超出屏幕会被截断。

接下来介绍的WrapFlow支持流式布局,内容溢出会自动折行显示,避免显示不全

import 'package:flutter/material.dart';

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

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Wrap Example',
      theme: ThemeData(
        primarySwatch: Colors.blue,
      ),
      home: Scaffold(
        appBar: AppBar(
          title: Text('Wrap Layout Example'),
        ),
        body: Center(
          child: Wrap(
            spacing: 8.0, // 子元素之间的间距
            runSpacing: 4.0, // 子元素行之间的间距
            alignment: WrapAlignment.center, // 子元素的对齐方式
            children: <Widget>[
              Chip(label: Text('Chip 1')),
              Chip(label: Text('Chip 2')),
              Chip(label: Text('Chip 3')),
              Chip(label: Text('Chip 4')),
              Chip(label: Text('Chip 5')),
              Chip(label: Text('Chip 6')),
              // 可以添加更多子元素来测试换行效果
            ],
          ),
        ),
      ),
    );
  }
}

image.png

在这个Wrap示例中,我们创建了一个包含多个Chip Widget的水平布局。由于使用了Wrap,当一行中的Chip数量达到屏幕宽度限制时,它们会自动换行到下一行。spacing属性控制子Widget之间的垂直间距(在这个例子中实际上由于水平排列所以看起来像是水平间距的效果,但在垂直方向上也会应用),runSpacing属性控制同一行内子Widget之间的水平间距。alignment属性控制子Widget在Wrap容器内的对齐方式。

2.17 Flow (流式布局)

在这个Flow示例中,我们创建了一个自定义的FlowDelegate来控制20个子Widget的布局。Flow本身是一个非常灵活的布局Widget,它允许你通过实现FlowDelegate来自定义子Widget的排列方式。在这个例子中,我们每行排列4个子Widget,并且当一行排满时换行到下一行。子Widget是彩色的容器,里面包含一个数字标签。

请注意,Flow的使用相对复杂一些,因为它需要你手动计算每个子Widget的位置和大小。在上面的例子中,我们使用了简单的数学运算来定位每个子Widget,但在实际应用中,你可能需要更复杂的逻辑来适应不同的布局需求。

2.18 LayoutBuilder 自定义适配布局

前面介绍的WrapFlow支持流式布局是显示不下自动换行,但是自定义显示多行,或者多列,什么条件触发都不好自定义。

LayoutBuilder

LayoutBuilder 是一个用于根据父组件提供的约束(Constraints)动态构建子组件的Widget。它通常用于需要根据可用空间来调整自身大小的场景。LayoutBuilder 接收一个回调函数,该回调函数接收一个BoxConstraints对象作为参数,并返回一个Widget。

也可以根据屏幕大小设计不同的uI,比如iPad上多列展示,iPhone上单列展示+菜单

LayoutBuilder(
 builder: (BuildContext context, BoxConstraints constraints) {
   // 根据constraints构建Widget
   // 例如,根据宽度或高度调整子组件的大小
   return Container(
     width: constraints.maxWidth * 0.5// 宽度为父组件宽度的一半
     height: constraints.maxHeight * 0.9, 
     color: Colors.blue,
  );
},
)

在这个例子中,LayoutBuilder 根据父组件提供的约束(constraints)来构建一个宽度是父组件一半、颜色为蓝色的Container

2.19 AfterLayout

当你在组件布局完成后需要回调去做一些事情时,AfterLayout就和适合。

AfterLayout 并不是Flutter框架中的一个直接组件或类。然而,当Widget的布局完成后,可以通过WidgetsBinding.instance.addPostFrameCallback来注册一个回调,这个回调会在当前帧的绘制完成后被调用。这可以用于执行一些需要在布局完成后才能进行的操作,比如获取Widget的实际大小或位置。

WidgetsBinding.instance.addPostFrameCallback((_) {
 // 布局完成后执行的代码
 // 例如,获取某个Widget的渲染框(RenderBox)并计算其大小或位置
 final renderBox = context.findRenderObject() as RenderBox;
 final size = renderBox.size;
 print('Widget size: $size');
});

在这个例子中,addPostFrameCallback 用于注册一个回调,该回调会在当前帧的绘制完成后执行。在回调中,我们使用context.findRenderObject()来获取与当前BuildContext关联的RenderBox对象,并打印出其大小。

需要注意的是,context.findRenderObject() 方法通常用于调试或特定情况下,因为它直接依赖于底层的渲染树。在大多数情况下,应该避免在Widget层直接操作渲染树。

三、容器

3.1 Container

Container是一个常用的组件,用于承载其他子组件,并提供布局和装饰功能。它支持设置边距、填充、背景色、边框、圆角等多种属性。

Container(
 margin: EdgeInsets.all(10),
 padding: EdgeInsets.all(20),
 color: Colors.grey,
 decorationBoxDecoration(
   border: Border.all(color: Colors.black),
   borderRadius: BorderRadius.circular(10),
),
 childText('Container示例'),
)

3.2 Padding(边距)

Padding组件用于为其子组件添加边距或填充。它可以通过EdgeInsets类来设置边距的大小,支持设置所有方向的统一边距、对称方向的边距以及单独方向的边距。

Padding(
 padding: EdgeInsets.all(20),
 child: Text('Padding示例'),
)

3.3 DecoratedBox(边框、阴影 等)

DecoratedBox组件用于为其子组件添加装饰效果,如背景色、边框、阴影等。它使用BoxDecoration类来定义装饰效果。

DecoratedBox(
 decorationBoxDecoration(
   color: Colors.blue,
   border: Border.all(color: Colors.black),
   borderRadius: BorderRadius.circular(10),
   boxShadow: [
     BoxShadow(color: Colors.black.withOpacity(0.5), blurRadius8offsetOffset(04)),
  ],
),
 childText('DecoratedBox示例'),
)

BoxDecoration类提供了多种属性来自定义装饰效果,包括颜色、图像、边框、圆角、阴影等。

3.4 Transform(变换)

Transform组件用于对其子组件进行矩阵变换,如平移、旋转和缩放。它使用Matrix4类来定义变换矩阵。

// 平移示例
Transform.translate(
 offsetOffset(500),
 childContainer(
   width100,
   height100,
   color: Colors.red,
   childCenter(childText('平移示例')),
),
)

// 旋转示例
Transform.rotate(
 angle0.5// 0.5弧度,即约28.65度
 childContainer(
   width100,
   height100,
   color: Colors.green,
   childCenter(childText('旋转示例')),
),
)

// 缩放示例
Transform.scale(
 scale0.5,
 childContainer(
   width100,
   height100,
   color: Colors.yellow,
   childCenter(childText('缩放示例')),
),
)

3.5 Clip(裁剪)、FittedBox(缩放)

Clip组件用于裁剪其子组件,支持多种裁剪形状,如矩形、圆形等。FittedBox组件则用于根据其父容器的大小来缩放其子组件。

// Clip示例
ClipRRect(
 borderRadius: BorderRadius.circular(10),
 childContainer(
   color: Colors.blue,
   width100,
   height100,
   childCenter(childText('Clip示例')),
),
)

// FittedBox示例
FittedBox(
 fit: BoxFit.cover, // 缩放方式
 child: Image.network(
   'https://example.com/image.jpg',
   fit: BoxFit.none, // 图片本身的缩放方式,这里不影响FittedBox
),
)

注意:FittedBox通常与图像或其他可缩放内容一起使用,以根据其父容器的大小进行缩放。

3.6 Scaffold(页面结构)

Scaffold是Flutter中用于构建页面结构的组件,它提供了对页面标题、侧边栏、底部导航和浮动按钮等元素的支持。

Scaffold(
 appBarAppBar(
   titleText('Scaffold示例'),
),
 bodyCenter(
   childText('页面内容'),
),
 floatingActionButtonFloatingActionButton(
   onPressed: () {},
   tooltip'点击我',
   childIcon(Icons.add),
),
 floatingActionButtonLocation: FloatingActionButtonLocation.centerDocked,
 bottomNavigationBarBottomNavigationBar(
   items: [
     BottomNavigationBarItem(
       iconIcon(Icons.home),
       label'首页',
    ),
     BottomNavigationBarItem(
       iconIcon(Icons.settings),
       label'设置',
    ),
  ],
),
)

四、滚动列表

4.1 SingleChildScrollView 可滚动列表

  • 概述:SingleChildScrollView是一个可滚动的视图,它只能包含单个子控件。当子控件的高度或宽度超过视口(viewport)时,可以使用SingleChildScrollView来实现滚动。
  • 数据量过多不建议使用,可以使用ListView
  • 示例
SingleChildScrollView(
 childColumn(
   children: <Widget>[
     Container(height100color: Colors.red),
     Container(height100color: Colors.green),
     Container(height100color: Colors.blue),
     Container(height100color: Colors.yellow),
  ],
),
);

4.2 ListView 可滚动列表

  • 概述:ListView是最常用的可滚动列表组件之一,用于在一个可滚动的列表中显示一系列的子控件。
  • 示例
ListView(
 children: <Widget>[
   ListTile(leadingIcon(Icons.map), titleText('Map')),
   ListTile(leadingIcon(Icons.photo_album), titleText('Album')),
   ListTile(leadingIcon(Icons.phone), titleText('Phone')),
],
);

当需要显示大量数据时,可以使用ListView.builder来避免同时创建所有子控件的问题:

ListView.builder(
 itemCount: items.length,
 itemBuilder: (context, index) {
   return ListTile(titleText('Item ${items[index]}'));
},
);

4.3 Slivers 复杂滚动视图基础

  • 概述:Slivers是Flutter中用于构建复杂滚动视图的基本构建块。它们通常与CustomScrollView一起使用,以实现自定义滚动效果。SliverAppBar是Slivers的一个常见示例,它用于在滚动视图顶部显示一个可伸缩的应用栏。
  • 示例(SliverAppBar):
CustomScrollView(
 slivers: <Widget>[
   SliverAppBar(
     titleText('Title'),
     expandedHeight200,
     flexibleSpaceFlexibleSpaceBar(
       background: Image.network('https://picsum.photos/200/300'fit: BoxFit.cover),
    ),
  ),
   // 其他Slivers...
],
);

4.4 SliverAppBar

SliverAppBar适用于需要在滚动页面中保持导航栏或标题栏的应用场景,如新闻应用、社交媒体应用等。它可以根据用户的滚动行为自动调整大小,从而提供更好的用户体验和视觉效果。

创建一个带有标题、背景图片和自定义高度的SliverAppBar:

SliverAppBar(
 titleText('SliverAppBar Title'),
 backgroundColor: Colors.purpleAccent,
 collapsedHeight60.0,
 expandedHeight200.0,
 floating: true,
 pinned: true,
 snap: true,
 flexibleSpaceFlexibleSpaceBar(
   titleText('Flexible Space Title'),
   background: Image.asset('images/head.jpg'fit: BoxFit.fill),
),
)

在这个示例中,我们设置了SliverAppBar的标题、背景颜色、折叠高度、展开高度、浮动、固定、立即展示等属性,并使用FlexibleSpaceBar组件在展开时显示自定义的标题和背景图片。

4.5 AnimatedList 操作动画列表

  • 概述:AnimatedList是一个StatefulWidget,它允许在添加或删除列表项时执行动画。
  • 示例
AnimatedList(
 key: globalKey,
 initialItemCount: data.length,
 itemBuilder: (context, index, animation) {
   return FadeTransition(
     opacity: animation,
     childListTile(
       keyValueKey(data[index]),
       titleText(data[index]),
       trailingIconButton(
         onPressed: () {
           onDelete(context, index);
        },
         icon: const Icon(Icons.delete, color: Colors.red, size30),
      ),
    ),
  );
},
);

// 删除和添加的逻辑...

4.6 GridView 网格列表

  • 概述:GridView是另一种常用的可滚动列表组件,它将子控件排列成网格布局。
  • 示例
GridView.count(
 crossAxisCount2,
 children: List.generate(8, (index) {
   return Container(
     color: Colors.blue,
     childCenter(childText('Item $index')),
  );
}),
);

当需要显示大量数据时,可以使用GridView.builder:

GridView.builder(
 gridDelegateSliverGridDelegateWithFixedCrossAxisCount(
   crossAxisCount2,
   mainAxisSpacing10.0,
   crossAxisSpacing10.0,
   childAspectRatio1.0,
),
 itemCount: items.length,
 itemBuilder: (context, index) {
   return Container(color: items[index]);
},
);

4.7 PageView 分页

PageView 是 Flutter 中用于实现分页滚动效果的组件。以下是一个简单的 PageView 示例,它展示了三张图片的分页滚动:

import 'package:flutter/material.dart';

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

class MyApp extends StatelessWidget {
 @override
 Widget build(BuildContext context) {
   return MaterialApp(
     homeScaffold(
       appBarAppBar(
         titleText('PageView 示例'),
      ),
       bodyPageView(
         children: [
           Image.network('https://picsum.photos/500/300?image=1'),
           Image.network('https://picsum.photos/500/300?image=2'),
           Image.network('https://picsum.photos/500/300?image=3'),
        ],
      ),
    ),
  );
}
}

image.png

4.8 TabView 选项卡切换

在 Flutter 中,TabBarTabBarView 通常一起使用来实现选项卡切换效果。以下是一个简单的 TabView 示例:

import 'package:flutter/material.dart';

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

class MyApp extends StatelessWidget {
 @override
 Widget build(BuildContext context) {
   return MaterialApp(
     homeDefaultTabController(
       length3,
       childScaffold(
         appBarAppBar(
           titleText('TabView 示例'),
           bottomTabBar(
             tabs: [
               Tab(iconIcon(Icons.directions_car), text'Car'),
               Tab(iconIcon(Icons.directions_transit), text'Transit'),
               Tab(iconIcon(Icons.directions_bike), text'Bike'),
            ],
          ),
        ),
         bodyTabBarView(
           children: [
             Icon(Icons.directions_car),
             Icon(Icons.directions_transit),
             Icon(Icons.directions_bike),
          ],
        ),
      ),
    ),
  );
}
}

image.png

注意:在实际应用中,TabBarView 的子组件通常是更复杂的布局或组件,而不仅仅是图标。

4.9 CustomScrollView 自定义的滚动视图

CustomScrollView 允许你创建自定义的滚动视图,它可以包含多种滚动模型,如 SliverListSliverGridSliverAppBar 等。以下是一个简单的 CustomScrollView 示例,它结合了 SliverAppBarSliverList

import 'package:flutter/material.dart';

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

class MyApp extends StatelessWidget {
 @override
 Widget build(BuildContext context) {
   return MaterialApp(
     homeScaffold(
       appBarAppBar(
         titleText('CustomScrollView 示例'),
      ),
       bodyCustomScrollView(
         slivers: <Widget>[
           SliverAppBar(
             titleText('Custom Scroll View'),
             expandedHeight200,
             flexibleSpaceFlexibleSpaceBar(
               background: Image.network(
                 'https://picsum.photos/200/300',
                 fit: BoxFit.cover,
              ),
            ),
          ),
           SliverList(
             delegateSliverChildBuilderDelegate(
              (context, index) {
                 return ListTile(
                   titleText('Item $index'),
                );
              },
               childCount20,
            ),
          ),
        ],
      ),
    ),
  );
}
}

image.png

4.10 NestedScrollView 嵌套滚动视图

NestedScrollView 允许你在滚动视图内部嵌套另一个滚动视图,通常用于实现复杂的滚动布局,如带有头部和可滚动内容的页面。以下是一个简单的 NestedScrollView 示例,它结合了 SliverAppBarColumn 内部的 ListView

import 'package:flutter/material.dart';

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

class MyApp extends StatelessWidget {
 @override
 Widget build(BuildContext context) {
   return MaterialApp(
     homeScaffold(
       appBarAppBar(
         titleText('NestedScrollView 示例'),
      ),
       bodyNestedScrollView(
         headerSliverBuilder: (context, value) {
           return <Widget>[
             SliverAppBar(
               titleText('Nested Scroll View'),
               expandedHeight200,
               flexibleSpaceFlexibleSpaceBar(
                 background: Image.network(
                   'https://picsum.photos/200/300',
                   fit: BoxFit.cover,
                ),
              ),
               pinned: true,
            ),
          ];
        },
         bodyColumn(
           children: <Widget>[
             Container(
               height150,
               color: Colors.grey[200],
               childCenter(childText('Fixed Header')),
            ),
             Expanded(
               child: ListView.builder(
                 itemCount20,
                 itemBuilder: (context, index) {
                   return ListTile(
                     titleText('Item $index'),
                  );
                },
              ),
            ),
          ],
        ),
      ),
    ),
  );
}
}

image.png

注意:在 NestedScrollViewbody 中使用 ColumnExpanded 时,Expanded 确保了 ListView 能够占用剩余的空间并允许其滚动。然而,这个简单的 NestedScrollView 示例可能不会完全按照预期工作,因为 NestedScrollView 通常与 Sliver 组件一起使用以实现更复杂的滚动效果。在实际应用中,你可能需要调整布局或使用 SliverToBoxAdapter 等组件来适应你的需求。

五、动画

在Flutter中,动画是提升用户体验和界面吸引力的关键元素。Flutter提供了一系列强大的动画组件,使得开发者能够轻松地创建各种复杂的动画效果。以下是一些Flutter中常用的动画组件:

5.1、核心动画组件

  1. Animation

    • 描述:Animation是Flutter动画系统中的核心抽象类,通常使用其子类如AnimationController来创建动画。它保存了动画的插值和状态。
    • 功能:可以通过监听其值的变化来更新UI,实现动画效果。
  2. AnimationController

    • 描述:AnimationController是动画控制器,用于控制动画的开始、停止、反向播放等。
    • 功能:它继承自Animation,并提供了forward()、stop()、reverse()等方法来控制动画的播放。
  3. Curve

    • 描述:Curve决定了动画执行的曲线,即动画变化的速率。
    • 功能:Flutter提供了多种预置的动画曲线,如线性(Curves.linear)、缓入(Curves.easeIn)、缓出(Curves.easeOut)等,也可以通过自定义Curve来实现特定的动画效果。
  4. Tween

    • 描述:Tween用于映射生成不同范围的值。因为AnimationController的动画值是double类型的,范围在0到1之间,所以Tween可以将这个值映射到其他范围,如颜色、尺寸等。
    • 功能:它提供了begin和end属性来指定映射的起始值和结束值,并通过animate()方法与AnimationController关联。

5.2、常用动画过渡组件

  1. FadeTransition

    • 描述:用于对透明度使用动画的组件。
    • 功能:通过改变opacity属性来实现淡入淡出的动画效果。
  2. ScaleTransition

    • 描述:用于对尺寸进行动画的组件。
    • 功能:通过改变scale属性来实现缩放动画效果。
  3. SizeTransition

    • 描述:用于对尺寸进行动画的组件,与ScaleTransition类似,但更灵活。
    • 功能:可以通过设置axisAlignment和animation属性来控制动画的方向和效果。
  4. RotationTransition

    • 描述:用于对旋转进行动画的组件。
    • 功能:通过改变turns属性来实现旋转动画效果。
  5. SlideTransition

    • 描述:用于对位置进行动画的组件。
    • 功能:通过改变position属性来实现滑动动画效果。
  6. AnimatedContainer

    • 描述:AnimatedContainer是Container的动画版本,可以通过修改动画过程中的尺寸、颜色、对齐方式等属性来实现容器的动画效果。
    • 功能:它提供了duration、curve等属性来控制动画的时长和曲线。
  7. AnimatedCrossFade

    • 描述:用于在两个子组件之间进行交叉淡入淡出的动画过渡。
    • 功能:通过crossFadeState属性来控制当前显示的子组件,并通过duration属性来控制动画的时长。

5.3、其他动画组件

  1. AnimatedBuilder

    • 描述:AnimatedBuilder是一个用于构建动画的通用组件。
    • 功能:它可以将动画效果和组件分离,使得动效可以应用于不同的组件。通过builder属性传入一个构建函数,该函数根据animation的值来构建组件。
  2. Hero

    • 描述:Hero动画用于在两个页面或组件之间共享和过渡相同的元素。
    • 功能:当元素从一个页面跳转到另一个页面时,Hero动画会保持该元素的外观和状态,并创建平滑的过渡效果。
  3. AnimatedDefaultTextStyle

    • 描述:用于文字样式动画效果的组件。
    • 功能:它允许开发者在动画过程中改变文字的样式,如字体大小、颜色等。

这些动画组件可以单独使用,也可以组合使用来创建更复杂的动画效果。通过合理地使用这些动画组件,开发者可以显著提升应用的用户体验和视觉效果。

5.4: 示例

5.4.1 使用 AnimationControllerTween 实现颜色渐变
import 'package:flutter/material.dart';

void main() => runApp(MyApp());

class MyApp extends StatelessWidget {
 @override
 Widget build(BuildContext context) {
   return MaterialApp(
     homeScaffold(
       appBarAppBar(titleText('Color Animation')),
       bodyColorAnimationDemo(),
    ),
  );
}
}

class ColorAnimationDemo extends StatefulWidget {
 @override
 _ColorAnimationDemoState createState() => _ColorAnimationDemoState();
}

class _ColorAnimationDemoState extends State<ColorAnimationDemo> with SingleTickerProviderStateMixin {
 late AnimationController _controller;
 late Animation<Color_animation;

 @override
 void initState() {
   super.initState();
   _controller = AnimationController(
     duration: const Duration(seconds2),
     vsync: this,
  )..repeat(reverse: true); // 反复播放动画,反向也播放

   _animation = ColorTween(
     begin: Colors.red,
     end: Colors.blue,
  ).animate(_controller);
}

 @override
 void dispose() {
   _controller.dispose();
   super.dispose();
}

 @override
 Widget build(BuildContext context) {
   return Center(
     childAnimatedBuilder(
       animation: _animation,
       childContainer(
         width100,
         height100,
         color: Colors.red, // 初始颜色,动画开始时会被覆盖
      ),
       builder: (context, child) {
         return Container(
           width100,
           height100,
           color: _animation.value,
        );
      },
    ),
  );
}
}
5.4.2: 使用 FadeTransition 实现淡入淡出
import 'package:flutter/material.dart';

void main() => runApp(MyApp());

class MyApp extends StatelessWidget {
 @override
 Widget build(BuildContext context) {
   return MaterialApp(
     homeScaffold(
       appBarAppBar(titleText('Fade Transition')),
       bodyFadeTransitionDemo(),
    ),
  );
}
}

class FadeTransitionDemo extends StatefulWidget {
 @override
 _FadeTransitionDemoState createState() => _FadeTransitionDemoState();
}

class _FadeTransitionDemoState extends State<FadeTransitionDemo> with SingleTickerProviderStateMixin {
 late AnimationController _controller;
 late Animation<double_animation;

 @override
 void initState() {
   super.initState();
   _controller = AnimationController(
     duration: const Duration(seconds2),
     vsync: this,
  )..forward(); // 开始播放动画

   _animation = Tween<double>(begin0.0end1.0).animate(_controller);
}

 @override
 void dispose() {
   _controller.dispose();
   super.dispose();
}

 @override
 Widget build(BuildContext context) {
   return Center(
     childFadeTransition(
       opacity: _animation,
       childContainer(
         width100,
         height100,
         color: Colors.blue,
      ),
    ),
  );
}
}
5.4.3: 使用 ScaleTransition 实现缩放
import 'package:flutter/material.dart';

void main() => runApp(MyApp());

class MyApp extends StatelessWidget {
 @override
 Widget build(BuildContext context) {
   return MaterialApp(
     homeScaffold(
       appBarAppBar(titleText('Scale Transition')),
       bodyScaleTransitionDemo(),
    ),
  );
}
}

class ScaleTransitionDemo extends StatefulWidget {
 @override
 _ScaleTransitionDemoState createState() => _ScaleTransitionDemoState();
}

class _ScaleTransitionDemoState extends State<ScaleTransitionDemo> with SingleTickerProviderStateMixin {
 late AnimationController _controller;
 late Animation<double_animation;

 @override
 void initState() {
   super.initState();
   _controller = AnimationController(
     duration: const Duration(seconds2),
     vsync: this,
  )..forward(); // 开始播放动画

   _animation = Tween<double>(begin0.5end1.5).animate(_controller);
}

 @override
 void dispose() {
   _controller.dispose();
   super.dispose();
}

 @override
 Widget build(BuildContext context) {
   return Center(
     childScaleTransition(
       scale: _animation,
       alignment: Alignment.center,
       childContainer(
         width100,
         height100,
         color: Colors.green,
      ),
    ),
  );
}
}