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

1,122 阅读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 类来设置。

maxLines、overflow:指定文本显示的最大行数

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

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,它代表文本的一个“片段”

使用 TextSpan 和 Text.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: () {
   // 处理点击事件
},
 style: ButtonStyle(
   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),
    ),
  ),
),
 child: Text('Custom Style Button'),
)

1.6 图标

Icon 组件

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

IconButton 显示图标的按钮

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

自定义图标

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

1.7 单选开关和复选框

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

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

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,则无行数限制。
  • maxLength和maxLengthEnforcement: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(
 value: 0.5, // 设置进度值,范围为0.0到1.0
 backgroundColor: Colors.grey[200], // 设置背景颜色
 valueColor: AlwaysStoppedAnimation<Color>(Colors.blue), // 设置进度条颜色
)

循环模式

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

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

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

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

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

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(
 totalSteps: 5, // 设置总步骤数
 currentStep: 3, // 设置当前步骤
 size: 24.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(
 value: 0.25, // 设置进度值,范围为0.0到1.0
 valueColor: AlwaysStoppedAnimation<Color>(Colors.pink), // 设置进度颜色
 backgroundColor: Colors.white, // 设置背景颜色
 borderColor: Colors.red, // 设置边框颜色
 borderWidth: 5.0, // 设置边框宽度
 direction: Axis.vertical, // 设置进度方向
 center: Text("加载中..."), // 设置中心文本
)

1.11 弹窗

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

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

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

showDialog(
 context: context,
 builder: (BuildContext context) {
   return SimpleDialog(
     title: Text('选择选项'),
     children: <Widget>[
       SimpleDialogOption(
         child: Text('选项1'),
         onPressed: () {
           Navigator.of(context).pop('选项1');
        },
      ),
       SimpleDialogOption(
         child: Text('选项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(
     title: Text('确认'),
     content: Text('您确定要继续吗?'),
     actions: <Widget>[
       TextButton(
         onPressed: () {
           Navigator.of(context).pop(false);
        },
         child: Text('取消'),
      ),
       TextButton(
         onPressed: () {
           Navigator.of(context).pop(true);
        },
         child: Text('确定'),
      ),
    ],
  );
},
).then((value) {
 if (value == true) {
   // 用户点击了确定
   print('用户确认了操作');
} else {
   // 用户点击了取消
   print('用户取消了操作');
}
});

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

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

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

showDialog(
 context: context,
 builder: (BuildContext context) {
   return Dialog(
     shape: RoundedRectangleBorder(
       borderRadius: BorderRadius.circular(0.0)
    ),
     backgroundColor: Colors.transparent,
     child: Container(
       decoration: BoxDecoration(
         color: Colors.white,
         borderRadius: BorderRadius.circular(0.0)
      ),
       constraints: BoxConstraints(
         maxWidth: MediaQuery.of(context).size.width,
         maxHeight: MediaQuery.of(context).size.height
      ),
       child: Column(
         mainAxisAlignment: MainAxisAlignment.center,
         children: <Widget>[
           Text('这是一个全屏对话框'),
           ElevatedButton(
             onPressed: () {
               Navigator.of(context).pop();
            },
             child: Text('关闭'),
          ),
        ],
      ),
    ),
  );
},
);

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',
     home: Scaffold(
       appBar: AppBar(
         title: Text('Flutter Form Example'),
      ),
       body: MyForm(),
    ),
  );
}
}

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,
     child: Column(
       crossAxisAlignment: CrossAxisAlignment.start,
       children: <Widget>[
         TextFormField(
           decoration: InputDecoration(
             labelText: '用户名',
             hintText: '请输入用户名',
             border: OutlineInputBorder(
               borderRadius: BorderRadius.all(Radius.circular(10)),
               borderSide: BorderSide(width: 1, color: Colors.grey),
            ),
          ),
           validator: (value) {
             if (value == null || value.isEmpty) {
               return '用户名不能为空';
            }
             return null;
          },
           onSaved: (value) {
             _username = value!;
          },
        ),
         Padding(
           padding: const EdgeInsets.symmetric(vertical: 16.0),
           child: TextFormField(
             decoration: InputDecoration(
               labelText: '密码',
               hintText: '请输入密码',
               border: OutlineInputBorder(
                 borderRadius: BorderRadius.all(Radius.circular(10)),
                 borderSide: BorderSide(width: 1, color: Colors.grey),
              ),
               suffixIcon: IconButton(
                 icon: Icon(Icons.visibility),
                 onPressed: () {
                   // 实现密码可见性切换的逻辑
                },
              ),
            ),
             obscureText: true,
             validator: (value) {
               if (value == null || value.isEmpty) {
                 return '密码不能为空';
              }
               return null;
            },
             onSaved: (value) {
               _password = value!;
            },
          ),
        ),
         Padding(
           padding: const EdgeInsets.symmetric(vertical: 16.0),
           child: ElevatedButton(
             onPressed: () {
               if (_formKey.currentState!.validate()) {
                 _formKey.currentState!.save();
                 ScaffoldMessenger.of(context).showSnackBar(
                   SnackBar(
                     content: Text('用户名: $_username, 密码: $_password'),
                  ),
                );
              }
            },
             child: Text('提交'),
          ),
        ),
      ],
    ),
  );
}
}

二、布局类组件

布局类组件都会包含一个或多个子组件,不同的布局类组件对子组件排列(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 通常与 CustomScrollView 和 SliverList、SliverGrid 等一起使用。以下是一个使用 SliverList 的简单示例:

CustomScrollView(
 slivers: <Widget>[
   SliverList(
     delegate: SliverChildListDelegate(
      [
         ListTile(title: Text('Item 1')),
         ListTile(title: Text('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(width: 50, height: 50, child: Placeholder()),
   // ...其他Widget
],
)

2.5 UnconstrainedBox 移除约束影响

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

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

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

2.6 AspectRatio

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

AspectRatio(
 aspectRatio: 16 / 9, // 宽高比
 child: Placeholder(),
)

2.7 LimitedBox

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

LimitedBox(
 maxWidth: 200,
 maxHeight: 100,
 child: Placeholder(),
)

2.8 FractionallySizedBox

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

FractionallySizedBox(
 widthFactor: 0.5, // 宽度占父Widget宽度的50%
 heightFactor: 0.3, // 高度占父Widget高度的30%
 child: Placeholder(),
)

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

2.9 层叠、绝对布局

顾名思义,层叠布局就是可以覆在Flutter中,Stack 和 Positioned 通常一起使用来创建一个重叠的子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 通过 top、bottom、left 和 right 属性来指定它相对于 Stack 容器的边界的距离。这些属性可以是固定的数值,也可以是相对于 Stack 尺寸的百分比(如果使用了 FractionalOffset 或者在布局构建函数中进行了相应的计算)。

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

2.11 Align 对齐方式

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

Align组件的属性

  • alignment:指定子组件在父组件中的对齐方式。该属性接受一个Alignment对象,该对象有两个属性x和y,取值范围为-1.0到1.0,分别表示相对于父组件宽度和高度的比例。例如,Alignment(-1.0, -1.0)表示左上角对齐,Alignment(1.0, 1.0)表示右下角对齐。Align组件也支持直接使用预设的对齐方式,如Alignment.topLeft、Alignment.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对象的x和y属性,我们可以将子组件对齐到父组件中的任何位置。例如,以下代码将子组件对齐到父组件的中心偏上位置:

Align(
 alignment: Alignment(0, -0.5), // 中心偏上位置
 child: Container(
   color: Colors.red,
   width: 50,
   height: 50,
),
)

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

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

2.13 Flex(弹性布局)

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

Flex 的主要属性包括:

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

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

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

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

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

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

2.14 Row(水平、线性布局)

Row的direction属性被固定为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(垂直、线性布局)

Column的direction属性被固定为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:

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

2.16 Wrap (流式布局)

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

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

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 自定义适配布局

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

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,
 decoration: BoxDecoration(
   border: Border.all(color: Colors.black),
   borderRadius: BorderRadius.circular(10),
),
 child: Text('Container示例'),
)

3.2 Padding(边距)

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

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

3.3 DecoratedBox(边框、阴影 等)

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

DecoratedBox(
 decoration: BoxDecoration(
   color: Colors.blue,
   border: Border.all(color: Colors.black),
   borderRadius: BorderRadius.circular(10),
   boxShadow: [
     BoxShadow(color: Colors.black.withOpacity(0.5), blurRadius: 8, offset: Offset(0, 4)),
  ],
),
 child: Text('DecoratedBox示例'),
)

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

3.4 Transform(变换)

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

// 平移示例
Transform.translate(
 offset: Offset(50, 0),
 child: Container(
   width: 100,
   height: 100,
   color: Colors.red,
   child: Center(child: Text('平移示例')),
),
)

// 旋转示例
Transform.rotate(
 angle: 0.5, // 0.5弧度,即约28.65度
 child: Container(
   width: 100,
   height: 100,
   color: Colors.green,
   child: Center(child: Text('旋转示例')),
),
)

// 缩放示例
Transform.scale(
 scale: 0.5,
 child: Container(
   width: 100,
   height: 100,
   color: Colors.yellow,
   child: Center(child: Text('缩放示例')),
),
)

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

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

// Clip示例
ClipRRect(
 borderRadius: BorderRadius.circular(10),
 child: Container(
   color: Colors.blue,
   width: 100,
   height: 100,
   child: Center(child: Text('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(
 appBar: AppBar(
   title: Text('Scaffold示例'),
),
 body: Center(
   child: Text('页面内容'),
),
 floatingActionButton: FloatingActionButton(
   onPressed: () {},
   tooltip: '点击我',
   child: Icon(Icons.add),
),
 floatingActionButtonLocation: FloatingActionButtonLocation.centerDocked,
 bottomNavigationBar: BottomNavigationBar(
   items: [
     BottomNavigationBarItem(
       icon: Icon(Icons.home),
       label: '首页',
    ),
     BottomNavigationBarItem(
       icon: Icon(Icons.settings),
       label: '设置',
    ),
  ],
),
)

四、滚动列表

4.1 SingleChildScrollView 可滚动列表

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

4.2 ListView 可滚动列表

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

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

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

4.3 Slivers 复杂滚动视图基础

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

4.4 SliverAppBar

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

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

SliverAppBar(
 title: Text('SliverAppBar Title'),
 backgroundColor: Colors.purpleAccent,
 collapsedHeight: 60.0,
 expandedHeight: 200.0,
 floating: true,
 pinned: true,
 snap: true,
 flexibleSpace: FlexibleSpaceBar(
   title: Text('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,
     child: ListTile(
       key: ValueKey(data[index]),
       title: Text(data[index]),
       trailing: IconButton(
         onPressed: () {
           onDelete(context, index);
        },
         icon: const Icon(Icons.delete, color: Colors.red, size: 30),
      ),
    ),
  );
},
);

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

4.6 GridView 网格列表

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

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

GridView.builder(
 gridDelegate: SliverGridDelegateWithFixedCrossAxisCount(
   crossAxisCount: 2,
   mainAxisSpacing: 10.0,
   crossAxisSpacing: 10.0,
   childAspectRatio: 1.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(
     home: Scaffold(
       appBar: AppBar(
         title: Text('PageView 示例'),
      ),
       body: PageView(
         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 中,TabBar 和 TabBarView 通常一起使用来实现选项卡切换效果。以下是一个简单的 TabView 示例:

import 'package:flutter/material.dart';

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

class MyApp extends StatelessWidget {
 @override
 Widget build(BuildContext context) {
   return MaterialApp(
     home: DefaultTabController(
       length: 3,
       child: Scaffold(
         appBar: AppBar(
           title: Text('TabView 示例'),
           bottom: TabBar(
             tabs: [
               Tab(icon: Icon(Icons.directions_car), text: 'Car'),
               Tab(icon: Icon(Icons.directions_transit), text: 'Transit'),
               Tab(icon: Icon(Icons.directions_bike), text: 'Bike'),
            ],
          ),
        ),
         body: TabBarView(
           children: [
             Icon(Icons.directions_car),
             Icon(Icons.directions_transit),
             Icon(Icons.directions_bike),
          ],
        ),
      ),
    ),
  );
}
}

image.png

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

4.9 CustomScrollView 自定义的滚动视图

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

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('CustomScrollView 示例'),
      ),
       body: CustomScrollView(
         slivers: <Widget>[
           SliverAppBar(
             title: Text('Custom Scroll View'),
             expandedHeight: 200,
             flexibleSpace: FlexibleSpaceBar(
               background: Image.network(
                 'https://picsum.photos/200/300',
                 fit: BoxFit.cover,
              ),
            ),
          ),
           SliverList(
             delegate: SliverChildBuilderDelegate(
              (context, index) {
                 return ListTile(
                   title: Text('Item $index'),
                );
              },
               childCount: 20,
            ),
          ),
        ],
      ),
    ),
  );
}
}

image.png

4.10 NestedScrollView 嵌套滚动视图

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

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('NestedScrollView 示例'),
      ),
       body: NestedScrollView(
         headerSliverBuilder: (context, value) {
           return <Widget>[
             SliverAppBar(
               title: Text('Nested Scroll View'),
               expandedHeight: 200,
               flexibleSpace: FlexibleSpaceBar(
                 background: Image.network(
                   'https://picsum.photos/200/300',
                   fit: BoxFit.cover,
                ),
              ),
               pinned: true,
            ),
          ];
        },
         body: Column(
           children: <Widget>[
             Container(
               height: 150,
               color: Colors.grey[200],
               child: Center(child: Text('Fixed Header')),
            ),
             Expanded(
               child: ListView.builder(
                 itemCount: 20,
                 itemBuilder: (context, index) {
                   return ListTile(
                     title: Text('Item $index'),
                  );
                },
              ),
            ),
          ],
        ),
      ),
    ),
  );
}
}

image.png

注意:在 NestedScrollView 的 body 中使用 Column 和 Expanded 时,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 使用 AnimationController 和 Tween 实现颜色渐变
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('Color Animation')),
       body: ColorAnimationDemo(),
    ),
  );
}
}

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(seconds: 2),
     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(
     child: AnimatedBuilder(
       animation: _animation,
       child: Container(
         width: 100,
         height: 100,
         color: Colors.red, // 初始颜色,动画开始时会被覆盖
      ),
       builder: (context, child) {
         return Container(
           width: 100,
           height: 100,
           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(
     home: Scaffold(
       appBar: AppBar(title: Text('Fade Transition')),
       body: FadeTransitionDemo(),
    ),
  );
}
}

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(seconds: 2),
     vsync: this,
  )..forward(); // 开始播放动画

   _animation = Tween<double>(begin: 0.0, end: 1.0).animate(_controller);
}

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

 @override
 Widget build(BuildContext context) {
   return Center(
     child: FadeTransition(
       opacity: _animation,
       child: Container(
         width: 100,
         height: 100,
         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(
     home: Scaffold(
       appBar: AppBar(title: Text('Scale Transition')),
       body: ScaleTransitionDemo(),
    ),
  );
}
}

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(seconds: 2),
     vsync: this,
  )..forward(); // 开始播放动画

   _animation = Tween<double>(begin: 0.5, end: 1.5).animate(_controller);
}

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

 @override
 Widget build(BuildContext context) {
   return Center(
     child: ScaleTransition(
       scale: _animation,
       alignment: Alignment.center,
       child: Container(
         width: 100,
         height: 100,
         color: Colors.green,
      ),
    ),
  );
}
}