Skip to content

3. flutter学习

名称连接备注
Flutter中文官网https://flutterchina.club
Flutter中文官方安装教程https://flutterchina.club/get-started/install/
dart官方语言https://dart.cn/
flutter官方文档https://docs.flutter.dev/get-started/install
flutter官方的中文文档https://docs.flutter.cn/release/archive

3.0 Helloworld

入口方法与函数

删除所有内容,输入以下内容,就是一个文本

dart
import 'package:flutter/material.dart';

void main() {
  runApp(const Center(
    child: Text(
      'Hello, World!',
      textDirection: TextDirection.ltr,
      style: TextStyle(fontSize: 24),
    ),
  ));
}

image-20250213210647206

可以修改颜色

dart
import 'package:flutter/material.dart';

void main() {
  runApp(const Center(
    child: Text(
      'Hello, World!',
      textDirection: TextDirection.ltr,
      style: TextStyle(fontSize: 24,
      color: Colors.red),
    ),
  ));
}

主题

使用MaterialApp 和 Scaffold两个组件装饰App

MaterialApp是一个方便的Widget,它封装了应用程序实现Material Design所需要的一些Widget。一 般作为顶层widget使用。 常用的属性:

  • home(主页)
  • title(标题)
  • color(颜色)
  • theme(主题)
  • routes(路由)

Scaffold

Scaffold 有下面几个主要属性:

  • appBar- 显示在界面顶部的一个 AppBar。
  • body-当前界面所显示的主要内容Widget。
  • drawer-抽屉菜单控件。
  • Scaffold是Material Design布局结构的基本实现。此类提供了用于显示drawer、snackbar和底部sheet
dart
import 'package:flutter/material.dart';

void main() {
  runApp(MaterialApp(
    home: Scaffold(
      appBar: AppBar(
        title: const Text('Hello World'),
      ),
      body: const Center(
        child: Text('Hello World'),
      ),
    ),
  ));
}

页面就会变成一下这个样子

image-20250213211231768

把flutter内容单独抽离成一个组件

在Futter中自定义組件其实就是一个类,这个类需要系StatelessWidget,StatefulWidget

前期我们都继承StatelessWidget。后期给大家讲StatefuIWidget的使用。

  • StatelessWidget 是无状态组件,状态不可变的widget
  • StatefulWidget 是有状态组件,持有的状态可能在widget生命周期改变
dart
import 'package:flutter/material.dart';


class MyApp extends StatelessWidget{

  @override
  Widget build(BuildContext context) {
    return const Center(
      child: Text('Hello World',
        textDirection: TextDirection.ltr,
        style: TextStyle(
          fontSize: 40,
          color: Colors.red,
        )
      ),
    );
  }

}

image-20250213211934657

Container组件

名称功能
alignmenttopCenter:顶部居中对齐topLeft:顶部左对齐topRight:顶部右对齐center:水平垂直居中对齐centerLeft:垂直居中水平居左对齐centerRight:垂直居中水。平居右对齐bottomCenter底部居中对齐bottomLeft:底部居左对齐。bottomRight:底部居右对齐
decorationdecoration: BoxDecoration( color: Colors.blue, border: Border.all( color:Colors.red, width: 2.0), borderRadius:BorderRadius.circular((8)),// 圆角 ,boxShadow: [ BoxShadow( color: Colors.blue, offset: Offset(2.0, 2.0),blurRadius: 10.0, ) ], ) //LinearGradient 背景线性渐变 RadialGradient径向渐变。gradient: LinearGradient( colors: [Colors.red, Colors.orange], ),
marginmargin属性是表示Container与外部其他组件的距离。 EdgeInsets.all(20.0),
paddingpadding就是Container的内边距,指Container边缘与Child之间的距离。padding:EdgeInsets.all(10.0)
transform让Container容易进行一些旋转之类的transform: Matrix4.rotationZ(0.2)
height容器高度
width容器宽度
child容器子元素
dart
import 'package:flutter/material.dart';


class MyApp extends StatelessWidget{

  @override
  Widget build(BuildContext context) {
    return Center(
      child: Container(
        alignment: Alignment.center,
        height: 200,
        width: 200,
        decoration: BoxDecoration(
            color: Colors.yellow,
            gradient: const LinearGradient(
//LinearGradient 背景线性渐变 RadialGradient径向渐变
              colors: [Colors.red, Colors.orange],
            ),
            boxShadow:const [
//卡片阴影
              BoxShadow(
                color: Colors.blue,
                offset: Offset(2.0, 2.0),
                blurRadius: 10.0,
              )
            ],
            border: Border.all(
                color: Colors.black,
                width: 1
            )
        ),
        transform:Matrix4.rotationZ(.2),
        child: const Text(
          "你好Flutter",
          style: TextStyle(fontSize: 20),
        ),
      ),
    );
  }

}

image-20250216151508762

创建按钮

dart
import 'package:flutter/material.dart';

class MyApp extends StatelessWidget{

  @override
  Widget build(BuildContext context) {
    return Center(
      child: Container(
        alignment: Alignment.center,
        height: 40,
        width: 200,
        margin: const EdgeInsets.fromLTRB(0, 20, 0, 0),
        decoration: BoxDecoration(
            color: Colors.blue,
            borderRadius: BorderRadius.circular(15)
        ),
        child: const Text(
          "按钮",
          style: TextStyle(fontSize: 20,color: Colors.white),
        ),
      ),
    );
  }

}

image-20250216164946788

Text组件

padding 和maring

padding 是让容器和里面的元素有相应的间距,margin是让容器和容器外部的其他容器有相应的间距

dart
Container(
margin: EdgeInsets.all(20.0), //容器外补白
color: Colors.orange,
child: Text("Hello world!"),
),
Container(
padding: EdgeInsets.all(20.0), //容器内补白
color: Colors.orange,
child: Text("Hello world!"),
),

平移

dart
transform: Matrix4.translationValues(-40,0,0),//位移
textAlign文本对齐方式(center居中,left左对齐,right右对齐,justfy两端对齐)
textDirection文本方向(ltr从左至右,rtl从右至左)
overflow文字超出屏幕之后的处理方式(clip裁剪,fade渐隐,ellipsis省略号)
textScaleFactor字体显示倍率
maxLines文字显示最大行数
style字体的样式设置

下面是 TextStyle 的参数

名称功能
decoration文字装饰线( none没有线, lineThrough删除线, overline上划线, underline下划线)
decorationColor文字装饰线颜色
decorationStyle文字装饰线风格([dashed,dotted]虚线, double两根线, solid一根实线, wavy波浪线)
wordSpacing单词间隙(如果是负值,会让单词变得更紧凑
letterSpacing字母间隙(如果是负值,会让字母变得更紧凑)
fontStyle文字样式(italic斜体, normal正常体)
fontSize文字大小
color文字颜色
fontWeight字体粗细(bold粗体, normal正常体)

图片

属性

名称类型说明
alignmentAlignment图片的对齐方式
color和colorBlendMode设置图片的背景颜色,通常和colorBlendMode配合一起使 用,这样可以是图片颜色和背景色混合。上面的图片就是进 行了颜色的混合,绿色背景和图片红色的混合
fitBoxFitfit属性用来控制图片的拉伸和挤压,这都是根据父容器来 的。 BoxFit.fill:全图显示,图片会被拉伸,并充满父容器。 BoxFit.contain:全图显示,显示原比例,可能会有空隙。BoxFit.cover:显示可能拉伸,可能裁切,充满(图片要充 满整个容器,还不变形)。 BoxFit.fitWidth:宽度充满(横 向充满),显示可能拉伸,可能裁切。 BoxFit.fitHeight :高度充满(竖向充满) ,显示可能拉伸,可能裁切。BoxFit.scaleDown:效果和contain差不多,但是此属性不 允许显示超过源图片大小,可小不可大。
repeat平铺ImageRepeat.repeat : 横向和纵向都进行重复,直到铺满整 个画布。 ImageRepeat.repeatX: 横向重复,纵向不重复。ImageRepeat.repeatY:纵向重复,横向不重复。
width宽度 一般结合ClipOval才能看到效果
height高度 一般结合ClipOval才能看到效果

加载网络图片

dart
import 'package:flutter/material.dart';

class MyApp extends StatelessWidget{

  @override
  Widget build(BuildContext context) {
    return Center(
      child: Container(
        height: 200,
        width: 200,
        decoration: BoxDecoration(
          color: Colors.yellow
        ),
        child: Image.network(
            "https://xiaowang.link/assets/image-20230930190308935.Dq3TvWDI.png",
          fit: BoxFit.fill,
        ),
      ),
    );
  }

}

加载网络图片(圆形)

dart
import 'package:flutter/material.dart';

void main() {
  runApp(MaterialApp(
    home: Scaffold(
        appBar: AppBar(
          title: const Text('Hello World'),
        ),
        body: Column(
          children: [MyApp(), Circle(),CircleMyAvatar()],
        )),
  ));
}

class MyApp extends StatelessWidget {
  const MyApp({Key? key}) : super(key: key);

  @override
  Widget build(BuildContext context) {
    return Center(
      child: Container(
        width: 150,
        height: 150,
        decoration: BoxDecoration(
            color: Colors.yellow,
            borderRadius: BorderRadius.circular(75),
            image: const DecorationImage(
                image: NetworkImage(
                  "https://www.itying.com/themes/itying/images/ionic4.png",
                ),
                fit: BoxFit.cover)),
      ),
    );
  }
}

class Circle extends StatelessWidget {
  const Circle({Key? key}) : super(key: key);

  @override
  Widget build(BuildContext context) {
    return CircleAvatar(
      radius: 50,
      backgroundImage:
          NetworkImage("https://www.itying.com/images/flutter/3.png"),
    );
  }
}

class CircleMyAvatar extends StatelessWidget {
  const CircleMyAvatar({Key? key}) : super(key: key);

  @override
  Widget build(BuildContext context) {
    return const CircleAvatar(
        radius: 110,
        backgroundColor: Color(0xffFDCF09),
        child: CircleAvatar(
          radius: 100,
          backgroundImage:
              NetworkImage("https://www.itying.com/images/flutter/3.png"),
        ));
  }
}

image-20250216180225222

加载本地图片

项目根目录新建images文件夹,images中新建2.x 3.x对应的文件

image-20250216180625913

然后,打开pubspec.yaml 声明一下添加的图片文件, 注意: 空格

yaml
flutter:

  assets:
    - images/1.jpeg
    - images/2.0x/1.jpeg
dart
import 'package:flutter/material.dart';

void main() {
  runApp(MaterialApp(
    home: Scaffold(
        appBar: AppBar(
          title: const Text('Hello World'),
        ),
        body: Column(
          children: [LocalImage()],
        )),
  ));
}

class LocalImage extends StatelessWidget {
  const LocalImage({Key? key}) : super(key: key);

  @override
  Widget build(BuildContext context) {
    return Container(
      width: 100,
      height: 100,
      alignment: Alignment.centerLeft,
      child: Image.asset('images/1.jpeg'),
    );
  }
}

图标

原始图标

dart
import 'package:flutter/material.dart';

void main() {
  runApp(MaterialApp(
      home: Scaffold(
    appBar: AppBar(
      title: const Text('Hello World'),
    ),
    body: MyApp(),
  )));
}

class MyApp extends StatelessWidget {
  const MyApp({Key? key}) : super(key: key);

  @override
  Widget build(BuildContext context) {
    return Center(
        child: Column(
      children: const [
        Icon(Icons.search, color: Colors.red, size: 40),
        SizedBox(height: 10),
        Icon(Icons.home),
        SizedBox(height: 10),
        Icon(Icons.category),
        SizedBox(height: 10),
        Icon(Icons.shop),
        SizedBox(height: 10),
      ],
    ));
  }
}

image-20250225191223016

自定义字体和图标

我们首先下载字体

image-20250225191825610

然后

image-20250225191950044

下载完后,将这几个文件写好

image-20250301123036427

编写代码

dart
import 'package:flutter/material.dart';

class MyIcons {
  static const IconData user = IconData(
    0xe52f, // unicode 值
    fontFamily: 'my', // 字体名称,与 pubspec.yaml 中定义的一致
  );

  static const IconData message = IconData(
    0xe61d,
    fontFamily: 'my',
  );

  static const IconData like = IconData(
    0xe61e,
    fontFamily: 'my',
  );
}

主文件中类

dart
import 'package:flutter/material.dart';
import 'fonts.dart'; // 导入自定义图标

void main() {
  runApp(MaterialApp(
      home: Scaffold(
        appBar: AppBar(
          title: const Text(
            '自定义图标演示',
          ),
        ),
        body: MyApp(),
      )));
}

class MyApp extends StatelessWidget {
  const MyApp({Key? key}) : super(key: key);

  @override
  Widget build(BuildContext context) {
    return Center(
        child: Column(
      children: const [
        // 使用第一个自定义图标
        Icon(
          MyIcons.user,
          size: 40,
          color: Colors.blue,
        ),
        SizedBox(height: 10),
        Text(
          '用户',
          style: TextStyle(
            fontFamily: 'AlimamaDaoLiTi',
            fontSize: 20,
            color: Colors.blue,
          ),
        ),
        SizedBox(height: 20),
        // 使用第二个自定义图标
        Icon(
          MyIcons.message,
          size: 40,
          color: Colors.green,
        ),
        SizedBox(height: 10),
        Text(
          '消息',
          style: TextStyle(
            fontFamily: 'AlimamaDaoLiTi',
            fontSize: 20,
            color: Colors.green,
          ),
        ),
        SizedBox(height: 20),
        // 使用第三个自定义图标
        Icon(
          MyIcons.like,
          size: 40,
          color: Colors.red,
        ),
        SizedBox(height: 10),
        Text(
          '喜欢',
          style: TextStyle(
            fontFamily: 'AlimamaDaoLiTi',
            fontSize: 20,
            color: Colors.red,
          ),
        ),
      ],
    ));
  }
}

ListView

名称类型描述
scrollDirectionAxisAxis.horizontal水平列表Axis.vertical垂直列表
paddingEdgeInsetsGeometry内边距
resolvebool组件反向排序
childrenList列表元素

静态ListView

dart
class ListViewMyApp extends StatelessWidget {
  const ListViewMyApp({Key? key}) : super(key: key);

  @override
  Widget build(BuildContext context) {
    return ListView(
        children: const <Widget>[
          ListTile(
            leading: Icon(Icons.home),
            title: Text('首页'),
          ),
          ListTile(
            leading: Icon(Icons.person),
            title: Text('我的'),
          ),
          ListTile(
            leading: Icon(Icons.search),
            title: Text('搜索'),
            trailing: Icon(Icons.arrow_forward),
          )
        ]
    );
  }
}

image-20250301124319835

dart
class ListViewDemo2 extends StatelessWidget {
  const ListViewDemo2({Key? key}) : super(key: key);

  @override
  Widget build(BuildContext context) {
    return ListView(
      padding: const EdgeInsets.all(16.0),
      children: <Widget>[
         ListTile(
          leading:const Icon(Icons.home),
          title: const Text('换新热潮节节攀升 消费市场焕发蓬勃生机'),
          subtitle: const Text('“非洲地区最现代化的铁路建设项目之一”(新时代中非合作)'),
          trailing:  Image.network("http://xiaowang.link/2.jpg"),
        ),
      ],
    );
  }
}

横向ListView

dart
class ListViewDemo3 extends StatelessWidget {
  const ListViewDemo3({Key? key}) : super(key: key);

  @override
  Widget build(BuildContext context) {
    return SizedBox(
      height: 200,
      child:  ListView(
        padding: const EdgeInsets.all(16.0),
        scrollDirection: Axis.horizontal,
        children: <Widget>[
          Container(
            height: 120,
            width: 200,
            child: Image.network("http://xiaowang.link/2.jpg"),
            decoration: BoxDecoration(
              border: Border.all(
                color: Colors.black,
                width: 1.0,
              ),
            ),
          ),
          Container(
            height: 120,
            width: 200,
            child: Image.network("http://xiaowang.link/2.jpg"),
            decoration: BoxDecoration(
              border: Border.all(
                color: Colors.black,
                width: 1.0,
              ),
            ),

          ),
          Container(
            height: 120,
            width: 200,
            child: Image.network("http://xiaowang.link/2.jpg"),
            decoration: BoxDecoration(
              color: Colors.yellow,
            ),
          ),
        ],
      )
    );
  }
}

image-20250301130954487

动态ListView

dart
import 'package:flutter/material.dart';

void main() {
  runApp(MaterialApp(
      home: Scaffold(
        appBar: AppBar(
          title: const Text(
            '动态列表示例',
          ),
        ),
        body: const DynamicListView(), // 使用新的动态列表组件
      )));
}

// 定义列表项数据类
class ListItem {
  final IconData icon;
  final String title;
  final Color color;

  const ListItem(this.icon, this.title, this.color);
}

// 添加一个新的动态列表组件
class DynamicListView extends StatefulWidget {
  const DynamicListView({Key? key}) : super(key: key);

  @override
  State<DynamicListView> createState() => _DynamicListViewState();
}

class _DynamicListViewState extends State<DynamicListView> {
  // 模拟的动态数据
  final List<Map<String, dynamic>> _items = [
    {
      'title': '新闻标题1',
      'subtitle': '新闻副标题1',
      'imageUrl': 'http://xiaowang.link/2.jpg',
      'time': '2024-03-20',
      'views': 1234
    },
    {
      'title': '新闻标题2',
      'subtitle': '新闻副标题2',
      'imageUrl': 'http://xiaowang.link/2.jpg',
      'time': '2024-03-21',
      'views': 2345
    },
    {
      'title': '新闻标题3',
      'subtitle': '新闻副标题3',
      'imageUrl': 'http://xiaowang.link/2.jpg',
      'time': '2024-03-22',
      'views': 3456
    },
  ];

  // 加载更多数据的方法
  void _loadMoreData() {
    setState(() {
      // 模拟添加新数据
      _items.add({
        'title': '新闻标题${_items.length + 1}',
        'subtitle': '新闻副标题${_items.length + 1}',
        'imageUrl': 'http://xiaowang.link/2.jpg',
        'time': '2025-03-23',
        'views': 4567
      });
    });
  }

  @override
  Widget build(BuildContext context) {
    return Column(
      children: [
        Expanded(
          child: ListView.builder(
            itemCount: _items.length,
            itemBuilder: (context, index) {
              final item = _items[index];
              return Card(
                margin: const EdgeInsets.all(8.0),
                child: ListTile(
                  leading: ClipRRect(
                    borderRadius: BorderRadius.circular(4),
                    child: Image.network(
                      item['imageUrl'],
                      width: 60,
                      height: 60,
                      fit: BoxFit.cover,
                    ),
                  ),
                  title: Text(
                    item['title'],
                    style: const TextStyle(
                      fontSize: 16,
                      fontWeight: FontWeight.bold,
                    ),
                  ),
                  subtitle: Column(
                    crossAxisAlignment: CrossAxisAlignment.start,
                    children: [
                      Text(
                        item['subtitle'],
                        style: const TextStyle(fontSize: 14),
                      ),
                      const SizedBox(height: 4),
                      Row(
                        children: [
                          Text(
                            item['time'],
                            style: TextStyle(
                              fontSize: 12,
                              color: Colors.grey[600],
                            ),
                          ),
                          const SizedBox(width: 16),
                          Icon(
                            Icons.remove_red_eye,
                            size: 16,
                            color: Colors.grey[600],
                          ),
                          const SizedBox(width: 4),
                          Text(
                            '${item['views']}',
                            style: TextStyle(
                              fontSize: 12,
                              color: Colors.grey[600],
                            ),
                          ),
                        ],
                      ),
                    ],
                  ),
                  onTap: () {
                    ScaffoldMessenger.of(context).showSnackBar(
                      SnackBar(
                        content: Text('点击了${item['title']}'),
                        duration: const Duration(seconds: 1),
                      ),
                    );
                  },
                ),
              );
            },
          ),
        ),
        ElevatedButton(
          onPressed: _loadMoreData,
          child: const Text('加载更多'),
        ),
      ],
    );
  }
}

image-20250301164405388

gridView

名称属性
scrollDirectionAxis滚动方法
paddingEdgeInsetsGeometry内边距
resolvebool组件反向排序
crossAxisSpacingdouble水平子Widget之间间 距
mainAxisSpacingdouble垂直子Widget之间间 距
crossAxisCountint 用在GridView.count一行的Widget数量
maxCrossAxisExtentdouble 用在GridView.extent横轴子元素的最大长 度
childAspectRatiodouble子Widget宽高比例
children[ ]
gridDelegateSliverGridDelegateWith FixedCrossAxisCount SliverGridDelegateWith MaxCrossAxisExtent控制布局主要用在 GridView.builder里 面

静态GridView

dart
import 'package:flutter/material.dart';

void main() {
  runApp(MaterialApp(
      home: Scaffold(
        appBar: AppBar(
          title: const Text(
            'GridView 示例',
          ),
        ),
        body: const GridViewDemo(),
      )));
}


class GridViewDemo extends StatelessWidget{

  const GridViewDemo({Key? key}) : super(key: key);


  @override
  Widget build(BuildContext context) {
    return GridView.count(
        crossAxisCount: 2,// 列数
        mainAxisSpacing: 10,
        crossAxisSpacing: 10,
        childAspectRatio: 1.5,
        children: [
          Container(
            color: Colors.red,
          ),
          Container(
            color: Colors.blue,
          ),
          Container(
            color: Colors.green,
          ),
          Container(
            color: Colors.yellow,
          ),
        ]
      );
  }
}

image-20250301165625822

GridView

dart
import 'package:flutter/material.dart';

void main() {
  runApp(MaterialApp(
      home: Scaffold(
        appBar: AppBar(
          title: const Text(
            'GridView 示例',
          ),
        ),
        body: const GridViewDemo(),
      )));
}


class GridViewDemo extends StatelessWidget{

  const GridViewDemo({Key? key}) : super(key: key);


  @override
  Widget build(BuildContext context) {
    return GridView.extent(
      // GridView.extent构造函数内部使用了SliverGridDelegateWithMaxCrossAxisExtent,
      // 我们通过它可以 快速的创建横轴子元素为固定最大长度的的GridView。
        maxCrossAxisExtent: 150,// 列数
        mainAxisSpacing: 10,// 垂直子Widget之间间 距
        crossAxisSpacing: 10, // 水平子Widget之间间 距
        childAspectRatio: 1.5, // 子Widget宽高比
        children: [
          Container(
            color: Colors.red,
          ),
          Container(
            color: Colors.blue,
          ),
          Container(
            color: Colors.green,
          ),
          Container(
            color: Colors.yellow,
          ),
        ]
      );
  }
}

动态的GridView

dart
import 'package:flutter/material.dart';

void main() {
  runApp(MaterialApp(
      home: Scaffold(
        appBar: AppBar(
          title: const Text(
            'GridView 示例',
          ),
        ),
        body: const GridViewDemo(),
      )));
}

class GridViewDemo extends StatefulWidget {
  const GridViewDemo({Key? key}) : super(key: key);

  @override
  State<GridViewDemo> createState() => _GridViewDemoState();
}

class _GridViewDemoState extends State<GridViewDemo> {
  // 模拟的网格数据
  final List<Map<String, dynamic>> _gridItems = [
    {
      'title': '新闻标题1',
      'imageUrl': 'http://xiaowang.link/2.jpg',
      'category': '科技',
      'views': 1234
    },
    {
      'title': '新闻标题2',
      'imageUrl': 'http://xiaowang.link/2.jpg',
      'category': '体育',
      'views': 2345
    },
    {
      'title': '新闻标题3',
      'imageUrl': 'http://xiaowang.link/2.jpg',
      'category': '娱乐',
      'views': 3456
    },
    {
      'title': '新闻标题4',
      'imageUrl': 'http://xiaowang.link/2.jpg',
      'category': '财经',
      'views': 4567
    },
    {
      'title': '新闻标题5',
      'imageUrl': 'http://xiaowang.link/2.jpg',
      'category': '教育',
      'views': 5678
    },
    {
      'title': '新闻标题6',
      'imageUrl': 'http://xiaowang.link/2.jpg',
      'category': '美食',
      'views': 6789
    },
  ];

  void _loadMoreData() {
    setState(() {
      _gridItems.add({
        'title': '新闻标题${_gridItems.length + 1}',
        'imageUrl': 'http://xiaowang.link/2.jpg',
        'category': '分类${_gridItems.length + 1}',
        'views': (_gridItems.length + 1) * 1000
      });
    });
  }

  @override
  Widget build(BuildContext context) {
    return Column(
      children: [
        Expanded(
          child: GridView.builder(
            padding: const EdgeInsets.all(8),
            gridDelegate: const SliverGridDelegateWithFixedCrossAxisCount(
              crossAxisCount: 2, // 每行显示2个
              mainAxisSpacing: 10, // 主轴间距
              crossAxisSpacing: 10, // 交叉轴间距
              childAspectRatio: 0.8, // 子项宽高比
            ),
            itemCount: _gridItems.length,
            itemBuilder: (context, index) {
              final item = _gridItems[index];
              return Card(
                elevation: 4,
                child: Column(
                  crossAxisAlignment: CrossAxisAlignment.start,
                  children: [
                    // 图片部分
                    Expanded(
                      flex: 3,
                      child: Container(
                        width: double.infinity,
                        decoration: BoxDecoration(
                          image: DecorationImage(
                            image: NetworkImage(item['imageUrl']),
                            fit: BoxFit.cover,
                          ),
                          borderRadius: const BorderRadius.only(
                            topLeft: Radius.circular(4),
                            topRight: Radius.circular(4),
                          ),
                        ),
                      ),
                    ),
                    // 文字内容部分
                    Expanded(
                      flex: 2,
                      child: Padding(
                        padding: const EdgeInsets.all(8.0),
                        child: Column(
                          crossAxisAlignment: CrossAxisAlignment.start,
                          mainAxisAlignment: MainAxisAlignment.spaceBetween,
                          children: [
                            Text(
                              item['title'],
                              style: const TextStyle(
                                fontSize: 16,
                                fontWeight: FontWeight.bold,
                              ),
                              maxLines: 2,
                              overflow: TextOverflow.ellipsis,
                            ),
                            Row(
                              mainAxisAlignment: MainAxisAlignment.spaceBetween,
                              children: [
                                Container(
                                  padding: const EdgeInsets.symmetric(
                                    horizontal: 8,
                                    vertical: 2,
                                  ),
                                  decoration: BoxDecoration(
                                    color: Colors.blue.withOpacity(0.1),
                                    borderRadius: BorderRadius.circular(12),
                                  ),
                                  child: Text(
                                    item['category'],
                                    style: const TextStyle(
                                      fontSize: 12,
                                      color: Colors.blue,
                                    ),
                                  ),
                                ),
                                Row(
                                  children: [
                                    const Icon(
                                      Icons.remove_red_eye,
                                      size: 14,
                                      color: Colors.grey,
                                    ),
                                    const SizedBox(width: 4),
                                    Text(
                                      '${item['views']}',
                                      style: const TextStyle(
                                        fontSize: 12,
                                        color: Colors.grey,
                                      ),
                                    ),
                                  ],
                                ),
                              ],
                            ),
                          ],
                        ),
                      ),
                    ),
                  ],
                ),
              );
            },
          ),
        ),
        ElevatedButton(
          onPressed: _loadMoreData,
          child: const Text('加载更多'),
        ),
      ],
    );
  }
}

image-20250301165118756

页面布局

Padding,Row

*属性**说明*
paddingpadding值, EdgeInsetss设置填充的值
child子组件
dart
import 'package:flutter/material.dart';

void main() {
  runApp(MaterialApp(
      home: Scaffold(
        appBar: AppBar(
          title: const Text(
            '基础布局示例',
          ),
        ),
        body: const BasicLayoutDemo(),
      )));
}

class BasicLayoutDemo extends StatelessWidget {
  const BasicLayoutDemo({Key? key}) : super(key: key);

  @override
  Widget build(BuildContext context) {
    return Column(
      children: [
        Row(
          children: [
            Expanded(
              child: Padding(
                padding: const EdgeInsets.all(8.0),
                child: Container(
                  height: 100,
                  color: Colors.red,
                  child: const Center(
                    child: Text(
                      '红色方块',
                      style: TextStyle(
                        color: Colors.white,
                        fontSize: 20,
                        fontWeight: FontWeight.bold,
                        shadows: [
                          Shadow(
                            color: Colors.black54,
                            offset: Offset(2, 2),
                            blurRadius: 3,
                          ),
                        ],
                      ),
                    ),
                  ),
                ),
              ),
            ),
            Expanded(
              child: Padding(
                padding: const EdgeInsets.all(8.0),
                child: Container(
                  height: 100,
                  color: Colors.blue,
                  child: const Center(
                    child: Text(
                      '蓝色方块',
                      style: TextStyle(
                        color: Colors.white,
                        fontSize: 20,
                        fontWeight: FontWeight.bold,
                        shadows: [
                          Shadow(
                            color: Colors.black54,
                            offset: Offset(2, 2),
                            blurRadius: 3,
                          ),
                        ],
                      ),
                    ),
                  ),
                ),
              ),
            ),
          ],
        ),
        Row(
          children: [
            Expanded(
              child: Padding(
                padding: const EdgeInsets.all(8.0),
                child: Container(
                  height: 100,
                  color: Colors.green,
                  child: const Center(
                    child: Text(
                      '绿色方块',
                      style: TextStyle(
                        color: Colors.white,
                        fontSize: 20,
                        fontWeight: FontWeight.bold,
                        shadows: [
                          Shadow(
                            color: Colors.black54,
                            offset: Offset(2, 2),
                            blurRadius: 3,
                          ),
                        ],
                      ),
                    ),
                  ),
                ),
              ),
            ),
            Expanded(
              child: Padding(
                padding: const EdgeInsets.all(8.0),
                child: Container(
                  height: 100,
                  color: Colors.yellow,
                  child: const Center(
                    child: Text(
                      '黄色方块',
                      style: TextStyle(
                        color: Colors.black87,
                        fontSize: 20,
                        fontWeight: FontWeight.bold,
                        shadows: [
                          Shadow(
                            color: Colors.white54,
                            offset: Offset(2, 2),
                            blurRadius: 3,
                          ),
                        ],
                      ),
                    ),
                  ),
                ),
              ),
            ),
          ],
        ),
      ],
    );
  }
}

image-20250301171612995

Row水平布局组件

*属性**说明*
mainAxisAlignment主轴的排序方式
crossAxisAlignment次轴的排序方式
children组件子元素

Column垂直布局组件

*属性**说明*
mainAxisAlignment主轴的排序方式
crossAxisAlignment次轴的排序方式
children组件子元素
dart
import 'package:flutter/material.dart';

void main() {
  runApp(MaterialApp(
      home: Scaffold(
        appBar: AppBar(
          title: const Text(
            '垂直布局示例'
          ),
        ),
        body: const ColumnLayoutDemo(),
      )));
}

class ColumnLayoutDemo extends StatelessWidget {
  const ColumnLayoutDemo({Key? key}) : super(key: key);

  @override
  Widget build(BuildContext context) {
    return Padding(
      padding: const EdgeInsets.all(16.0),
      child: Column(
        crossAxisAlignment: CrossAxisAlignment.stretch, // 让子组件横向撑满
        children: [
          Container(
            height: 80,
            decoration: BoxDecoration(
              color: Colors.blue[200],
              borderRadius: BorderRadius.circular(8),
              boxShadow: const [
                BoxShadow(
                  color: Colors.black26,
                  offset: Offset(0, 2),
                  blurRadius: 4,
                ),
              ],
            ),
            child: const Center(
              child: Text(
                '第一行内容',
                style: TextStyle(
                  fontSize: 18,
                  color: Colors.white,
                  fontWeight: FontWeight.bold,
                ),
              ),
            ),
          ),
          const SizedBox(height: 16), // 间距
          Container(
            height: 120,
            decoration: BoxDecoration(
              color: Colors.green[300],
              borderRadius: BorderRadius.circular(8),
              boxShadow: const [
                BoxShadow(
                  color: Colors.black26,
                  offset: Offset(0, 2),
                  blurRadius: 4,
                ),
              ],
            ),
            child: const Center(
              child: Text(
                '第二行内容',
                style: TextStyle(
                  fontSize: 18,
                  color: Colors.white,
                  fontWeight: FontWeight.bold,
                ),
              ),
            ),
          ),
          const SizedBox(height: 16), // 间距
          Container(
            height: 100,
            decoration: BoxDecoration(
              color: Colors.orange[300],
              borderRadius: BorderRadius.circular(8),
              boxShadow: const [
                BoxShadow(
                  color: Colors.black26,
                  offset: Offset(0, 2),
                  blurRadius: 4,
                ),
              ],
            ),
            child: const Center(
              child: Text(
                '第三行内容',
                style: TextStyle(
                  fontSize: 18,
                  color: Colors.white,
                  fontWeight: FontWeight.bold,
                ),
              ),
            ),
          ),
        ],
      ),
    );
  }
}

image-20250301172033533

弹性布局Flex

dart
import 'package:flutter/material.dart';

void main() {
  runApp(MaterialApp(
      home: Scaffold(
    appBar: AppBar(
      title: const Text('Flex组件示例'),
    ),
    body: const FlexDemo(),
  )));
}

class FlexDemo extends StatelessWidget {
  const FlexDemo({Key? key}) : super(key: key);

  @override
  Widget build(BuildContext context) {
    return Padding(
      padding: const EdgeInsets.all(16.0),
      child: Column(
        crossAxisAlignment: CrossAxisAlignment.start,
        children: [
          const Text(
            '不使用Flex的普通Row布局:',
            style: TextStyle(fontSize: 16, fontWeight: FontWeight.bold),
          ),
          const SizedBox(height: 10),
          Row(
            children: [
              Container(
                width: 80,
                height: 50,
                color: Colors.blue,
                child: const Center(
                  child: Text(
                    '固定宽度',
                    style: TextStyle(color: Colors.white),
                  ),
                ),
              ),
              const SizedBox(width: 10),
              Container(
                width: 120,
                height: 50,
                color: Colors.green,
                child: const Center(
                  child: Text(
                    '固定宽度',
                    style: TextStyle(color: Colors.white),
                  ),
                ),
              ),
            ],
          ),
          const SizedBox(height: 30),
          const Text(
            '使用Flex的水平布局:',
            style: TextStyle(fontSize: 16, fontWeight: FontWeight.bold),
          ),
          const SizedBox(height: 10),
          Flex(
            direction: Axis.horizontal,
            children: [
              Flexible(
                flex: 1,
                child: Container(
                  height: 50,
                  color: Colors.blue,
                  child: const Center(
                    child: Text(
                      'flex: 1',
                      style: TextStyle(color: Colors.white),
                    ),
                  ),
                ),
              ),
              const SizedBox(width: 10),
              Flexible(
                flex: 2,
                child: Container(
                  height: 50,
                  color: Colors.green,
                  child: const Center(
                    child: Text(
                      'flex: 2',
                      style: TextStyle(color: Colors.white),
                    ),
                  ),
                ),
              ),
            ],
          ),
          const SizedBox(height: 30),
          const Text(
            '使用Flex的垂直布局:',
            style: TextStyle(fontSize: 16, fontWeight: FontWeight.bold),
          ),
          const SizedBox(height: 10),
          Container(
            height: 200,
            color: Colors.grey[200],
            child: Flex(
              direction: Axis.vertical,
              children: [
                Flexible(
                  flex: 2,
                  child: Container(
                    color: Colors.orange,
                    child: const Center(
                      child: Text(
                        'flex: 2',
                        style: TextStyle(color: Colors.white),
                      ),
                    ),
                  ),
                ),
                const SizedBox(height: 10),
                Flexible(
                  flex: 1,
                  child: Container(
                    color: Colors.purple,
                    child: const Center(
                      child: Text(
                        'flex: 1',
                        style: TextStyle(color: Colors.white),
                      ),
                    ),
                  ),
                ),
              ],
            ),
          ),
        ],
      ),
    );
  }
}

image-20250301173220116

Expand组件

dart
import 'package:flutter/material.dart';

void main() {
  runApp(MaterialApp(
      home: Scaffold(
    appBar: AppBar(
      title: const Text('Expanded组件示例'),
    ),
    body: const ExpandedDemo(),
  )));
}

class ExpandedDemo extends StatelessWidget {
  const ExpandedDemo({Key? key}) : super(key: key);

  @override
  Widget build(BuildContext context) {
    return Padding(
      padding: const EdgeInsets.all(16.0),
      child: Column(
        crossAxisAlignment: CrossAxisAlignment.start,
        children: [
          const Text(
            '不使用Expanded的效果:',
            style: TextStyle(fontSize: 16, fontWeight: FontWeight.bold),
          ),
          const SizedBox(height: 10),
          Row(
            children: [
              Container(
                width: 100,
                height: 50,
                color: Colors.blue,
                child: const Center(
                  child: Text(
                    '固定宽度',
                    style: TextStyle(color: Colors.white),
                  ),
                ),
              ),
              Container(
                width: 100,
                height: 50,
                color: Colors.green,
                child: const Center(
                  child: Text(
                    '固定宽度',
                    style: TextStyle(color: Colors.white),
                  ),
                ),
              ),
            ],
          ),
          const SizedBox(height: 30),
          const Text(
            '使用Expanded的效果:',
            style: TextStyle(fontSize: 16, fontWeight: FontWeight.bold),
          ),
          const SizedBox(height: 10),
          Row(
            children: [
              Expanded(
                flex: 1,
                child: Container(
                  height: 50,
                  color: Colors.blue,
                  child: const Center(
                    child: Text(
                      'flex: 1',
                      style: TextStyle(color: Colors.white),
                    ),
                  ),
                ),
              ),
              const SizedBox(width: 10),
              Expanded(
                flex: 2,
                child: Container(
                  height: 50,
                  color: Colors.green,
                  child: const Center(
                    child: Text(
                      'flex: 2',
                      style: TextStyle(color: Colors.white),
                    ),
                  ),
                ),
              ),
            ],
          ),
          const SizedBox(height: 20),
          Row(
            children: [
              Container(
                width: 100,
                height: 50,
                color: Colors.orange,
                child: const Center(
                  child: Text(
                    '固定宽度',
                    style: TextStyle(color: Colors.white),
                  ),
                ),
              ),
              const SizedBox(width: 10),
              Expanded(
                child: Container(
                  height: 50,
                  color: Colors.purple,
                  child: const Center(
                    child: Text(
                      '自适应宽度',
                      style: TextStyle(color: Colors.white),
                    ),
                  ),
                ),
              ),
            ],
          ),
        ],
      ),
    );
  }
}

image-20250301172547637

Stack和Position

dart
import 'package:flutter/material.dart';

void main() {
  runApp(MaterialApp(
      home: Scaffold(
    appBar: AppBar(
      title: const Text('Stack和Positioned示例'),
    ),
    body: const StackDemo(),
  )));
}

class StackDemo extends StatelessWidget {
  const StackDemo({Key? key}) : super(key: key);

  @override
  Widget build(BuildContext context) {
    return Padding(
      padding: const EdgeInsets.all(16.0),
      child: Column(
        crossAxisAlignment: CrossAxisAlignment.start,
        children: [
          const Text(
            '不使用Stack的普通布局:',
            style: TextStyle(fontSize: 16, fontWeight: FontWeight.bold),
          ),
          const SizedBox(height: 10),
          Container(
            width: double.infinity,
            height: 150,
            color: Colors.grey[200],
            child: Column(
              mainAxisAlignment: MainAxisAlignment.center,
              children: [
                Container(
                  width: 100,
                  height: 100,
                  color: Colors.blue,
                  child: const Center(
                    child: Text(
                      '普通布局',
                      style: TextStyle(color: Colors.white),
                    ),
                  ),
                ),
              ],
            ),
          ),
          const SizedBox(height: 30),
          const Text(
            '使用Stack的层叠布局:',
            style: TextStyle(fontSize: 16, fontWeight: FontWeight.bold),
          ),
          const SizedBox(height: 10),
          Container(
            width: double.infinity,
            height: 150,
            color: Colors.grey[200],
            child: Stack(
              children: [
                Container(
                  width: 100,
                  height: 100,
                  color: Colors.blue,
                  child: const Center(
                    child: Text(
                      '底层',
                      style: TextStyle(color: Colors.white),
                    ),
                  ),
                ),
                Positioned(
                  left: 50,
                  top: 50,
                  child: Container(
                    width: 100,
                    height: 100,
                    color: Colors.green.withOpacity(0.8),
                    child: const Center(
                      child: Text(
                        '层叠1',
                        style: TextStyle(color: Colors.white),
                      ),
                    ),
                  ),
                ),
                Positioned(
                  left: 100,
                  top: 20,
                  child: Container(
                    width: 80,
                    height: 80,
                    color: Colors.red.withOpacity(0.8),
                    child: const Center(
                      child: Text(
                        '层叠2',
                        style: TextStyle(color: Colors.white),
                      ),
                    ),
                  ),
                ),
              ],
            ),
          ),
          const SizedBox(height: 30),
          const Text(
            '使用Stack实现定位布局:',
            style: TextStyle(fontSize: 16, fontWeight: FontWeight.bold),
          ),
          const SizedBox(height: 10),
          Container(
            width: double.infinity,
            height: 150,
            color: Colors.grey[200],
            child: Stack(
              children: [
                Positioned.fill(
                  child: Container(
                    color: Colors.blue[100],
                    child: const Center(
                      child: Text('背景层'),
                    ),
                  ),
                ),
                Positioned(
                  right: 10,
                  top: 10,
                  child: Container(
                    padding: const EdgeInsets.all(8),
                    color: Colors.red,
                    child: const Text(
                      '右上角',
                      style: TextStyle(color: Colors.white),
                    ),
                  ),
                ),
                Positioned(
                  left: 10,
                  bottom: 10,
                  child: Container(
                    padding: const EdgeInsets.all(8),
                    color: Colors.green,
                    child: const Text(
                      '左下角',
                      style: TextStyle(color: Colors.white),
                    ),
                  ),
                ),
              ],
            ),
          ),
        ],
      ),
    );
  }
}

image-20250301174612320

Align

Align 组件可以调整子组件的位置 , Stack组件中结合Align组件也可以控制每个子元素的显示位置

属性说明
alignment配置所有子元素的显示位置
child子组件

AspectRatio

AspectRatio的作用是根据设置调整子元素child的宽高比。

AspectRatio首先会在布局限制条件允许的范围内尽可能的扩展, widget的高度是由宽度和比率决定 的,类似于BoxFit中的contain,按照固定比率去尽量占满区域。

如果在满足所有限制条件过后无法找到一个可行的尺寸, AspectRatio最终将会去优先适应布局限制条 件,而忽略所设置的比率。

*属性**说明*
aspectRatio宽高比,最终可能不会根据这个值去布局,具体则要看综合因素,外层是否允许 按照这种比率进行布局,这只是一个参考值
child子组件
dart
import 'package:flutter/material.dart';

void main() {
  runApp(MaterialApp(
      home: Scaffold(
    appBar: AppBar(
      title: const Text('AspectRatio示例'),
    ),
    body: const AspectRatioDemo(),
  )));
}

class AspectRatioDemo extends StatelessWidget {
  const AspectRatioDemo({Key? key}) : super(key: key);

  @override
  Widget build(BuildContext context) {
    return Padding(
      padding: const EdgeInsets.all(16.0),
      child: Column(
        crossAxisAlignment: CrossAxisAlignment.start,
        children: [
          const Text(
            '不使用AspectRatio的普通容器:',
            style: TextStyle(fontSize: 16, fontWeight: FontWeight.bold),
          ),
          const SizedBox(height: 10),
          Container(
            width: 200,
            height: 100,
            color: Colors.blue,
            child: const Center(
              child: Text(
                '固定尺寸\n200 x 100',
                textAlign: TextAlign.center,
                style: TextStyle(color: Colors.white),
              ),
            ),
          ),
          const SizedBox(height: 30),
          const Text(
            '使用AspectRatio (aspectRatio: 16/9):',
            style: TextStyle(fontSize: 16, fontWeight: FontWeight.bold),
          ),
          const SizedBox(height: 10),
          Container(
            width: double.infinity,
            color: Colors.grey[200],
            child: AspectRatio(
              aspectRatio: 16 / 9, // 16:9 宽高比
              child: Container(
                color: Colors.green,
                child: const Center(
                  child: Text(
                    '16:9 宽高比\n会随着宽度自动调整高度',
                    textAlign: TextAlign.center,
                    style: TextStyle(color: Colors.white),
                  ),
                ),
              ),
            ),
          ),
          const SizedBox(height: 30),
          const Text(
            '使用AspectRatio (aspectRatio: 1):',
            style: TextStyle(fontSize: 16, fontWeight: FontWeight.bold),
          ),
          const SizedBox(height: 10),
          Row(
            children: [
              Container(
                width: 150,
                color: Colors.grey[200],
                child: AspectRatio(
                  aspectRatio: 1, // 1:1 正方形
                  child: Container(
                    color: Colors.orange,
                    child: const Center(
                      child: Text(
                        '1:1\n正方形',
                        textAlign: TextAlign.center,
                        style: TextStyle(color: Colors.white),
                      ),
                    ),
                  ),
                ),
              ),
              const SizedBox(width: 20),
              Expanded(
                child: Container(
                  color: Colors.grey[200],
                  child: AspectRatio(
                    aspectRatio: 1, // 1:1 正方形
                    child: Container(
                      color: Colors.purple,
                      child: const Center(
                        child: Text(
                          '1:1\n自适应宽度的正方形',
                          textAlign: TextAlign.center,
                          style: TextStyle(color: Colors.white),
                        ),
                      ),
                    ),
                  ),
                ),
              ),
            ],
          ),
        ],
      ),
    );
  }
}

image-20250301181139868

Card

Card是卡片组件块,内容可以由大多数类型的Widget构成, Card具有圆角和阴影,这让它看起来有立 体感

*属性**说明*
margin外边距
child子组件
elevation阴影值的深度
color背景颜色
shadowColor阴影颜色
margin外边距
clipBehaviorclipBehavior 内容溢出的剪切方式 Clip.none不剪切 Clip.hardEdge裁剪但不应 用抗锯齿 Clip.antiAlias裁剪而且抗锯齿 Clip.antiAliasWithSaveLayer带有抗锯 齿的剪辑,并在剪辑之后立即保存saveLayer
ShapeCard的阴影效果,默认的阴影效果为圆角的长方形边。 shape: const RoundedRectangleBorder(borderRadius: BorderRadius.all(Radius.circular(10)) ),

按钮

ElevatedButton

文本按钮TextButton

边框按钮OutlinedButton

图标按钮IconButton

dart
import 'package:flutter/material.dart';

void main() {
  runApp(MaterialApp(
      home: Scaffold(
    appBar: AppBar(
      title: const Text('Flutter按钮示例'),
    ),
    body: const ButtonsDemo(),
  )));
}

class ButtonsDemo extends StatelessWidget {
  const ButtonsDemo({Key? key}) : super(key: key);

  @override
  Widget build(BuildContext context) {
    return SingleChildScrollView(
      padding: const EdgeInsets.all(16.0),
      child: Column(
        crossAxisAlignment: CrossAxisAlignment.start,
        children: [
          _buildSection('ElevatedButton(凸起按钮):', [
            ElevatedButton(
              onPressed: () {},
              child: const Text('基础样式'),
            ),
            ElevatedButton(
              onPressed: () {},
              style: ElevatedButton.styleFrom(
                backgroundColor: Colors.green,
                foregroundColor: Colors.white,
                padding:
                    const EdgeInsets.symmetric(horizontal: 30, vertical: 15),
                textStyle: const TextStyle(fontSize: 18),
              ),
              child: const Text('自定义样式'),
            ),
            ElevatedButton.icon(
              onPressed: () {},
              icon: const Icon(Icons.add),
              label: const Text('带图标的按钮'),
            ),
            ElevatedButton(
              onPressed: null, // null 表示按钮禁用
              child: const Text('禁用状态'),
            ),
          ]),
          _buildSection('TextButton(文本按钮):', [
            TextButton(
              onPressed: () {},
              child: const Text('基础样式'),
            ),
            TextButton(
              onPressed: () {},
              style: TextButton.styleFrom(
                foregroundColor: Colors.green,
                textStyle: const TextStyle(fontSize: 18),
                padding: const EdgeInsets.all(16),
              ),
              child: const Text('自定义样式'),
            ),
            TextButton.icon(
              onPressed: () {},
              icon: const Icon(Icons.favorite),
              label: const Text('带图标的按钮'),
            ),
          ]),
          _buildSection('OutlinedButton(轮廓按钮):', [
            OutlinedButton(
              onPressed: () {},
              child: const Text('基础样式'),
            ),
            OutlinedButton(
              onPressed: () {},
              style: OutlinedButton.styleFrom(
                foregroundColor: Colors.orange,
                side: const BorderSide(color: Colors.orange, width: 2),
                padding:
                    const EdgeInsets.symmetric(horizontal: 30, vertical: 15),
                textStyle: const TextStyle(fontSize: 18),
              ),
              child: const Text('自定义样式'),
            ),
            OutlinedButton.icon(
              onPressed: () {},
              icon: const Icon(Icons.save),
              label: const Text('带图标的按钮'),
            ),
          ]),
          _buildSection('IconButton(图标按钮):', [
            IconButton(
              onPressed: () {},
              icon: const Icon(Icons.favorite),
            ),
            IconButton(
              onPressed: () {},
              icon: const Icon(Icons.alarm),
              color: Colors.red,
              iconSize: 30,
              tooltip: '设置提示文本',
            ),
            IconButton(
              onPressed: () {},
              style: IconButton.styleFrom(
                backgroundColor: Colors.blue[100],
                padding: const EdgeInsets.all(16),
              ),
              icon: const Icon(Icons.home),
            ),
          ]),
          _buildSection('FloatingActionButton(浮动按钮):', [
            FloatingActionButton(
              onPressed: () {},
              child: const Icon(Icons.add),
            ),
            FloatingActionButton.extended(
              onPressed: () {},
              icon: const Icon(Icons.share),
              label: const Text('分享'),
            ),
            FloatingActionButton.small(
              onPressed: () {},
              child: const Icon(Icons.edit),
            ),
          ]),
        ],
      ),
    );
  }

  Widget _buildSection(String title, List<Widget> buttons) {
    return Column(
      crossAxisAlignment: CrossAxisAlignment.start,
      children: [
        Text(
          title,
          style: const TextStyle(fontSize: 16, fontWeight: FontWeight.bold),
        ),
        const SizedBox(height: 10),
        Wrap(
          spacing: 10,
          runSpacing: 10,
          children: buttons,
        ),
        const SizedBox(height: 30),
      ],
    );
  }
}

image-20250301183641879

Wrap组件

Wrap可以实现流布局,单行的Wrap跟Row表现几乎一致,单列的Wrap则跟Column表现几乎一致。但 Row与Column都是单行单列的, Wrap则突破了这个限制, mainAxis上空间不足时,则向crossAxis上 去扩展显示。

属性说明
direction主轴的方向,默认水平
alignment主轴的对其方式
spacing主轴方向上的间距
textDirection文本方向
verticalDirection定义了children摆放顺序,默认是down ,见Flex相关属性介绍。
runAlignmentrun的对齐方式。 run可以理解为新的行或者列,如果是水平方向布局的话, run可以理解为新的一行
runSpacingrun的间距
dart
import 'package:flutter/material.dart';

void main() {
  runApp(MaterialApp(
      home: Scaffold(
    appBar: AppBar(
      title: const Text('Wrap 组件示例'),
    ),
    body: const WrapDemo(),
  )));
}

class WrapDemo extends StatelessWidget {
  const WrapDemo({Key? key}) : super(key: key);

  @override
  Widget build(BuildContext context) {
    return SingleChildScrollView(
      padding: const EdgeInsets.all(16.0),
      child: Column(
        crossAxisAlignment: CrossAxisAlignment.start,
        children: [
          _buildSection(
            '基础 Wrap(默认属性):',
            Wrap(
              children: _buildExampleBoxes(),
            ),
          ),
          _buildSection(
            'spacing(水平间距):',
            Wrap(
              spacing: 20, // 水平间距
              children: _buildExampleBoxes(),
            ),
          ),
          _buildSection(
            'runSpacing(垂直间距):',
            Wrap(
              spacing: 10,
              runSpacing: 20, // 垂直间距
              children: _buildExampleBoxes(),
            ),
          ),
          _buildSection(
            'alignment - WrapAlignment.center:',
            Wrap(
              spacing: 10,
              alignment: WrapAlignment.center, // 主轴对齐方式
              children: _buildExampleBoxes(),
            ),
          ),
          _buildSection(
            'alignment - WrapAlignment.end:',
            Wrap(
              spacing: 10,
              alignment: WrapAlignment.end,
              children: _buildExampleBoxes(),
            ),
          ),
          _buildSection(
            'alignment - WrapAlignment.spaceBetween:',
            Wrap(
              spacing: 10,
              alignment: WrapAlignment.spaceBetween,
              children: _buildExampleBoxes(),
            ),
          ),
          _buildSection(
            'alignment - WrapAlignment.spaceAround:',
            Wrap(
              spacing: 10,
              alignment: WrapAlignment.spaceAround,
              children: _buildExampleBoxes(),
            ),
          ),
          _buildSection(
            'alignment - WrapAlignment.spaceEvenly:',
            Wrap(
              spacing: 10,
              alignment: WrapAlignment.spaceEvenly,
              children: _buildExampleBoxes(),
            ),
          ),
          _buildSection(
            'crossAxisAlignment - WrapCrossAlignment.start:',
            Wrap(
              spacing: 10,
              runSpacing: 10,
              crossAxisAlignment: WrapCrossAlignment.start,
              children: _buildExampleBoxes(withDifferentHeights: true),
            ),
          ),
          _buildSection(
            'crossAxisAlignment - WrapCrossAlignment.center:',
            Wrap(
              spacing: 10,
              runSpacing: 10,
              crossAxisAlignment: WrapCrossAlignment.center,
              children: _buildExampleBoxes(withDifferentHeights: true),
            ),
          ),
          _buildSection(
            'crossAxisAlignment - WrapCrossAlignment.end:',
            Wrap(
              spacing: 10,
              runSpacing: 10,
              crossAxisAlignment: WrapCrossAlignment.end,
              children: _buildExampleBoxes(withDifferentHeights: true),
            ),
          ),
          _buildSection(
            'direction - Axis.vertical:',
            Container(
              height: 200,
              child: Wrap(
                direction: Axis.vertical,
                spacing: 10,
                runSpacing: 10,
                children: _buildExampleBoxes(count: 8),
              ),
            ),
          ),
          _buildSection(
            'verticalDirection - VerticalDirection.up:',
            Container(
              height: 150,
              child: Wrap(
                direction: Axis.vertical,
                spacing: 10,
                runSpacing: 10,
                verticalDirection: VerticalDirection.up,
                children: _buildExampleBoxes(count: 8),
              ),
            ),
          ),
          _buildSection(
            'textDirection - TextDirection.rtl:',
            Wrap(
              spacing: 10,
              textDirection: TextDirection.rtl,
              children: _buildExampleBoxes(),
            ),
          ),
        ],
      ),
    );
  }

  Widget _buildSection(String title, Widget content) {
    return Column(
      crossAxisAlignment: CrossAxisAlignment.start,
      children: [
        Text(
          title,
          style: const TextStyle(fontSize: 16, fontWeight: FontWeight.bold),
        ),
        const SizedBox(height: 10),
        Container(
          padding: const EdgeInsets.all(10),
          decoration: BoxDecoration(
            border: Border.all(color: Colors.grey),
            borderRadius: BorderRadius.circular(8),
          ),
          child: content,
        ),
        const SizedBox(height: 30),
      ],
    );
  }

  List<Widget> _buildExampleBoxes(
      {int count = 6, bool withDifferentHeights = false}) {
    final colors = [
      Colors.red,
      Colors.green,
      Colors.blue,
      Colors.orange,
      Colors.purple,
      Colors.teal,
    ];

    return List.generate(count, (index) {
      double height = withDifferentHeights
          ? (index % 3 == 0
              ? 30.0
              : index % 3 == 1
                  ? 50.0
                  : 70.0)
          : 40.0;

      return Container(
        width: 60,
        height: height,
        alignment: Alignment.center,
        decoration: BoxDecoration(
          color: colors[index % colors.length],
          borderRadius: BorderRadius.circular(4),
        ),
        child: Text(
          '${index + 1}',
          style:
              const TextStyle(color: Colors.white, fontWeight: FontWeight.bold),
        ),
      );
    });
  }
}

image-20250302121606811

StatefulWidget

dart
import 'package:flutter/material.dart';

void main() {
  runApp(MaterialApp(
      home: Scaffold(
    appBar: AppBar(
      title: const Text('Flutter按钮示例'),
    ),
    body:  MyApp(),
  )));
}

class MyApp extends StatelessWidget {
  int count = 0;

  void onPressed() {
    count++;
    print(count);
  }

  @override
  Widget build(BuildContext context) {
    return Center(
      child: Column(
        children: [
          Text("${count}",style: TextStyle(fontSize: 30),),
          SizedBox(
            height: 20,
          ),
          ElevatedButton(onPressed: onPressed, child: const Text("增加"))
        ],
      ),
    );
  }
}

image-20250302123908503

后台上的值确实发生的变动,但是页面没有跟着刷新

dart
import 'dart:ffi';

import 'package:flutter/material.dart';

void main() {
  runApp(MaterialApp(
      home: Scaffold(
    appBar: AppBar(
      title: const Text('Flutter按钮示例'),
    ),
    body: CountDemo(),
  )));
}

class CountDemo extends StatefulWidget {
  const CountDemo({Key? key}) : super(key: key);

  @override
  State<CountDemo> createState() => _CountDemoState();
}

class _CountDemoState extends State<CountDemo> {
  int _count = 0;

  @override
  Widget build(BuildContext context) {
    return Center(
        child: Column(mainAxisAlignment: MainAxisAlignment.center, children: [
      Text('$_count'),
      ElevatedButton(
          onPressed: () {
            setState(() {
              _count++;
            });
          },
          child: const Text('点击增加')),
    ]));
  }
}

image-20250302124702628

dart
import 'dart:ffi';

import 'package:flutter/material.dart';

void main() {
  runApp(MaterialApp(
      home: Scaffold(
    appBar: AppBar(
      title: const Text('Flutter按钮示例'),
    ),
    body: const CountDemo(),
  )));
}

class CountDemo extends StatefulWidget {
  const CountDemo({Key? key}) : super(key: key);

  @override
  State<CountDemo> createState() => _CountDemoState();
}

// 定义列表项数据类
class ListItem {
  final IconData icon;
  final String title;
  final Color color;

  const ListItem(this.icon, this.title, this.color);
}

class _CountDemoState extends State<CountDemo> {
  final List<ListItem> items = [];

  @override
  void initState() {
    super.initState();
    // 初始化放在 initState 中只执行一次
    _init();
  }

  void _init() {
    items.add(const ListItem(Icons.home, 'Home', Colors.blue));
    items.add(const ListItem(Icons.settings, 'Settings', Colors.red));
    items.add(const ListItem(Icons.person, 'Profile', Colors.green));
  }

  void _loadData() {
    setState(() {
      items.add(ListItem(Icons.home, "标题${items.length + 1}", Colors.blue));
    });
  }

  @override
  Widget build(BuildContext context) {
    return Column(children: [
      // 给 ListView 一个固定或弹性的高度
      Expanded(
        child: ListView.builder(
            itemCount: items.length,
            itemBuilder: (context, index) {
              return ListTile(
                leading: Icon(
                  items[index].icon,
                  color: items[index].color,
                  size: 30,
                ),
                title: Text(
                  items[index].title,
                ),
              );
            }),
      ),
      Padding(
        padding: const EdgeInsets.all(16.0),
        child: ElevatedButton(onPressed: _loadData, child: const Text('点击增加')),
      ),
    ]);
  }
}

ListView.builder父元素必须得有明确的高度,不然会报错

image-20250302130830876

BottomNavigationBar

BottomNavigationBar 是底部导航条,可以让我们定义底部Tab切换, bottomNavigationBar是 Scaffold组件的参数。

*属性名**说明*
itemsList 底部导航条按钮集合
iconSizeicon
currentIndex默认选中第几个
onTap选中变化回调函数
fixedColor选中的颜色
typeBottomNavigationBarType.fixed BottomNavigationBarType.shifting
dart
import 'dart:ffi';

import 'package:flutter/material.dart';

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

class MyApp extends StatelessWidget {
  const MyApp({Key? key}) : super(key: key);

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'BottomNavigationBar 示例',
      theme: ThemeData(
        primarySwatch: Colors.blue,
        visualDensity: VisualDensity.adaptivePlatformDensity,
      ),
      home: const BottomNavDemo(),
    );
  }
}

class BottomNavDemo extends StatefulWidget {
  const BottomNavDemo({Key? key}) : super(key: key);

  @override
  State<BottomNavDemo> createState() => _BottomNavDemoState();
}

class _BottomNavDemoState extends State<BottomNavDemo> {
  // 当前选中的索引
  int _currentIndex = 0;

  // 控制是否使用移动模式(shifting)
  bool _isShifting = false;

  // 页面控制器
  late PageController _pageController;

  // 导航页面
  final List<Widget> _pages = [
    const HomePage(),
    const MessagePage(),
    const NotificationPage(),
    const ProfilePage(),
  ];

  @override
  void initState() {
    super.initState();
    // 初始化PageController,设置初始页面为当前选中索引
    _pageController = PageController(initialPage: _currentIndex);
  }

  @override
  void dispose() {
    // 释放PageController资源
    _pageController.dispose();
    super.dispose();
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: const Text('BottomNavigationBar 示例'),
        actions: [
          // 添加一个开关来切换导航栏模式
          Switch(
            value: _isShifting,
            onChanged: (value) {
              setState(() {
                _isShifting = value;
              });
            },
          ),
          const Padding(
            padding: EdgeInsets.only(right: 16.0),
            child: Center(
              child: Text('移动模式'),
            ),
          ),
        ],
      ),
      // 使用PageView替换原来的直接显示页面的方式
      body: PageView(
        controller: _pageController,
        // 滑动改变页面时更新当前索引
        onPageChanged: (index) {
          setState(() {
            _currentIndex = index;
          });
        },
        // 添加页面
        children: _pages,
      ),
      bottomNavigationBar: BottomNavigationBar(
        // 当前选中的索引
        currentIndex: _currentIndex,

        // 切换页面的回调
        onTap: (index) {
          setState(() {
            _currentIndex = index;
            // 使用动画切换到选中的页面
            _pageController.animateToPage(
              index,
              duration: const Duration(milliseconds: 300),
              curve: Curves.easeInOut,
            );
          });
        },

        // 设置导航栏类型(固定 fixed 或移动 shifting)
        type: _isShifting
            ? BottomNavigationBarType.shifting
            : BottomNavigationBarType.fixed,

        // 未选中时的颜色
        unselectedItemColor: Colors.grey,

        // 选中时的颜色
        selectedItemColor: _isShifting ? Colors.white : Colors.blue,

        // 选中项文本大小
        selectedFontSize: 14,

        // 未选中项文本大小
        unselectedFontSize: 12,

        // 图标与文本间距
        iconSize: 24,

        // 项目标签是否显示
        showUnselectedLabels: true,

        // 导航项
        items: [
          BottomNavigationBarItem(
            icon: const Icon(Icons.home),
            label: '首页',
            backgroundColor: _isShifting ? Colors.blue : null,
          ),
          BottomNavigationBarItem(
            icon: const Icon(Icons.message),
            label: '消息',
            backgroundColor: _isShifting ? Colors.orange : null,
          ),
          BottomNavigationBarItem(
            icon: const Icon(Icons.notifications),
            label: '通知',
            backgroundColor: _isShifting ? Colors.red : null,
          ),
          BottomNavigationBarItem(
            icon: const Icon(Icons.person),
            label: '我的',
            backgroundColor: _isShifting ? Colors.green : null,
          ),
        ],
      ),
    );
  }
}

// 示例页面 - 首页
class HomePage extends StatelessWidget {
  const HomePage({Key? key}) : super(key: key);

  @override
  Widget build(BuildContext context) {
    return Center(
      child: Column(
        mainAxisAlignment: MainAxisAlignment.center,
        children: [
          Icon(Icons.home, size: 80, color: Colors.blue),
          const SizedBox(height: 20),
          const Text(
            '首页',
            style: TextStyle(fontSize: 24, fontWeight: FontWeight.bold),
          ),
          const SizedBox(height: 10),
          const Text('这是首页内容', style: TextStyle(fontSize: 16)),
          const SizedBox(height: 20),
          const Text('向右滑动切换到下一页',
              style: TextStyle(fontSize: 14, color: Colors.grey)),
        ],
      ),
    );
  }
}

// 示例页面 - 消息
class MessagePage extends StatelessWidget {
  const MessagePage({Key? key}) : super(key: key);

  @override
  Widget build(BuildContext context) {
    return Center(
      child: Column(
        mainAxisAlignment: MainAxisAlignment.center,
        children: [
          Icon(Icons.message, size: 80, color: Colors.orange),
          const SizedBox(height: 20),
          const Text(
            '消息',
            style: TextStyle(fontSize: 24, fontWeight: FontWeight.bold),
          ),
          const SizedBox(height: 10),
          const Text('这里是您的消息列表', style: TextStyle(fontSize: 16)),
          const SizedBox(height: 20),
          const Text('左右滑动可切换页面',
              style: TextStyle(fontSize: 14, color: Colors.grey)),
        ],
      ),
    );
  }
}

// 示例页面 - 通知
class NotificationPage extends StatelessWidget {
  const NotificationPage({Key? key}) : super(key: key);

  @override
  Widget build(BuildContext context) {
    return Center(
      child: Column(
        mainAxisAlignment: MainAxisAlignment.center,
        children: [
          Icon(Icons.notifications, size: 80, color: Colors.red),
          const SizedBox(height: 20),
          const Text(
            '通知',
            style: TextStyle(fontSize: 24, fontWeight: FontWeight.bold),
          ),
          const SizedBox(height: 10),
          const Text('您有3条未读通知', style: TextStyle(fontSize: 16)),
          const SizedBox(height: 20),
          const Text('左右滑动可切换页面',
              style: TextStyle(fontSize: 14, color: Colors.grey)),
        ],
      ),
    );
  }
}

// 示例页面 - 我的
class ProfilePage extends StatelessWidget {
  const ProfilePage({Key? key}) : super(key: key);

  @override
  Widget build(BuildContext context) {
    return Center(
      child: Column(
        mainAxisAlignment: MainAxisAlignment.center,
        children: [
          Icon(Icons.person, size: 80, color: Colors.green),
          const SizedBox(height: 20),
          const Text(
            '个人资料',
            style: TextStyle(fontSize: 24, fontWeight: FontWeight.bold),
          ),
          const SizedBox(height: 10),
          const Text('个人信息设置', style: TextStyle(fontSize: 16)),
          const SizedBox(height: 20),
          const Text('向左滑动切换到上一页',
              style: TextStyle(fontSize: 14, color: Colors.grey)),
        ],
      ),
    );
  }
}

image-20250302133313533

FloatingActionButton

dart
import 'dart:ffi';

import 'package:flutter/material.dart';

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

class MyApp extends StatelessWidget {
  const MyApp({Key? key}) : super(key: key);

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'BottomNavigationBar 示例',
      theme: ThemeData(
        primarySwatch: Colors.blue,
        visualDensity: VisualDensity.adaptivePlatformDensity,
      ),
      home: const BottomNavDemo(),
    );
  }
}

class BottomNavDemo extends StatefulWidget {
  const BottomNavDemo({Key? key}) : super(key: key);

  @override
  State<BottomNavDemo> createState() => _BottomNavDemoState();
}

class _BottomNavDemoState extends State<BottomNavDemo> {
  // 当前选中的索引
  int _currentIndex = 0;

  // 控制是否使用移动模式(shifting)
  bool _isShifting = false;

  // 页面控制器
  late PageController _pageController;

  // 导航页面
  final List<Widget> _pages = [
    const HomePage(),
    const MessagePage(),
    const NotificationPage(),
    const ProfilePage(),
  ];

  @override
  void initState() {
    super.initState();
    // 初始化PageController,设置初始页面为当前选中索引
    _pageController = PageController(initialPage: _currentIndex);
  }

  @override
  void dispose() {
    // 释放PageController资源
    _pageController.dispose();
    super.dispose();
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: const Text('BottomNavigationBar 示例'),
        actions: [
          // 添加一个开关来切换导航栏模式
          Switch(
            value: _isShifting,
            onChanged: (value) {
              setState(() {
                _isShifting = value;
              });
            },
          ),
          const Padding(
            padding: EdgeInsets.only(right: 16.0),
            child: Center(
              child: Text('移动模式'),
            ),
          ),
        ],
      ),
      // 使用PageView替换原来的直接显示页面的方式
      body: PageView(
        controller: _pageController,
        // 滑动改变页面时更新当前索引
        onPageChanged: (index) {
          setState(() {
            _currentIndex = index;
          });
        },
        // 添加页面
        children: _pages,
      ),
      // 添加浮动按钮
      floatingActionButton: FloatingActionButton(
        onPressed: () {
          ScaffoldMessenger.of(context).showSnackBar(
            const SnackBar(content: Text('您点击了浮动按钮')),
          );
        },
        backgroundColor: Colors.orange,
        child: const CircleAvatar(
          backgroundColor: Colors.white,
          radius: 15,
          child: Icon(
            Icons.add,
            size: 24,
            color: Colors.orange,
          ),
        ),
      ),
      // 设置浮动按钮位置
      floatingActionButtonLocation: FloatingActionButtonLocation.centerDocked,
      // 底部导航栏
      bottomNavigationBar: BottomNavigationBar(
        // 当前选中的索引
        currentIndex: _currentIndex,

        // 切换页面的回调
        onTap: (index) {
          setState(() {
            _currentIndex = index;
            // 使用动画切换到选中的页面
            _pageController.animateToPage(
              index,
              duration: const Duration(milliseconds: 300),
              curve: Curves.easeInOut,
            );
          });
        },

        // 设置导航栏类型(固定 fixed 或移动 shifting)
        type: _isShifting
            ? BottomNavigationBarType.shifting
            : BottomNavigationBarType.fixed,

        // 未选中时的颜色
        unselectedItemColor: Colors.grey,

        // 选中时的颜色
        selectedItemColor: _isShifting ? Colors.white : Colors.blue,

        // 选中项文本大小
        selectedFontSize: 14,

        // 未选中项文本大小
        unselectedFontSize: 12,

        // 图标与文本间距
        iconSize: 24,

        // 项目标签是否显示
        showUnselectedLabels: true,

        // 导航项
        items: [
          BottomNavigationBarItem(
            icon: const Icon(Icons.home),
            label: '首页',
            backgroundColor: _isShifting ? Colors.blue : null,
          ),
          BottomNavigationBarItem(
            icon: const Icon(Icons.message),
            label: '消息',
            backgroundColor: _isShifting ? Colors.orange : null,
          ),
          BottomNavigationBarItem(
            icon: const Icon(Icons.message),
            label: '发布',
            backgroundColor: _isShifting ? Colors.orange : null,
          ),
          BottomNavigationBarItem(
            icon: const Icon(Icons.notifications),
            label: '通知',
            backgroundColor: _isShifting ? Colors.red : null,
          ),
          BottomNavigationBarItem(
            icon: const Icon(Icons.person),
            label: '我的',
            backgroundColor: _isShifting ? Colors.green : null,
          ),
        ],
      ),
    );
  }
}

// 示例页面 - 首页
class HomePage extends StatelessWidget {
  const HomePage({Key? key}) : super(key: key);

  @override
  Widget build(BuildContext context) {
    return Center(
      child: Column(
        mainAxisAlignment: MainAxisAlignment.center,
        children: [
          Icon(Icons.home, size: 80, color: Colors.blue),
          const SizedBox(height: 20),
          const Text(
            '首页',
            style: TextStyle(fontSize: 24, fontWeight: FontWeight.bold),
          ),
          const SizedBox(height: 10),
          const Text('这是首页内容', style: TextStyle(fontSize: 16)),
          const SizedBox(height: 20),
          const Text('向右滑动切换到下一页',
              style: TextStyle(fontSize: 14, color: Colors.grey)),
        ],
      ),
    );
  }
}

// 示例页面 - 消息
class MessagePage extends StatelessWidget {
  const MessagePage({Key? key}) : super(key: key);

  @override
  Widget build(BuildContext context) {
    return Center(
      child: Column(
        mainAxisAlignment: MainAxisAlignment.center,
        children: [
          Icon(Icons.message, size: 80, color: Colors.orange),
          const SizedBox(height: 20),
          const Text(
            '消息',
            style: TextStyle(fontSize: 24, fontWeight: FontWeight.bold),
          ),
          const SizedBox(height: 10),
          const Text('这里是您的消息列表', style: TextStyle(fontSize: 16)),
          const SizedBox(height: 20),
          const Text('左右滑动可切换页面',
              style: TextStyle(fontSize: 14, color: Colors.grey)),
        ],
      ),
    );
  }
}

// 示例页面 - 通知
class NotificationPage extends StatelessWidget {
  const NotificationPage({Key? key}) : super(key: key);

  @override
  Widget build(BuildContext context) {
    return Center(
      child: Column(
        mainAxisAlignment: MainAxisAlignment.center,
        children: [
          Icon(Icons.notifications, size: 80, color: Colors.red),
          const SizedBox(height: 20),
          const Text(
            '通知',
            style: TextStyle(fontSize: 24, fontWeight: FontWeight.bold),
          ),
          const SizedBox(height: 10),
          const Text('您有3条未读通知', style: TextStyle(fontSize: 16)),
          const SizedBox(height: 20),
          const Text('左右滑动可切换页面',
              style: TextStyle(fontSize: 14, color: Colors.grey)),
        ],
      ),
    );
  }
}

// 示例页面 - 我的
class ProfilePage extends StatelessWidget {
  const ProfilePage({Key? key}) : super(key: key);

  @override
  Widget build(BuildContext context) {
    return Center(
      child: Column(
        mainAxisAlignment: MainAxisAlignment.center,
        children: [
          Icon(Icons.person, size: 80, color: Colors.green),
          const SizedBox(height: 20),
          const Text(
            '个人资料',
            style: TextStyle(fontSize: 24, fontWeight: FontWeight.bold),
          ),
          const SizedBox(height: 10),
          const Text('个人信息设置', style: TextStyle(fontSize: 16)),
          const SizedBox(height: 20),
          const Text('向左滑动切换到上一页',
              style: TextStyle(fontSize: 14, color: Colors.grey)),
        ],
      ),
    );
  }
}

Drawer

dart
import 'dart:ffi';

import 'package:flutter/material.dart';

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

class MyApp extends StatelessWidget {
  const MyApp({Key? key}) : super(key: key);

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Drawer 示例',
      theme: ThemeData(
        primarySwatch: Colors.blue,
        visualDensity: VisualDensity.adaptivePlatformDensity,
      ),
      home: const DrawerDemo(),
    );
  }
}

class DrawerDemo extends StatefulWidget {
  const DrawerDemo({Key? key}) : super(key: key);

  @override
  State<DrawerDemo> createState() => _DrawerDemoState();
}

class _DrawerDemoState extends State<DrawerDemo> {
  // 当前选中的菜单项
  int _selectedIndex = 0;
  // 控制抽屉菜单的Key
  final GlobalKey<ScaffoldState> _scaffoldKey = GlobalKey<ScaffoldState>();
  // 是否启用手势
  bool _enableGesture = true;
  // 当前抽屉样式
  DrawerStyle _currentStyle = DrawerStyle.basic;

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      key: _scaffoldKey,
      appBar: AppBar(
        title: const Text('Drawer 抽屉菜单示例'),
        // 显示菜单按钮,点击打开抽屉
        leading: IconButton(
          icon: const Icon(Icons.menu),
          onPressed: () {
            _scaffoldKey.currentState?.openDrawer();
          },
        ),
        actions: [
          // 切换抽屉样式
          IconButton(
            icon: const Icon(Icons.style),
            onPressed: _changeDrawerStyle,
            tooltip: '切换抽屉样式',
          ),
          // 切换手势开关
          IconButton(
            icon: Icon(_enableGesture ? Icons.swipe : Icons.swipe_left),
            onPressed: () {
              setState(() {
                _enableGesture = !_enableGesture;
              });
            },
            tooltip: '${_enableGesture ? '禁用' : '启用'}滑动手势',
          ),
        ],
      ),
      // 左侧抽屉菜单
      drawer: Drawer(
        // 抽屉宽度
        width: 280,
        // 是否启用手势打开抽屉
        semanticLabel: '导航菜单',
        // 背景颜色
        backgroundColor: Colors.white,
        // 阴影高度
        elevation: 16.0,
        // 抽屉内容
        child: _buildDrawerContent(),
      ),
      // 右侧抽屉菜单
      endDrawer: Drawer(
        child: ListView(
          padding: EdgeInsets.zero,
          children: [
            const DrawerHeader(
              decoration: BoxDecoration(
                color: Colors.orangeAccent,
              ),
              child: Text(
                '右侧抽屉菜单',
                style: TextStyle(
                  color: Colors.white,
                  fontSize: 24,
                ),
              ),
            ),
            ListTile(
              leading: const Icon(Icons.notification_important),
              title: const Text('通知设置'),
              onTap: () {
                Navigator.pop(context);
              },
            ),
            ListTile(
              leading: const Icon(Icons.settings),
              title: const Text('应用设置'),
              onTap: () {
                Navigator.pop(context);
              },
            ),
          ],
        ),
      ),
      // 拖动手势设置
      drawerEnableOpenDragGesture: _enableGesture,
      endDrawerEnableOpenDragGesture: _enableGesture,
      // 正文内容
      body: Center(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: [
            Text(
              '当前选中: ${_getSelectedTitle()}',
              style: Theme.of(context).textTheme.headlineLarge,
            ),
            const SizedBox(height: 20),
            ElevatedButton(
              onPressed: () {
                _scaffoldKey.currentState?.openDrawer();
              },
              child: const Text('打开左侧抽屉'),
            ),
            const SizedBox(height: 10),
            ElevatedButton(
              onPressed: () {
                _scaffoldKey.currentState?.openEndDrawer();
              },
              child: const Text('打开右侧抽屉'),
            ),
            const SizedBox(height: 30),
            Text(
              '当前抽屉样式: ${_getStyleName()}',
              style: Theme.of(context).textTheme.titleLarge,
            ),
            const SizedBox(height: 10),
            Text(
              '滑动手势: ${_enableGesture ? '启用' : '禁用'}',
              style: Theme.of(context).textTheme.titleMedium,
            ),
          ],
        ),
      ),
    );
  }

  // 构建抽屉内容
  Widget _buildDrawerContent() {
    switch (_currentStyle) {
      case DrawerStyle.basic:
        return _buildBasicDrawer();
      case DrawerStyle.userAccount:
        return _buildUserAccountDrawer();
      case DrawerStyle.custom:
        return _buildCustomDrawer();
      default:
        return _buildBasicDrawer();
    }
  }

  // 基本抽屉样式
  Widget _buildBasicDrawer() {
    return ListView(
      // 取消内边距
      padding: EdgeInsets.zero,
      children: [
        // 抽屉头部
        const DrawerHeader(
          decoration: BoxDecoration(
            color: Colors.blue,
          ),
          child: Text(
            '基本抽屉菜单',
            style: TextStyle(
              color: Colors.white,
              fontSize: 24,
            ),
          ),
        ),
        // 菜单项
        ListTile(
          leading: const Icon(Icons.home),
          title: const Text('首页'),
          selected: _selectedIndex == 0,
          onTap: () {
            _selectItem(0);
          },
        ),
        ListTile(
          leading: const Icon(Icons.person),
          title: const Text('个人中心'),
          selected: _selectedIndex == 1,
          onTap: () {
            _selectItem(1);
          },
        ),
        // 分割线
        const Divider(),
        ListTile(
          leading: const Icon(Icons.settings),
          title: const Text('设置'),
          selected: _selectedIndex == 2,
          onTap: () {
            _selectItem(2);
          },
        ),
        ListTile(
          leading: const Icon(Icons.help),
          title: const Text('帮助与反馈'),
          selected: _selectedIndex == 3,
          onTap: () {
            _selectItem(3);
          },
        ),
      ],
    );
  }

  // 用户账户抽屉样式
  Widget _buildUserAccountDrawer() {
    return ListView(
      padding: EdgeInsets.zero,
      children: [
        // 用户账户头部
        const UserAccountsDrawerHeader(
          // 用户头像
          currentAccountPicture: CircleAvatar(
            backgroundImage:
                NetworkImage('https://randomuser.me/api/portraits/men/78.jpg'),
          ),
          // 其他头像
          otherAccountsPictures: [
            CircleAvatar(
              backgroundImage: NetworkImage(
                  'https://randomuser.me/api/portraits/women/76.jpg'),
            ),
          ],
          // 账户名称
          accountName: Text('张三'),
          // 账户邮箱
          accountEmail: Text('zhangsan@example.com'),
          // 装饰
          decoration: BoxDecoration(
            color: Colors.deepPurple,
            image: DecorationImage(
              image: NetworkImage(
                  'https://images.unsplash.com/photo-1557682250-33bd709cbe85'),
              fit: BoxFit.cover,
            ),
          ),
        ),
        ListTile(
          leading: const Icon(Icons.home),
          title: const Text('首页'),
          selected: _selectedIndex == 0,
          onTap: () {
            _selectItem(0);
          },
        ),
        ListTile(
          leading: const Icon(Icons.person),
          title: const Text('个人中心'),
          selected: _selectedIndex == 1,
          onTap: () {
            _selectItem(1);
          },
        ),
        ExpansionTile(
          leading: const Icon(Icons.work),
          title: const Text('我的工作'),
          children: [
            ListTile(
              leading: const Icon(Icons.card_travel),
              title: const Text('项目管理'),
              selected: _selectedIndex == 4,
              onTap: () {
                _selectItem(4);
              },
            ),
            ListTile(
              leading: const Icon(Icons.timeline),
              title: const Text('工作报告'),
              selected: _selectedIndex == 5,
              onTap: () {
                _selectItem(5);
              },
            ),
          ],
        ),
        const Divider(),
        ListTile(
          leading: const Icon(Icons.settings),
          title: const Text('设置'),
          selected: _selectedIndex == 2,
          onTap: () {
            _selectItem(2);
          },
        ),
        ListTile(
          leading: const Icon(Icons.exit_to_app),
          title: const Text('退出登录'),
          onTap: () {
            // 关闭抽屉
            Navigator.pop(context);
            // 显示退出提示
            ScaffoldMessenger.of(context).showSnackBar(
              const SnackBar(content: Text('退出登录')),
            );
          },
        ),
      ],
    );
  }

  // 自定义抽屉样式
  Widget _buildCustomDrawer() {
    return Column(
      children: [
        // 自定义头部区域
        Container(
          height: 200,
          width: double.infinity,
          decoration: const BoxDecoration(
            gradient: LinearGradient(
              colors: [Colors.teal, Colors.cyan],
              begin: Alignment.topCenter,
              end: Alignment.bottomCenter,
            ),
          ),
          child: Column(
            mainAxisAlignment: MainAxisAlignment.center,
            children: [
              Container(
                decoration: BoxDecoration(
                  shape: BoxShape.circle,
                  border: Border.all(color: Colors.white, width: 2),
                ),
                child: const CircleAvatar(
                  radius: 50,
                  backgroundImage: NetworkImage(
                      'https://randomuser.me/api/portraits/men/78.jpg'),
                ),
              ),
              const SizedBox(height: 10),
              const Text(
                '李四',
                style: TextStyle(
                  color: Colors.white,
                  fontSize: 20,
                  fontWeight: FontWeight.bold,
                ),
              ),
              const Text(
                'lisi@example.com',
                style: TextStyle(
                  color: Colors.white70,
                  fontSize: 14,
                ),
              ),
            ],
          ),
        ),
        // 菜单项
        Expanded(
          child: Container(
            color: Colors.grey[100],
            child: ListView(
              padding: const EdgeInsets.all(8.0),
              children: [
                _buildCustomMenuItem(0, Icons.home, '首页'),
                _buildCustomMenuItem(1, Icons.person, '个人中心'),
                _buildCustomMenuItem(2, Icons.settings, '设置'),
                _buildCustomMenuItem(3, Icons.notifications, '通知中心'),
                const SizedBox(height: 20),
                const Padding(
                  padding: EdgeInsets.symmetric(horizontal: 16),
                  child: Text(
                    '其他功能',
                    style: TextStyle(
                      color: Colors.grey,
                      fontWeight: FontWeight.bold,
                    ),
                  ),
                ),
                _buildCustomMenuItem(6, Icons.favorite, '我的收藏'),
                _buildCustomMenuItem(7, Icons.wallet, '我的钱包'),
              ],
            ),
          ),
        ),
        // 底部区域
        Container(
          padding: const EdgeInsets.symmetric(vertical: 16),
          decoration: BoxDecoration(
            color: Colors.white,
            boxShadow: [
              BoxShadow(
                color: Colors.grey.withOpacity(0.3),
                blurRadius: 5,
                offset: const Offset(0, -3),
              ),
            ],
          ),
          child: Row(
            mainAxisAlignment: MainAxisAlignment.spaceEvenly,
            children: [
              IconButton(
                icon: const Icon(Icons.settings),
                onPressed: () {
                  _selectItem(2);
                },
              ),
              IconButton(
                icon: const Icon(Icons.info),
                onPressed: () {
                  Navigator.pop(context);
                  ScaffoldMessenger.of(context).showSnackBar(
                    const SnackBar(content: Text('关于应用')),
                  );
                },
              ),
              IconButton(
                icon: const Icon(Icons.exit_to_app),
                onPressed: () {
                  Navigator.pop(context);
                  ScaffoldMessenger.of(context).showSnackBar(
                    const SnackBar(content: Text('退出登录')),
                  );
                },
              ),
            ],
          ),
        ),
      ],
    );
  }

  // 自定义菜单项
  Widget _buildCustomMenuItem(int index, IconData icon, String title) {
    final bool isSelected = _selectedIndex == index;
    return Card(
      elevation: isSelected ? 2 : 0,
      margin: const EdgeInsets.symmetric(vertical: 4, horizontal: 8),
      color: isSelected ? Colors.teal.shade50 : Colors.white,
      child: ListTile(
        leading: Icon(
          icon,
          color: isSelected ? Colors.teal : Colors.grey[700],
        ),
        title: Text(
          title,
          style: TextStyle(
            fontWeight: isSelected ? FontWeight.bold : FontWeight.normal,
            color: isSelected ? Colors.teal : Colors.black,
          ),
        ),
        trailing: isSelected
            ? const Icon(
                Icons.check_circle,
                color: Colors.teal,
              )
            : null,
        onTap: () {
          _selectItem(index);
        },
      ),
    );
  }

  // 选择菜单项
  void _selectItem(int index) {
    setState(() {
      _selectedIndex = index;
    });
    // 关闭抽屉
    Navigator.pop(context);
  }

  // 获取选中的标题
  String _getSelectedTitle() {
    switch (_selectedIndex) {
      case 0:
        return '首页';
      case 1:
        return '个人中心';
      case 2:
        return '设置';
      case 3:
        return '帮助与反馈';
      case 4:
        return '项目管理';
      case 5:
        return '工作报告';
      case 6:
        return '我的收藏';
      case 7:
        return '我的钱包';
      default:
        return '首页';
    }
  }

  // 切换抽屉样式
  void _changeDrawerStyle() {
    setState(() {
      switch (_currentStyle) {
        case DrawerStyle.basic:
          _currentStyle = DrawerStyle.userAccount;
          break;
        case DrawerStyle.userAccount:
          _currentStyle = DrawerStyle.custom;
          break;
        case DrawerStyle.custom:
          _currentStyle = DrawerStyle.basic;
          break;
      }
    });
  }

  // 获取样式名称
  String _getStyleName() {
    switch (_currentStyle) {
      case DrawerStyle.basic:
        return '基本样式';
      case DrawerStyle.userAccount:
        return '用户账户样式';
      case DrawerStyle.custom:
        return '自定义样式';
      default:
        return '';
    }
  }
}

// 抽屉样式枚举
enum DrawerStyle {
  basic,
  userAccount,
  custom,
}

image-20250302151722869

AppBar TabBar TabBarView

AppBar

*属性*描述
leading在标题前面显示的一个控件,在首页通常显示应用的 logo;在其他界面通 常显示为返回按钮
title标题,通常显示为当前界面的标题文字,可以放组件
actions通常使用 IconButton 来表示,可以放按钮组
bottom通常放tabBar,标题下面显示一个 Tab 导航栏
backgroundColor导航背景颜色
iconTheme图标样式
centerTitle标题是否居中显示
dart
import 'package:flutter/material.dart';

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

class MyApp extends StatelessWidget {
  const MyApp({Key? key}) : super(key: key);

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'TabBar 示例',
      theme: ThemeData(
        primarySwatch: Colors.blue,
        visualDensity: VisualDensity.adaptivePlatformDensity,
      ),
      home: const TabBarDemo(),
    );
  }
}

class TabBarDemo extends StatefulWidget {
  const TabBarDemo({Key? key}) : super(key: key);

  @override
  State<TabBarDemo> createState() => _TabBarDemoState();
}

class _TabBarDemoState extends State<TabBarDemo> with TickerProviderStateMixin {
  // TabController控制器
  late TabController _tabController;

  // 标签页数据
  final List<TabItem> _tabs = [
    TabItem(title: '首页', icon: Icons.home),
    TabItem(title: '消息', icon: Icons.message),
    TabItem(title: '联系人', icon: Icons.contacts),
    TabItem(title: '发现', icon: Icons.explore),
    TabItem(title: '我的', icon: Icons.person),
  ];

  // 当前页面索引
  int _currentIndex = 0;

  // 自定义标签样式
  bool _isScrollable = true;
  bool _showIcons = true;
  Color _indicatorColor = Colors.red;
  TabBarIndicatorSize _indicatorSize = TabBarIndicatorSize.label;
  double _indicatorWeight = 3.0;

  @override
  void initState() {
    super.initState();
    // 初始化TabController
    _tabController = TabController(
      length: _tabs.length,
      vsync: this,
      initialIndex: _currentIndex,
    );

    // 监听标签页切换
    _tabController.addListener(() {
      if (_tabController.indexIsChanging) {
        setState(() {
          _currentIndex = _tabController.index;
        });
      }
    });
  }

  @override
  void dispose() {
    // 释放控制器资源
    _tabController.dispose();
    super.dispose();
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: const Text('TabBar 顶部滑动导航'),
        actions: [
          // 样式切换按钮
          IconButton(
            icon: const Icon(Icons.settings),
            onPressed: _showStyleOptions,
            tooltip: '自定义TabBar样式',
          ),
        ],
        // 在AppBar底部添加TabBar
        bottom: TabBar(
          controller: _tabController,
          // 是否可滚动
          isScrollable: _isScrollable,
          // 指示器颜色
          indicatorColor: _indicatorColor,
          // 指示器高度
          indicatorWeight: _indicatorWeight,
          // 指示器大小
          indicatorSize: _indicatorSize,
          // 标签样式
          labelColor: Colors.black,
          unselectedLabelColor: Colors.black12,
          labelStyle: const TextStyle(fontWeight: FontWeight.bold),
          unselectedLabelStyle: const TextStyle(fontWeight: FontWeight.normal),
          // 标签动画持续时间
          tabAlignment: _isScrollable ? TabAlignment.start : TabAlignment.fill,
          // 构建Tab标签
          tabs: _buildTabs(),
        ),
      ),
      // TabBarView用于展示对应标签页的内容
      body: TabBarView(
        controller: _tabController,
        // 构建页面内容
        children: _buildTabContents(),
      ),
      // 底部控制按钮
      floatingActionButton: FloatingActionButton(
        onPressed: () {
          // 切换到下一个标签页
          final int nextIndex = (_currentIndex + 1) % _tabs.length;
          _tabController.animateTo(nextIndex);
        },
        child: const Icon(Icons.navigate_next),
        tooltip: '下一个标签页',
      ),
    );
  }

  // 构建标签项
  List<Widget> _buildTabs() {
    return _tabs.map((tab) {
      if (_showIcons) {
        return Tab(
          icon: Icon(tab.icon),
          text: tab.title,
        );
      } else {
        return Tab(text: tab.title);
      }
    }).toList();
  }

  // 构建标签页内容
  List<Widget> _buildTabContents() {
    return _tabs.map((tab) {
      return TabContentPage(
        title: tab.title,
        icon: tab.icon,
        index: _tabs.indexOf(tab),
      );
    }).toList();
  }

  // 显示样式设置对话框
  void _showStyleOptions() {
    showModalBottomSheet(
      context: context,
      builder: (BuildContext context) {
        return StatefulBuilder(
          builder: (context, setModalState) {
            return Container(
              padding: const EdgeInsets.all(16),
              child: Column(
                mainAxisSize: MainAxisSize.min,
                crossAxisAlignment: CrossAxisAlignment.start,
                children: [
                  const Text(
                    'TabBar 样式设置',
                    style: TextStyle(
                      fontSize: 18,
                      fontWeight: FontWeight.bold,
                    ),
                  ),
                  const SizedBox(height: 16),

                  // 可滚动开关
                  SwitchListTile(
                    title: const Text('可滚动标签'),
                    subtitle: const Text('启用后标签可以水平滚动'),
                    value: _isScrollable,
                    onChanged: (value) {
                      setModalState(() {
                        _isScrollable = value;
                      });
                      setState(() {
                        _isScrollable = value;
                      });
                    },
                  ),

                  // 显示图标开关
                  SwitchListTile(
                    title: const Text('显示图标'),
                    subtitle: const Text('在标签上显示图标'),
                    value: _showIcons,
                    onChanged: (value) {
                      setModalState(() {
                        _showIcons = value;
                      });
                      setState(() {
                        _showIcons = value;
                      });
                    },
                  ),

                  // 指示器宽度设置
                  ListTile(
                    title: const Text('指示器大小'),
                    subtitle: const Text('标签宽度或内容宽度'),
                    trailing: DropdownButton<TabBarIndicatorSize>(
                      value: _indicatorSize,
                      onChanged: (TabBarIndicatorSize? value) {
                        if (value != null) {
                          setModalState(() {
                            _indicatorSize = value;
                          });
                          setState(() {
                            _indicatorSize = value;
                          });
                        }
                      },
                      items: const [
                        DropdownMenuItem(
                          value: TabBarIndicatorSize.label,
                          child: Text('标签内容宽度'),
                        ),
                        DropdownMenuItem(
                          value: TabBarIndicatorSize.tab,
                          child: Text('整个标签宽度'),
                        ),
                      ],
                    ),
                  ),

                  // 指示器颜色选择
                  ListTile(
                    title: const Text('指示器颜色'),
                    trailing: Row(
                      mainAxisSize: MainAxisSize.min,
                      children: [
                        _buildColorOption(Colors.red),
                        _buildColorOption(Colors.blue),
                        _buildColorOption(Colors.green),
                        _buildColorOption(Colors.orange),
                      ],
                    ),
                  ),

                  // 指示器高度滑块
                  ListTile(
                    title:
                        Text('指示器高度: ${_indicatorWeight.toStringAsFixed(1)}'),
                    subtitle: Slider(
                      value: _indicatorWeight,
                      min: 1.0,
                      max: 6.0,
                      divisions: 10,
                      onChanged: (value) {
                        setModalState(() {
                          _indicatorWeight = value;
                        });
                        setState(() {
                          _indicatorWeight = value;
                        });
                      },
                    ),
                  ),
                ],
              ),
            );
          },
        );
      },
    );
  }

  // 构建颜色选择按钮
  Widget _buildColorOption(Color color) {
    return GestureDetector(
      onTap: () {
        setState(() {
          _indicatorColor = color;
        });
        Navigator.pop(context);
      },
      child: Container(
        margin: const EdgeInsets.symmetric(horizontal: 4),
        width: 30,
        height: 30,
        decoration: BoxDecoration(
          color: color,
          shape: BoxShape.circle,
          border: Border.all(
            color: _indicatorColor == color ? Colors.white : Colors.grey,
            width: _indicatorColor == color ? 2 : 1,
          ),
        ),
      ),
    );
  }
}

// 标签页内容
class TabContentPage extends StatelessWidget {
  final String title;
  final IconData icon;
  final int index;

  const TabContentPage({
    Key? key,
    required this.title,
    required this.icon,
    required this.index,
  }) : super(key: key);

  @override
  Widget build(BuildContext context) {
    // 根据索引生成不同的布局
    switch (index) {
      case 0:
        return _buildHomePage();
      case 1:
        return _buildMessagesPage();
      case 2:
        return _buildContactsPage();
      case 3:
        return _buildExplorePage();
      case 4:
        return _buildProfilePage();
      default:
        return _buildDefaultPage();
    }
  }

  // 首页内容
  Widget _buildHomePage() {
    return CustomScrollView(
      slivers: [
        SliverPadding(
          padding: const EdgeInsets.all(16),
          sliver: SliverGrid(
            gridDelegate: const SliverGridDelegateWithFixedCrossAxisCount(
              crossAxisCount: 2,
              mainAxisSpacing: 10,
              crossAxisSpacing: 10,
              childAspectRatio: 1.5,
            ),
            delegate: SliverChildBuilderDelegate(
              (context, index) {
                return Card(
                  color: Colors.blue[(index % 3 + 1) * 100],
                  child: Center(
                    child: Text(
                      '内容项 ${index + 1}',
                      style: const TextStyle(
                        color: Colors.white,
                        fontWeight: FontWeight.bold,
                      ),
                    ),
                  ),
                );
              },
              childCount: 6,
            ),
          ),
        ),
        SliverList(
          delegate: SliverChildBuilderDelegate(
            (context, index) {
              return ListTile(
                leading: const Icon(Icons.article),
                title: Text('新闻标题 ${index + 1}'),
                subtitle: Text('新闻简介内容 ${index + 1}'),
                trailing: const Icon(Icons.arrow_forward_ios, size: 16),
              );
            },
            childCount: 10,
          ),
        ),
      ],
    );
  }

  // 消息页面
  Widget _buildMessagesPage() {
    return ListView.builder(
      itemCount: 15,
      itemBuilder: (context, index) {
        return ListTile(
          leading: CircleAvatar(
            backgroundColor: Colors.green[200],
            child: Text('${(index + 1) % 10}'),
          ),
          title: Text('消息 ${index + 1}'),
          subtitle: Text('这是一条消息内容示例 ${index + 1}'),
          trailing: Text(
              '${(index * 5) % 24}:${(index * 7) % 60 < 10 ? '0' : ''}${(index * 7) % 60}'),
        );
      },
    );
  }

  // 联系人页面
  Widget _buildContactsPage() {
    // 分组的联系人
    final Map<String, List<String>> groupedContacts = {
      'A': ['阿里', '艾伦', '安妮'],
      'B': ['白云', '鲍勃', '本杰明'],
      'C': ['陈明', '崔健', '蔡依林'],
      'D': ['戴维', '杜甫', '邓丽君'],
    };

    return ListView.builder(
      itemCount: groupedContacts.length,
      itemBuilder: (context, index) {
        String key = groupedContacts.keys.elementAt(index);
        List<String> contacts = groupedContacts[key]!;

        return Column(
          crossAxisAlignment: CrossAxisAlignment.start,
          children: [
            // 分组标题
            Container(
              padding: const EdgeInsets.only(left: 16, top: 8, bottom: 8),
              color: Colors.grey[200],
              child: Text(
                key,
                style: const TextStyle(
                  fontWeight: FontWeight.bold,
                  color: Colors.blue,
                ),
              ),
            ),
            // 分组内容
            ...contacts.map((name) {
              return ListTile(
                leading: CircleAvatar(
                  child: Text(name.substring(0, 1)),
                ),
                title: Text(name),
                trailing: const Icon(Icons.call),
              );
            }).toList(),
          ],
        );
      },
    );
  }

  // 发现页面
  Widget _buildExplorePage() {
    return GridView.builder(
      padding: const EdgeInsets.all(16),
      gridDelegate: const SliverGridDelegateWithFixedCrossAxisCount(
        crossAxisCount: 2,
        mainAxisSpacing: 16,
        crossAxisSpacing: 16,
        childAspectRatio: 0.8,
      ),
      itemCount: 10,
      itemBuilder: (context, index) {
        return Card(
          elevation: 3,
          shape: RoundedRectangleBorder(
            borderRadius: BorderRadius.circular(12),
          ),
          clipBehavior: Clip.antiAlias,
          child: Column(
            crossAxisAlignment: CrossAxisAlignment.start,
            children: [
              // 图片
              Container(
                height: 120,
                color: Colors.purple[(index % 3 + 1) * 100],
                child: Center(
                  child: Icon(
                    [Icons.image, Icons.landscape, Icons.camera][index % 3],
                    size: 40,
                    color: Colors.white,
                  ),
                ),
              ),
              // 内容
              Padding(
                padding: const EdgeInsets.all(8.0),
                child: Column(
                  crossAxisAlignment: CrossAxisAlignment.start,
                  children: [
                    Text(
                      '发现项目 ${index + 1}',
                      style: const TextStyle(
                        fontWeight: FontWeight.bold,
                        fontSize: 16,
                      ),
                    ),
                    const SizedBox(height: 4),
                    Text(
                      '这是一个简短的描述内容 ${index + 1}',
                      style: TextStyle(
                        color: Colors.grey[600],
                        fontSize: 12,
                      ),
                    ),
                  ],
                ),
              ),
            ],
          ),
        );
      },
    );
  }

  // 个人中心页面
  Widget _buildProfilePage() {
    return SingleChildScrollView(
      child: Column(
        children: [
          // 头部个人信息
          Container(
            padding: const EdgeInsets.all(20),
            decoration: BoxDecoration(
              gradient: LinearGradient(
                colors: [Colors.blue[700]!, Colors.blue[500]!],
                begin: Alignment.topCenter,
                end: Alignment.bottomCenter,
              ),
            ),
            child: Column(
              children: const [
                CircleAvatar(
                  radius: 50,
                  backgroundImage: NetworkImage(
                    'https://randomuser.me/api/portraits/men/32.jpg',
                  ),
                ),
                SizedBox(height: 10),
                Text(
                  '张三',
                  style: TextStyle(
                    color: Colors.white,
                    fontSize: 22,
                    fontWeight: FontWeight.bold,
                  ),
                ),
                Text(
                  '高级开发工程师',
                  style: TextStyle(
                    color: Colors.white70,
                    fontSize: 16,
                  ),
                ),
              ],
            ),
          ),

          // 统计信息
          Padding(
            padding: const EdgeInsets.symmetric(vertical: 16),
            child: Row(
              mainAxisAlignment: MainAxisAlignment.spaceEvenly,
              children: [
                _buildStatItem('关注', '128'),
                _buildStatItem('粉丝', '256'),
                _buildStatItem('点赞', '1024'),
              ],
            ),
          ),

          const Divider(),

          // 菜单列表
          _buildProfileMenuItem(Icons.bookmark, '我的收藏'),
          _buildProfileMenuItem(Icons.history, '浏览历史'),
          _buildProfileMenuItem(Icons.wallet, '我的钱包'),

          const Divider(),

          _buildProfileMenuItem(Icons.settings, '设置'),
          _buildProfileMenuItem(Icons.help, '帮助与反馈'),

          const SizedBox(height: 20),
        ],
      ),
    );
  }

  // 个人中心统计项
  Widget _buildStatItem(String title, String count) {
    return Column(
      children: [
        Text(
          count,
          style: const TextStyle(
            fontSize: 20,
            fontWeight: FontWeight.bold,
          ),
        ),
        Text(
          title,
          style: TextStyle(
            color: Colors.grey[600],
          ),
        ),
      ],
    );
  }

  // 个人中心菜单项
  Widget _buildProfileMenuItem(IconData icon, String title) {
    return ListTile(
      leading: Icon(icon),
      title: Text(title),
      trailing: const Icon(Icons.arrow_forward_ios, size: 16),
    );
  }

  // 默认页面
  Widget _buildDefaultPage() {
    return Center(
      child: Column(
        mainAxisAlignment: MainAxisAlignment.center,
        children: [
          Icon(
            icon,
            size: 80,
            color: Colors.blue[300],
          ),
          const SizedBox(height: 16),
          Text(
            title,
            style: const TextStyle(
              fontSize: 24,
              fontWeight: FontWeight.bold,
            ),
          ),
          const SizedBox(height: 16),
          Text(
            '这是 $title 页面的内容',
            style: const TextStyle(fontSize: 16),
          ),
        ],
      ),
    );
  }
}

// 标签数据类
class TabItem {
  final String title;
  final IconData icon;

  TabItem({required this.title, required this.icon});
}

TabBar

属性描述
tabs显示的标签内容, 一般使用Tab对象,也可以是其他的Widget
controllerTabController对象
isScrollable是否可滚动
indicatorColor指示器颜色
indicatorWeight指示器高度
indicatorPadding底部指示器的Padding
indicator指示器decoration,例如边框等
indicatorSize指示器大小计算方式, TabBarIndicatorSize.label跟文字等 宽,TabBarIndicatorSize.tab跟每个tab等宽
labelColor选中label颜色
labelStyle选中label的Style
labelPadding每个label的padding值
unselectedLabelColor未选中label颜色
unselectedLabelStyle未选中label的Style

路由

普通路由

Flutter中的路由通俗的讲就是页面跳转。在Flutter中通过Navigator组件管理路由导航。 并提供了管理堆栈的方法。如:Navigator.push和Navigtor.pop

dart
Center(
child :  ElevatedButton(onPressed :  (){ Navigator .of(context) .push(
MaterialPageRoute(builder :  (context){ return  const  SearchPage() ;
}) ) ;
} ,  child:  const  Text( "跳转到搜索页面")) , )
dart
import 'package:flutter/material.dart';

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

class MyApp extends StatelessWidget {
  const MyApp({Key? key}) : super(key: key);

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      debugShowCheckedModeBanner: false,
      title: '简单导航示例',
      theme: ThemeData(
        primarySwatch: Colors.blue,
        useMaterial3: true,
      ),
      home: const HomePage(),
    );
  }
}

// 主页面
class HomePage extends StatelessWidget {
  const HomePage({Key? key}) : super(key: key);

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: const Text('首页'),
        backgroundColor: Colors.blue,
        foregroundColor: Colors.white,
      ),
      body: Center(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: [
            const Text(
              '导航示例',
              style: TextStyle(fontSize: 24, fontWeight: FontWeight.bold),
            ),
            const SizedBox(height: 50),

            // 页面导航按钮
            _buildNavigationButton(
              context,
              title: '前往第一页',
              color: Colors.green,
              destination: const FirstPage(),
            ),

            _buildNavigationButton(
              context,
              title: '前往第二页',
              color: Colors.orange,
              destination: const SecondPage(),
            ),

            _buildNavigationButton(
              context,
              title: '前往第三页',
              color: Colors.purple,
              destination: const ThirdPage(),
            ),

            _buildNavigationButton(
              context,
              title: '传递参数到详情页',
              color: Colors.red,
              onPressed: () {
                // 传递参数到新页面
                Navigator.push(
                  context,
                  MaterialPageRoute(
                    builder: (context) => const DetailPage(
                      title: '详情页',
                      message: '这是从首页传递的消息',
                    ),
                  ),
                );
              },
            ),
          ],
        ),
      ),
    );
  }

  // 构建导航按钮
  Widget _buildNavigationButton(
    BuildContext context, {
    required String title,
    required Color color,
    Widget? destination,
    VoidCallback? onPressed,
  }) {
    return Padding(
      padding: const EdgeInsets.symmetric(vertical: 8),
      child: ElevatedButton(
        style: ElevatedButton.styleFrom(
          backgroundColor: color,
          foregroundColor: Colors.white,
          minimumSize: const Size(200, 50),
          shape: RoundedRectangleBorder(
            borderRadius: BorderRadius.circular(10),
          ),
        ),
        onPressed: onPressed ??
            () {
              // 基本导航:从当前页面跳转到目标页面
              if (destination != null) {
                Navigator.push(
                  context,
                  MaterialPageRoute(builder: (context) => destination),
                );
              }
            },
        child: Text(title),
      ),
    );
  }
}

// 第一个目标页面
class FirstPage extends StatelessWidget {
  const FirstPage({Key? key}) : super(key: key);

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: const Text('第一页'),
        backgroundColor: Colors.green,
        foregroundColor: Colors.white,
      ),
      body: Center(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: [
            const Icon(
              Icons.looks_one,
              size: 100,
              color: Colors.green,
            ),
            const SizedBox(height: 20),
            const Text(
              '这是第一页',
              style: TextStyle(fontSize: 24, fontWeight: FontWeight.bold),
            ),
            const SizedBox(height: 50),

            // 返回按钮
            ElevatedButton(
              style: ElevatedButton.styleFrom(
                backgroundColor: Colors.green,
                foregroundColor: Colors.white,
                minimumSize: const Size(150, 40),
              ),
              onPressed: () {
                // 返回上一页
                Navigator.pop(context);
              },
              child: const Text('返回'),
            ),

            const SizedBox(height: 20),

            // 前往下一页按钮
            OutlinedButton(
              style: OutlinedButton.styleFrom(
                foregroundColor: Colors.green,
                side: const BorderSide(color: Colors.green),
                minimumSize: const Size(150, 40),
              ),
              onPressed: () {
                Navigator.push(
                  context,
                  MaterialPageRoute(builder: (context) => const SecondPage()),
                );
              },
              child: const Text('前往第二页'),
            ),
          ],
        ),
      ),
    );
  }
}

// 第二个目标页面
class SecondPage extends StatelessWidget {
  const SecondPage({Key? key}) : super(key: key);

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: const Text('第二页'),
        backgroundColor: Colors.orange,
        foregroundColor: Colors.white,
      ),
      body: Center(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: [
            const Icon(
              Icons.looks_two,
              size: 100,
              color: Colors.orange,
            ),
            const SizedBox(height: 20),
            const Text(
              '这是第二页',
              style: TextStyle(fontSize: 24, fontWeight: FontWeight.bold),
            ),
            const SizedBox(height: 50),

            // 返回按钮
            ElevatedButton(
              style: ElevatedButton.styleFrom(
                backgroundColor: Colors.orange,
                foregroundColor: Colors.white,
                minimumSize: const Size(150, 40),
              ),
              onPressed: () {
                // 返回上一页
                Navigator.pop(context);
              },
              child: const Text('返回'),
            ),

            const SizedBox(height: 20),

            // 返回首页按钮
            OutlinedButton(
              style: OutlinedButton.styleFrom(
                foregroundColor: Colors.orange,
                side: const BorderSide(color: Colors.orange),
                minimumSize: const Size(150, 40),
              ),
              onPressed: () {
                // 返回到首页(弹出所有页面直到首页)
                Navigator.popUntil(context, (route) => route.isFirst);
              },
              child: const Text('返回首页'),
            ),
          ],
        ),
      ),
    );
  }
}

// 第三个目标页面
class ThirdPage extends StatelessWidget {
  const ThirdPage({Key? key}) : super(key: key);

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: const Text('第三页'),
        backgroundColor: Colors.purple,
        foregroundColor: Colors.white,
      ),
      body: Center(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: [
            const Icon(
              Icons.looks_3,
              size: 100,
              color: Colors.purple,
            ),
            const SizedBox(height: 20),
            const Text(
              '这是第三页',
              style: TextStyle(fontSize: 24, fontWeight: FontWeight.bold),
            ),
            const SizedBox(height: 30),
            const Padding(
              padding: EdgeInsets.symmetric(horizontal: 40),
              child: Text(
                '在这个页面,您可以选择返回上一页或返回数据到首页',
                textAlign: TextAlign.center,
                style: TextStyle(fontSize: 16),
              ),
            ),
            const SizedBox(height: 50),

            // 返回按钮
            ElevatedButton(
              style: ElevatedButton.styleFrom(
                backgroundColor: Colors.purple,
                foregroundColor: Colors.white,
                minimumSize: const Size(200, 40),
              ),
              onPressed: () {
                // 返回上一页
                Navigator.pop(context);
              },
              child: const Text('返回不带数据'),
            ),

            const SizedBox(height: 20),

            // 返回数据按钮
            ElevatedButton(
              style: ElevatedButton.styleFrom(
                backgroundColor: Colors.purple.shade800,
                foregroundColor: Colors.white,
                minimumSize: const Size(200, 40),
              ),
              onPressed: () {
                // 返回数据到上一页
                Navigator.pop(context, '这是从第三页返回的数据!');
              },
              child: const Text('返回并传递数据'),
            ),
          ],
        ),
      ),
    );
  }
}

// 详情页 - 接收参数
class DetailPage extends StatelessWidget {
  final String title;
  final String message;

  const DetailPage({
    Key? key,
    required this.title,
    required this.message,
  }) : super(key: key);

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text(title),
        backgroundColor: Colors.red,
        foregroundColor: Colors.white,
      ),
      body: Center(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: [
            const Icon(
              Icons.info_outline,
              size: 80,
              color: Colors.red,
            ),
            const SizedBox(height: 30),
            const Text(
              '参数传递示例',
              style: TextStyle(fontSize: 24, fontWeight: FontWeight.bold),
            ),
            const SizedBox(height: 20),

            // 显示传入的参数
            Container(
              margin: const EdgeInsets.symmetric(horizontal: 40),
              padding: const EdgeInsets.all(20),
              decoration: BoxDecoration(
                color: Colors.red.shade50,
                borderRadius: BorderRadius.circular(10),
                border: Border.all(color: Colors.red.shade200),
              ),
              child: Column(
                children: [
                  const Text(
                    '接收到的参数:',
                    style: TextStyle(fontWeight: FontWeight.bold),
                  ),
                  const SizedBox(height: 10),
                  Text('标题: $title'),
                  const SizedBox(height: 5),
                  Text('消息: $message'),
                ],
              ),
            ),

            const SizedBox(height: 50),

            // 返回按钮
            ElevatedButton(
              style: ElevatedButton.styleFrom(
                backgroundColor: Colors.red,
                foregroundColor: Colors.white,
                minimumSize: const Size(150, 40),
              ),
              onPressed: () {
                // 返回上一页
                Navigator.pop(context);
              },
              child: const Text('返回'),
            ),
          ],
        ),
      ),
    );
  }
}

命名路由

dart
import 'package:flutter/material.dart';

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

class MyApp extends StatelessWidget {
  const MyApp({Key? key}) : super(key: key);

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      debugShowCheckedModeBanner: false,
      title: '命名路由示例',
      theme: ThemeData(
        primarySwatch: Colors.blue,
        useMaterial3: true,
      ),
      // 注册命名路由
      initialRoute: '/',
      routes: {
        '/': (context) => const HomePage(),
        '/first': (context) => const FirstPage(),
        '/second': (context) => const SecondPage(),
        '/third': (context) => const ThirdPage(),
      },
      // 路由生成器 - 处理参数传递和动态路由
      onGenerateRoute: (settings) {
        if (settings.name == '/detail') {
          // 从参数中提取需要的数据
          final args = settings.arguments as Map<String, String>;
          // 返回带参数的路由
          return MaterialPageRoute(
            builder: (context) {
              return DetailPage(
                title: args['title'] ?? '详情页',
                message: args['message'] ?? '无消息内容',
              );
            },
          );
        }
        // 未处理的命名路由
        return null;
      },
      // 处理未知路由
      onUnknownRoute: (settings) {
        return MaterialPageRoute(
          builder: (context) => const NotFoundPage(),
        );
      },
    );
  }
}

// 主页面
class HomePage extends StatefulWidget {
  const HomePage({Key? key}) : super(key: key);

  @override
  State<HomePage> createState() => _HomePageState();
}

class _HomePageState extends State<HomePage> {
  String? _returnedData;

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: const Text('命名路由导航示例'),
        backgroundColor: Colors.blue,
        foregroundColor: Colors.white,
      ),
      body: Center(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: [
            const Text(
              '命名路由导航',
              style: TextStyle(fontSize: 24, fontWeight: FontWeight.bold),
            ),
            const SizedBox(height: 20),
            const Padding(
              padding: EdgeInsets.symmetric(horizontal: 40),
              child: Text(
                '使用路由名称进行导航,而不是直接传递Widget',
                textAlign: TextAlign.center,
                style: TextStyle(fontSize: 16),
              ),
            ),
            const SizedBox(height: 50),

            // 显示第三页返回的数据
            if (_returnedData != null)
              Container(
                margin:
                    const EdgeInsets.symmetric(horizontal: 40, vertical: 20),
                padding: const EdgeInsets.all(15),
                decoration: BoxDecoration(
                  color: Colors.green.shade50,
                  borderRadius: BorderRadius.circular(10),
                  border: Border.all(color: Colors.green.shade200),
                ),
                child: Column(
                  children: [
                    const Text(
                      '返回的数据:',
                      style: TextStyle(fontWeight: FontWeight.bold),
                    ),
                    const SizedBox(height: 8),
                    Text(_returnedData!),
                  ],
                ),
              ),

            // 页面导航按钮
            _buildNavigationButton(
              title: '前往第一页',
              color: Colors.green,
              onPressed: () {
                Navigator.pushNamed(context, '/first');
              },
            ),

            _buildNavigationButton(
              title: '前往第二页',
              color: Colors.orange,
              onPressed: () {
                Navigator.pushNamed(context, '/second');
              },
            ),

            _buildNavigationButton(
              title: '前往第三页',
              color: Colors.purple,
              onPressed: () async {
                // 等待返回结果
                final result = await Navigator.pushNamed(context, '/third');
                setState(() {
                  _returnedData = result as String?;
                });
              },
            ),

            _buildNavigationButton(
              title: '传递参数到详情页',
              color: Colors.red,
              onPressed: () {
                Navigator.pushNamed(
                  context,
                  '/detail',
                  arguments: {
                    'title': '详情页标题',
                    'message': '这是通过命名路由传递的参数',
                  },
                );
              },
            ),

            _buildNavigationButton(
              title: '前往未定义页面',
              color: Colors.grey.shade800,
              onPressed: () {
                Navigator.pushNamed(context, '/notExist');
              },
            ),
          ],
        ),
      ),
    );
  }

  // 构建导航按钮
  Widget _buildNavigationButton({
    required String title,
    required Color color,
    required VoidCallback onPressed,
  }) {
    return Padding(
      padding: const EdgeInsets.symmetric(vertical: 8),
      child: ElevatedButton(
        style: ElevatedButton.styleFrom(
          backgroundColor: color,
          foregroundColor: Colors.white,
          minimumSize: const Size(250, 50),
          shape: RoundedRectangleBorder(
            borderRadius: BorderRadius.circular(10),
          ),
        ),
        onPressed: onPressed,
        child: Text(title),
      ),
    );
  }
}

// 第一个目标页面
class FirstPage extends StatelessWidget {
  const FirstPage({Key? key}) : super(key: key);

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: const Text('第一页'),
        backgroundColor: Colors.green,
        foregroundColor: Colors.white,
      ),
      body: Center(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: [
            const Icon(
              Icons.looks_one,
              size: 100,
              color: Colors.green,
            ),
            const SizedBox(height: 20),
            const Text(
              '这是第一页',
              style: TextStyle(fontSize: 24, fontWeight: FontWeight.bold),
            ),
            const SizedBox(height: 20),
            const Text(
              '当前路由: /first',
              style: TextStyle(fontSize: 16, color: Colors.grey),
            ),
            const SizedBox(height: 50),

            // 返回按钮
            ElevatedButton(
              style: ElevatedButton.styleFrom(
                backgroundColor: Colors.green,
                foregroundColor: Colors.white,
                minimumSize: const Size(150, 40),
              ),
              onPressed: () {
                // 返回上一页
                Navigator.pop(context);
              },
              child: const Text('返回'),
            ),

            const SizedBox(height: 20),

            // 使用命名路由导航
            OutlinedButton(
              style: OutlinedButton.styleFrom(
                foregroundColor: Colors.green,
                side: const BorderSide(color: Colors.green),
                minimumSize: const Size(200, 40),
              ),
              onPressed: () {
                Navigator.pushNamed(context, '/second');
              },
              child: const Text('命名路由跳转到第二页'),
            ),

            const SizedBox(height: 20),

            // 替换当前路由
            OutlinedButton(
              style: OutlinedButton.styleFrom(
                foregroundColor: Colors.blue,
                side: const BorderSide(color: Colors.blue),
                minimumSize: const Size(200, 40),
              ),
              onPressed: () {
                // 使用替换方式导航
                Navigator.pushReplacementNamed(context, '/second');
              },
              child: const Text('替换当前路由 (pushReplacement)'),
            ),
          ],
        ),
      ),
    );
  }
}

// 第二个目标页面
class SecondPage extends StatelessWidget {
  const SecondPage({Key? key}) : super(key: key);

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: const Text('第二页'),
        backgroundColor: Colors.orange,
        foregroundColor: Colors.white,
      ),
      body: Center(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: [
            const Icon(
              Icons.looks_two,
              size: 100,
              color: Colors.orange,
            ),
            const SizedBox(height: 20),
            const Text(
              '这是第二页',
              style: TextStyle(fontSize: 24, fontWeight: FontWeight.bold),
            ),
            const SizedBox(height: 20),
            const Text(
              '当前路由: /second',
              style: TextStyle(fontSize: 16, color: Colors.grey),
            ),
            const SizedBox(height: 50),

            // 返回按钮
            ElevatedButton(
              style: ElevatedButton.styleFrom(
                backgroundColor: Colors.orange,
                foregroundColor: Colors.white,
                minimumSize: const Size(150, 40),
              ),
              onPressed: () {
                // 返回上一页
                Navigator.pop(context);
              },
              child: const Text('返回'),
            ),

            const SizedBox(height: 20),

            // 返回首页按钮 - 使用命名路由
            OutlinedButton(
              style: OutlinedButton.styleFrom(
                foregroundColor: Colors.orange,
                side: const BorderSide(color: Colors.orange),
                minimumSize: const Size(200, 40),
              ),
              onPressed: () {
                // 返回到首页 (使用命名路由的方式)
                Navigator.pushNamedAndRemoveUntil(
                  context,
                  '/',
                  (route) => false, // 移除所有路由
                );
              },
              child: const Text('返回首页 (命名路由方式)'),
            ),

            const SizedBox(height: 20),

            // 常规方式返回首页
            OutlinedButton(
              style: OutlinedButton.styleFrom(
                foregroundColor: Colors.blue,
                side: const BorderSide(color: Colors.blue),
                minimumSize: const Size(200, 40),
              ),
              onPressed: () {
                // 返回到首页 (常规方式)
                Navigator.popUntil(context, (route) => route.isFirst);
              },
              child: const Text('返回首页 (常规方式)'),
            ),
          ],
        ),
      ),
    );
  }
}

// 第三个目标页面 - 返回数据
class ThirdPage extends StatelessWidget {
  const ThirdPage({Key? key}) : super(key: key);

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: const Text('第三页'),
        backgroundColor: Colors.purple,
        foregroundColor: Colors.white,
      ),
      body: Center(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: [
            const Icon(
              Icons.looks_3,
              size: 100,
              color: Colors.purple,
            ),
            const SizedBox(height: 20),
            const Text(
              '这是第三页',
              style: TextStyle(fontSize: 24, fontWeight: FontWeight.bold),
            ),
            const SizedBox(height: 20),
            const Text(
              '当前路由: /third',
              style: TextStyle(fontSize: 16, color: Colors.grey),
            ),
            const SizedBox(height: 30),
            const Padding(
              padding: EdgeInsets.symmetric(horizontal: 40),
              child: Text(
                '在命名路由中也可以返回数据给上一个页面',
                textAlign: TextAlign.center,
                style: TextStyle(fontSize: 16),
              ),
            ),
            const SizedBox(height: 50),

            // 返回按钮
            ElevatedButton(
              style: ElevatedButton.styleFrom(
                backgroundColor: Colors.purple,
                foregroundColor: Colors.white,
                minimumSize: const Size(200, 40),
              ),
              onPressed: () {
                // 返回上一页
                Navigator.pop(context);
              },
              child: const Text('返回不带数据'),
            ),

            const SizedBox(height: 20),

            // 返回数据按钮
            ElevatedButton(
              style: ElevatedButton.styleFrom(
                backgroundColor: Colors.purple.shade800,
                foregroundColor: Colors.white,
                minimumSize: const Size(200, 40),
              ),
              onPressed: () {
                // 返回数据到上一页
                Navigator.pop(context, '这是从第三页通过命名路由返回的数据!');
              },
              child: const Text('返回并传递数据'),
            ),
          ],
        ),
      ),
    );
  }
}

// 详情页 - 接收参数
class DetailPage extends StatelessWidget {
  final String title;
  final String message;

  const DetailPage({
    Key? key,
    required this.title,
    required this.message,
  }) : super(key: key);

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text(title),
        backgroundColor: Colors.red,
        foregroundColor: Colors.white,
      ),
      body: Center(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: [
            const Icon(
              Icons.info_outline,
              size: 80,
              color: Colors.red,
            ),
            const SizedBox(height: 30),
            const Text(
              '命名路由参数传递示例',
              style: TextStyle(fontSize: 24, fontWeight: FontWeight.bold),
            ),
            const SizedBox(height: 10),
            const Text(
              '使用 onGenerateRoute 实现参数传递',
              style: TextStyle(fontSize: 16, color: Colors.grey),
            ),
            const SizedBox(height: 20),

            // 显示传入的参数
            Container(
              margin: const EdgeInsets.symmetric(horizontal: 40),
              padding: const EdgeInsets.all(20),
              decoration: BoxDecoration(
                color: Colors.red.shade50,
                borderRadius: BorderRadius.circular(10),
                border: Border.all(color: Colors.red.shade200),
              ),
              child: Column(
                children: [
                  const Text(
                    '接收到的参数:',
                    style: TextStyle(fontWeight: FontWeight.bold),
                  ),
                  const SizedBox(height: 10),
                  Text('标题: $title'),
                  const SizedBox(height: 5),
                  Text('消息: $message'),
                ],
              ),
            ),

            const SizedBox(height: 50),

            // 返回按钮
            ElevatedButton(
              style: ElevatedButton.styleFrom(
                backgroundColor: Colors.red,
                foregroundColor: Colors.white,
                minimumSize: const Size(150, 40),
              ),
              onPressed: () {
                // 返回上一页
                Navigator.pop(context);
              },
              child: const Text('返回'),
            ),
          ],
        ),
      ),
    );
  }
}

// 404页面 - 未知路由
class NotFoundPage extends StatelessWidget {
  const NotFoundPage({Key? key}) : super(key: key);

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: const Text('404 - 页面不存在'),
        backgroundColor: Colors.red,
        foregroundColor: Colors.white,
      ),
      body: Center(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: [
            Icon(
              Icons.error_outline,
              size: 100,
              color: Colors.red.shade300,
            ),
            const SizedBox(height: 20),
            const Text(
              '404',
              style: TextStyle(
                fontSize: 48,
                fontWeight: FontWeight.bold,
                color: Colors.red,
              ),
            ),
            const SizedBox(height: 10),
            const Text(
              '页面不存在',
              style: TextStyle(fontSize: 24),
            ),
            const SizedBox(height: 20),
            const Padding(
              padding: EdgeInsets.symmetric(horizontal: 40),
              child: Text(
                '您尝试访问的路由未定义,触发了 onUnknownRoute 处理器',
                textAlign: TextAlign.center,
                style: TextStyle(fontSize: 16),
              ),
            ),
            const SizedBox(height: 40),
            ElevatedButton(
              style: ElevatedButton.styleFrom(
                backgroundColor: Colors.red,
                foregroundColor: Colors.white,
                minimumSize: const Size(150, 40),
              ),
              onPressed: () {
                // 返回首页
                Navigator.pushReplacementNamed(context, '/');
              },
              child: const Text('返回首页'),
            ),
          ],
        ),
      ),
    );
  }
}

image-20250302213910867

dialog

dart
import 'package:flutter/material.dart';

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

class MyApp extends StatelessWidget {
  const MyApp({Key? key}) : super(key: key);

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      debugShowCheckedModeBanner: false,
      title: 'Flutter 对话框示例',
      theme: ThemeData(
        primarySwatch: Colors.blue,
        useMaterial3: true,
      ),
      home: const DialogDemoPage(),
    );
  }
}

class DialogDemoPage extends StatefulWidget {
  const DialogDemoPage({Key? key}) : super(key: key);

  @override
  State<DialogDemoPage> createState() => _DialogDemoPageState();
}

class _DialogDemoPageState extends State<DialogDemoPage> {
  String _selectedOption = '未选择';
  String _result = '无结果';

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: const Text('对话框示例'),
        backgroundColor: Colors.blue,
        foregroundColor: Colors.white,
      ),
      body: Center(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: [
            const Text(
              'Flutter 对话框演示',
              style: TextStyle(fontSize: 24, fontWeight: FontWeight.bold),
            ),
            const SizedBox(height: 60),

            // 显示结果
            Container(
              padding: const EdgeInsets.all(16),
              margin: const EdgeInsets.symmetric(horizontal: 40),
              decoration: BoxDecoration(
                color: Colors.grey[100],
                borderRadius: BorderRadius.circular(10),
                border: Border.all(color: Colors.grey[300]!),
              ),
              child: Column(
                children: [
                  Text(
                    '选择的选项: $_selectedOption',
                    style: const TextStyle(fontSize: 16),
                  ),
                  const SizedBox(height: 8),
                  Text(
                    '操作结果: $_result',
                    style: const TextStyle(fontSize: 16),
                  ),
                ],
              ),
            ),

            const SizedBox(height: 60),

            // AlertDialog 按钮
            _buildDialogButton(
              title: '显示 AlertDialog',
              color: Colors.blue,
              onPressed: _showAlertDialog,
            ),

            // SimpleDialog 按钮
            _buildDialogButton(
              title: '显示 SimpleDialog',
              color: Colors.green,
              onPressed: _showSimpleDialog,
            ),

            // ModalBottomSheet 按钮
            _buildDialogButton(
              title: '显示 ModalBottomSheet',
              color: Colors.orange,
              onPressed: _showModalBottomSheet,
            ),
          ],
        ),
      ),
    );
  }

  // 构建按钮
  Widget _buildDialogButton({
    required String title,
    required Color color,
    required VoidCallback onPressed,
  }) {
    return Padding(
      padding: const EdgeInsets.symmetric(vertical: 8),
      child: ElevatedButton(
        style: ElevatedButton.styleFrom(
          backgroundColor: color,
          foregroundColor: Colors.white,
          minimumSize: const Size(250, 50),
          shape: RoundedRectangleBorder(
            borderRadius: BorderRadius.circular(10),
          ),
        ),
        onPressed: onPressed,
        child: Text(title),
      ),
    );
  }

  // 显示 AlertDialog
  void _showAlertDialog() {
    showDialog(
      context: context,
      builder: (BuildContext context) {
        return AlertDialog(
          title: const Text('警告对话框'),
          content: const Text(
            'AlertDialog 通常用于需要用户确认或取消的情况,例如删除操作或其他重要决定。',
          ),
          actions: [
            TextButton(
              onPressed: () {
                Navigator.of(context).pop();
                setState(() {
                  _result = '取消了操作';
                });
              },
              child: const Text('取消'),
            ),
            TextButton(
              onPressed: () {
                Navigator.of(context).pop();
                setState(() {
                  _result = '确认了操作';
                });
              },
              child: const Text('确认'),
            ),
          ],
        );
      },
    );
  }

  // 显示 SimpleDialog
  void _showSimpleDialog() {
    showDialog(
      context: context,
      builder: (BuildContext context) {
        return SimpleDialog(
          title: const Text('选择一个选项'),
          children: [
            SimpleDialogOption(
              onPressed: () {
                Navigator.pop(context);
                setState(() {
                  _selectedOption = '选项 A';
                  _result = '选择了选项 A';
                });
              },
              child: const Row(
                children: [
                  Icon(Icons.account_circle, color: Colors.blue),
                  SizedBox(width: 10),
                  Text('选项 A'),
                ],
              ),
            ),
            SimpleDialogOption(
              onPressed: () {
                Navigator.pop(context);
                setState(() {
                  _selectedOption = '选项 B';
                  _result = '选择了选项 B';
                });
              },
              child: const Row(
                children: [
                  Icon(Icons.account_balance, color: Colors.green),
                  SizedBox(width: 10),
                  Text('选项 B'),
                ],
              ),
            ),
            SimpleDialogOption(
              onPressed: () {
                Navigator.pop(context);
                setState(() {
                  _selectedOption = '选项 C';
                  _result = '选择了选项 C';
                });
              },
              child: const Row(
                children: [
                  Icon(Icons.account_balance_wallet, color: Colors.purple),
                  SizedBox(width: 10),
                  Text('选项 C'),
                ],
              ),
            ),
            Padding(
              padding: const EdgeInsets.all(8.0),
              child: TextButton(
                onPressed: () {
                  Navigator.pop(context);
                  setState(() {
                    _result = '取消了选择';
                  });
                },
                child: const Text('取消'),
              ),
            ),
          ],
        );
      },
    );
  }

  // 显示 ModalBottomSheet
  void _showModalBottomSheet() {
    showModalBottomSheet(
      context: context,
      shape: const RoundedRectangleBorder(
        borderRadius: BorderRadius.vertical(top: Radius.circular(20)),
      ),
      builder: (BuildContext context) {
        return SizedBox(
          height: 350,
          child: Column(
            children: [
              Container(
                padding: const EdgeInsets.all(16),
                child: const Row(
                  children: [
                    Text(
                      '底部菜单',
                      style: TextStyle(
                        fontSize: 20,
                        fontWeight: FontWeight.bold,
                      ),
                    ),
                    Spacer(),
                    Icon(Icons.drag_handle),
                  ],
                ),
              ),
              const Divider(height: 1),
              Expanded(
                child: ListView(
                  children: [
                    _buildBottomSheetItem(
                      icon: Icons.share,
                      title: '分享',
                      onTap: () {
                        Navigator.pop(context);
                        setState(() {
                          _selectedOption = '分享';
                          _result = '点击了分享按钮';
                        });
                      },
                    ),
                    _buildBottomSheetItem(
                      icon: Icons.link,
                      title: '复制链接',
                      onTap: () {
                        Navigator.pop(context);
                        setState(() {
                          _selectedOption = '复制链接';
                          _result = '复制了链接';
                        });
                      },
                    ),
                    _buildBottomSheetItem(
                      icon: Icons.edit,
                      title: '编辑',
                      onTap: () {
                        Navigator.pop(context);
                        setState(() {
                          _selectedOption = '编辑';
                          _result = '进入编辑模式';
                        });
                      },
                    ),
                    _buildBottomSheetItem(
                      icon: Icons.delete,
                      title: '删除',
                      textColor: Colors.red,
                      onTap: () {
                        Navigator.pop(context);
                        setState(() {
                          _selectedOption = '删除';
                          _result = '删除了内容';
                        });
                      },
                    ),
                    const SizedBox(height: 20),
                    Padding(
                      padding: const EdgeInsets.symmetric(horizontal: 16),
                      child: ElevatedButton(
                        style: ElevatedButton.styleFrom(
                          backgroundColor: Colors.grey[200],
                          foregroundColor: Colors.black,
                        ),
                        onPressed: () {
                          Navigator.pop(context);
                          setState(() {
                            _result = '取消了底部菜单操作';
                          });
                        },
                        child: const Text('取消'),
                      ),
                    ),
                  ],
                ),
              ),
            ],
          ),
        );
      },
    );
  }

  // 构建底部菜单项
  Widget _buildBottomSheetItem({
    required IconData icon,
    required String title,
    Color? textColor,
    required VoidCallback onTap,
  }) {
    return ListTile(
      leading: Icon(icon),
      title: Text(
        title,
        style: TextStyle(color: textColor),
      ),
      onTap: onTap,
    );
  }
}

image-20250305204513808

toast

dependencies:
  flutter:
    sdk: flutter

  fluttertoast: ^8.2.3  # 添加这一行
dart
import 'package:flutter/material.dart';
import 'package:fluttertoast/fluttertoast.dart';

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

class MyApp extends StatelessWidget {
  const MyApp({Key? key}) : super(key: key);

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      debugShowCheckedModeBanner: false,
      title: 'Flutter Toast 示例',
      theme: ThemeData(
        primarySwatch: Colors.blue,
        useMaterial3: true,
      ),
      home: const ToastDemoPage(),
    );
  }
}

class ToastDemoPage extends StatefulWidget {
  const ToastDemoPage({Key? key}) : super(key: key);

  @override
  State<ToastDemoPage> createState() => _ToastDemoPageState();
}

class _ToastDemoPageState extends State<ToastDemoPage> {
  String _lastToastMessage = '尚未显示 Toast';

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: const Text('Toast 消息示例'),
        backgroundColor: Colors.blue,
        foregroundColor: Colors.white,
      ),
      body: Center(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: [
            const Text(
              'Flutter Toast 演示',
              style: TextStyle(fontSize: 24, fontWeight: FontWeight.bold),
            ),
            const SizedBox(height: 40),

            // 显示上一次Toast信息
            Container(
              padding: const EdgeInsets.all(16),
              margin: const EdgeInsets.symmetric(horizontal: 40),
              decoration: BoxDecoration(
                color: Colors.grey[100],
                borderRadius: BorderRadius.circular(10),
                border: Border.all(color: Colors.grey[300]!),
              ),
              child: Column(
                children: [
                  const Text(
                    '上一次显示:',
                    style: TextStyle(fontWeight: FontWeight.bold),
                  ),
                  const SizedBox(height: 8),
                  Text(
                    _lastToastMessage,
                    style: const TextStyle(fontSize: 14),
                    textAlign: TextAlign.center,
                  ),
                ],
              ),
            ),

            const SizedBox(height: 50),

            // 显示基本Toast
            _buildToastButton(
              title: '显示基本 Toast',
              color: Colors.blue,
              onPressed: () {
                _showToast('这是一个基本的 Toast 消息');
              },
            ),

            // 显示长时间Toast
            _buildToastButton(
              title: '显示长时间 Toast',
              color: Colors.green,
              onPressed: () {
                _showLongToast('这是一个长时间显示的 Toast 消息');
              },
            ),

            // 显示顶部Toast
            _buildToastButton(
              title: '显示顶部 Toast',
              color: Colors.orange,
              onPressed: () {
                _showTopToast('这个 Toast 显示在屏幕顶部');
              },
            ),

            // 显示中间Toast
            _buildToastButton(
              title: '显示中间 Toast',
              color: Colors.purple,
              onPressed: () {
                _showCenterToast('这个 Toast 显示在屏幕中间');
              },
            ),

            // 显示带背景色的Toast
            _buildToastButton(
              title: '彩色 Toast',
              color: Colors.red,
              onPressed: () {
                _showColoredToast('这是带有自定义颜色的 Toast');
              },
            ),

            // 取消所有Toast
            _buildToastButton(
              title: '取消所有 Toast',
              color: Colors.grey[700]!,
              onPressed: () {
                Fluttertoast.cancel();
                setState(() {
                  _lastToastMessage = '已取消所有 Toast';
                });
              },
            ),
          ],
        ),
      ),
    );
  }

  // 构建Toast按钮
  Widget _buildToastButton({
    required String title,
    required Color color,
    required VoidCallback onPressed,
  }) {
    return Padding(
      padding: const EdgeInsets.symmetric(vertical: 8, horizontal: 40),
      child: ElevatedButton(
        style: ElevatedButton.styleFrom(
          backgroundColor: color,
          foregroundColor: Colors.white,
          minimumSize: const Size(double.infinity, 50),
          shape: RoundedRectangleBorder(
            borderRadius: BorderRadius.circular(10),
          ),
        ),
        onPressed: onPressed,
        child: Text(title),
      ),
    );
  }

  // 显示基本Toast
  void _showToast(String message) {
    Fluttertoast.showToast(
      msg: message,
      toastLength: Toast.LENGTH_SHORT,
      gravity: ToastGravity.BOTTOM,
      timeInSecForIosWeb: 1,
      backgroundColor: Colors.black87,
      textColor: Colors.white,
      fontSize: 16.0,
    );

    setState(() {
      _lastToastMessage = message;
    });
  }

  // 显示长时间Toast
  void _showLongToast(String message) {
    Fluttertoast.showToast(
      msg: message,
      toastLength: Toast.LENGTH_LONG,
      gravity: ToastGravity.BOTTOM,
      timeInSecForIosWeb: 3,
      backgroundColor: Colors.black87,
      textColor: Colors.white,
      fontSize: 16.0,
    );

    setState(() {
      _lastToastMessage = message;
    });
  }

  // 显示顶部Toast
  void _showTopToast(String message) {
    Fluttertoast.showToast(
      msg: message,
      toastLength: Toast.LENGTH_SHORT,
      gravity: ToastGravity.TOP,
      timeInSecForIosWeb: 1,
      backgroundColor: Colors.black87,
      textColor: Colors.white,
      fontSize: 16.0,
    );

    setState(() {
      _lastToastMessage = message;
    });
  }

  // 显示中间Toast
  void _showCenterToast(String message) {
    Fluttertoast.showToast(
      msg: message,
      toastLength: Toast.LENGTH_SHORT,
      gravity: ToastGravity.CENTER,
      timeInSecForIosWeb: 1,
      backgroundColor: Colors.black87,
      textColor: Colors.white,
      fontSize: 16.0,
    );

    setState(() {
      _lastToastMessage = message;
    });
  }

  // 显示带颜色的Toast
  void _showColoredToast(String message) {
    Fluttertoast.showToast(
      msg: message,
      toastLength: Toast.LENGTH_SHORT,
      gravity: ToastGravity.BOTTOM,
      timeInSecForIosWeb: 1,
      backgroundColor: Colors.red,
      textColor: Colors.white,
      fontSize: 16.0,
    );

    setState(() {
      _lastToastMessage = message;
    });
  }
}

image-20250305205346622

自定义dialog

dart
import 'package:flutter/material.dart';
import 'package:fluttertoast/fluttertoast.dart';

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

class MyApp extends StatelessWidget {
  const MyApp({Key? key}) : super(key: key);

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      debugShowCheckedModeBanner: false,
      title: '自定义对话框示例',
      theme: ThemeData(
        primarySwatch: Colors.blue,
        useMaterial3: true,
      ),
      home: const CustomDialogDemoPage(),
    );
  }
}

class CustomDialogDemoPage extends StatefulWidget {
  const CustomDialogDemoPage({Key? key}) : super(key: key);

  @override
  State<CustomDialogDemoPage> createState() => _CustomDialogDemoPageState();
}

class _CustomDialogDemoPageState extends State<CustomDialogDemoPage> {
  String _dialogResult = '未进行操作';
  String _inputValue = '';

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: const Text('自定义对话框示例'),
        backgroundColor: Colors.blue,
        foregroundColor: Colors.white,
      ),
      body: Center(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: [
            const Text(
              '自定义对话框演示',
              style: TextStyle(fontSize: 24, fontWeight: FontWeight.bold),
            ),
            const SizedBox(height: 40),

            // 显示对话框操作结果
            Container(
              padding: const EdgeInsets.all(16),
              margin: const EdgeInsets.symmetric(horizontal: 40),
              decoration: BoxDecoration(
                color: Colors.grey[100],
                borderRadius: BorderRadius.circular(10),
                border: Border.all(color: Colors.grey[300]!),
              ),
              child: Column(
                children: [
                  const Text(
                    '上一次操作结果:',
                    style: TextStyle(fontWeight: FontWeight.bold),
                  ),
                  const SizedBox(height: 8),
                  Text(
                    _dialogResult,
                    style: const TextStyle(fontSize: 14),
                    textAlign: TextAlign.center,
                  ),
                  if (_inputValue.isNotEmpty) ...[
                    const SizedBox(height: 8),
                    Text(
                      '输入值: $_inputValue',
                      style: const TextStyle(
                        fontSize: 14,
                        fontStyle: FontStyle.italic,
                      ),
                    ),
                  ],
                ],
              ),
            ),

            const SizedBox(height: 50),

            // 基本自定义对话框
            _buildDialogButton(
              title: '基本样式自定义对话框',
              color: Colors.blue,
              onPressed: () => _showBasicCustomDialog(),
            ),

            // 带输入框的对话框
            _buildDialogButton(
              title: '带输入框的对话框',
              color: Colors.green,
              onPressed: () => _showInputDialog(),
            ),

            // 自定义形状对话框
            _buildDialogButton(
              title: '自定义形状对话框',
              color: Colors.orange,
              onPressed: () => _showCustomShapeDialog(),
            ),

            // 带动画效果的对话框
            _buildDialogButton(
              title: '带动画效果的对话框',
              color: Colors.purple,
              onPressed: () => _showAnimatedDialog(),
            ),

            // 完全自定义对话框
            _buildDialogButton(
              title: '完全自定义对话框',
              color: Colors.red,
              onPressed: () => _showFullCustomDialog(),
            ),
          ],
        ),
      ),
    );
  }

  // 构建对话框按钮
  Widget _buildDialogButton({
    required String title,
    required Color color,
    required VoidCallback onPressed,
  }) {
    return Padding(
      padding: const EdgeInsets.symmetric(vertical: 8, horizontal: 40),
      child: ElevatedButton(
        style: ElevatedButton.styleFrom(
          backgroundColor: color,
          foregroundColor: Colors.white,
          minimumSize: const Size(double.infinity, 50),
          shape: RoundedRectangleBorder(
            borderRadius: BorderRadius.circular(10),
          ),
        ),
        onPressed: onPressed,
        child: Text(title),
      ),
    );
  }

  // 基本自定义对话框
  Future<void> _showBasicCustomDialog() async {
    final result = await showDialog<String>(
      context: context,
      builder: (BuildContext context) {
        return Dialog(
          shape: RoundedRectangleBorder(
            borderRadius: BorderRadius.circular(16),
          ),
          elevation: 8,
          backgroundColor: Colors.white,
          child: Padding(
            padding: const EdgeInsets.all(20),
            child: Column(
              mainAxisSize: MainAxisSize.min,
              children: [
                const Icon(
                  Icons.info_outline,
                  color: Colors.blue,
                  size: 48,
                ),
                const SizedBox(height: 16),
                const Text(
                  '自定义对话框',
                  style: TextStyle(
                    fontSize: 20,
                    fontWeight: FontWeight.bold,
                  ),
                ),
                const SizedBox(height: 12),
                const Text(
                  '这是一个基本样式的自定义对话框,通过修改Dialog组件的属性来自定义外观。',
                  textAlign: TextAlign.center,
                  style: TextStyle(fontSize: 16),
                ),
                const SizedBox(height: 24),
                Row(
                  mainAxisAlignment: MainAxisAlignment.spaceEvenly,
                  children: [
                    TextButton(
                      onPressed: () {
                        Navigator.pop(context, '取消');
                      },
                      child: const Text('取消'),
                    ),
                    ElevatedButton(
                      style: ElevatedButton.styleFrom(
                        backgroundColor: Colors.blue,
                        foregroundColor: Colors.white,
                      ),
                      onPressed: () {
                        Navigator.pop(context, '确认');
                      },
                      child: const Text('确认'),
                    ),
                  ],
                ),
              ],
            ),
          ),
        );
      },
    );

    if (result != null) {
      setState(() {
        _dialogResult = '用户点击了: $result';
      });
    }
  }

  // 带输入框的对话框
  Future<void> _showInputDialog() async {
    final TextEditingController controller = TextEditingController();

    final result = await showDialog<Map<String, dynamic>>(
      context: context,
      builder: (BuildContext context) {
        return Dialog(
          shape: RoundedRectangleBorder(
            borderRadius: BorderRadius.circular(16),
          ),
          elevation: 8,
          child: Padding(
            padding: const EdgeInsets.all(20),
            child: Column(
              mainAxisSize: MainAxisSize.min,
              children: [
                const Text(
                  '请输入信息',
                  style: TextStyle(
                    fontSize: 20,
                    fontWeight: FontWeight.bold,
                  ),
                ),
                const SizedBox(height: 20),
                TextField(
                  controller: controller,
                  decoration: const InputDecoration(
                    labelText: '您的名字',
                    border: OutlineInputBorder(),
                    prefixIcon: Icon(Icons.person),
                  ),
                ),
                const SizedBox(height: 24),
                Row(
                  mainAxisAlignment: MainAxisAlignment.end,
                  children: [
                    TextButton(
                      onPressed: () {
                        Navigator.pop(context);
                      },
                      child: const Text('取消'),
                    ),
                    const SizedBox(width: 8),
                    ElevatedButton(
                      style: ElevatedButton.styleFrom(
                        backgroundColor: Colors.green,
                        foregroundColor: Colors.white,
                      ),
                      onPressed: () {
                        Navigator.pop(context, {
                          'action': '提交',
                          'input': controller.text,
                        });
                      },
                      child: const Text('提交'),
                    ),
                  ],
                ),
              ],
            ),
          ),
        );
      },
    );

    if (result != null) {
      setState(() {
        _dialogResult = '用户${result['action']}了表单';
        _inputValue = result['input'];
      });
    }
  }

  // 自定义形状对话框
  Future<void> _showCustomShapeDialog() async {
    final result = await showDialog<String>(
      context: context,
      builder: (BuildContext context) {
        return Dialog(
          // 完全自定义形状
          shape: const RoundedRectangleBorder(
            borderRadius: BorderRadius.only(
              topLeft: Radius.circular(24),
              topRight: Radius.circular(0),
              bottomLeft: Radius.circular(0),
              bottomRight: Radius.circular(24),
            ),
          ),
          elevation: 8,
          backgroundColor: Colors.orange[50],
          child: Container(
            padding: const EdgeInsets.all(20),
            decoration: BoxDecoration(
              border: Border.all(color: Colors.orange, width: 3),
              borderRadius: const BorderRadius.only(
                topLeft: Radius.circular(20),
                topRight: Radius.circular(0),
                bottomLeft: Radius.circular(0),
                bottomRight: Radius.circular(20),
              ),
            ),
            child: Column(
              mainAxisSize: MainAxisSize.min,
              children: [
                Row(
                  children: [
                    Icon(
                      Icons.design_services,
                      color: Colors.orange[800],
                      size: 30,
                    ),
                    const SizedBox(width: 12),
                    const Text(
                      '自定义形状对话框',
                      style: TextStyle(
                        fontSize: 18,
                        fontWeight: FontWeight.bold,
                        color: Colors.deepOrange,
                      ),
                    ),
                  ],
                ),
                const SizedBox(height: 16),
                const Text(
                  '这个对话框展示了如何完全自定义形状、边框和背景',
                  style: TextStyle(fontSize: 15),
                ),
                const SizedBox(height: 24),
                ElevatedButton.icon(
                  icon: const Icon(Icons.check_circle),
                  label: const Text('知道了'),
                  style: ElevatedButton.styleFrom(
                    backgroundColor: Colors.orange,
                    foregroundColor: Colors.white,
                  ),
                  onPressed: () {
                    Navigator.pop(context, '关闭了自定义形状对话框');
                  },
                ),
              ],
            ),
          ),
        );
      },
    );

    if (result != null) {
      setState(() {
        _dialogResult = result;
        _inputValue = '';
      });
    }
  }

  // 带动画效果的对话框
  Future<void> _showAnimatedDialog() async {
    final result = await showGeneralDialog<String>(
      context: context,
      pageBuilder: (_, __, ___) => Container(), // 这里不使用
      transitionBuilder: (context, animation, secondaryAnimation, child) {
        // 自定义动画
        final curvedAnimation = CurvedAnimation(
          parent: animation,
          curve: Curves.easeInOutBack,
        );

        return ScaleTransition(
          scale: Tween<double>(begin: 0.5, end: 1.0).animate(curvedAnimation),
          child: FadeTransition(
            opacity:
                Tween<double>(begin: 0.5, end: 1.0).animate(curvedAnimation),
            child: Dialog(
              shape: RoundedRectangleBorder(
                borderRadius: BorderRadius.circular(20),
              ),
              backgroundColor: Colors.purple[50],
              child: Container(
                padding: const EdgeInsets.all(20),
                child: Column(
                  mainAxisSize: MainAxisSize.min,
                  children: [
                    // 动画图标
                    TweenAnimationBuilder(
                      tween: Tween<double>(begin: 0, end: 1),
                      duration: const Duration(milliseconds: 800),
                      builder: (context, double value, child) {
                        return Transform.rotate(
                          angle: value * 2 * 3.14159,
                          child: Icon(
                            Icons.motion_photos_on,
                            color: Colors.purple,
                            size: 50 * value,
                          ),
                        );
                      },
                    ),
                    const SizedBox(height: 16),
                    const Text(
                      '带动画的对话框',
                      style: TextStyle(
                        fontSize: 20,
                        fontWeight: FontWeight.bold,
                        color: Colors.purple,
                      ),
                    ),
                    const SizedBox(height: 12),
                    const Text(
                      '这个对话框使用了自定义动画效果,展示了如何创建更生动的用户界面',
                      textAlign: TextAlign.center,
                      style: TextStyle(fontSize: 15),
                    ),
                    const SizedBox(height: 24),
                    ElevatedButton(
                      style: ElevatedButton.styleFrom(
                        backgroundColor: Colors.purple,
                        foregroundColor: Colors.white,
                      ),
                      onPressed: () {
                        Navigator.pop(context, '关闭了动画对话框');
                      },
                      child: const Text('关闭'),
                    ),
                  ],
                ),
              ),
            ),
          ),
        );
      },
      transitionDuration: const Duration(milliseconds: 800),
      barrierDismissible: true,
      barrierLabel: '',
      barrierColor: Colors.black54,
    );

    if (result != null) {
      setState(() {
        _dialogResult = result;
        _inputValue = '';
      });
    }
  }

  // 完全自定义对话框
  Future<void> _showFullCustomDialog() async {
    final result = await showDialog<String>(
      context: context,
      builder: (BuildContext context) {
        return Dialog(
          insetPadding: const EdgeInsets.all(10),
          backgroundColor: Colors.transparent,
          elevation: 0,
          child: Stack(
            clipBehavior: Clip.none,
            alignment: Alignment.center,
            children: [
              // 卡片主体
              Container(
                width: double.infinity,
                height: 300,
                decoration: BoxDecoration(
                  borderRadius: BorderRadius.circular(15),
                  gradient: const LinearGradient(
                    colors: [Color(0xFFFF416C), Color(0xFFFF4B2B)],
                    begin: Alignment.topLeft,
                    end: Alignment.bottomRight,
                  ),
                  boxShadow: [
                    BoxShadow(
                      color: Colors.red.withOpacity(0.5),
                      blurRadius: 12,
                      offset: const Offset(0, 5),
                    ),
                  ],
                ),
                padding: const EdgeInsets.fromLTRB(20, 50, 20, 20),
                child: Column(
                  mainAxisSize: MainAxisSize.min,
                  children: [
                    const Text(
                      '完全自定义对话框',
                      style: TextStyle(
                        fontSize: 22,
                        fontWeight: FontWeight.bold,
                        color: Colors.white,
                      ),
                    ),
                    const SizedBox(height: 15),
                    Expanded(
                      child: Container(
                        padding: const EdgeInsets.all(15),
                        decoration: BoxDecoration(
                          color: Colors.white.withOpacity(0.2),
                          borderRadius: BorderRadius.circular(10),
                        ),
                        child: const Center(
                          child: Text(
                            '这个对话框展示了如何创建完全自定义的样式,包括渐变背景、自定义形状、悬浮图标等高级定制功能',
                            textAlign: TextAlign.center,
                            style: TextStyle(
                              color: Colors.white,
                              fontSize: 16,
                            ),
                          ),
                        ),
                      ),
                    ),
                    const SizedBox(height: 15),
                    Row(
                      mainAxisAlignment: MainAxisAlignment.spaceEvenly,
                      children: [
                        TextButton.icon(
                          icon: const Icon(Icons.cancel, color: Colors.white),
                          label: const Text(
                            '取消',
                            style: TextStyle(color: Colors.white),
                          ),
                          onPressed: () {
                            Navigator.pop(context, '取消了自定义操作');
                          },
                        ),
                        ElevatedButton.icon(
                          icon: const Icon(Icons.check),
                          label: const Text('确认'),
                          style: ElevatedButton.styleFrom(
                            backgroundColor: Colors.white,
                            foregroundColor: Colors.red,
                          ),
                          onPressed: () {
                            Navigator.pop(context, '确认了自定义操作');
                          },
                        ),
                      ],
                    ),
                  ],
                ),
              ),
              // 顶部悬浮图标
              Positioned(
                top: -30,
                child: CircleAvatar(
                  radius: 40,
                  backgroundColor: Colors.white,
                  child: Icon(
                    Icons.favorite,
                    size: 50,
                    color: Colors.red[700],
                  ),
                ),
              ),
            ],
          ),
        );
      },
    );

    if (result != null) {
      setState(() {
        _dialogResult = result;
        _inputValue = '';
      });
    }
  }
}

image-20250305210107652

轮播图

dart
import 'package:flutter/material.dart';
import 'package:fluttertoast/fluttertoast.dart';
import 'dart:async';

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

class MyApp extends StatelessWidget {
  const MyApp({Key? key}) : super(key: key);

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      debugShowCheckedModeBanner: false,
      title: 'PageView 示例',
      theme: ThemeData(
        primarySwatch: Colors.blue,
        useMaterial3: true,
      ),
      home: const HomePage(),
    );
  }
}

class HomePage extends StatefulWidget {
  const HomePage({Key? key}) : super(key: key);

  @override
  State<HomePage> createState() => _HomePageState();
}

class _HomePageState extends State<HomePage>
    with SingleTickerProviderStateMixin {
  // Tab控制器
  late TabController _tabController;

  @override
  void initState() {
    super.initState();
    _tabController = TabController(length: 2, vsync: this);
  }

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

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: const Text('PageView 和轮播图示例'),
        backgroundColor: Colors.blue,
        foregroundColor: Colors.white,
        bottom: TabBar(
          controller: _tabController,
          tabs: const [
            Tab(text: '页面切换', icon: Icon(Icons.view_carousel)),
            Tab(text: '轮播图', icon: Icon(Icons.slideshow)),
          ],
          labelColor: Colors.white,
          unselectedLabelColor: Colors.white70,
          indicatorColor: Colors.white,
        ),
      ),
      body: TabBarView(
        controller: _tabController,
        children: const [
          // 基本PageView页面
          BasicPageViewDemo(),

          // 轮播图
          CarouselDemo(),
        ],
      ),
    );
  }
}

// 基本PageView示例页面
class BasicPageViewDemo extends StatefulWidget {
  const BasicPageViewDemo({Key? key}) : super(key: key);

  @override
  State<BasicPageViewDemo> createState() => _BasicPageViewDemoState();
}

class _BasicPageViewDemoState extends State<BasicPageViewDemo> {
  // PageView控制器
  final PageController _pageController = PageController(initialPage: 0);
  int _currentPage = 0;

  // 页面内容列表
  final List<PageContent> _pages = [
    PageContent(
      title: '第一页',
      description: '这是PageView的第一页,向左滑动查看更多内容',
      color: Colors.blue,
      icon: Icons.looks_one,
    ),
    PageContent(
      title: '第二页',
      description: '这是第二页,您可以在页面之间左右滑动',
      color: Colors.green,
      icon: Icons.looks_two,
    ),
    PageContent(
      title: '第三页',
      description: '这是最后一页,您可以尝试不同的PageView滚动方向和物理特性',
      color: Colors.orange,
      icon: Icons.looks_3,
    ),
  ];

  // 滚动方向和物理特性设置
  Axis _scrollDirection = Axis.horizontal;
  bool _pageSnapping = true;
  bool _reverse = false;

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

  @override
  Widget build(BuildContext context) {
    return Column(
      children: [
        // 设置区域
        _buildSettings(),

        // 分隔线
        const Divider(height: 0),

        // 页面指示器
        Container(
          padding: const EdgeInsets.symmetric(vertical: 8),
          color: Colors.grey[200],
          child: Row(
            mainAxisAlignment: MainAxisAlignment.center,
            children: [
              Text(
                '当前页面: ${_currentPage + 1}/${_pages.length}',
                style: const TextStyle(fontWeight: FontWeight.bold),
              ),
              const SizedBox(width: 20),
              // 页面指示器点
              Row(
                mainAxisSize: MainAxisSize.min,
                children: List.generate(_pages.length, (index) {
                  return Container(
                    margin: const EdgeInsets.symmetric(horizontal: 3),
                    width: _currentPage == index ? 18 : 8,
                    height: 8,
                    decoration: BoxDecoration(
                      color: _currentPage == index
                          ? _pages[_currentPage].color
                          : Colors.grey[300],
                      borderRadius: BorderRadius.circular(4),
                    ),
                  );
                }),
              ),
            ],
          ),
        ),

        // PageView主体
        Expanded(
          child: PageView.builder(
            controller: _pageController,
            scrollDirection: _scrollDirection,
            reverse: _reverse,
            pageSnapping: _pageSnapping,
            onPageChanged: (int page) {
              setState(() {
                _currentPage = page;
              });
            },
            itemCount: _pages.length,
            itemBuilder: (context, index) {
              final page = _pages[index];
              return Container(
                color: page.color.withOpacity(0.3),
                child: Center(
                  child: Card(
                    margin: const EdgeInsets.all(30),
                    elevation: 6,
                    shape: RoundedRectangleBorder(
                      borderRadius: BorderRadius.circular(15),
                    ),
                    color: Colors.white,
                    child: Padding(
                      padding: const EdgeInsets.all(20),
                      child: Column(
                        mainAxisSize: MainAxisSize.min,
                        children: [
                          Icon(
                            page.icon,
                            size: 80,
                            color: page.color,
                          ),
                          const SizedBox(height: 20),
                          Text(
                            page.title,
                            style: TextStyle(
                              fontSize: 28,
                              fontWeight: FontWeight.bold,
                              color: page.color,
                            ),
                          ),
                          const SizedBox(height: 15),
                          Text(
                            page.description,
                            textAlign: TextAlign.center,
                            style: const TextStyle(fontSize: 16),
                          ),
                          const SizedBox(height: 30),
                          Row(
                            mainAxisAlignment: MainAxisAlignment.center,
                            children: [
                              // 前一页按钮
                              if (index > 0)
                                ElevatedButton.icon(
                                  icon: const Icon(Icons.chevron_left),
                                  label: const Text('上一页'),
                                  style: ElevatedButton.styleFrom(
                                    backgroundColor: Colors.grey[200],
                                    foregroundColor: Colors.black87,
                                  ),
                                  onPressed: () {
                                    _pageController.previousPage(
                                      duration:
                                          const Duration(milliseconds: 300),
                                      curve: Curves.easeInOut,
                                    );
                                  },
                                ),

                              const SizedBox(width: 10),

                              // 下一页按钮
                              if (index < _pages.length - 1)
                                ElevatedButton.icon(
                                  icon: const Icon(Icons.chevron_right),
                                  label: const Text('下一页'),
                                  style: ElevatedButton.styleFrom(
                                    backgroundColor: page.color,
                                    foregroundColor: Colors.white,
                                  ),
                                  onPressed: () {
                                    _pageController.nextPage(
                                      duration:
                                          const Duration(milliseconds: 300),
                                      curve: Curves.easeInOut,
                                    );
                                  },
                                ),
                            ],
                          ),
                        ],
                      ),
                    ),
                  ),
                ),
              );
            },
          ),
        ),
      ],
    );
  }

  // 页面设置区域
  Widget _buildSettings() {
    return Container(
      padding: const EdgeInsets.all(12),
      color: Colors.grey[100],
      child: Column(
        children: [
          // 滚动方向设置
          Row(
            children: [
              const Text('滚动方向:'),
              const SizedBox(width: 15),
              ChoiceChip(
                label: const Text('水平'),
                selected: _scrollDirection == Axis.horizontal,
                onSelected: (selected) {
                  if (selected) {
                    setState(() {
                      _scrollDirection = Axis.horizontal;
                    });
                  }
                },
              ),
              const SizedBox(width: 8),
              ChoiceChip(
                label: const Text('垂直'),
                selected: _scrollDirection == Axis.vertical,
                onSelected: (selected) {
                  if (selected) {
                    setState(() {
                      _scrollDirection = Axis.vertical;
                    });
                  }
                },
              ),
            ],
          ),
          // 更多设置
          Row(
            children: [
              // 页面对齐设置
              Row(
                children: [
                  const Text('页面对齐:'),
                  Switch(
                    value: _pageSnapping,
                    onChanged: (value) {
                      setState(() {
                        _pageSnapping = value;
                      });
                    },
                  ),
                ],
              ),
              const SizedBox(width: 15),
              // 反向设置
              Row(
                children: [
                  const Text('反向:'),
                  Switch(
                    value: _reverse,
                    onChanged: (value) {
                      setState(() {
                        _reverse = value;
                      });
                    },
                  ),
                ],
              ),
            ],
          ),
        ],
      ),
    );
  }
}

// 自动轮播图示例
class CarouselDemo extends StatefulWidget {
  const CarouselDemo({Key? key}) : super(key: key);

  @override
  State<CarouselDemo> createState() => _CarouselDemoState();
}

class _CarouselDemoState extends State<CarouselDemo> {
  // 轮播图控制器
  final PageController _carouselController = PageController(initialPage: 0);

  // 轮播图数据
  final List<CarouselItem> _carouselItems = [
    CarouselItem(
      title: '风景图片 1',
      description: '美丽的山脉和湖泊',
      color: Colors.blue,
      image:
          'https://images.unsplash.com/photo-1506744038136-46273834b3fb?w=600&q=80',
    ),
    CarouselItem(
      title: '风景图片 2',
      description: '平静的海洋',
      color: Colors.green,
      image:
          'https://images.unsplash.com/photo-1507525428034-b723cf961d3e?w=600&q=80',
    ),
    CarouselItem(
      title: '风景图片 3',
      description: '壮观的日落',
      color: Colors.orange,
      image:
          'https://images.unsplash.com/photo-1510414842594-a61c69b5ae57?w=600&q=80',
    ),
    CarouselItem(
      title: '风景图片 4',
      description: '神秘的森林',
      color: Colors.purple,
      image:
          'https://images.unsplash.com/photo-1542273917363-3b1817f69a2d?w=600&q=80',
    ),
    CarouselItem(
      title: '风景图片 5',
      description: '城市天际线',
      color: Colors.red,
      image:
          'https://images.unsplash.com/photo-1477959858617-67f85cf4f1df?w=600&q=80',
    ),
  ];

  int _currentCarouselPage = 0;
  bool _isAutoPlay = true;
  int _autoPlayIntervalSeconds = 3;
  Timer? _autoPlayTimer;

  @override
  void initState() {
    super.initState();
    _startAutoPlay();
  }

  @override
  void dispose() {
    _stopAutoPlay();
    _carouselController.dispose();
    super.dispose();
  }

  // 开始自动播放
  void _startAutoPlay() {
    if (_isAutoPlay) {
      _autoPlayTimer = Timer.periodic(
        Duration(seconds: _autoPlayIntervalSeconds),
        (timer) {
          if (_carouselController.hasClients) {
            if (_currentCarouselPage < _carouselItems.length - 1) {
              _carouselController.nextPage(
                duration: const Duration(milliseconds: 500),
                curve: Curves.easeInOut,
              );
            } else {
              _carouselController.animateToPage(
                0,
                duration: const Duration(milliseconds: 500),
                curve: Curves.easeInOut,
              );
            }
          }
        },
      );
    }
  }

  // 停止自动播放
  void _stopAutoPlay() {
    _autoPlayTimer?.cancel();
    _autoPlayTimer = null;
  }

  @override
  Widget build(BuildContext context) {
    return Column(
      children: [
        // 设置区域
        Container(
          padding: const EdgeInsets.all(12),
          color: Colors.grey[100],
          child: Column(
            children: [
              // 自动播放设置
              Row(
                children: [
                  const Text('自动播放:'),
                  Switch(
                    value: _isAutoPlay,
                    onChanged: (value) {
                      setState(() {
                        _isAutoPlay = value;
                        if (_isAutoPlay) {
                          _startAutoPlay();
                        } else {
                          _stopAutoPlay();
                        }
                      });
                    },
                  ),
                  const SizedBox(width: 20),
                  const Text('间隔:'),
                  const SizedBox(width: 5),
                  DropdownButton<int>(
                    value: _autoPlayIntervalSeconds,
                    items: [2, 3, 5, 8].map((int value) {
                      return DropdownMenuItem<int>(
                        value: value,
                        child: Text('$value秒'),
                      );
                    }).toList(),
                    onChanged: (int? newValue) {
                      if (newValue != null) {
                        setState(() {
                          _autoPlayIntervalSeconds = newValue;
                          if (_isAutoPlay) {
                            _stopAutoPlay();
                            _startAutoPlay();
                          }
                        });
                      }
                    },
                  ),
                ],
              ),
            ],
          ),
        ),

        // 主轮播图区域
        Expanded(
          child: Stack(
            alignment: Alignment.bottomCenter,
            children: [
              // 轮播图PageView
              PageView.builder(
                controller: _carouselController,
                onPageChanged: (int page) {
                  setState(() {
                    _currentCarouselPage = page;
                  });
                },
                itemCount: _carouselItems.length,
                itemBuilder: (context, index) {
                  final item = _carouselItems[index];
                  return Stack(
                    fit: StackFit.expand,
                    children: [
                      // 背景图片
                      Image.network(
                        item.image,
                        fit: BoxFit.cover,
                        loadingBuilder: (context, child, loadingProgress) {
                          if (loadingProgress == null) return child;
                          return Container(
                            color: item.color.withOpacity(0.3),
                            child: const Center(
                              child: CircularProgressIndicator(),
                            ),
                          );
                        },
                        errorBuilder: (context, error, stackTrace) {
                          return Container(
                            color: item.color.withOpacity(0.5),
                            child: const Center(
                              child: Icon(
                                Icons.broken_image,
                                size: 80,
                                color: Colors.white,
                              ),
                            ),
                          );
                        },
                      ),

                      // 颜色覆盖层
                      Container(
                        decoration: BoxDecoration(
                          gradient: LinearGradient(
                            begin: Alignment.topCenter,
                            end: Alignment.bottomCenter,
                            colors: [
                              Colors.transparent,
                              Colors.black.withOpacity(0.7),
                            ],
                          ),
                        ),
                      ),

                      // 内容
                      Padding(
                        padding: const EdgeInsets.all(20),
                        child: Column(
                          mainAxisAlignment: MainAxisAlignment.end,
                          crossAxisAlignment: CrossAxisAlignment.start,
                          children: [
                            Text(
                              item.title,
                              style: const TextStyle(
                                color: Colors.white,
                                fontSize: 24,
                                fontWeight: FontWeight.bold,
                              ),
                            ),
                            const SizedBox(height: 8),
                            Text(
                              item.description,
                              style: const TextStyle(
                                color: Colors.white,
                                fontSize: 16,
                              ),
                            ),
                            const SizedBox(height: 30),
                          ],
                        ),
                      ),
                    ],
                  );
                },
              ),

              // 底部指示器
              Padding(
                padding: const EdgeInsets.only(bottom: 16),
                child: Row(
                  mainAxisAlignment: MainAxisAlignment.center,
                  children: List.generate(_carouselItems.length, (index) {
                    return GestureDetector(
                      onTap: () {
                        _carouselController.animateToPage(
                          index,
                          duration: const Duration(milliseconds: 300),
                          curve: Curves.easeInOut,
                        );
                      },
                      child: Container(
                        margin: const EdgeInsets.symmetric(horizontal: 4),
                        width: _currentCarouselPage == index ? 20 : 10,
                        height: 10,
                        decoration: BoxDecoration(
                          color: _currentCarouselPage == index
                              ? _carouselItems[index].color
                              : Colors.white.withOpacity(0.5),
                          borderRadius: BorderRadius.circular(5),
                        ),
                      ),
                    );
                  }),
                ),
              ),
            ],
          ),
        ),

        // 轮播图手动控制区域
        Container(
          padding: const EdgeInsets.symmetric(vertical: 15),
          color: Colors.grey[200],
          child: Row(
            mainAxisAlignment: MainAxisAlignment.center,
            children: [
              // 上一张按钮
              ElevatedButton.icon(
                icon: const Icon(Icons.chevron_left),
                label: const Text('上一张'),
                style: ElevatedButton.styleFrom(
                  backgroundColor: Colors.white,
                  foregroundColor: Colors.black87,
                ),
                onPressed: () {
                  if (_currentCarouselPage > 0) {
                    _carouselController.previousPage(
                      duration: const Duration(milliseconds: 300),
                      curve: Curves.easeInOut,
                    );
                  } else {
                    _carouselController.animateToPage(
                      _carouselItems.length - 1,
                      duration: const Duration(milliseconds: 300),
                      curve: Curves.easeInOut,
                    );
                  }
                },
              ),

              const SizedBox(width: 15),

              // 下一张按钮
              ElevatedButton.icon(
                icon: const Icon(Icons.chevron_right),
                label: const Text('下一张'),
                style: ElevatedButton.styleFrom(
                  backgroundColor: _carouselItems[_currentCarouselPage].color,
                  foregroundColor: Colors.white,
                ),
                onPressed: () {
                  if (_currentCarouselPage < _carouselItems.length - 1) {
                    _carouselController.nextPage(
                      duration: const Duration(milliseconds: 300),
                      curve: Curves.easeInOut,
                    );
                  } else {
                    _carouselController.animateToPage(
                      0,
                      duration: const Duration(milliseconds: 300),
                      curve: Curves.easeInOut,
                    );
                  }
                },
              ),
            ],
          ),
        ),
      ],
    );
  }
}

// 页面内容数据类
class PageContent {
  final String title;
  final String description;
  final Color color;
  final IconData icon;

  PageContent({
    required this.title,
    required this.description,
    required this.color,
    required this.icon,
  });
}

// 轮播图项目数据类
class CarouselItem {
  final String title;
  final String description;
  final Color color;
  final String image;

  CarouselItem({
    required this.title,
    required this.description,
    required this.color,
    required this.image,
  });
}

image-20250305211526979

flutter的key

我们平时一定接触过很多的 Widget,比如 Container、 Row、 Column 等,它们在我们绘制界面的过程 中发挥着重要的作用。但是不知道你有没有注意到,在几乎每个 Widget 的构造函数中,都有一个共同 的参数,它们通常在参数列表的第一个,那就是 Key。

在Flutter中, *Key**是不能重复使用的*,所以Key一般用来做唯一标识。组件在更新的时候,其状态的保 存主要是通过判断****组件的类型*或者*key********值****是否一致。因此,当各组件的类型不同的时候,类型已经足够 用来区分不同的组件了,此时我们可以不必使用key。但是如果同时存在多个同一类型的控件的时候, 此时类型已经无法作为区分的条件了,我们就需要使用到key。

Flutter key子类包含

局部键(LocalKey):ValueKey、 ObjectKey、 UniqueKey

全局键(GlobalKey): GlobalKey、 GlobalObjectKey

dart
import 'package:flutter/material.dart';
import 'package:fluttertoast/fluttertoast.dart';
import 'dart:async';
import 'dart:math' as math;

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

class MyApp extends StatelessWidget {
  const MyApp({Key? key}) : super(key: key);

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      debugShowCheckedModeBanner: false,
      title: 'Flutter Key 示例',
      theme: ThemeData(
        primarySwatch: Colors.blue,
        useMaterial3: true,
      ),
      home: const KeyDemoHomePage(),
    );
  }
}

class KeyDemoHomePage extends StatefulWidget {
  const KeyDemoHomePage({Key? key}) : super(key: key);

  @override
  State<KeyDemoHomePage> createState() => _KeyDemoHomePageState();
}

class _KeyDemoHomePageState extends State<KeyDemoHomePage> {
  int _selectedIndex = 0;

  // 所有演示页面
  final List<Widget> _demoPages = [
    const ListKeyDemo(),
    const GlobalKeyDemo(),
    const UniqueKeyDemo(),
    const ValueKeyDemo(),
  ];

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: const Text('Flutter Key 示例'),
        backgroundColor: Colors.blue,
        foregroundColor: Colors.white,
      ),
      body: _demoPages[_selectedIndex],
      bottomNavigationBar: BottomNavigationBar(
        type: BottomNavigationBarType.fixed,
        currentIndex: _selectedIndex,
        onTap: (index) {
          setState(() {
            _selectedIndex = index;
          });
        },
        items: const [
          BottomNavigationBarItem(
            icon: Icon(Icons.list),
            label: '列表Key',
          ),
          BottomNavigationBarItem(
            icon: Icon(Icons.public),
            label: 'GlobalKey',
          ),
          BottomNavigationBarItem(
            icon: Icon(Icons.key),
            label: 'UniqueKey',
          ),
          BottomNavigationBarItem(
            icon: Icon(Icons.data_object),
            label: 'ValueKey',
          ),
        ],
      ),
    );
  }
}

// 1. 列表Key示例 - 展示使用/不使用Key的差异
class ListKeyDemo extends StatefulWidget {
  const ListKeyDemo({Key? key}) : super(key: key);

  @override
  State<ListKeyDemo> createState() => _ListKeyDemoState();
}

class _ListKeyDemoState extends State<ListKeyDemo> {
  final List<String> _items = ['项目 A', '项目 B', '项目 C'];
  bool _useKeys = true;

  void _swapItems() {
    setState(() {
      final temp = _items[0];
      _items[0] = _items[1];
      _items[1] = temp;
    });
  }

  @override
  Widget build(BuildContext context) {
    return Padding(
      padding: const EdgeInsets.all(16.0),
      child: Column(
        crossAxisAlignment: CrossAxisAlignment.start,
        children: [
          const Text(
            '列表项Key示例',
            style: TextStyle(fontSize: 22, fontWeight: FontWeight.bold),
          ),
          const SizedBox(height: 10),
          Text(
            '这个示例展示了为列表项设置Key的重要性。当列表项位置交换时,如果不使用Key,Flutter无法正确保持状态。',
            style: TextStyle(fontSize: 16, color: Colors.grey[700]),
          ),
          const SizedBox(height: 24),

          // 使用/不使用Key的开关
          SwitchListTile(
            title: const Text('使用Key'),
            subtitle: Text(_useKeys ? '为每个列表项使用ValueKey' : '不使用Key'),
            value: _useKeys,
            onChanged: (value) {
              setState(() {
                _useKeys = value;
              });
            },
          ),

          const SizedBox(height: 16),

          // 交换按钮
          ElevatedButton.icon(
            icon: const Icon(Icons.swap_horiz),
            label: const Text('交换前两个项目'),
            onPressed: _swapItems,
          ),

          const SizedBox(height: 24),

          Expanded(
            child: ListView.builder(
              itemCount: _items.length,
              itemBuilder: (context, index) {
                // 根据开关决定是否使用Key
                return _useKeys
                    ? ColorfulItem(
                        key: ValueKey(_items[index]),
                        title: _items[index],
                      )
                    : ColorfulItem(
                        title: _items[index],
                      );
              },
            ),
          ),
        ],
      ),
    );
  }
}

// 可改变颜色的列表项
class ColorfulItem extends StatefulWidget {
  final String title;

  const ColorfulItem({
    Key? key,
    required this.title,
  }) : super(key: key);

  @override
  State<ColorfulItem> createState() => _ColorfulItemState();
}

class _ColorfulItemState extends State<ColorfulItem> {
  late Color _color;

  @override
  void initState() {
    super.initState();
    // 随机生成颜色
    _color =
        Color((math.Random().nextDouble() * 0xFFFFFF).toInt()).withOpacity(1.0);
  }

  @override
  Widget build(BuildContext context) {
    return Card(
      margin: const EdgeInsets.symmetric(vertical: 8),
      child: ListTile(
        leading: CircleAvatar(
          backgroundColor: _color,
          child: Text(widget.title.substring(widget.title.length - 1)),
        ),
        title: Text(widget.title),
        subtitle:
            Text('颜色代码: 0x${_color.value.toRadixString(16).toUpperCase()}'),
        trailing: IconButton(
          icon: const Icon(Icons.refresh),
          onPressed: () {
            setState(() {
              // 更改颜色
              _color = Color((math.Random().nextDouble() * 0xFFFFFF).toInt())
                  .withOpacity(1.0);
            });
          },
        ),
      ),
    );
  }
}

// 2. GlobalKey示例 - 访问其他Widget的状态
class GlobalKeyDemo extends StatefulWidget {
  const GlobalKeyDemo({Key? key}) : super(key: key);

  @override
  State<GlobalKeyDemo> createState() => _GlobalKeyDemoState();
}

class _GlobalKeyDemoState extends State<GlobalKeyDemo> {
  // 创建GlobalKey
  final GlobalKey<_CounterWidgetState> _counterKey =
      GlobalKey<_CounterWidgetState>();

  @override
  Widget build(BuildContext context) {
    return Padding(
      padding: const EdgeInsets.all(16.0),
      child: Column(
        crossAxisAlignment: CrossAxisAlignment.start,
        children: [
          const Text(
            'GlobalKey示例',
            style: TextStyle(fontSize: 22, fontWeight: FontWeight.bold),
          ),
          const SizedBox(height: 10),
          Text(
            'GlobalKey允许访问其他小部件的状态。本例中,父Widget可以直接调用子Widget的方法。',
            style: TextStyle(fontSize: 16, color: Colors.grey[700]),
          ),
          const SizedBox(height: 30),

          // 使用GlobalKey的计数器部件
          CounterWidget(key: _counterKey),

          const SizedBox(height: 30),

          // 通过GlobalKey控制计数器的按钮
          Row(
            mainAxisAlignment: MainAxisAlignment.center,
            children: [
              ElevatedButton.icon(
                icon: const Icon(Icons.add),
                label: const Text('外部增加'),
                onPressed: () {
                  // 通过GlobalKey调用子Widget的方法
                  _counterKey.currentState?.increment();
                },
                style: ElevatedButton.styleFrom(
                  backgroundColor: Colors.green,
                  foregroundColor: Colors.white,
                ),
              ),
              const SizedBox(width: 16),
              ElevatedButton.icon(
                icon: const Icon(Icons.remove),
                label: const Text('外部减少'),
                onPressed: () {
                  // 通过GlobalKey调用子Widget的方法
                  _counterKey.currentState?.decrement();
                },
                style: ElevatedButton.styleFrom(
                  backgroundColor: Colors.red,
                  foregroundColor: Colors.white,
                ),
              ),
            ],
          ),

          const SizedBox(height: 16),
          ElevatedButton(
            child: const Text('获取当前计数值'),
            onPressed: () {
              // 通过GlobalKey获取状态信息
              final currentValue = _counterKey.currentState?.counter ?? 0;
              ScaffoldMessenger.of(context).showSnackBar(
                SnackBar(
                  content: Text('当前计数: $currentValue'),
                  duration: const Duration(seconds: 1),
                ),
              );
            },
          ),
        ],
      ),
    );
  }
}

// GlobalKey示例中的计数器Widget
class CounterWidget extends StatefulWidget {
  const CounterWidget({Key? key}) : super(key: key);

  @override
  State<CounterWidget> createState() => _CounterWidgetState();
}

class _CounterWidgetState extends State<CounterWidget> {
  int counter = 0;

  // 供外部通过GlobalKey调用的方法
  void increment() {
    setState(() {
      counter++;
    });
  }

  void decrement() {
    setState(() {
      if (counter > 0) counter--;
    });
  }

  @override
  Widget build(BuildContext context) {
    return Container(
      padding: const EdgeInsets.all(16),
      decoration: BoxDecoration(
        color: Colors.blue.withOpacity(0.1),
        borderRadius: BorderRadius.circular(16),
        border: Border.all(color: Colors.blue.withOpacity(0.5)),
      ),
      child: Column(
        children: [
          const Text(
            '计数器组件',
            style: TextStyle(fontSize: 18, fontWeight: FontWeight.bold),
          ),
          const SizedBox(height: 16),
          Text(
            '$counter',
            style: const TextStyle(fontSize: 48, fontWeight: FontWeight.bold),
          ),
          const SizedBox(height: 16),
          Row(
            mainAxisAlignment: MainAxisAlignment.center,
            children: [
              ElevatedButton(
                child: const Text('内部增加'),
                onPressed: increment,
              ),
              const SizedBox(width: 16),
              ElevatedButton(
                child: const Text('内部减少'),
                onPressed: decrement,
              ),
            ],
          ),
        ],
      ),
    );
  }
}

// 3. UniqueKey示例 - 强制重建Widget
class UniqueKeyDemo extends StatefulWidget {
  const UniqueKeyDemo({Key? key}) : super(key: key);

  @override
  State<UniqueKeyDemo> createState() => _UniqueKeyDemoState();
}

class _UniqueKeyDemoState extends State<UniqueKeyDemo> {
  // 初始创建两个UniqueKey
  Key _leftWidgetKey = UniqueKey();
  Key _rightWidgetKey = UniqueKey();

  void _resetWidgets() {
    setState(() {
      // 重新分配新的UniqueKey,强制两个组件重建
      _leftWidgetKey = UniqueKey();
      _rightWidgetKey = UniqueKey();
    });
  }

  void _swapWidgets() {
    setState(() {
      // 交换两个Key,这会导致状态移动
      final tempKey = _leftWidgetKey;
      _leftWidgetKey = _rightWidgetKey;
      _rightWidgetKey = tempKey;
    });
  }

  @override
  Widget build(BuildContext context) {
    return Padding(
      padding: const EdgeInsets.all(16.0),
      child: Column(
        crossAxisAlignment: CrossAxisAlignment.start,
        children: [
          const Text(
            'UniqueKey示例',
            style: TextStyle(fontSize: 22, fontWeight: FontWeight.bold),
          ),
          const SizedBox(height: 10),
          Text(
            'UniqueKey每次创建都是唯一的。当需要强制组件重建或保持状态跟随部件移动时非常有用。',
            style: TextStyle(fontSize: 16, color: Colors.grey[700]),
          ),
          const SizedBox(height: 24),

          // 操作按钮
          Row(
            mainAxisAlignment: MainAxisAlignment.center,
            children: [
              ElevatedButton(
                onPressed: _swapWidgets,
                child: const Text('交换位置'),
              ),
              const SizedBox(width: 16),
              ElevatedButton(
                onPressed: _resetWidgets,
                child: const Text('重置组件'),
              ),
            ],
          ),

          const SizedBox(height: 40),

          // 两个使用UniqueKey的组件
          Expanded(
            child: Row(
              children: [
                Expanded(
                  child: Container(
                    margin: const EdgeInsets.all(8),
                    padding: const EdgeInsets.all(8),
                    decoration: BoxDecoration(
                      color: Colors.blue.withOpacity(0.1),
                      borderRadius: BorderRadius.circular(12),
                    ),
                    child: Column(
                      children: [
                        const Text('左侧',
                            style: TextStyle(fontWeight: FontWeight.bold)),
                        Expanded(
                          child: Center(
                            child: RandomColorTile(key: _leftWidgetKey),
                          ),
                        ),
                      ],
                    ),
                  ),
                ),
                Expanded(
                  child: Container(
                    margin: const EdgeInsets.all(8),
                    padding: const EdgeInsets.all(8),
                    decoration: BoxDecoration(
                      color: Colors.orange.withOpacity(0.1),
                      borderRadius: BorderRadius.circular(12),
                    ),
                    child: Column(
                      children: [
                        const Text('右侧',
                            style: TextStyle(fontWeight: FontWeight.bold)),
                        Expanded(
                          child: Center(
                            child: RandomColorTile(key: _rightWidgetKey),
                          ),
                        ),
                      ],
                    ),
                  ),
                ),
              ],
            ),
          ),
        ],
      ),
    );
  }
}

// 随机颜色方块组件
class RandomColorTile extends StatefulWidget {
  const RandomColorTile({Key? key}) : super(key: key);

  @override
  State<RandomColorTile> createState() => _RandomColorTileState();
}

class _RandomColorTileState extends State<RandomColorTile> {
  late Color _color;
  int _clickCount = 0;

  @override
  void initState() {
    super.initState();
    _color =
        Color((math.Random().nextDouble() * 0xFFFFFF).toInt()).withOpacity(1.0);
  }

  void _changeColor() {
    setState(() {
      _color = Color((math.Random().nextDouble() * 0xFFFFFF).toInt())
          .withOpacity(1.0);
      _clickCount++;
    });
  }

  @override
  Widget build(BuildContext context) {
    return Column(
      mainAxisSize: MainAxisSize.min,
      children: [
        GestureDetector(
          onTap: _changeColor,
          child: Container(
            width: 120,
            height: 120,
            decoration: BoxDecoration(
              color: _color,
              borderRadius: BorderRadius.circular(12),
              boxShadow: [
                BoxShadow(
                  color: Colors.black.withOpacity(0.2),
                  blurRadius: 5,
                  offset: const Offset(0, 2),
                ),
              ],
            ),
            child: Center(
              child: Text(
                '$_clickCount',
                style: const TextStyle(
                  color: Colors.white,
                  fontSize: 24,
                  fontWeight: FontWeight.bold,
                ),
              ),
            ),
          ),
        ),
        const SizedBox(height: 12),
        Text('点击改变颜色'),
        Text(
          '颜色: 0x${_color.value.toRadixString(16).toUpperCase()}',
          style: const TextStyle(fontSize: 12),
        ),
      ],
    );
  }
}

// 4. ValueKey示例 - 基于值的键
class ValueKeyDemo extends StatefulWidget {
  const ValueKeyDemo({Key? key}) : super(key: key);

  @override
  State<ValueKeyDemo> createState() => _ValueKeyDemoState();
}

class _ValueKeyDemoState extends State<ValueKeyDemo> {
  List<TodoItem> _todos = [];
  final TextEditingController _controller = TextEditingController();

  @override
  void initState() {
    super.initState();
    // 初始数据
    _todos = [
      TodoItem(id: '1', text: '学习Flutter Key'),
      TodoItem(id: '2', text: '理解StatefulWidget生命周期'),
      TodoItem(id: '3', text: '掌握状态管理'),
    ];
  }

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

  void _addTodo() {
    if (_controller.text.isNotEmpty) {
      setState(() {
        final newId = DateTime.now().millisecondsSinceEpoch.toString();
        _todos.add(TodoItem(id: newId, text: _controller.text));
        _controller.clear();
      });
    }
  }

  void _removeTodo(String id) {
    setState(() {
      _todos.removeWhere((todo) => todo.id == id);
    });
  }

  void _moveUp(int index) {
    if (index > 0) {
      setState(() {
        final item = _todos.removeAt(index);
        _todos.insert(index - 1, item);
      });
    }
  }

  void _moveDown(int index) {
    if (index < _todos.length - 1) {
      setState(() {
        final item = _todos.removeAt(index);
        _todos.insert(index + 1, item);
      });
    }
  }

  @override
  Widget build(BuildContext context) {
    return Padding(
      padding: const EdgeInsets.all(16.0),
      child: Column(
        crossAxisAlignment: CrossAxisAlignment.start,
        children: [
          const Text(
            'ValueKey示例',
            style: TextStyle(fontSize: 22, fontWeight: FontWeight.bold),
          ),
          const SizedBox(height: 10),
          Text(
            'ValueKey基于值创建Key,适用于列表项具有唯一标识符的情况。每个待办项的状态跟随其ID移动。',
            style: TextStyle(fontSize: 16, color: Colors.grey[700]),
          ),
          const SizedBox(height: 20),

          // 添加待办事项
          Row(
            children: [
              Expanded(
                child: TextField(
                  controller: _controller,
                  decoration: const InputDecoration(
                    hintText: '输入新待办事项',
                    border: OutlineInputBorder(),
                  ),
                ),
              ),
              const SizedBox(width: 16),
              ElevatedButton(
                onPressed: _addTodo,
                child: const Text('添加'),
              ),
            ],
          ),

          const SizedBox(height: 20),
          const Text(
            '待办事项列表:',
            style: TextStyle(fontSize: 18, fontWeight: FontWeight.bold),
          ),
          const SizedBox(height: 10),

          Expanded(
            child: ListView.builder(
              itemCount: _todos.length,
              itemBuilder: (context, index) {
                final todo = _todos[index];
                // 使用ValueKey作为每个待办项的Key
                return TodoListItem(
                  key: ValueKey(todo.id),
                  todo: todo,
                  onDelete: () => _removeTodo(todo.id),
                  onMoveUp: () => _moveUp(index),
                  onMoveDown: () => _moveDown(index),
                );
              },
            ),
          ),
        ],
      ),
    );
  }
}

// 待办事项数据类
class TodoItem {
  final String id;
  final String text;

  TodoItem({required this.id, required this.text});
}

// 待办事项列表项
class TodoListItem extends StatefulWidget {
  final TodoItem todo;
  final VoidCallback onDelete;
  final VoidCallback onMoveUp;
  final VoidCallback onMoveDown;

  const TodoListItem({
    Key? key,
    required this.todo,
    required this.onDelete,
    required this.onMoveUp,
    required this.onMoveDown,
  }) : super(key: key);

  @override
  State<TodoListItem> createState() => _TodoListItemState();
}

class _TodoListItemState extends State<TodoListItem> {
  bool _isCompleted = false;

  @override
  Widget build(BuildContext context) {
    return Card(
      margin: const EdgeInsets.symmetric(vertical: 6),
      child: ListTile(
        leading: Checkbox(
          value: _isCompleted,
          onChanged: (value) {
            setState(() {
              _isCompleted = value ?? false;
            });
          },
        ),
        title: Text(
          widget.todo.text,
          style: TextStyle(
            decoration: _isCompleted ? TextDecoration.lineThrough : null,
            color: _isCompleted ? Colors.grey : null,
          ),
        ),
        subtitle: Text('ID: ${widget.todo.id}'),
        trailing: Row(
          mainAxisSize: MainAxisSize.min,
          children: [
            IconButton(
              icon: const Icon(Icons.arrow_upward),
              onPressed: widget.onMoveUp,
              tooltip: '上移',
            ),
            IconButton(
              icon: const Icon(Icons.arrow_downward),
              onPressed: widget.onMoveDown,
              tooltip: '下移',
            ),
            IconButton(
              icon: const Icon(Icons.delete),
              onPressed: widget.onDelete,
              tooltip: '删除',
            ),
          ],
        ),
      ),
    );
  }
}

AnimatedList 实现动态列表

属性描述
keyglobalKey final globalKey = GlobalKey();
initialItemCount子元素数量
itemBuilder方法 ( BuildContext context, int index, Animation animation) {}
dart
import 'package:flutter/material.dart';
import 'dart:math' as math;

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

class MyApp extends StatelessWidget {
  const MyApp({Key? key}) : super(key: key);

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      debugShowCheckedModeBanner: false,
      title: 'AnimatedList 示例',
      theme: ThemeData(
        primarySwatch: Colors.blue,
        useMaterial3: true,
      ),
      home: const AnimatedListDemo(),
    );
  }
}

class AnimatedListDemo extends StatefulWidget {
  const AnimatedListDemo({Key? key}) : super(key: key);

  @override
  State<AnimatedListDemo> createState() => _AnimatedListDemoState();
}

class _AnimatedListDemoState extends State<AnimatedListDemo> {
  // 列表项数据
  final List<ItemData> _items = [];

  // AnimatedList的GlobalKey - 用于访问AnimatedList的状态
  final GlobalKey<AnimatedListState> _listKey = GlobalKey<AnimatedListState>();

  // 添加项目时选择随机颜色
  final List<Color> _colors = [
    Colors.red,
    Colors.blue,
    Colors.green,
    Colors.purple,
    Colors.orange,
    Colors.teal,
    Colors.pink,
    Colors.indigo,
  ];

  // 动画持续时间
  final Duration _duration = const Duration(milliseconds: 350);

  // 滑入方向
  SlideDirection _slideDirection = SlideDirection.fromBottom;

  @override
  void initState() {
    super.initState();
    // 初始化列表数据
    _addItem(); // 添加一个初始项目
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: const Text('AnimatedList 示例'),
        backgroundColor: Colors.blue,
        foregroundColor: Colors.white,
      ),
      body: Column(
        children: [
          // 顶部控制面板
          _buildControlPanel(),

          // 分隔线
          const Divider(height: 0),

          // AnimatedList
          Expanded(
            child: AnimatedList(
              key: _listKey,
              initialItemCount: _items.length,
              padding: const EdgeInsets.all(10),
              itemBuilder: (context, index, animation) {
                // 使用动画构建列表项
                return _buildItem(_items[index], animation, index);
              },
            ),
          ),
        ],
      ),
      // 浮动添加按钮
      floatingActionButton: FloatingActionButton(
        child: const Icon(Icons.add),
        onPressed: _addItem,
        tooltip: '添加项目',
      ),
    );
  }

  // 构建控制面板
  Widget _buildControlPanel() {
    return Container(
      padding: const EdgeInsets.all(12),
      color: Colors.grey[100],
      child: Column(
        crossAxisAlignment: CrossAxisAlignment.start,
        children: [
          const Text(
            'AnimatedList 演示',
            style: TextStyle(fontSize: 18, fontWeight: FontWeight.bold),
          ),
          const SizedBox(height: 4),
          Text(
            '当添加或删除列表项时,将显示动画效果',
            style: TextStyle(fontSize: 14, color: Colors.grey[700]),
          ),
          const SizedBox(height: 12),

          // 操作按钮行
          Row(
            children: [
              // 添加项目按钮
              ElevatedButton.icon(
                icon: const Icon(Icons.add),
                label: const Text('添加项目'),
                style: ElevatedButton.styleFrom(
                  backgroundColor: Colors.green,
                  foregroundColor: Colors.white,
                ),
                onPressed: _addItem,
              ),
              const SizedBox(width: 10),

              // 删除第一个项目按钮
              ElevatedButton.icon(
                icon: const Icon(Icons.remove),
                label: const Text('删除第一项'),
                style: ElevatedButton.styleFrom(
                  backgroundColor: Colors.red,
                  foregroundColor: Colors.white,
                ),
                onPressed: _items.isNotEmpty ? () => _removeItem(0) : null,
              ),
              const SizedBox(width: 10),

              // 清空列表按钮
              ElevatedButton.icon(
                icon: const Icon(Icons.clear_all),
                label: const Text('清空列表'),
                style: ElevatedButton.styleFrom(
                  backgroundColor: Colors.orange,
                  foregroundColor: Colors.white,
                ),
                onPressed: _items.isNotEmpty ? _clearList : null,
              ),
            ],
          ),

          const SizedBox(height: 10),

          // 动画方向设置
          Wrap(
            spacing: 8,
            runSpacing: 8,
            children: [
              const Text('滑入方向:'),
              ChoiceChip(
                label: const Text('从底部'),
                selected: _slideDirection == SlideDirection.fromBottom,
                onSelected: (selected) {
                  if (selected) {
                    setState(() {
                      _slideDirection = SlideDirection.fromBottom;
                    });
                  }
                },
              ),
              ChoiceChip(
                label: const Text('从顶部'),
                selected: _slideDirection == SlideDirection.fromTop,
                onSelected: (selected) {
                  if (selected) {
                    setState(() {
                      _slideDirection = SlideDirection.fromTop;
                    });
                  }
                },
              ),
              ChoiceChip(
                label: const Text('从左侧'),
                selected: _slideDirection == SlideDirection.fromLeft,
                onSelected: (selected) {
                  if (selected) {
                    setState(() {
                      _slideDirection = SlideDirection.fromLeft;
                    });
                  }
                },
              ),
              ChoiceChip(
                label: const Text('从右侧'),
                selected: _slideDirection == SlideDirection.fromRight,
                onSelected: (selected) {
                  if (selected) {
                    setState(() {
                      _slideDirection = SlideDirection.fromRight;
                    });
                  }
                },
              ),
            ],
          ),
        ],
      ),
    );
  }

  // 构建动画列表项
  Widget _buildItem(ItemData item, Animation<double> animation, int index) {
    // 根据滑入方向决定初始偏移
    Offset beginOffset;
    switch (_slideDirection) {
      case SlideDirection.fromTop:
        beginOffset = const Offset(0.0, -1.0);
        break;
      case SlideDirection.fromBottom:
        beginOffset = const Offset(0.0, 1.0);
        break;
      case SlideDirection.fromLeft:
        beginOffset = const Offset(-1.0, 0.0);
        break;
      case SlideDirection.fromRight:
        beginOffset = const Offset(1.0, 0.0);
        break;
    }

    // 组合动画:滑动 + 透明度
    return SlideTransition(
      position: Tween<Offset>(
        begin: beginOffset,
        end: Offset.zero,
      ).animate(
        CurvedAnimation(
          parent: animation,
          curve: Curves.easeOutCubic,
        ),
      ),
      child: FadeTransition(
        opacity: animation,
        child: Card(
          elevation: 4,
          margin: const EdgeInsets.symmetric(vertical: 8, horizontal: 4),
          color: item.color.withOpacity(0.9),
          child: ListTile(
            contentPadding: const EdgeInsets.symmetric(
              horizontal: 16,
              vertical: 8,
            ),
            leading: CircleAvatar(
              radius: 25,
              backgroundColor: Colors.white.withOpacity(0.5),
              child: Text(
                item.id.toString(),
                style: TextStyle(
                  fontWeight: FontWeight.bold,
                  color: item.color.withOpacity(1.0),
                ),
              ),
            ),
            title: Text(
              '项目 ${item.id}',
              style: const TextStyle(
                fontSize: 18,
                fontWeight: FontWeight.bold,
                color: Colors.white,
              ),
            ),
            subtitle: Text(
              '添加时间: ${item.timestamp}',
              style: TextStyle(
                color: Colors.white.withOpacity(0.8),
              ),
            ),
            trailing: IconButton(
              icon: const Icon(Icons.delete, color: Colors.white),
              onPressed: () => _removeItem(index),
              tooltip: '删除此项',
            ),
          ),
        ),
      ),
    );
  }

  // 添加项目
  void _addItem() {
    int id = _items.isNotEmpty ? _items.last.id + 1 : 1;
    Color color = _colors[math.Random().nextInt(_colors.length)];

    // 格式化当前时间
    final now = DateTime.now();
    final timeString =
        '${now.hour.toString().padLeft(2, '0')}:${now.minute.toString().padLeft(2, '0')}:${now.second.toString().padLeft(2, '0')}';

    // 创建新项目
    final newItem = ItemData(
      id: id,
      color: color,
      timestamp: timeString,
    );

    // 1. 更新数据源
    _items.add(newItem);

    // 2. 通知 AnimatedList 插入了一个新项目
    _listKey.currentState?.insertItem(
      _items.length - 1,
      duration: _duration,
    );
  }

  // 删除项目
  void _removeItem(int index) {
    if (index >= 0 && index < _items.length) {
      // 1. 保存要删除的项目,以便在动画期间显示
      final removedItem = _items[index];

      // 2. 从数据源中移除
      _items.removeAt(index);

      // 3. 通知 AnimatedList 移除一个项目,并提供一个构建器来显示移除动画
      _listKey.currentState?.removeItem(
        index,
        (context, animation) => _buildItem(removedItem, animation, index),
        duration: _duration,
      );
    }
  }

  // 清空列表
  void _clearList() {
    // 从最后一个开始删除,直到列表为空
    while (_items.isNotEmpty) {
      _removeItem(_items.length - 1);
    }
  }
}

// 列表项数据类
class ItemData {
  final int id;
  final Color color;
  final String timestamp;

  ItemData({
    required this.id,
    required this.color,
    required this.timestamp,
  });
}

// 滑入方向枚举
enum SlideDirection {
  fromTop,
  fromBottom,
  fromLeft,
  fromRight,
}

动画

在任何系统的UI框架中,动画实现的原理都是相同的,即:在一段时间内,快速地多次改变UI外观;由

于人眼会产生视觉暂留,所以最终看到的就是一个“连续”的动画,这和电影的原理是一样的。我们将UI

的一次改变称为一个动画帧,对应一次屏幕刷新,而决定动画流畅度的一个重要指标就是帧率

FPS(Frame Per Second),即每秒的动画帧数。很明显,帧率越高则动画就会越流畅!一般情况下,

对于人眼来说,动画帧率超过16 FPS,就基本能看了,超过 32 FPS就会感觉相对平滑,而超过 32

FPS,大多数人基本上就感受不到差别了。由于动画的每一帧都是要改变UI输出,所以在一个时间段内

连续的改变UI输出是比较耗资源的,对设备的软硬件系统要求都较高,所以在UI系统中,动画的平均帧

率是重要的性能指标,而在Flutter中,理想情况下是可以实现 60FPS 的,这和原生应用能达到的帧率

是基本是持平的。

FLutter中的动画主要分为;隐式动画、显式动画、自定义隐式动画、自定义显式动画、和Hero动画

隐式动画

通过几行代码就可以实现隐式动画,由于隐式动画背后的实现原理和繁琐的操作细节都被隐去了,所以

叫隐式动画,FLutter中提供的 AnimatedContainer、AnimatedPadding、AnimatedPositioned、

AnimatedOpacity、AnimatedDefaultTextStyle、AnimatedSwitcher都属于隐式动画。

隐式动画中可以通过 duration 配置动画时长、可以通过 Curve (曲线)来配置动画过程

dart
import 'package:flutter/material.dart';
import 'dart:math' as math;

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

class MyApp extends StatelessWidget {
  const MyApp({Key? key}) : super(key: key);

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      debugShowCheckedModeBanner: false,
      title: '隐式动画示例',
      theme: ThemeData(
        primarySwatch: Colors.blue,
        useMaterial3: true,
      ),
      home: const ImplicitAnimationsDemo(),
    );
  }
}

class ImplicitAnimationsDemo extends StatefulWidget {
  const ImplicitAnimationsDemo({Key? key}) : super(key: key);

  @override
  State<ImplicitAnimationsDemo> createState() => _ImplicitAnimationsDemoState();
}

class _ImplicitAnimationsDemoState extends State<ImplicitAnimationsDemo> {
  int _selectedIndex = 0;

  // 所有动画页面
  final List<Widget> _demoPages = [
    const AnimatedContainerDemo(),
    const AnimatedOpacityDemo(),
    const AnimatedPositionedDemo(),
    const AnimatedCrossFadeDemo(),
    const TweenAnimationBuilderDemo(),
  ];

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: const Text('隐式动画示例'),
        backgroundColor: Colors.blue,
        foregroundColor: Colors.white,
      ),
      body: _demoPages[_selectedIndex],
      bottomNavigationBar: BottomNavigationBar(
        currentIndex: _selectedIndex,
        onTap: (index) {
          setState(() {
            _selectedIndex = index;
          });
        },
        type: BottomNavigationBarType.fixed,
        selectedItemColor: Colors.blue,
        unselectedItemColor: Colors.grey,
        items: const [
          BottomNavigationBarItem(
            icon: Icon(Icons.animation),
            label: 'Container',
          ),
          BottomNavigationBarItem(
            icon: Icon(Icons.opacity),
            label: 'Opacity',
          ),
          BottomNavigationBarItem(
            icon: Icon(Icons.moving),
            label: 'Positioned',
          ),
          BottomNavigationBarItem(
            icon: Icon(Icons.swap_horiz),
            label: 'CrossFade',
          ),
          BottomNavigationBarItem(
            icon: Icon(Icons.build),
            label: 'Tween',
          ),
        ],
      ),
    );
  }
}

// 1. AnimatedContainer Demo
class AnimatedContainerDemo extends StatefulWidget {
  const AnimatedContainerDemo({Key? key}) : super(key: key);

  @override
  State<AnimatedContainerDemo> createState() => _AnimatedContainerDemoState();
}

class _AnimatedContainerDemoState extends State<AnimatedContainerDemo> {
  // 容器属性状态
  double _width = 100;
  double _height = 100;
  Color _color = Colors.blue;
  double _borderRadius = 8;
  double _rotation = 0;

  // 动画持续时间
  Duration _duration = const Duration(milliseconds: 500);

  // 动画曲线
  Curve _curve = Curves.easeInOut;

  @override
  Widget build(BuildContext context) {
    return Column(
      children: [
        Expanded(
          child: Center(
            child: Padding(
              padding: const EdgeInsets.all(16.0),
              child: Column(
                mainAxisAlignment: MainAxisAlignment.center,
                children: [
                  const Text(
                    'AnimatedContainer',
                    style: TextStyle(fontSize: 24, fontWeight: FontWeight.bold),
                  ),
                  const SizedBox(height: 8),
                  Text(
                    '点击按钮改变容器属性,观察平滑过渡效果',
                    style: TextStyle(color: Colors.grey[600]),
                  ),
                  const SizedBox(height: 40),
                  // 动画容器
                  TweenAnimationBuilder<double>(
                    tween: Tween<double>(begin: 0, end: _rotation),
                    duration: _duration,
                    curve: _curve,
                    builder: (context, value, child) {
                      return Transform.rotate(
                        angle: value,
                        child: child,
                      );
                    },
                    child: AnimatedContainer(
                      width: _width,
                      height: _height,
                      decoration: BoxDecoration(
                        color: _color,
                        borderRadius: BorderRadius.circular(_borderRadius),
                        boxShadow: [
                          BoxShadow(
                            color: _color.withOpacity(0.5),
                            blurRadius: 10,
                            offset: const Offset(0, 5),
                          ),
                        ],
                      ),
                      duration: _duration,
                      curve: _curve,
                      child: const Center(
                        child: Text(
                          'Hello!',
                          style: TextStyle(
                            color: Colors.white,
                            fontWeight: FontWeight.bold,
                          ),
                        ),
                      ),
                    ),
                  ),
                ],
              ),
            ),
          ),
        ),
        // 控制面板
        Container(
          padding: const EdgeInsets.all(16),
          color: Colors.grey[200],
          child: Column(
            children: [
              // 动画控制按钮行
              Row(
                mainAxisAlignment: MainAxisAlignment.spaceEvenly,
                children: [
                  ElevatedButton(
                    child: const Text('改变尺寸'),
                    onPressed: _changeSize,
                  ),
                  ElevatedButton(
                    child: const Text('改变颜色'),
                    onPressed: _changeColor,
                  ),
                  ElevatedButton(
                    child: const Text('改变形状'),
                    onPressed: _changeShape,
                  ),
                ],
              ),
              const SizedBox(height: 10),
              // 动画持续时间设置
              Row(
                children: [
                  const Text('持续时间: '),
                  Expanded(
                    child: Slider(
                      min: 200,
                      max: 2000,
                      divisions: 9,
                      value: _duration.inMilliseconds.toDouble(),
                      label: '${_duration.inMilliseconds} ms',
                      onChanged: (value) {
                        setState(() {
                          _duration = Duration(milliseconds: value.round());
                        });
                      },
                    ),
                  ),
                  Text('${_duration.inMilliseconds} ms'),
                ],
              ),
              // 动画曲线选择
              Row(
                children: [
                  const Text('动画曲线: '),
                  Expanded(
                    child: DropdownButton<Curve>(
                      value: _curve,
                      isExpanded: true,
                      onChanged: (Curve? newValue) {
                        if (newValue != null) {
                          setState(() {
                            _curve = newValue;
                          });
                        }
                      },
                      items: [
                        DropdownMenuItem(
                          value: Curves.linear,
                          child: const Text('线性'),
                        ),
                        DropdownMenuItem(
                          value: Curves.easeInOut,
                          child: const Text('渐入渐出'),
                        ),
                        DropdownMenuItem(
                          value: Curves.bounceOut,
                          child: const Text('弹跳'),
                        ),
                        DropdownMenuItem(
                          value: Curves.elasticOut,
                          child: const Text('弹性'),
                        ),
                        DropdownMenuItem(
                          value: Curves.fastOutSlowIn,
                          child: const Text('快出慢入'),
                        ),
                      ],
                    ),
                  ),
                ],
              ),
            ],
          ),
        ),
      ],
    );
  }

  // 改变尺寸
  void _changeSize() {
    setState(() {
      _width = _width == 100 ? 200 : 100;
      _height = _height == 100 ? 150 : 100;
      _rotation = _rotation == 0 ? math.pi / 4 : 0;
    });
  }

  // 改变颜色
  void _changeColor() {
    setState(() {
      _color = _color == Colors.blue
          ? Colors.orange
          : _color == Colors.orange
              ? Colors.green
              : Colors.blue;
    });
  }

  // 改变形状
  void _changeShape() {
    setState(() {
      _borderRadius = _borderRadius == 8 ? 50 : 8;
    });
  }
}

// 2. AnimatedOpacity Demo
class AnimatedOpacityDemo extends StatefulWidget {
  const AnimatedOpacityDemo({Key? key}) : super(key: key);

  @override
  State<AnimatedOpacityDemo> createState() => _AnimatedOpacityDemoState();
}

class _AnimatedOpacityDemoState extends State<AnimatedOpacityDemo> {
  double _opacity1 = 1.0;
  double _opacity2 = 1.0;
  double _opacity3 = 1.0;
  Duration _duration = const Duration(milliseconds: 500);

  @override
  Widget build(BuildContext context) {
    return Column(
      children: [
        Expanded(
          child: Padding(
            padding: const EdgeInsets.all(16),
            child: Column(
              mainAxisAlignment: MainAxisAlignment.center,
              children: [
                const Text(
                  'AnimatedOpacity',
                  style: TextStyle(fontSize: 24, fontWeight: FontWeight.bold),
                ),
                const SizedBox(height: 8),
                Text(
                  '控制不同元素的透明度动画',
                  style: TextStyle(color: Colors.grey[600]),
                ),
                const SizedBox(height: 60),
                // 三个可切换透明度的卡片
                Row(
                  mainAxisAlignment: MainAxisAlignment.spaceEvenly,
                  children: [
                    _buildOpacityCard(
                      'Card 1',
                      Colors.red,
                      _opacity1,
                      () => setState(
                          () => _opacity1 = _opacity1 == 1.0 ? 0.0 : 1.0),
                    ),
                    _buildOpacityCard(
                      'Card 2',
                      Colors.green,
                      _opacity2,
                      () => setState(
                          () => _opacity2 = _opacity2 == 1.0 ? 0.0 : 1.0),
                    ),
                    _buildOpacityCard(
                      'Card 3',
                      Colors.blue,
                      _opacity3,
                      () => setState(
                          () => _opacity3 = _opacity3 == 1.0 ? 0.0 : 1.0),
                    ),
                  ],
                ),
              ],
            ),
          ),
        ),
        // 控制面板
        Container(
          padding: const EdgeInsets.all(16),
          color: Colors.grey[200],
          child: Column(
            children: [
              // 所有卡片控制
              Row(
                mainAxisAlignment: MainAxisAlignment.spaceEvenly,
                children: [
                  ElevatedButton(
                    child: const Text('显示全部'),
                    onPressed: () {
                      setState(() {
                        _opacity1 = 1.0;
                        _opacity2 = 1.0;
                        _opacity3 = 1.0;
                      });
                    },
                  ),
                  ElevatedButton(
                    child: const Text('隐藏全部'),
                    onPressed: () {
                      setState(() {
                        _opacity1 = 0.0;
                        _opacity2 = 0.0;
                        _opacity3 = 0.0;
                      });
                    },
                  ),
                  ElevatedButton(
                    child: const Text('交替显示'),
                    onPressed: () {
                      setState(() {
                        _opacity1 = _opacity1 == 0.0 ? 1.0 : 0.0;
                        _opacity2 = _opacity2 == 0.0 ? 1.0 : 0.0;
                        _opacity3 = _opacity3 == 0.0 ? 1.0 : 0.0;
                      });
                    },
                  ),
                ],
              ),
              const SizedBox(height: 10),
              // 动画持续时间
              Row(
                children: [
                  const Text('持续时间: '),
                  Expanded(
                    child: Slider(
                      min: 200,
                      max: 2000,
                      divisions: 9,
                      value: _duration.inMilliseconds.toDouble(),
                      label: '${_duration.inMilliseconds} ms',
                      onChanged: (value) {
                        setState(() {
                          _duration = Duration(milliseconds: value.round());
                        });
                      },
                    ),
                  ),
                  Text('${_duration.inMilliseconds} ms'),
                ],
              ),
            ],
          ),
        ),
      ],
    );
  }

  // 构建一个可切换透明度的卡片
  Widget _buildOpacityCard(
    String title,
    Color color,
    double opacity,
    VoidCallback onTap,
  ) {
    return GestureDetector(
      onTap: onTap,
      child: Column(
        children: [
          AnimatedOpacity(
            opacity: opacity,
            duration: _duration,
            child: Container(
              width: 100,
              height: 100,
              decoration: BoxDecoration(
                color: color,
                borderRadius: BorderRadius.circular(10),
                boxShadow: [
                  BoxShadow(
                    color: color.withOpacity(0.5),
                    blurRadius: 10,
                    offset: const Offset(0, 5),
                  ),
                ],
              ),
              child: Center(
                child: Text(
                  title,
                  style: const TextStyle(
                    color: Colors.white,
                    fontWeight: FontWeight.bold,
                  ),
                ),
              ),
            ),
          ),
          const SizedBox(height: 10),
          Text(
            '透明度: ${opacity.toStringAsFixed(1)}',
            style: TextStyle(color: Colors.grey[700]),
          ),
          Text(
            '点击切换',
            style: TextStyle(color: Colors.grey[500], fontSize: 12),
          ),
        ],
      ),
    );
  }
}

// 3. AnimatedPositioned Demo
class AnimatedPositionedDemo extends StatefulWidget {
  const AnimatedPositionedDemo({Key? key}) : super(key: key);

  @override
  State<AnimatedPositionedDemo> createState() => _AnimatedPositionedDemoState();
}

class _AnimatedPositionedDemoState extends State<AnimatedPositionedDemo> {
  // 位置状态
  double _top = 20;
  double _left = 20;
  double _width = 100;
  double _height = 100;

  // 预设位置
  final List<Map<String, double>> _positions = [
    {'top': 20, 'left': 20, 'width': 100, 'height': 100},
    {'top': 20, 'left': 200, 'width': 150, 'height': 80},
    {'top': 200, 'left': 20, 'width': 80, 'height': 150},
    {'top': 200, 'left': 200, 'width': 120, 'height': 120},
    {'top': 100, 'left': 100, 'width': 150, 'height': 150},
  ];

  int _currentPosition = 0;
  Duration _duration = const Duration(milliseconds: 500);
  Curve _curve = Curves.easeInOut;

  @override
  Widget build(BuildContext context) {
    return Column(
      children: [
        Expanded(
          child: Stack(
            children: [
              // 背景网格
              ...List.generate(10, (row) {
                return List.generate(10, (col) {
                  return Positioned(
                    top: row * 40.0,
                    left: col * 40.0,
                    child: Container(
                      width: 40,
                      height: 40,
                      decoration: BoxDecoration(
                        border: Border.all(color: Colors.grey.withOpacity(0.2)),
                      ),
                    ),
                  );
                });
              }).expand((element) => element).toList(),

              // 可移动元素
              AnimatedPositioned(
                top: _top,
                left: _left,
                width: _width,
                height: _height,
                duration: _duration,
                curve: _curve,
                child: GestureDetector(
                  onTap: _moveToNextPosition,
                  child: Container(
                    decoration: BoxDecoration(
                      color: Colors.blue,
                      borderRadius: BorderRadius.circular(10),
                      boxShadow: [
                        BoxShadow(
                          color: Colors.blue.withOpacity(0.5),
                          blurRadius: 10,
                          offset: const Offset(0, 5),
                        ),
                      ],
                    ),
                    child: const Center(
                      child: Text(
                        '点击移动',
                        style: TextStyle(
                          color: Colors.white,
                          fontWeight: FontWeight.bold,
                        ),
                      ),
                    ),
                  ),
                ),
              ),

              // 标题
              Positioned(
                top: 16,
                left: 16,
                child: Column(
                  crossAxisAlignment: CrossAxisAlignment.start,
                  children: [
                    const Text(
                      'AnimatedPositioned',
                      style:
                          TextStyle(fontSize: 24, fontWeight: FontWeight.bold),
                    ),
                    Text(
                      '点击蓝色方块或按钮切换位置',
                      style: TextStyle(color: Colors.grey[600]),
                    ),
                  ],
                ),
              ),
            ],
          ),
        ),
        // 控制面板
        Container(
          padding: const EdgeInsets.all(16),
          color: Colors.grey[200],
          child: Column(
            children: [
              // 位置切换按钮
              Row(
                mainAxisAlignment: MainAxisAlignment.spaceEvenly,
                children: [
                  ElevatedButton(
                    child: const Text('上一个位置'),
                    onPressed: _moveToPreviousPosition,
                  ),
                  ElevatedButton(
                    child: const Text('随机位置'),
                    onPressed: _moveToRandomPosition,
                  ),
                  ElevatedButton(
                    child: const Text('下一个位置'),
                    onPressed: _moveToNextPosition,
                  ),
                ],
              ),
              const SizedBox(height: 10),
              // 动画设置
              Row(
                mainAxisAlignment: MainAxisAlignment.spaceBetween,
                children: [
                  // 动画持续时间设置
                  Expanded(
                    child: Row(
                      children: [
                        const Text('持续时间: '),
                        Expanded(
                          child: Slider(
                            min: 200,
                            max: 2000,
                            divisions: 9,
                            value: _duration.inMilliseconds.toDouble(),
                            label: '${_duration.inMilliseconds} ms',
                            onChanged: (value) {
                              setState(() {
                                _duration =
                                    Duration(milliseconds: value.round());
                              });
                            },
                          ),
                        ),
                      ],
                    ),
                  ),
                  Text('${_duration.inMilliseconds} ms'),
                ],
              ),
              // 动画曲线选择
              Row(
                children: [
                  const Text('动画曲线: '),
                  Expanded(
                    child: DropdownButton<Curve>(
                      value: _curve,
                      isExpanded: true,
                      onChanged: (Curve? newValue) {
                        if (newValue != null) {
                          setState(() {
                            _curve = newValue;
                          });
                        }
                      },
                      items: [
                        DropdownMenuItem(
                          value: Curves.linear,
                          child: const Text('线性'),
                        ),
                        DropdownMenuItem(
                          value: Curves.easeInOut,
                          child: const Text('渐入渐出'),
                        ),
                        DropdownMenuItem(
                          value: Curves.bounceOut,
                          child: const Text('弹跳'),
                        ),
                        DropdownMenuItem(
                          value: Curves.elasticOut,
                          child: const Text('弹性'),
                        ),
                      ],
                    ),
                  ),
                ],
              ),
            ],
          ),
        ),
      ],
    );
  }

  // 切换到下一个位置
  void _moveToNextPosition() {
    setState(() {
      _currentPosition = (_currentPosition + 1) % _positions.length;
      _updatePosition();
    });
  }

  // 切换到上一个位置
  void _moveToPreviousPosition() {
    setState(() {
      _currentPosition =
          (_currentPosition - 1 + _positions.length) % _positions.length;
      _updatePosition();
    });
  }

  // 切换到随机位置
  void _moveToRandomPosition() {
    setState(() {
      _currentPosition = math.Random().nextInt(_positions.length);
      _updatePosition();
    });
  }

  // 更新位置
  void _updatePosition() {
    _top = _positions[_currentPosition]['top']!;
    _left = _positions[_currentPosition]['left']!;
    _width = _positions[_currentPosition]['width']!;
    _height = _positions[_currentPosition]['height']!;
  }
}

// 4. AnimatedCrossFade Demo
class AnimatedCrossFadeDemo extends StatefulWidget {
  const AnimatedCrossFadeDemo({Key? key}) : super(key: key);

  @override
  State<AnimatedCrossFadeDemo> createState() => _AnimatedCrossFadeDemoState();
}

class _AnimatedCrossFadeDemoState extends State<AnimatedCrossFadeDemo> {
  // 当前显示状态
  CrossFadeState _crossFadeState = CrossFadeState.showFirst;
  Duration _duration = const Duration(milliseconds: 500);

  @override
  Widget build(BuildContext context) {
    return Column(
      children: [
        Expanded(
          child: Padding(
            padding: const EdgeInsets.all(16),
            child: Column(
              mainAxisAlignment: MainAxisAlignment.center,
              children: [
                const Text(
                  'AnimatedCrossFade',
                  style: TextStyle(fontSize: 24, fontWeight: FontWeight.bold),
                ),
                const SizedBox(height: 8),
                Text(
                  '在两个子组件之间平滑切换',
                  style: TextStyle(color: Colors.grey[600]),
                ),
                const SizedBox(height: 40),

                // 交叉淡入淡出动画
                AnimatedCrossFade(
                  firstChild: _buildFirstChild(),
                  secondChild: _buildSecondChild(),
                  crossFadeState: _crossFadeState,
                  duration: _duration,
                  layoutBuilder:
                      (topChild, topChildKey, bottomChild, bottomChildKey) {
                    return Stack(
                      alignment: Alignment.center,
                      children: [
                        Positioned(
                          key: bottomChildKey,
                          child: bottomChild,
                        ),
                        Positioned(
                          key: topChildKey,
                          child: topChild,
                        ),
                      ],
                    );
                  },
                ),

                const SizedBox(height: 40),

                // 切换按钮
                ElevatedButton.icon(
                  icon: Icon(_crossFadeState == CrossFadeState.showFirst
                      ? Icons.arrow_forward
                      : Icons.arrow_back),
                  label: Text(_crossFadeState == CrossFadeState.showFirst
                      ? '显示第二个'
                      : '显示第一个'),
                  onPressed: () {
                    setState(() {
                      _crossFadeState =
                          _crossFadeState == CrossFadeState.showFirst
                              ? CrossFadeState.showSecond
                              : CrossFadeState.showFirst;
                    });
                  },
                ),
              ],
            ),
          ),
        ),
        // 控制面板
        Container(
          padding: const EdgeInsets.all(16),
          color: Colors.grey[200],
          child: Column(
            children: [
              // 动画持续时间
              Row(
                children: [
                  const Text('持续时间: '),
                  Expanded(
                    child: Slider(
                      min: 200,
                      max: 2000,
                      divisions: 9,
                      value: _duration.inMilliseconds.toDouble(),
                      label: '${_duration.inMilliseconds} ms',
                      onChanged: (value) {
                        setState(() {
                          _duration = Duration(milliseconds: value.round());
                        });
                      },
                    ),
                  ),
                  Text('${_duration.inMilliseconds} ms'),
                ],
              ),
              // 当前状态
              Padding(
                padding: const EdgeInsets.symmetric(vertical: 8),
                child: Text(
                  '当前显示: ${_crossFadeState == CrossFadeState.showFirst ? "第一个组件" : "第二个组件"}',
                  style: const TextStyle(fontWeight: FontWeight.bold),
                ),
              ),
            ],
          ),
        ),
      ],
    );
  }

  // 第一个子组件
  Widget _buildFirstChild() {
    return Container(
      width: 250,
      height: 250,
      decoration: BoxDecoration(
        color: Colors.blue,
        borderRadius: BorderRadius.circular(20),
        boxShadow: [
          BoxShadow(
            color: Colors.blue.withOpacity(0.5),
            blurRadius: 10,
            offset: const Offset(0, 5),
          ),
        ],
      ),
      child: const Center(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: [
            Icon(
              Icons.brightness_7,
              size: 80,
              color: Colors.white,
            ),
            SizedBox(height: 16),
            Text(
              '白天模式',
              style: TextStyle(
                color: Colors.white,
                fontSize: 24,
                fontWeight: FontWeight.bold,
              ),
            ),
          ],
        ),
      ),
    );
  }

  // 第二个子组件
  Widget _buildSecondChild() {
    return Container(
      width: 250,
      height: 250,
      decoration: BoxDecoration(
        color: Colors.indigo[900],
        borderRadius: BorderRadius.circular(20),
        boxShadow: [
          BoxShadow(
            color: Colors.indigo.withOpacity(0.5),
            blurRadius: 10,
            offset: const Offset(0, 5),
          ),
        ],
      ),
      child: Center(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: [
            const Icon(
              Icons.brightness_3,
              size: 80,
              color: Colors.white,
            ),
            const SizedBox(height: 16),
            const Text(
              '夜间模式',
              style: TextStyle(
                color: Colors.white,
                fontSize: 24,
                fontWeight: FontWeight.bold,
              ),
            ),
            const SizedBox(height: 16),
            // 添加星星
            Row(
              mainAxisAlignment: MainAxisAlignment.center,
              children: List.generate(5, (index) {
                return Padding(
                  padding: const EdgeInsets.symmetric(horizontal: 4),
                  child: Icon(
                    Icons.star,
                    size: 16,
                    color: Colors.yellow[300],
                  ),
                );
              }),
            ),
          ],
        ),
      ),
    );
  }
}

// 5. TweenAnimationBuilder Demo
class TweenAnimationBuilderDemo extends StatefulWidget {
  const TweenAnimationBuilderDemo({Key? key}) : super(key: key);

  @override
  State<TweenAnimationBuilderDemo> createState() =>
      _TweenAnimationBuilderDemoState();
}

class _TweenAnimationBuilderDemoState extends State<TweenAnimationBuilderDemo> {
  double _progress = 0.0;
  Duration _duration = const Duration(milliseconds: 1500);
  Color _colorStart = Colors.blue;
  Color _colorEnd = Colors.red;
  double _rotationEnd = 0;

  @override
  Widget build(BuildContext context) {
    return Column(
      children: [
        Expanded(
          child: Padding(
            padding: const EdgeInsets.all(16),
            child: Column(
              mainAxisAlignment: MainAxisAlignment.center,
              children: [
                const Text(
                  'TweenAnimationBuilder',
                  style: TextStyle(fontSize: 24, fontWeight: FontWeight.bold),
                ),
                const SizedBox(height: 8),
                Text(
                  '使用Tween创建自定义动画',
                  style: TextStyle(color: Colors.grey[600]),
                ),
                const SizedBox(height: 40),

                // 自定义Tween动画
                TweenAnimationBuilder<double>(
                  tween: Tween<double>(begin: 0, end: _progress),
                  duration: _duration,
                  curve: Curves.easeInOut,
                  builder: (context, value, _) {
                    return Column(
                      children: [
                        // 进度指示器
                        Container(
                          width: 300,
                          height: 20,
                          decoration: BoxDecoration(
                            color: Colors.grey[200],
                            borderRadius: BorderRadius.circular(10),
                          ),
                          child: Stack(
                            children: [
                              // 进度条
                              FractionallySizedBox(
                                widthFactor: value,
                                child: Container(
                                  decoration: BoxDecoration(
                                    gradient: LinearGradient(
                                      colors: [_colorStart, _colorEnd],
                                    ),
                                    borderRadius: BorderRadius.circular(10),
                                  ),
                                ),
                              ),
                            ],
                          ),
                        ),
                        const SizedBox(height: 8),
                        // 进度百分比
                        Text(
                          '${(value * 100).toInt()}%',
                          style: const TextStyle(
                            fontSize: 16,
                            fontWeight: FontWeight.bold,
                          ),
                        ),
                        const SizedBox(height: 40),

                        // 旋转和颜色动画
                        TweenAnimationBuilder<double>(
                          tween: Tween<double>(begin: 0, end: _rotationEnd),
                          duration: _duration,
                          curve: Curves.easeInOut,
                          builder: (context, rotation, child) {
                            return Transform.rotate(
                              angle: rotation,
                              child: TweenAnimationBuilder<Color?>(
                                tween: ColorTween(
                                    begin: _colorStart, end: _colorEnd),
                                duration: _duration,
                                curve: Curves.easeInOut,
                                builder: (context, color, _) {
                                  return Container(
                                    width: 150,
                                    height: 150,
                                    decoration: BoxDecoration(
                                      color: color,
                                      borderRadius:
                                          BorderRadius.circular(value * 75),
                                      boxShadow: [
                                        BoxShadow(
                                          color: (color ?? Colors.grey)
                                              .withOpacity(0.5),
                                          blurRadius: 10,
                                          offset: const Offset(0, 5),
                                        ),
                                      ],
                                    ),
                                    child: Center(
                                      child: Text(
                                        '${(value * 100).toInt()}%',
                                        style: const TextStyle(
                                          color: Colors.white,
                                          fontSize: 24,
                                          fontWeight: FontWeight.bold,
                                        ),
                                      ),
                                    ),
                                  );
                                },
                              ),
                            );
                          },
                        ),
                      ],
                    );
                  },
                ),
              ],
            ),
          ),
        ),
        // 控制面板
        Container(
          padding: const EdgeInsets.all(16),
          color: Colors.grey[200],
          child: Column(
            children: [
              // 进度滑块
              Row(
                children: [
                  const Text('进度: '),
                  Expanded(
                    child: Slider(
                      min: 0,
                      max: 1,
                      divisions: 100,
                      value: _progress,
                      label: '${(_progress * 100).toInt()}%',
                      onChanged: (value) {
                        setState(() {
                          _progress = value;
                        });
                      },
                    ),
                  ),
                  Text('${(_progress * 100).toInt()}%'),
                ],
              ),
              // 颜色选择
              Row(
                mainAxisAlignment: MainAxisAlignment.spaceEvenly,
                children: [
                  _buildColorButton('蓝->红', Colors.blue, Colors.red),
                  _buildColorButton('绿->紫', Colors.green, Colors.purple),
                  _buildColorButton('黄->橙', Colors.yellow, Colors.orange),
                  _buildColorButton('青->粉', Colors.cyan, Colors.pink),
                ],
              ),
              const SizedBox(height: 10),
              // 旋转按钮
              Row(
                mainAxisAlignment: MainAxisAlignment.spaceEvenly,
                children: [
                  ElevatedButton(
                    child: const Text('旋转 0°'),
                    onPressed: () {
                      setState(() {
                        _rotationEnd = 0;
                      });
                    },
                  ),
                  ElevatedButton(
                    child: const Text('旋转 90°'),
                    onPressed: () {
                      setState(() {
                        _rotationEnd = math.pi / 2;
                      });
                    },
                  ),
                  ElevatedButton(
                    child: const Text('旋转 180°'),
                    onPressed: () {
                      setState(() {
                        _rotationEnd = math.pi;
                      });
                    },
                  ),
                ],
              ),
              const SizedBox(height: 10),
              // 动画持续时间
              Row(
                children: [
                  const Text('持续时间: '),
                  Expanded(
                    child: Slider(
                      min: 500,
                      max: 3000,
                      divisions: 25,
                      value: _duration.inMilliseconds.toDouble(),
                      label: '${_duration.inMilliseconds} ms',
                      onChanged: (value) {
                        setState(() {
                          _duration = Duration(milliseconds: value.round());
                        });
                      },
                    ),
                  ),
                  Text('${_duration.inMilliseconds} ms'),
                ],
              ),
            ],
          ),
        ),
      ],
    );
  }

  // 构建颜色选择按钮
  Widget _buildColorButton(String label, Color start, Color end) {
    return ElevatedButton(
      style: ElevatedButton.styleFrom(
        backgroundColor: Colors.white,
        padding: const EdgeInsets.symmetric(horizontal: 10),
      ),
      onPressed: () {
        setState(() {
          _colorStart = start;
          _colorEnd = end;
        });
      },
      child: Text(
        label,
        style: TextStyle(
          color: _colorStart == start && _colorEnd == end
              ? Colors.black
              : Colors.grey,
        ),
      ),
    );
  }
}

显式动画

常见的显式动画有RotationTransition、 FadeTransition、 ScaleTransition、 SlideTransition、

AnimatedIcon。在显示动画中开发者需要创建一个AnimationController,通过AnimationController 控制动画的开始、暂停、重置、跳转、倒播等。

dart
import 'package:flutter/material.dart';
import 'dart:math' as math;

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

class MyApp extends StatelessWidget {
  const MyApp({Key? key}) : super(key: key);

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      debugShowCheckedModeBanner: false,
      title: '显式动画示例',
      theme: ThemeData(
        primarySwatch: Colors.blue,
        useMaterial3: true,
      ),
      home: const TransitionAnimationDemo(),
    );
  }
}

class TransitionAnimationDemo extends StatefulWidget {
  const TransitionAnimationDemo({Key? key}) : super(key: key);

  @override
  State<TransitionAnimationDemo> createState() =>
      _TransitionAnimationDemoState();
}

class _TransitionAnimationDemoState extends State<TransitionAnimationDemo>
    with SingleTickerProviderStateMixin {
  // 动画控制器
  late AnimationController _controller;

  // 各种动画
  late Animation<double> _rotationAnimation;
  late Animation<double> _opacityAnimation;
  late Animation<double> _scaleAnimation;
  late Animation<Offset> _slideAnimation;

  // 动画状态跟踪
  bool _isRotating = false;
  bool _isFading = false;
  bool _isScaling = false;
  bool _isSliding = false;

  // 滑动方向
  Offset _slideDirection = const Offset(0.0, 0.0);

  @override
  void initState() {
    super.initState();

    // 初始化动画控制器
    _controller = AnimationController(
      duration: const Duration(milliseconds: 800),
      vsync: this,
    );

    // 初始化各种动画
    _rotationAnimation = Tween<double>(
      begin: 0.0,
      end: 2 * math.pi,
    ).animate(CurvedAnimation(
      parent: _controller,
      curve: Curves.easeInOut,
    ));

    _opacityAnimation = Tween<double>(
      begin: 1.0,
      end: 0.3,
    ).animate(CurvedAnimation(
      parent: _controller,
      curve: Curves.easeInOut,
    ));

    _scaleAnimation = Tween<double>(
      begin: 1.0,
      end: 1.5,
    ).animate(CurvedAnimation(
      parent: _controller,
      curve: Curves.easeInOut,
    ));

    _slideAnimation = Tween<Offset>(
      begin: Offset.zero,
      end: const Offset(0.0, 0.5), // 默认向下滑动
    ).animate(CurvedAnimation(
      parent: _controller,
      curve: Curves.easeInOut,
    ));

    // 添加状态监听,用于重置动画状态
    _controller.addStatusListener((status) {
      if (status == AnimationStatus.completed) {
        _controller.reverse();
      } else if (status == AnimationStatus.dismissed) {
        // 在动画完全返回后更新状态
        if (mounted) {
          setState(() {
            _isRotating = false;
            _isFading = false;
            _isScaling = false;
            _isSliding = false;
          });
        }
      }
    });
  }

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

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: const Text('显式动画转换示例'),
        backgroundColor: Colors.blue,
        foregroundColor: Colors.white,
      ),
      body: Center(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: [
            // 动画容器
            SizedBox(
              height: 250,
              child: Center(
                child: _buildAnimatedContainer(),
              ),
            ),

            const SizedBox(height: 30),

            // 动画控制按钮
            Wrap(
              spacing: 10,
              runSpacing: 10,
              alignment: WrapAlignment.center,
              children: [
                _buildAnimationButton(
                  title: '旋转 (Rotation)',
                  color: Colors.blue,
                  isActive: _isRotating,
                  onPressed: _toggleRotation,
                ),
                _buildAnimationButton(
                  title: '淡入淡出 (Fade)',
                  color: Colors.green,
                  isActive: _isFading,
                  onPressed: _toggleFade,
                ),
                _buildAnimationButton(
                  title: '缩放 (Scale)',
                  color: Colors.orange,
                  isActive: _isScaling,
                  onPressed: _toggleScale,
                ),
                _buildAnimationButton(
                  title: '滑动 (Slide)',
                  color: Colors.purple,
                  isActive: _isSliding,
                  onPressed: _toggleSlide,
                ),
                _buildAnimationButton(
                  title: '播放全部',
                  color: Colors.red,
                  isActive:
                      _isRotating && _isFading && _isScaling && _isSliding,
                  onPressed: _playAllAnimations,
                ),
              ],
            ),

            const SizedBox(height: 20),

            // 滑动方向控制
            if (_isSliding)
              Padding(
                padding: const EdgeInsets.all(16.0),
                child: Column(
                  children: [
                    const Text('滑动方向:',
                        style: TextStyle(fontWeight: FontWeight.bold)),
                    const SizedBox(height: 8),
                    Row(
                      mainAxisAlignment: MainAxisAlignment.spaceEvenly,
                      children: [
                        _buildDirectionButton(
                          icon: Icons.arrow_upward,
                          direction: const Offset(0.0, -0.5),
                          isSelected:
                              _slideDirection == const Offset(0.0, -0.5),
                        ),
                        _buildDirectionButton(
                          icon: Icons.arrow_downward,
                          direction: const Offset(0.0, 0.5),
                          isSelected: _slideDirection == const Offset(0.0, 0.5),
                        ),
                        _buildDirectionButton(
                          icon: Icons.arrow_back,
                          direction: const Offset(-0.5, 0.0),
                          isSelected:
                              _slideDirection == const Offset(-0.5, 0.0),
                        ),
                        _buildDirectionButton(
                          icon: Icons.arrow_forward,
                          direction: const Offset(0.5, 0.0),
                          isSelected: _slideDirection == const Offset(0.5, 0.0),
                        ),
                      ],
                    ),
                  ],
                ),
              ),

            const SizedBox(height: 20),

            // 显示动画状态
            Padding(
              padding: const EdgeInsets.symmetric(horizontal: 20),
              child: Text(
                '活动动画: ' +
                    (_isRotating ? '旋转 ' : '') +
                    (_isFading ? '淡入淡出 ' : '') +
                    (_isScaling ? '缩放 ' : '') +
                    (_isSliding ? '滑动 ' : '') +
                    ((!_isRotating && !_isFading && !_isScaling && !_isSliding)
                        ? '无'
                        : ''),
                textAlign: TextAlign.center,
                style: const TextStyle(fontWeight: FontWeight.bold),
              ),
            ),
          ],
        ),
      ),
    );
  }

  // 构建带有所有转换效果的动画容器
  Widget _buildAnimatedContainer() {
    // 从内到外嵌套各种转换
    Widget container = Container(
      width: 100,
      height: 100,
      decoration: BoxDecoration(
        color: Colors.blue,
        borderRadius: BorderRadius.circular(20),
        boxShadow: [
          BoxShadow(
            color: Colors.blue.withOpacity(0.5),
            blurRadius: 10,
            offset: const Offset(0, 5),
          ),
        ],
      ),
      child: const Center(
        child: Text(
          'Flutter',
          style: TextStyle(
            color: Colors.white,
            fontWeight: FontWeight.bold,
          ),
        ),
      ),
    );

    // 应用淡入淡出效果
    if (_isFading) {
      container = FadeTransition(
        opacity: _opacityAnimation,
        child: container,
      );
    }

    // 应用缩放效果
    if (_isScaling) {
      container = ScaleTransition(
        scale: _scaleAnimation,
        child: container,
      );
    }

    // 应用旋转效果
    if (_isRotating) {
      container = RotationTransition(
        turns: _rotationAnimation,
        child: container,
      );
    }

    // 应用滑动效果
    if (_isSliding) {
      container = SlideTransition(
        position: _slideAnimation,
        child: container,
      );
    }

    return container;
  }

  // 构建动画按钮
  Widget _buildAnimationButton({
    required String title,
    required Color color,
    required bool isActive,
    required VoidCallback onPressed,
  }) {
    return ElevatedButton(
      style: ElevatedButton.styleFrom(
        backgroundColor: isActive ? color : Colors.grey[300],
        foregroundColor: isActive ? Colors.white : Colors.black87,
        padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 12),
      ),
      onPressed: onPressed,
      child: Text(title),
    );
  }

  // 构建方向按钮
  Widget _buildDirectionButton({
    required IconData icon,
    required Offset direction,
    required bool isSelected,
  }) {
    return IconButton(
      icon: Icon(icon),
      color: isSelected ? Colors.blue : Colors.grey,
      style: IconButton.styleFrom(
        backgroundColor: isSelected ? Colors.blue.withOpacity(0.2) : null,
      ),
      onPressed: () {
        _changeSlideDirection(direction);
      },
    );
  }

  // 切换旋转动画
  void _toggleRotation() {
    setState(() {
      _isRotating = !_isRotating;
      if (_isRotating) {
        _controller.forward(from: 0.0);
      } else {
        _controller.stop();
        _controller.reverse();
      }
    });
  }

  // 切换淡入淡出动画
  void _toggleFade() {
    setState(() {
      _isFading = !_isFading;
      if (_isFading) {
        _controller.forward(from: 0.0);
      } else {
        _controller.stop();
        _controller.reverse();
      }
    });
  }

  // 切换缩放动画
  void _toggleScale() {
    setState(() {
      _isScaling = !_isScaling;
      if (_isScaling) {
        _controller.forward(from: 0.0);
      } else {
        _controller.stop();
        _controller.reverse();
      }
    });
  }

  // 切换滑动动画
  void _toggleSlide() {
    setState(() {
      _isSliding = !_isSliding;
      if (_isSliding) {
        _controller.forward(from: 0.0);
      } else {
        _controller.stop();
        _controller.reverse();
      }
    });
  }

  // 改变滑动方向
  void _changeSlideDirection(Offset direction) {
    if (_slideDirection != direction) {
      setState(() {
        _slideDirection = direction;
        _slideAnimation = Tween<Offset>(
          begin: Offset.zero,
          end: direction,
        ).animate(CurvedAnimation(
          parent: _controller,
          curve: Curves.easeInOut,
        ));

        if (_isSliding) {
          _controller.forward(from: 0.0);
        }
      });
    }
  }

  // 播放所有动画
  void _playAllAnimations() {
    setState(() {
      _isRotating = true;
      _isFading = true;
      _isScaling = true;
      _isSliding = true;
      _controller.forward(from: 0.0);
    });
  }
}

交错动画

AnimationController 用于控制动画,它包含动画的启动forward()、停止 stop()、反向播放 reverse()等方法。AnimationController 会在动画的每一帧,就会生成一个新的值。默认情况 下, AnimationController 在给定的时间段内线性的生成从0.0到1.0(默认区间)的数字,我们也 可以通过 lowerbound 和 upperBound 来修改 AnimationController 生成数字的区间。

dart
import 'package:flutter/material.dart';
import 'dart:math' as math;

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

class MyApp extends StatelessWidget {
  const MyApp({Key? key}) : super(key: key);

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      debugShowCheckedModeBanner: false,
      title: '交错式动画示例',
      theme: ThemeData(
        primarySwatch: Colors.blue,
        useMaterial3: true,
      ),
      home: const StaggeredAnimationDemo(),
    );
  }
}

class StaggeredAnimationDemo extends StatefulWidget {
  const StaggeredAnimationDemo({Key? key}) : super(key: key);

  @override
  State<StaggeredAnimationDemo> createState() => _StaggeredAnimationDemoState();
}

class _StaggeredAnimationDemoState extends State<StaggeredAnimationDemo>
    with SingleTickerProviderStateMixin {
  // 动画控制器
  late AnimationController _controller;

  // 动画类型
  int _selectedAnimationIndex = 0;
  final List<String> _animationTypes = [
    '登录表单',
    '卡片列表',
    '波浪效果',
    '菜单展开',
    '加载指示器'
  ];

  @override
  void initState() {
    super.initState();

    // 初始化动画控制器
    _controller = AnimationController(
      duration: const Duration(milliseconds: 2000),
      vsync: this,
    );
  }

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

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: const Text('交错式动画示例'),
        backgroundColor: Colors.blue,
        foregroundColor: Colors.white,
      ),
      body: Column(
        children: [
          // 动画类型选择器
          Container(
            padding: const EdgeInsets.all(16),
            color: Colors.grey[100],
            child: Column(
              crossAxisAlignment: CrossAxisAlignment.start,
              children: [
                const Text(
                  '交错式动画示例',
                  style: TextStyle(
                    fontSize: 18,
                    fontWeight: FontWeight.bold,
                  ),
                ),
                const SizedBox(height: 4),
                Text(
                  '多个动画按照时间顺序依次或部分重叠执行',
                  style: TextStyle(
                    fontSize: 14,
                    color: Colors.grey[700],
                  ),
                ),
                const SizedBox(height: 16),

                // 动画类型选择
                SingleChildScrollView(
                  scrollDirection: Axis.horizontal,
                  child: Row(
                    children: List.generate(
                      _animationTypes.length,
                      (index) => Padding(
                        padding: const EdgeInsets.only(right: 8),
                        child: ChoiceChip(
                          label: Text(_animationTypes[index]),
                          selected: _selectedAnimationIndex == index,
                          onSelected: (selected) {
                            if (selected) {
                              setState(() {
                                _selectedAnimationIndex = index;
                              });
                              // 重置动画
                              _controller.reset();
                            }
                          },
                        ),
                      ),
                    ),
                  ),
                ),

                const SizedBox(height: 16),

                // 动画控制按钮
                Row(
                  mainAxisAlignment: MainAxisAlignment.center,
                  children: [
                    ElevatedButton.icon(
                      icon: const Icon(Icons.play_arrow),
                      label: const Text('播放'),
                      style: ElevatedButton.styleFrom(
                        backgroundColor: Colors.green,
                        foregroundColor: Colors.white,
                      ),
                      onPressed: () {
                        _controller.reset();
                        _controller.forward();
                      },
                    ),
                    const SizedBox(width: 16),
                    ElevatedButton.icon(
                      icon: const Icon(Icons.replay),
                      label: const Text('重置'),
                      style: ElevatedButton.styleFrom(
                        backgroundColor: Colors.orange,
                        foregroundColor: Colors.white,
                      ),
                      onPressed: () {
                        _controller.reset();
                      },
                    ),
                  ],
                ),
              ],
            ),
          ),

          // 动画展示区域
          Expanded(
            child: _buildSelectedAnimation(),
          ),
        ],
      ),
    );
  }

  // 构建选中的动画
  Widget _buildSelectedAnimation() {
    switch (_selectedAnimationIndex) {
      case 0:
        return LoginFormAnimation(controller: _controller);
      case 1:
        return CardListAnimation(controller: _controller);
      case 2:
        return WaveAnimation(controller: _controller);
      case 3:
        return MenuAnimation(controller: _controller);
      case 4:
        return LoadingAnimation(controller: _controller);
      default:
        return const Center(child: Text('未知动画类型'));
    }
  }
}

// 1. 登录表单动画
class LoginFormAnimation extends StatelessWidget {
  final AnimationController controller;

  // 各个元素的动画
  late final Animation<double> _logoSizeAnimation;
  late final Animation<double> _logoOpacityAnimation;
  late final Animation<Offset> _usernameSlideAnimation;
  late final Animation<Offset> _passwordSlideAnimation;
  late final Animation<double> _buttonScaleAnimation;

  LoginFormAnimation({Key? key, required this.controller}) : super(key: key) {
    // Logo大小动画 (0.0-0.2)
    _logoSizeAnimation = Tween<double>(
      begin: 0.0,
      end: 1.0,
    ).animate(
      CurvedAnimation(
        parent: controller,
        curve: const Interval(0.0, 0.2, curve: Curves.easeOut),
      ),
    );

    // Logo透明度动画 (0.0-0.3)
    _logoOpacityAnimation = Tween<double>(
      begin: 0.0,
      end: 1.0,
    ).animate(
      CurvedAnimation(
        parent: controller,
        curve: const Interval(0.0, 0.3, curve: Curves.easeOut),
      ),
    );

    // 用户名输入框滑入动画 (0.2-0.5)
    _usernameSlideAnimation = Tween<Offset>(
      begin: const Offset(1.5, 0.0),
      end: Offset.zero,
    ).animate(
      CurvedAnimation(
        parent: controller,
        curve: const Interval(0.2, 0.5, curve: Curves.easeOut),
      ),
    );

    // 密码输入框滑入动画 (0.3-0.6)
    _passwordSlideAnimation = Tween<Offset>(
      begin: const Offset(1.5, 0.0),
      end: Offset.zero,
    ).animate(
      CurvedAnimation(
        parent: controller,
        curve: const Interval(0.3, 0.6, curve: Curves.easeOut),
      ),
    );

    // 登录按钮缩放动画 (0.5-0.8)
    _buttonScaleAnimation = Tween<double>(
      begin: 0.0,
      end: 1.0,
    ).animate(
      CurvedAnimation(
        parent: controller,
        curve: const Interval(0.5, 0.8, curve: Curves.elasticOut),
      ),
    );
  }

  @override
  Widget build(BuildContext context) {
    return AnimatedBuilder(
      animation: controller,
      builder: (context, child) {
        return Center(
          child: Padding(
            padding: const EdgeInsets.all(32.0),
            child: Column(
              mainAxisSize: MainAxisSize.min,
              children: [
                // Logo
                Opacity(
                  opacity: _logoOpacityAnimation.value,
                  child: Transform.scale(
                    scale: _logoSizeAnimation.value,
                    child: Container(
                      width: 100,
                      height: 100,
                      decoration: const BoxDecoration(
                        shape: BoxShape.circle,
                        color: Colors.blue,
                      ),
                      child: const Icon(
                        Icons.person,
                        size: 60,
                        color: Colors.white,
                      ),
                    ),
                  ),
                ),

                const SizedBox(height: 40),

                // 用户名输入框
                SlideTransition(
                  position: _usernameSlideAnimation,
                  child: const TextField(
                    decoration: InputDecoration(
                      labelText: '用户名',
                      prefixIcon: Icon(Icons.person_outline),
                      border: OutlineInputBorder(),
                    ),
                  ),
                ),

                const SizedBox(height: 16),

                // 密码输入框
                SlideTransition(
                  position: _passwordSlideAnimation,
                  child: const TextField(
                    obscureText: true,
                    decoration: InputDecoration(
                      labelText: '密码',
                      prefixIcon: Icon(Icons.lock_outline),
                      border: OutlineInputBorder(),
                    ),
                  ),
                ),

                const SizedBox(height: 32),

                // 登录按钮
                ScaleTransition(
                  scale: _buttonScaleAnimation,
                  child: SizedBox(
                    width: double.infinity,
                    height: 50,
                    child: ElevatedButton(
                      style: ElevatedButton.styleFrom(
                        backgroundColor: Colors.blue,
                        foregroundColor: Colors.white,
                      ),
                      onPressed: () {},
                      child: const Text(
                        '登录',
                        style: TextStyle(fontSize: 18),
                      ),
                    ),
                  ),
                ),
              ],
            ),
          ),
        );
      },
    );
  }
}

// 2. 卡片列表动画
class CardListAnimation extends StatelessWidget {
  final AnimationController controller;
  final int itemCount = 10;

  CardListAnimation({Key? key, required this.controller}) : super(key: key);

  // 获取每个卡片的动画
  Animation<Offset> _getItemSlideAnimation(int index) {
    // 为每个卡片设置不同的动画间隔,使它们依次出现
    final startTime = 0.05 * index;
    final endTime = startTime + 0.3; // 每个动画持续0.3

    return Tween<Offset>(
      begin: const Offset(0.0, 1.0),
      end: Offset.zero,
    ).animate(
      CurvedAnimation(
        parent: controller,
        curve: Interval(startTime, endTime, curve: Curves.easeOut),
      ),
    );
  }

  Animation<double> _getItemOpacityAnimation(int index) {
    final startTime = 0.05 * index;
    final endTime = startTime + 0.3;

    return Tween<double>(
      begin: 0.0,
      end: 1.0,
    ).animate(
      CurvedAnimation(
        parent: controller,
        curve: Interval(startTime, endTime, curve: Curves.easeOut),
      ),
    );
  }

  @override
  Widget build(BuildContext context) {
    return AnimatedBuilder(
      animation: controller,
      builder: (context, child) {
        return ListView.builder(
          padding: const EdgeInsets.all(16),
          itemCount: itemCount,
          itemBuilder: (context, index) {
            final slideAnimation = _getItemSlideAnimation(index);
            final opacityAnimation = _getItemOpacityAnimation(index);

            return SlideTransition(
              position: slideAnimation,
              child: FadeTransition(
                opacity: opacityAnimation,
                child: Padding(
                  padding: const EdgeInsets.only(bottom: 16),
                  child: Card(
                    elevation: 4,
                    child: ListTile(
                      leading: CircleAvatar(
                        backgroundColor:
                            Colors.primaries[index % Colors.primaries.length],
                        child: Text('${index + 1}'),
                      ),
                      title: Text('卡片项目 ${index + 1}'),
                      subtitle: Text('这是一个交错动画的卡片项目'),
                      trailing: const Icon(Icons.arrow_forward_ios, size: 16),
                    ),
                  ),
                ),
              ),
            );
          },
        );
      },
    );
  }
}

// 3. 波浪效果动画
class WaveAnimation extends StatelessWidget {
  final AnimationController controller;
  final int itemCount = 20;

  WaveAnimation({Key? key, required this.controller}) : super(key: key);

  @override
  Widget build(BuildContext context) {
    return AnimatedBuilder(
      animation: controller,
      builder: (context, child) {
        return Center(
          child: Wrap(
            spacing: 10,
            runSpacing: 10,
            alignment: WrapAlignment.center,
            children: List.generate(itemCount, (index) {
              // 为每个点创建一个波浪效果的动画
              final delay = index % 5 * 0.1; // 每行5个,同一列的延迟相同

              final scaleAnimation = Tween<double>(
                begin: 0.0,
                end: 1.0,
              ).animate(
                CurvedAnimation(
                  parent: controller,
                  curve: Interval(
                    delay,
                    delay + 0.5,
                    curve: Curves.elasticOut,
                  ),
                ),
              );

              final colorAnimation = ColorTween(
                begin: Colors.blue[100],
                end: Colors.primaries[index % Colors.primaries.length],
              ).animate(
                CurvedAnimation(
                  parent: controller,
                  curve: Interval(
                    delay,
                    delay + 0.5,
                    curve: Curves.easeOut,
                  ),
                ),
              );

              return ScaleTransition(
                scale: scaleAnimation,
                child: Container(
                  width: 60,
                  height: 60,
                  decoration: BoxDecoration(
                    color: colorAnimation.value,
                    shape: BoxShape.circle,
                    boxShadow: [
                      BoxShadow(
                        color: (colorAnimation.value ?? Colors.grey)
                            .withOpacity(0.5),
                        blurRadius: 5,
                        offset: const Offset(0, 3),
                      ),
                    ],
                  ),
                  child: Center(
                    child: Text(
                      '${index + 1}',
                      style: const TextStyle(
                        color: Colors.white,
                        fontWeight: FontWeight.bold,
                      ),
                    ),
                  ),
                ),
              );
            }),
          ),
        );
      },
    );
  }
}

// 4. 菜单展开动画
class MenuAnimation extends StatelessWidget {
  final AnimationController controller;

  // 菜单项
  final List<MenuItemData> _menuItems = [
    MenuItemData(icon: Icons.home, label: '首页', color: Colors.blue),
    MenuItemData(icon: Icons.search, label: '搜索', color: Colors.green),
    MenuItemData(icon: Icons.notifications, label: '通知', color: Colors.orange),
    MenuItemData(icon: Icons.settings, label: '设置', color: Colors.purple),
    MenuItemData(icon: Icons.person, label: '个人', color: Colors.red),
  ];

  MenuAnimation({Key? key, required this.controller}) : super(key: key);

  @override
  Widget build(BuildContext context) {
    // 主按钮旋转动画
    final mainButtonRotation = Tween<double>(
      begin: 0.0,
      end: 0.875, // 旋转315度
    ).animate(
      CurvedAnimation(
        parent: controller,
        curve: const Interval(0.0, 0.3, curve: Curves.easeOut),
      ),
    );

    return AnimatedBuilder(
      animation: controller,
      builder: (context, child) {
        return Stack(
          alignment: Alignment.center,
          children: [
            // 菜单项
            ..._buildMenuItems(context),

            // 主按钮
            Positioned(
              bottom: 50,
              child: Transform.rotate(
                angle: mainButtonRotation.value * 2 * math.pi,
                child: FloatingActionButton(
                  backgroundColor: Colors.blue,
                  onPressed: () {},
                  child: const Icon(
                    Icons.add,
                    size: 30,
                    color: Colors.white,
                  ),
                ),
              ),
            ),
          ],
        );
      },
    );
  }

  // 构建菜单项
  List<Widget> _buildMenuItems(BuildContext context) {
    return List.generate(_menuItems.length, (index) {
      // 计算每个菜单项的位置
      final double angle = math.pi / 2 / (_menuItems.length - 1) * index;
      final double radius = 120; // 菜单半径

      // 计算菜单项的位置
      final double x = radius * math.cos(angle);
      final double y = -radius * math.sin(angle); // 负号使菜单向上展开

      // 菜单项的动画
      final delay = 0.1 * index;
      final itemAnimation = Tween<double>(
        begin: 0.0,
        end: 1.0,
      ).animate(
        CurvedAnimation(
          parent: controller,
          curve: Interval(0.2 + delay, 0.5 + delay, curve: Curves.easeOut),
        ),
      );

      final item = _menuItems[index];

      return Positioned(
        bottom: 50 + y * itemAnimation.value,
        left: MediaQuery.of(context).size.width / 2 +
            x * itemAnimation.value -
            28,
        child: Transform.scale(
          scale: itemAnimation.value,
          child: Opacity(
            opacity: itemAnimation.value,
            child: FloatingActionButton.small(
              heroTag: 'menu_$index',
              backgroundColor: item.color,
              onPressed: () {},
              child: Icon(
                item.icon,
                color: Colors.white,
              ),
            ),
          ),
        ),
      );
    });
  }
}

// 菜单项数据
class MenuItemData {
  final IconData icon;
  final String label;
  final Color color;

  MenuItemData({
    required this.icon,
    required this.label,
    required this.color,
  });
}

// 5. 加载指示器动画
class LoadingAnimation extends StatelessWidget {
  final AnimationController controller;
  final int dotCount = 5;

  LoadingAnimation({Key? key, required this.controller}) : super(key: key);

  @override
  Widget build(BuildContext context) {
    return AnimatedBuilder(
      animation: controller,
      builder: (context, child) {
        return Center(
          child: Column(
            mainAxisAlignment: MainAxisAlignment.center,
            children: [
              // 加载文本
              FadeTransition(
                opacity: Tween<double>(begin: 0.0, end: 1.0).animate(
                  CurvedAnimation(
                    parent: controller,
                    curve: const Interval(0.0, 0.2, curve: Curves.easeOut),
                  ),
                ),
                child: const Text(
                  '加载中...',
                  style: TextStyle(
                    fontSize: 24,
                    fontWeight: FontWeight.bold,
                  ),
                ),
              ),

              const SizedBox(height: 40),

              // 加载点
              Row(
                mainAxisAlignment: MainAxisAlignment.center,
                children: List.generate(dotCount, (index) {
                  // 为每个点创建一个弹跳动画
                  final delay = index * 0.1;
                  final jumpAnimation = TweenSequence<double>([
                    TweenSequenceItem(
                      tween: Tween<double>(begin: 0, end: 20),
                      weight: 1,
                    ),
                    TweenSequenceItem(
                      tween: Tween<double>(begin: 20, end: 0),
                      weight: 1,
                    ),
                  ]).animate(
                    CurvedAnimation(
                      parent: controller,
                      curve: Interval(
                        delay,
                        delay + 0.4,
                        curve: Curves.easeInOut,
                      ),
                    ),
                  );

                  // 颜色动画
                  final colorAnimation = ColorTween(
                    begin: Colors.blue[100],
                    end: Colors.blue,
                  ).animate(
                    CurvedAnimation(
                      parent: controller,
                      curve: Interval(
                        delay,
                        delay + 0.4,
                        curve: Curves.easeInOut,
                      ),
                    ),
                  );

                  return Container(
                    margin: const EdgeInsets.symmetric(horizontal: 5),
                    child: Transform.translate(
                      offset: Offset(0, -jumpAnimation.value),
                      child: Container(
                        width: 20,
                        height: 20,
                        decoration: BoxDecoration(
                          color: colorAnimation.value,
                          shape: BoxShape.circle,
                        ),
                      ),
                    ),
                  );
                }),
              ),

              const SizedBox(height: 60),

              // 进度条
              Padding(
                padding: const EdgeInsets.symmetric(horizontal: 50),
                child: LinearProgressIndicator(
                  value: controller.value,
                  backgroundColor: Colors.grey[200],
                  color: Colors.blue,
                  minHeight: 10,
                  borderRadius: BorderRadius.circular(5),
                ),
              ),

              const SizedBox(height: 20),

              // 进度文本
              Text(
                '${(controller.value * 100).toInt()}%',
                style: const TextStyle(
                  fontSize: 18,
                  fontWeight: FontWeight.bold,
                ),
              ),
            ],
          ),
        );
      },
    );
  }
}

自定义动画

dart
import 'package:flutter/material.dart';
import 'dart:math' as math;

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

class MyApp extends StatelessWidget {
  const MyApp({Key? key}) : super(key: key);

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      debugShowCheckedModeBanner: false,
      title: 'TweenAnimationBuilder 示例',
      theme: ThemeData(
        primarySwatch: Colors.blue,
        useMaterial3: true,
      ),
      home: const TweenAnimationDemo(),
    );
  }
}

class TweenAnimationDemo extends StatefulWidget {
  const TweenAnimationDemo({Key? key}) : super(key: key);

  @override
  State<TweenAnimationDemo> createState() => _TweenAnimationDemoState();
}

class _TweenAnimationDemoState extends State<TweenAnimationDemo> {
  // 动画属性
  double _size = 100;
  Color _color = Colors.blue;
  double _borderRadius = 20;
  double _rotation = 0;
  Offset _offset = Offset.zero;
  double _progress = 0.0;

  // 动画持续时间
  Duration _duration = const Duration(milliseconds: 800);

  // 动画曲线
  Curve _curve = Curves.easeInOut;

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: const Text('TweenAnimationBuilder 示例'),
        backgroundColor: Colors.blue,
        foregroundColor: Colors.white,
      ),
      body: Column(
        children: [
          // 动画展示区域
          Expanded(
            child: Center(
              child: Stack(
                alignment: Alignment.center,
                children: [
                  // 进度指示器背景
                  Container(
                    width: 300,
                    height: 300,
                    decoration: BoxDecoration(
                      color: Colors.grey[200],
                      shape: BoxShape.circle,
                    ),
                    child: Center(
                      child: TweenAnimationBuilder<double>(
                        tween: Tween<double>(begin: 0, end: _progress),
                        duration: _duration,
                        curve: _curve,
                        builder: (context, value, _) {
                          return SizedBox(
                            width: 280,
                            height: 280,
                            child: CircularProgressIndicator(
                              value: value,
                              strokeWidth: 10,
                              backgroundColor: Colors.grey[300],
                              color: _color,
                            ),
                          );
                        },
                      ),
                    ),
                  ),

                  // 主要动画元素
                  TweenAnimationBuilder<Offset>(
                    tween: Tween<Offset>(begin: Offset.zero, end: _offset),
                    duration: _duration,
                    curve: _curve,
                    builder: (context, offset, child) {
                      return Transform.translate(
                        offset: offset * 100, // 放大偏移量
                        child: child,
                      );
                    },
                    child: TweenAnimationBuilder<double>(
                      tween: Tween<double>(begin: 0, end: _rotation),
                      duration: _duration,
                      curve: _curve,
                      builder: (context, rotation, child) {
                        return Transform.rotate(
                          angle: rotation,
                          child: child,
                        );
                      },
                      child: TweenAnimationBuilder<double>(
                        tween: Tween<double>(begin: 0, end: _size),
                        duration: _duration,
                        curve: _curve,
                        builder: (context, size, child) {
                          return TweenAnimationBuilder<double>(
                            tween: Tween<double>(begin: 0, end: _borderRadius),
                            duration: _duration,
                            curve: _curve,
                            builder: (context, borderRadius, child) {
                              return TweenAnimationBuilder<Color?>(
                                tween:
                                    ColorTween(begin: Colors.grey, end: _color),
                                duration: _duration,
                                curve: _curve,
                                builder: (context, color, child) {
                                  return Container(
                                    width: size,
                                    height: size,
                                    decoration: BoxDecoration(
                                      color: color,
                                      borderRadius:
                                          BorderRadius.circular(borderRadius),
                                      boxShadow: [
                                        BoxShadow(
                                          color: (color ?? Colors.grey)
                                              .withOpacity(0.5),
                                          blurRadius: 10,
                                          offset: const Offset(0, 5),
                                        ),
                                      ],
                                    ),
                                    child: child,
                                  );
                                },
                                child: Center(
                                  child: Text(
                                    '${(_progress * 100).toInt()}%',
                                    style: const TextStyle(
                                      color: Colors.white,
                                      fontWeight: FontWeight.bold,
                                      fontSize: 20,
                                    ),
                                  ),
                                ),
                              );
                            },
                          );
                        },
                      ),
                    ),
                  ),
                ],
              ),
            ),
          ),

          // 控制面板
          Container(
            padding: const EdgeInsets.all(16),
            color: Colors.grey[200],
            child: Column(
              crossAxisAlignment: CrossAxisAlignment.start,
              children: [
                const Text(
                  'TweenAnimationBuilder 控制面板',
                  style: TextStyle(
                    fontSize: 18,
                    fontWeight: FontWeight.bold,
                  ),
                ),
                const SizedBox(height: 16),

                // 进度控制
                Row(
                  children: [
                    const Text('进度: '),
                    Expanded(
                      child: Slider(
                        value: _progress,
                        min: 0.0,
                        max: 1.0,
                        divisions: 100,
                        label: '${(_progress * 100).toInt()}%',
                        onChanged: (value) {
                          setState(() {
                            _progress = value;
                          });
                        },
                      ),
                    ),
                    Text('${(_progress * 100).toInt()}%'),
                  ],
                ),

                // 大小控制
                Row(
                  children: [
                    const Text('大小: '),
                    Expanded(
                      child: Slider(
                        value: _size,
                        min: 50,
                        max: 200,
                        divisions: 15,
                        label: '${_size.toInt()}',
                        onChanged: (value) {
                          setState(() {
                            _size = value;
                          });
                        },
                      ),
                    ),
                    Text('${_size.toInt()}'),
                  ],
                ),

                // 圆角控制
                Row(
                  children: [
                    const Text('圆角: '),
                    Expanded(
                      child: Slider(
                        value: _borderRadius,
                        min: 0,
                        max: 100,
                        divisions: 10,
                        label: '${_borderRadius.toInt()}',
                        onChanged: (value) {
                          setState(() {
                            _borderRadius = value;
                          });
                        },
                      ),
                    ),
                    Text('${_borderRadius.toInt()}'),
                  ],
                ),

                // 旋转控制
                Row(
                  children: [
                    const Text('旋转: '),
                    Expanded(
                      child: Slider(
                        value: _rotation / (2 * math.pi),
                        min: 0,
                        max: 1,
                        divisions: 8,
                        label: '${(_rotation / (2 * math.pi) * 360).toInt()}°',
                        onChanged: (value) {
                          setState(() {
                            _rotation = value * 2 * math.pi;
                          });
                        },
                      ),
                    ),
                    Text('${(_rotation / (2 * math.pi) * 360).toInt()}°'),
                  ],
                ),

                const SizedBox(height: 16),

                // 颜色选择
                Row(
                  children: [
                    const Text('颜色: '),
                    const SizedBox(width: 8),
                    _buildColorButton(Colors.blue),
                    _buildColorButton(Colors.red),
                    _buildColorButton(Colors.green),
                    _buildColorButton(Colors.orange),
                    _buildColorButton(Colors.purple),
                  ],
                ),

                const SizedBox(height: 16),

                // 位置控制
                Row(
                  children: [
                    const Text('位置: '),
                    const SizedBox(width: 8),
                    IconButton(
                      icon: const Icon(Icons.arrow_upward),
                      onPressed: () => _setOffset(0, -1),
                      color: _offset.dy < 0 ? Colors.blue : Colors.grey,
                    ),
                    IconButton(
                      icon: const Icon(Icons.arrow_downward),
                      onPressed: () => _setOffset(0, 1),
                      color: _offset.dy > 0 ? Colors.blue : Colors.grey,
                    ),
                    IconButton(
                      icon: const Icon(Icons.arrow_back),
                      onPressed: () => _setOffset(-1, 0),
                      color: _offset.dx < 0 ? Colors.blue : Colors.grey,
                    ),
                    IconButton(
                      icon: const Icon(Icons.arrow_forward),
                      onPressed: () => _setOffset(1, 0),
                      color: _offset.dx > 0 ? Colors.blue : Colors.grey,
                    ),
                    IconButton(
                      icon: const Icon(Icons.center_focus_strong),
                      onPressed: () => _setOffset(0, 0),
                      color: _offset == Offset.zero ? Colors.blue : Colors.grey,
                    ),
                  ],
                ),

                const SizedBox(height: 16),

                // 预设动画按钮
                Row(
                  mainAxisAlignment: MainAxisAlignment.spaceEvenly,
                  children: [
                    ElevatedButton(
                      onPressed: _animateToCircle,
                      child: const Text('圆形'),
                    ),
                    ElevatedButton(
                      onPressed: _animateToSquare,
                      child: const Text('方形'),
                    ),
                    ElevatedButton(
                      onPressed: _animateToRandom,
                      child: const Text('随机'),
                    ),
                    ElevatedButton(
                      onPressed: _resetAnimation,
                      child: const Text('重置'),
                    ),
                  ],
                ),

                const SizedBox(height: 16),

                // 动画持续时间
                Row(
                  children: [
                    const Text('持续时间: '),
                    Expanded(
                      child: Slider(
                        value: _duration.inMilliseconds.toDouble(),
                        min: 200,
                        max: 2000,
                        divisions: 9,
                        label: '${_duration.inMilliseconds} ms',
                        onChanged: (value) {
                          setState(() {
                            _duration = Duration(milliseconds: value.toInt());
                          });
                        },
                      ),
                    ),
                    Text('${_duration.inMilliseconds} ms'),
                  ],
                ),

                // 动画曲线
                Row(
                  children: [
                    const Text('动画曲线: '),
                    const SizedBox(width: 8),
                    Expanded(
                      child: DropdownButton<Curve>(
                        value: _curve,
                        isExpanded: true,
                        onChanged: (Curve? newValue) {
                          if (newValue != null) {
                            setState(() {
                              _curve = newValue;
                            });
                          }
                        },
                        items: [
                          DropdownMenuItem(
                            value: Curves.linear,
                            child: const Text('线性'),
                          ),
                          DropdownMenuItem(
                            value: Curves.easeInOut,
                            child: const Text('渐入渐出'),
                          ),
                          DropdownMenuItem(
                            value: Curves.bounceOut,
                            child: const Text('弹跳'),
                          ),
                          DropdownMenuItem(
                            value: Curves.elasticOut,
                            child: const Text('弹性'),
                          ),
                          DropdownMenuItem(
                            value: Curves.fastOutSlowIn,
                            child: const Text('快出慢入'),
                          ),
                        ],
                      ),
                    ),
                  ],
                ),
              ],
            ),
          ),
        ],
      ),
    );
  }

  // 构建颜色选择按钮
  Widget _buildColorButton(Color color) {
    return GestureDetector(
      onTap: () {
        setState(() {
          _color = color;
        });
      },
      child: Container(
        margin: const EdgeInsets.symmetric(horizontal: 4),
        width: 30,
        height: 30,
        decoration: BoxDecoration(
          color: color,
          shape: BoxShape.circle,
          border: Border.all(
            color: _color == color ? Colors.white : Colors.grey,
            width: _color == color ? 2 : 1,
          ),
          boxShadow: [
            if (_color == color)
              BoxShadow(
                color: color.withOpacity(0.5),
                blurRadius: 6,
                spreadRadius: 1,
              ),
          ],
        ),
      ),
    );
  }

  // 设置偏移量
  void _setOffset(double dx, double dy) {
    setState(() {
      _offset = Offset(dx, dy);
    });
  }

  // 预设动画:圆形
  void _animateToCircle() {
    setState(() {
      _size = 150;
      _borderRadius = 75;
      _color = Colors.blue;
      _rotation = 0;
      _offset = Offset.zero;
      _progress = 1.0;
    }); 
  }

  // 预设动画:方形
  void _animateToSquare() {
    setState(() {
      _size = 150;
      _borderRadius = 0;
      _color = Colors.red;
      _rotation = math.pi / 4; // 45度
      _offset = Offset.zero;
      _progress = 0.75;
    });
  }

  // 预设动画:随机
  void _animateToRandom() {
    final random = math.Random();
    setState(() {
      _size = random.nextDouble() * 150 + 50;
      _borderRadius = random.nextDouble() * 100;
      _color = Color.fromRGBO(
        random.nextInt(255),
        random.nextInt(255),
        random.nextInt(255),
        1,
      );
      _rotation = random.nextDouble() * 2 * math.pi;
      _offset = Offset(
        (random.nextDouble() * 2 - 1) * 0.5,
        (random.nextDouble() * 2 - 1) * 0.5,
      );
      _progress = random.nextDouble();
    });
  }

  // 重置动画
  void _resetAnimation() {
    setState(() {
      _size = 100;
      _borderRadius = 20;
      _color = Colors.blue;
      _rotation = 0;
      _offset = Offset.zero;
      _progress = 0.0;
    });
  }
}

Getx学习

通俗的讲:当我们想在多个页面(组件/idget)之间共享状态(数据),或者一个页面(组 件/idget)中的多个子组件之间共享状态(数据),这个时候我们就可以用Flutter中的状态管理来管 理统一的状态(数据),实现不同组件之间的传值和数据共享。 现在Flutter的状态管埋方案很多,redux、bloc、state、provider、Getx, provider是官方提供的状态管理解决方案,主要功能就是状态管理。Getx是第三方的状态管理插件, 不仅具有状态管理的功能,还具有路由管理、主题管理、国际化多语言管理,Obx局部更新、网络请 求、数据验证等功能,相比其他状态管理插件Getx简单、功能星大并且高性能。

状态

dart
import 'package:flutter/material.dart';
import 'package:get/get.dart';

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

class MyApp extends StatelessWidget {
  const MyApp({Key? key}) : super(key: key);

  @override
  Widget build(BuildContext context) {
    // 使用GetMaterialApp替代MaterialApp
    return GetMaterialApp(
      title: 'GetX 状态管理示例',
      debugShowCheckedModeBanner: false,
      theme: ThemeData(
        primarySwatch: Colors.blue,
        useMaterial3: true,
      ),
      // 定义命名路由
      getPages: [
        GetPage(name: '/', page: () => const HomePage()),
        GetPage(name: '/detail', page: () => const DetailPage()),
        GetPage(name: '/settings', page: () => const SettingsPage()),
        GetPage(name: '/user', page: () => const UserProfilePage()),
      ],
      initialRoute: '/',
    );
  }
}

// 计数器控制器 - 简单状态管理
class CounterController extends GetxController {
  // 使用.obs使变量变为可观察对象
  RxInt count = 0.obs;
  RxBool isDarkMode = false.obs;
  RxString userName = "访客".obs;

  RxString email= "".obs;

  // 增加计数
  void increment() {
    count++;
    // 不需要调用notifyListeners或setState
  }

  // 减少计数
  void decrement() {
    if (count > 0) {
      count--;
    }
  }

  // 重置计数
  void reset() {
    count.value = 0;
  }

  // 切换主题
  void toggleTheme() {
    isDarkMode.value = !isDarkMode.value;
    Get.changeTheme(
      isDarkMode.value ? ThemeData.dark() : ThemeData.light(),
    );
  }

  // 更新用户名
  void updateUserName(String name) {
    userName.value = name;
  }

  void updateEmail(String email) {
    this.email.value = email;
  }
}

// 产品控制器 - 管理产品列表
class ProductController extends GetxController {
  RxList<Product> products = <Product>[].obs;
  RxBool isLoading = false.obs;

  @override
  void onInit() {
    super.onInit();
    // 模拟从API加载数据
    fetchProducts();
  }

  // 模拟API调用
  Future<void> fetchProducts() async {
    isLoading.value = true;
    // 模拟网络延迟
    await Future.delayed(const Duration(seconds: 1));

    // 模拟数据
    final List<Product> fetchedProducts = [
      Product(id: 1, name: '智能手机', price: 4999, quantity: 10),
      Product(id: 2, name: '笔记本电脑', price: 8999, quantity: 5),
      Product(id: 3, name: '无线耳机', price: 999, quantity: 20),
      Product(id: 4, name: '智能手表', price: 1999, quantity: 15),
      Product(id: 5, name: '平板电脑', price: 3999, quantity: 8),
    ];

    products.value = fetchedProducts;
    isLoading.value = false;
  }

  // 添加产品到购物车
  void addToCart(Product product) {
    final index = products.indexWhere((p) => p.id == product.id);
    if (index != -1 && products[index].quantity > 0) {
      // 减少库存
      products[index].quantity--;
      // 通知购物车控制器
      Get.find<CartController>().addItem(product);
      // 显示提示
      Get.snackbar(
        '已添加到购物车',
        '${product.name} 已添加到您的购物车',
        snackPosition: SnackPosition.BOTTOM,
        duration: const Duration(seconds: 2),
      );
    } else {
      Get.snackbar(
        '无法添加',
        '${product.name} 库存不足',
        snackPosition: SnackPosition.BOTTOM,
        backgroundColor: Colors.red,
        colorText: Colors.white,
      );
    }
  }

  // 刷新产品列表
  void refreshProducts() {
    fetchProducts();
  }
}

// 购物车控制器
class CartController extends GetxController {
  RxList<CartItem> items = <CartItem>[].obs;

  // 计算总价
  double get totalPrice =>
      items.fold(0, (sum, item) => sum + (item.product.price * item.quantity));

  // 计算商品总数
  int get itemCount => items.fold(0, (sum, item) => sum + item.quantity);

  // 添加商品到购物车
  void addItem(Product product) {
    final index = items.indexWhere((item) => item.product.id == product.id);
    if (index != -1) {
      // 如果商品已在购物车中,增加数量
      items[index].quantity++;
    } else {
      // 否则添加新商品
      items.add(CartItem(product: product, quantity: 1));
    }
    update(); // 通知监听器更新
  }

  // 从购物车移除商品
  void removeItem(int productId) {
    final index = items.indexWhere((item) => item.product.id == productId);
    if (index != -1) {
      // 如果数量大于1,减少数量
      if (items[index].quantity > 1) {
        items[index].quantity--;
      } else {
        // 否则移除商品
        items.removeAt(index);
      }
      update();
    }
  }

  // 清空购物车
  void clearCart() {
    items.clear();
    update();
  }
}

// 产品模型
class Product {
  final int id;
  final String name;
  final double price;
  int quantity;

  Product({
    required this.id,
    required this.name,
    required this.price,
    required this.quantity,
  });
}

// 购物车项模型
class CartItem {
  final Product product;
  int quantity;

  CartItem({
    required this.product,
    required this.quantity,
  });
}

// 主页
class HomePage extends StatelessWidget {
  const HomePage({Key? key}) : super(key: key);

  @override
  Widget build(BuildContext context) {
    // 注册控制器 - 懒加载模式
    final CounterController counterController = Get.put(CounterController());
    final ProductController productController = Get.put(ProductController());
    // 注册购物车控制器为单例
    Get.put(CartController(), permanent: true);

    return Scaffold(
      appBar: AppBar(
        title: const Text('GetX 状态管理示例'),
        actions: [
          // 使用Obx观察购物车数量变化
          Obx(() {
            final cartController = Get.find<CartController>();
            return Badge(
              label: Text('${cartController.itemCount}'),
              isLabelVisible: cartController.itemCount > 0,
              child: IconButton(
                icon: const Icon(Icons.shopping_cart),
                onPressed: () {
                  Get.toNamed('/detail');
                },
              ),
            );
          }),
          IconButton(
            icon: const Icon(Icons.settings),
            onPressed: () {
              Get.toNamed('/settings');
            },
          ),
        ],
      ),
      drawer: _buildDrawer(),
      body: Column(
        children: [
          // 计数器部分
          Card(
            margin: const EdgeInsets.all(16),
            child: Padding(
              padding: const EdgeInsets.all(16),
              child: Column(
                children: [
                  const Text(
                    '计数器示例',
                    style: TextStyle(
                      fontSize: 18,
                      fontWeight: FontWeight.bold,
                    ),
                  ),
                  const SizedBox(height: 16),
                  // 使用Obx观察计数变化
                  Obx(() => Text(
                        '当前计数: ${counterController.count.value}',
                        style: const TextStyle(fontSize: 24),
                      )),
                  const SizedBox(height: 16),
                  Row(
                    mainAxisAlignment: MainAxisAlignment.center,
                    children: [
                      ElevatedButton(
                        onPressed: counterController.decrement,
                        child: const Icon(Icons.remove),
                      ),
                      const SizedBox(width: 16),
                      ElevatedButton(
                        onPressed: counterController.increment,
                        child: const Icon(Icons.add),
                      ),
                      const SizedBox(width: 16),
                      ElevatedButton(
                        onPressed: counterController.reset,
                        child: const Icon(Icons.refresh),
                      ),
                    ],
                  ),
                ],
              ),
            ),
          ),

          // 产品列表部分
          Expanded(
            child: Obx(() {
              if (productController.isLoading.value) {
                return const Center(child: CircularProgressIndicator());
              }

              return RefreshIndicator(
                onRefresh: () => productController.fetchProducts(),
                child: ListView.builder(
                  padding: const EdgeInsets.all(16),
                  itemCount: productController.products.length,
                  itemBuilder: (context, index) {
                    final product = productController.products[index];
                    return Card(
                      margin: const EdgeInsets.only(bottom: 16),
                      child: ListTile(
                        title: Text(product.name),
                        subtitle:
                            Text('价格: ¥${product.price.toStringAsFixed(2)}'),
                        trailing: Row(
                          mainAxisSize: MainAxisSize.min,
                          children: [
                            Text('库存: ${product.quantity}'),
                            const SizedBox(width: 16),
                            IconButton(
                              icon: const Icon(Icons.add_shopping_cart),
                              onPressed: () =>
                                  productController.addToCart(product),
                            ),
                          ],
                        ),
                      ),
                    );
                  },
                ),
              );
            }),
          ),
        ],
      ),
    );
  }

  // 构建抽屉菜单
  Widget _buildDrawer() {
    final CounterController counterController = Get.find<CounterController>();

    return Drawer(
      child: ListView(
        padding: EdgeInsets.zero,
        children: [
          // 抽屉头部
          Obx(() => UserAccountsDrawerHeader(
                accountName: Text(counterController.userName.value),
                accountEmail: const Text('user@example.com'),
                currentAccountPicture: const CircleAvatar(
                  child: Icon(Icons.person, size: 50),
                ),
                decoration: BoxDecoration(
                  color: counterController.isDarkMode.value
                      ? Colors.grey[800]
                      : Colors.blue,
                ),
              )),

          // 抽屉菜单项
          ListTile(
            leading: const Icon(Icons.home),
            title: const Text('首页'),
            onTap: () {
              Get.back();
            },
          ),
          ListTile(
            leading: const Icon(Icons.shopping_cart),
            title: const Text('购物车'),
            onTap: () {
              Get.back();
              Get.toNamed('/detail');
            },
          ),
          ListTile(
            leading: const Icon(Icons.settings),
            title: const Text('设置'),
            onTap: () {
              Get.back();
              Get.toNamed('/settings');
            },
          ),
          ListTile(
            leading: const Icon(Icons.person),
            title: const Text('用户资料'),
            onTap: () {
              Get.back();
              Get.toNamed('/user');
            },
          ),
          const Divider(),
          // 主题切换
          Obx(() => ListTile(
                leading: Icon(
                  counterController.isDarkMode.value
                      ? Icons.light_mode
                      : Icons.dark_mode,
                ),
                title: Text(
                  counterController.isDarkMode.value ? '切换到亮色模式' : '切换到暗色模式',
                ),
                onTap: () {
                  counterController.toggleTheme();
                  Get.back();
                },
              )),
        ],
      ),
    );
  }
}

// 详情页 - 购物车
class DetailPage extends StatelessWidget {
  const DetailPage({Key? key}) : super(key: key);

  @override
  Widget build(BuildContext context) {
    // 获取购物车控制器
    final CartController cartController = Get.find<CartController>();

    return Scaffold(
      appBar: AppBar(
        title: const Text('购物车'),
      ),
      body: Column(
        children: [
          Expanded(
            // 使用GetBuilder监听CartController的更新
            child: GetBuilder<CartController>(
              builder: (controller) {
                if (controller.items.isEmpty) {
                  return const Center(
                    child: Text(
                      '购物车为空',
                      style: TextStyle(fontSize: 18),
                    ),
                  );
                }

                return ListView.builder(
                  padding: const EdgeInsets.all(16),
                  itemCount: controller.items.length,
                  itemBuilder: (context, index) {
                    final item = controller.items[index];
                    return Card(
                      margin: const EdgeInsets.only(bottom: 16),
                      child: ListTile(
                        title: Text(item.product.name),
                        subtitle: Text(
                          '单价: ¥${item.product.price.toStringAsFixed(2)} × ${item.quantity}',
                        ),
                        trailing: Row(
                          mainAxisSize: MainAxisSize.min,
                          children: [
                            Text(
${(item.product.price * item.quantity).toStringAsFixed(2)}',
                              style:
                                  const TextStyle(fontWeight: FontWeight.bold),
                            ),
                            const SizedBox(width: 16),
                            IconButton(
                              icon: const Icon(Icons.remove_circle_outline),
                              onPressed: () =>
                                  controller.removeItem(item.product.id),
                            ),
                          ],
                        ),
                      ),
                    );
                  },
                );
              },
            ),
          ),

          // 底部结算栏
          Container(
            padding: const EdgeInsets.all(16),
            decoration: BoxDecoration(
              color: Colors.white,
              boxShadow: [
                BoxShadow(
                  color: Colors.grey.withOpacity(0.3),
                  blurRadius: 5,
                  offset: const Offset(0, -3),
                ),
              ],
            ),
            child: Row(
              mainAxisAlignment: MainAxisAlignment.spaceBetween,
              children: [
                // 使用Obx观察总价变化
                Obx(() => Text(
                      '总计: ¥${cartController.totalPrice.toStringAsFixed(2)}',
                      style: const TextStyle(
                        fontSize: 18,
                        fontWeight: FontWeight.bold,
                      ),
                    )),
                Row(
                  children: [
                    OutlinedButton(
                      onPressed: () {
                        cartController.clearCart();
                      },
                      child: const Text('清空购物车'),
                    ),
                    const SizedBox(width: 16),
                    ElevatedButton(
                      onPressed: () {
                        if (cartController.items.isNotEmpty) {
                          Get.snackbar(
                            '订单已提交',
                            '您的订单已成功提交',
                            snackPosition: SnackPosition.BOTTOM,
                            backgroundColor: Colors.green,
                            colorText: Colors.white,
                          );
                          cartController.clearCart();
                        } else {
                          Get.snackbar(
                            '无法提交',
                            '购物车为空',
                            snackPosition: SnackPosition.BOTTOM,
                            backgroundColor: Colors.red,
                            colorText: Colors.white,
                          );
                        }
                      },
                      child: const Text('结算'),
                    ),
                  ],
                ),
              ],
            ),
          ),
        ],
      ),
    );
  }
}

// 设置页面
class SettingsPage extends StatelessWidget {
  const SettingsPage({Key? key}) : super(key: key);

  @override
  Widget build(BuildContext context) {
    final CounterController counterController = Get.find<CounterController>();

    return Scaffold(
      appBar: AppBar(
        title: const Text('设置'),
      ),
      body: ListView(
        padding: const EdgeInsets.all(16),
        children: [
          Card(
            child: Padding(
              padding: const EdgeInsets.all(16),
              child: Column(
                crossAxisAlignment: CrossAxisAlignment.start,
                children: [
                  const Text(
                    '主题设置',
                    style: TextStyle(
                      fontSize: 18,
                      fontWeight: FontWeight.bold,
                    ),
                  ),
                  const SizedBox(height: 16),
                  // 使用Obx观察主题变化
                  Obx(() => SwitchListTile(
                        title: const Text('暗色模式'),
                        subtitle: const Text('切换应用主题'),
                        value: counterController.isDarkMode.value,
                        onChanged: (value) {
                          counterController.toggleTheme();
                        },
                      )),
                ],
              ),
            ),
          ),
          const SizedBox(height: 16),
          Card(
            child: Padding(
              padding: const EdgeInsets.all(16),
              child: Column(
                crossAxisAlignment: CrossAxisAlignment.start,
                children: [
                  const Text(
                    '通知设置',
                    style: TextStyle(
                      fontSize: 18,
                      fontWeight: FontWeight.bold,
                    ),
                  ),
                  const SizedBox(height: 16),
                  SwitchListTile(
                    title: const Text('推送通知'),
                    subtitle: const Text('接收应用推送通知'),
                    value: true,
                    onChanged: (value) {
                      Get.snackbar(
                        '设置已更新',
                        '推送通知设置已更改',
                        snackPosition: SnackPosition.BOTTOM,
                      );
                    },
                  ),
                  SwitchListTile(
                    title: const Text('电子邮件通知'),
                    subtitle: const Text('接收电子邮件通知'),
                    value: false,
                    onChanged: (value) {
                      Get.snackbar(
                        '设置已更新',
                        '电子邮件通知设置已更改',
                        snackPosition: SnackPosition.BOTTOM,
                      );
                    },
                  ),
                ],
              ),
            ),
          ),
          const SizedBox(height: 16),
          Card(
            child: Padding(
              padding: const EdgeInsets.all(16),
              child: Column(
                crossAxisAlignment: CrossAxisAlignment.start,
                children: [
                  const Text(
                    '关于',
                    style: TextStyle(
                      fontSize: 18,
                      fontWeight: FontWeight.bold,
                    ),
                  ),
                  const SizedBox(height: 16),
                  const ListTile(
                    title: Text('应用版本'),
                    subtitle: Text('1.0.0'),
                    trailing: Icon(Icons.info_outline),
                  ),
                  ListTile(
                    title: const Text('检查更新'),
                    trailing: const Icon(Icons.update),
                    onTap: () {
                      Get.dialog(
                        AlertDialog(
                          title: const Text('检查更新'),
                          content: const Text('您的应用已是最新版本'),
                          actions: [
                            TextButton(
                              onPressed: () => Get.back(),
                              child: const Text('确定'),
                            ),
                          ],
                        ),
                      );
                    },
                  ),
                  ListTile(
                    title: const Text('隐私政策'),
                    trailing: const Icon(Icons.privacy_tip_outlined),
                    onTap: () {
                      Get.dialog(
                        AlertDialog(
                          title: const Text('隐私政策'),
                          content: const SingleChildScrollView(
                            child: Text(
                              '这是一个示例隐私政策,实际应用中应包含完整的隐私条款。\n\n'
                              '我们重视您的隐私,并致力于保护您的个人信息。本隐私政策描述了我们如何收集、使用和共享您的信息。',
                            ),
                          ),
                          actions: [
                            TextButton(
                              onPressed: () => Get.back(),
                              child: const Text('关闭'),
                            ),
                          ],
                        ),
                      );
                    },
                  ),
                ],
              ),
            ),
          ),
        ],
      ),
    );
  }
}

// 用户资料页面
class UserProfilePage extends StatelessWidget {
  const UserProfilePage({Key? key}) : super(key: key);

  @override
  Widget build(BuildContext context) {
    final CounterController counterController = Get.find<CounterController>();
    final TextEditingController textController = TextEditingController();
    final TextEditingController emailController = TextEditingController();
    final TextEditingController phoneController = TextEditingController();

    // 初始化文本控制器
    textController.text = counterController.userName.value;
    emailController.text = counterController.email.value;

    return Scaffold(
      appBar: AppBar(
        title: const Text('用户资料'),
      ),
      body: ListView(
        padding: const EdgeInsets.all(16),
        children: [
          // 用户头像
          Center(
            child: Column(
              children: [
                const CircleAvatar(
                  radius: 50,
                  child: Icon(Icons.person, size: 50),
                ),
                const SizedBox(height: 16),
                // 使用Obx观察用户名变化
                Obx(() => Text(
                      counterController.userName.value,
                      style: const TextStyle(
                        fontSize: 24,
                        fontWeight: FontWeight.bold,
                      ),
                    )),
                const Text(
                  'user@example.com',
                  style: TextStyle(
                    color: Colors.grey,
                  ),
                ),
              ],
            ),
          ),

          const SizedBox(height: 32),

          // 用户信息表单
          Card(
            child: Padding(
              padding: const EdgeInsets.all(16),
              child: Column(
                crossAxisAlignment: CrossAxisAlignment.start,
                children: [
                  const Text(
                    '个人信息',
                    style: TextStyle(
                      fontSize: 18,
                      fontWeight: FontWeight.bold,
                    ),
                  ),
                  const SizedBox(height: 16),
                  TextField(
                    controller: textController,
                    decoration: const InputDecoration(
                      labelText: '用户名',
                      border: OutlineInputBorder(),
                      prefixIcon: Icon(Icons.person_outline),
                    ),
                  ),
                  const SizedBox(height: 16),
                   TextField(
                    decoration: InputDecoration(
                      labelText: '电子邮件',
                      border: OutlineInputBorder(),
                      prefixIcon: Icon(Icons.email_outlined),
                    ),
                    controller: emailController,
                    // initialValue: 'user@example.com',
                    keyboardType: TextInputType.emailAddress,
                  ),
                  const SizedBox(height: 16),
                   TextField(
                    decoration: InputDecoration(
                      labelText: '电话号码',
                      border: OutlineInputBorder(),
                      prefixIcon: Icon(Icons.phone_outlined),
                    ),
                    keyboardType: TextInputType.phone,
                     controller: phoneController,
                  ),
                  const SizedBox(height: 24),
                  SizedBox(
                    width: double.infinity,
                    child: ElevatedButton(
                      onPressed: () {
                        // 更新用户名
                        counterController.updateUserName(textController.text);
                        counterController.updateEmail(emailController.text);
                        Get.snackbar(
                          '资料已更新',
                          '您的个人资料已成功更新',
                          snackPosition: SnackPosition.BOTTOM,
                          backgroundColor: Colors.green,
                          colorText: Colors.white,
                        );
                      },
                      child: const Padding(
                        padding: EdgeInsets.symmetric(vertical: 12),
                        child: Text(
                          '保存更改',
                          style: TextStyle(fontSize: 16),
                        ),
                      ),
                    ),
                  ),
                ],
              ),
            ),
          ),
        ],
      ),
    );
  }
}

路由

diglog

dart
import 'package:flutter/material.dart';
import 'package:get/get.dart';

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

class MyApp extends StatelessWidget {
  const MyApp({Key? key}) : super(key: key);

  @override
  Widget build(BuildContext context) {
    return GetMaterialApp(
      title: 'GetX 对话框和提示消息示例',
      debugShowCheckedModeBanner: false,
      theme: ThemeData(
        primarySwatch: Colors.blue,
        useMaterial3: true,
      ),
      home: const DialogSnackbarDemo(),
    );
  }
}

class DialogSnackbarDemo extends StatelessWidget {
  const DialogSnackbarDemo({Key? key}) : super(key: key);

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: const Text('GetX 对话框和提示消息'),
        backgroundColor: Colors.blue,
        foregroundColor: Colors.white,
      ),
      body: SingleChildScrollView(
        padding: const EdgeInsets.all(16),
        child: Column(
          crossAxisAlignment: CrossAxisAlignment.stretch,
          children: [
            // 对话框部分
            _buildSectionTitle('对话框示例 (Get.dialog)'),
            const SizedBox(height: 16),

            // 基本对话框
            _buildDemoCard(
              title: '基本对话框',
              description: '显示一个简单的确认对话框',
              buttonText: '显示对话框',
              onPressed: _showBasicDialog,
            ),

            // 自定义对话框
            _buildDemoCard(
              title: '自定义对话框',
              description: '显示一个带有自定义内容和样式的对话框',
              buttonText: '显示自定义对话框',
              onPressed: _showCustomDialog,
            ),

            // 全屏对话框
            _buildDemoCard(
              title: '全屏对话框',
              description: '显示一个占据整个屏幕的对话框',
              buttonText: '显示全屏对话框',
              onPressed: _showFullScreenDialog,
            ),

            // 表单对话框
            _buildDemoCard(
              title: '表单对话框',
              description: '显示一个包含表单的对话框',
              buttonText: '显示表单对话框',
              onPressed: _showFormDialog,
            ),

            // 底部弹出对话框
            _buildDemoCard(
              title: '底部弹出对话框',
              description: '从底部弹出的对话框',
              buttonText: '显示底部对话框',
              onPressed: _showBottomDialog,
            ),

            const SizedBox(height: 32),

            // Snackbar部分
            _buildSectionTitle('提示消息示例 (Get.snackbar)'),
            const SizedBox(height: 16),

            // 基本Snackbar
            _buildDemoCard(
              title: '基本提示消息',
              description: '显示一个简单的提示消息',
              buttonText: '显示提示消息',
              onPressed: _showBasicSnackbar,
            ),

            // 不同位置的Snackbar
            _buildDemoCard(
              title: '不同位置的提示消息',
              description: '在屏幕顶部或底部显示提示消息',
              buttonText: '显示不同位置的提示消息',
              onPressed: _showPositionedSnackbar,
            ),

            // 自定义样式的Snackbar
            _buildDemoCard(
              title: '自定义样式的提示消息',
              description: '自定义提示消息的颜色、图标和按钮',
              buttonText: '显示自定义提示消息',
              onPressed: _showCustomSnackbar,
            ),

            // 持续时间不同的Snackbar
            _buildDemoCard(
              title: '不同持续时间的提示消息',
              description: '设置提示消息的显示时长',
              buttonText: '显示长时间提示消息',
              onPressed: _showLongDurationSnackbar,
            ),

            // 带进度条的Snackbar
            _buildDemoCard(
              title: '带进度条的提示消息',
              description: '显示一个带有进度指示的提示消息',
              buttonText: '显示进度提示消息',
              onPressed: _showProgressSnackbar,
            ),
          ],
        ),
      ),
    );
  }

  // 构建章节标题
  Widget _buildSectionTitle(String title) {
    return Container(
      padding: const EdgeInsets.symmetric(vertical: 8, horizontal: 16),
      decoration: BoxDecoration(
        color: Colors.blue.withOpacity(0.1),
        borderRadius: BorderRadius.circular(8),
        border: Border.all(color: Colors.blue.withOpacity(0.3)),
      ),
      child: Text(
        title,
        style: const TextStyle(
          fontSize: 20,
          fontWeight: FontWeight.bold,
          color: Colors.blue,
        ),
      ),
    );
  }

  // 构建演示卡片
  Widget _buildDemoCard({
    required String title,
    required String description,
    required String buttonText,
    required VoidCallback onPressed,
  }) {
    return Card(
      margin: const EdgeInsets.only(bottom: 16),
      elevation: 2,
      child: Padding(
        padding: const EdgeInsets.all(16),
        child: Column(
          crossAxisAlignment: CrossAxisAlignment.start,
          children: [
            Text(
              title,
              style: const TextStyle(
                fontSize: 18,
                fontWeight: FontWeight.bold,
              ),
            ),
            const SizedBox(height: 8),
            Text(
              description,
              style: TextStyle(
                color: Colors.grey[700],
              ),
            ),
            const SizedBox(height: 16),
            SizedBox(
              width: double.infinity,
              child: ElevatedButton(
                onPressed: onPressed,
                style: ElevatedButton.styleFrom(
                  padding: const EdgeInsets.symmetric(vertical: 12),
                ),
                child: Text(buttonText),
              ),
            ),
          ],
        ),
      ),
    );
  }

  // ===== 对话框示例方法 =====

  // 基本对话框
  void _showBasicDialog() {
    Get.dialog(
      AlertDialog(
        title: const Text('确认操作'),
        content: const Text('您确定要执行此操作吗?'),
        actions: [
          TextButton(
            onPressed: () => Get.back(),
            child: const Text('取消'),
          ),
          TextButton(
            onPressed: () {
              Get.back();
              Get.snackbar('操作成功', '您已确认操作');
            },
            child: const Text('确认'),
          ),
        ],
      ),
      barrierDismissible: true, // 点击背景可关闭对话框
    );
  }

  // 自定义对话框
  void _showCustomDialog() {
    Get.dialog(
      Dialog(
        shape: RoundedRectangleBorder(
          borderRadius: BorderRadius.circular(16),
        ),
        elevation: 0,
        backgroundColor: Colors.transparent,
        child: Container(
          padding: const EdgeInsets.all(20),
          decoration: BoxDecoration(
            color: Colors.white,
            shape: BoxShape.rectangle,
            borderRadius: BorderRadius.circular(16),
            boxShadow: const [
              BoxShadow(
                color: Colors.black26,
                blurRadius: 10.0,
                offset: Offset(0.0, 10.0),
              ),
            ],
          ),
          child: Column(
            mainAxisSize: MainAxisSize.min,
            children: [
              Container(
                padding: const EdgeInsets.all(16),
                decoration: const BoxDecoration(
                  color: Colors.blue,
                  shape: BoxShape.circle,
                ),
                child: const Icon(
                  Icons.info_outline,
                  color: Colors.white,
                  size: 40,
                ),
              ),
              const SizedBox(height: 24),
              const Text(
                '自定义对话框',
                style: TextStyle(
                  fontSize: 22,
                  fontWeight: FontWeight.bold,
                ),
              ),
              const SizedBox(height: 16),
              const Text(
                '这是一个使用自定义样式的对话框示例,您可以根据需要自定义对话框的外观和内容。',
                textAlign: TextAlign.center,
                style: TextStyle(fontSize: 16),
              ),
              const SizedBox(height: 24),
              Row(
                mainAxisAlignment: MainAxisAlignment.center,
                children: [
                  ElevatedButton(
                    onPressed: () => Get.back(),
                    style: ElevatedButton.styleFrom(
                      backgroundColor: Colors.blue,
                      foregroundColor: Colors.white,
                      shape: RoundedRectangleBorder(
                        borderRadius: BorderRadius.circular(30),
                      ),
                      padding: const EdgeInsets.symmetric(
                        horizontal: 30,
                        vertical: 12,
                      ),
                    ),
                    child: const Text('确定'),
                  ),
                ],
              ),
            ],
          ),
        ),
      ),
    );
  }

  // 全屏对话框
  void _showFullScreenDialog() {
    Get.dialog(
      Dialog.fullscreen(
        child: Scaffold(
          appBar: AppBar(
            title: const Text('全屏对话框'),
            leading: IconButton(
              icon: const Icon(Icons.close),
              onPressed: () => Get.back(),
            ),
          ),
          body: Center(
            child: Column(
              mainAxisAlignment: MainAxisAlignment.center,
              children: [
                const Icon(
                  Icons.fullscreen,
                  size: 80,
                  color: Colors.blue,
                ),
                const SizedBox(height: 24),
                const Text(
                  '这是一个全屏对话框',
                  style: TextStyle(
                    fontSize: 24,
                    fontWeight: FontWeight.bold,
                  ),
                ),
                const SizedBox(height: 16),
                const Padding(
                  padding: EdgeInsets.symmetric(horizontal: 32),
                  child: Text(
                    '全屏对话框适用于需要显示大量内容或复杂交互的场景',
                    textAlign: TextAlign.center,
                    style: TextStyle(fontSize: 16),
                  ),
                ),
                const SizedBox(height: 40),
                ElevatedButton(
                  onPressed: () => Get.back(),
                  style: ElevatedButton.styleFrom(
                    padding: const EdgeInsets.symmetric(
                      horizontal: 32,
                      vertical: 16,
                    ),
                  ),
                  child: const Text('关闭对话框'),
                ),
              ],
            ),
          ),
        ),
      ),
    );
  }

  // 表单对话框
  void _showFormDialog() {
    final TextEditingController nameController = TextEditingController();
    final TextEditingController emailController = TextEditingController();

    Get.dialog(
      AlertDialog(
        title: const Text('用户信息'),
        content: SingleChildScrollView(
          child: Column(
            mainAxisSize: MainAxisSize.min,
            children: [
              TextField(
                controller: nameController,
                decoration: const InputDecoration(
                  labelText: '姓名',
                  prefixIcon: Icon(Icons.person),
                ),
              ),
              const SizedBox(height: 16),
              TextField(
                controller: emailController,
                decoration: const InputDecoration(
                  labelText: '电子邮件',
                  prefixIcon: Icon(Icons.email),
                ),
                keyboardType: TextInputType.emailAddress,
              ),
            ],
          ),
        ),
        actions: [
          TextButton(
            onPressed: () => Get.back(),
            child: const Text('取消'),
          ),
          ElevatedButton(
            onPressed: () {
              if (nameController.text.isEmpty || emailController.text.isEmpty) {
                Get.snackbar(
                  '错误',
                  '请填写所有字段',
                  backgroundColor: Colors.red,
                  colorText: Colors.white,
                );
              } else {
                Get.back();
                Get.snackbar(
                  '提交成功',
                  '姓名: ${nameController.text}\n电子邮件: ${emailController.text}',
                  backgroundColor: Colors.green,
                  colorText: Colors.white,
                );
              }
            },
            child: const Text('提交'),
          ),
        ],
      ),
    );
  }

  // 底部弹出对话框
  void _showBottomDialog() {
    Get.bottomSheet(
      Container(
        padding: const EdgeInsets.all(20),
        decoration: const BoxDecoration(
          color: Colors.white,
          borderRadius: BorderRadius.only(
            topLeft: Radius.circular(20),
            topRight: Radius.circular(20),
          ),
        ),
        child: Column(
          mainAxisSize: MainAxisSize.min,
          children: [
            Container(
              width: 40,
              height: 5,
              margin: const EdgeInsets.only(bottom: 20),
              decoration: BoxDecoration(
                color: Colors.grey[300],
                borderRadius: BorderRadius.circular(10),
              ),
            ),
            const Text(
              '底部弹出对话框',
              style: TextStyle(
                fontSize: 20,
                fontWeight: FontWeight.bold,
              ),
            ),
            const SizedBox(height: 16),
            const Text(
              '这是一个从底部弹出的对话框,通常用于显示操作菜单或简单表单。',
              textAlign: TextAlign.center,
            ),
            const SizedBox(height: 24),
            ListTile(
              leading: const Icon(Icons.share),
              title: const Text('分享'),
              onTap: () {
                Get.back();
                Get.snackbar('操作', '您点击了分享');
              },
            ),
            ListTile(
              leading: const Icon(Icons.link),
              title: const Text('复制链接'),
              onTap: () {
                Get.back();
                Get.snackbar('操作', '您点击了复制链接');
              },
            ),
            ListTile(
              leading: const Icon(Icons.edit),
              title: const Text('编辑'),
              onTap: () {
                Get.back();
                Get.snackbar('操作', '您点击了编辑');
              },
            ),
            const SizedBox(height: 16),
            SizedBox(
              width: double.infinity,
              child: ElevatedButton(
                onPressed: () => Get.back(),
                style: ElevatedButton.styleFrom(
                  backgroundColor: Colors.red,
                  foregroundColor: Colors.white,
                  padding: const EdgeInsets.symmetric(vertical: 12),
                ),
                child: const Text('关闭'),
              ),
            ),
          ],
        ),
      ),
      isDismissible: true,
      enableDrag: true,
    );
  }

  // ===== Snackbar示例方法 =====

  // 基本Snackbar
  void _showBasicSnackbar() {
    Get.snackbar(
      '提示',
      '这是一个基本的提示消息',
      snackPosition: SnackPosition.BOTTOM,
    );
  }

  // 不同位置的Snackbar
  void _showPositionedSnackbar() {
    // 先显示顶部Snackbar
    Get.snackbar(
      '顶部提示',
      '这个提示消息显示在屏幕顶部',
      snackPosition: SnackPosition.TOP,
    );

    // 延迟1秒后显示底部Snackbar
    Future.delayed(const Duration(seconds: 1), () {
      Get.snackbar(
        '底部提示',
        '这个提示消息显示在屏幕底部',
        snackPosition: SnackPosition.BOTTOM,
      );
    });
  }

  // 自定义样式的Snackbar
  void _showCustomSnackbar() {
    Get.snackbar(
      '自定义提示',
      '这是一个自定义样式的提示消息',
      backgroundColor: Colors.purple,
      colorText: Colors.white,
      borderRadius: 10,
      margin: const EdgeInsets.all(15),
      padding: const EdgeInsets.symmetric(horizontal: 20, vertical: 15),
      icon: const Icon(
        Icons.notifications,
        color: Colors.white,
      ),
      mainButton: TextButton(
        onPressed: () {
          Get.closeCurrentSnackbar();
        },
        child: const Text(
          '关闭',
          style: TextStyle(color: Colors.white),
        ),
      ),
      snackPosition: SnackPosition.BOTTOM,
    );
  }

  // 持续时间不同的Snackbar
  void _showLongDurationSnackbar() {
    Get.snackbar(
      '长时间提示',
      '这个提示消息将显示5秒钟',
      duration: const Duration(seconds: 5),
      snackPosition: SnackPosition.BOTTOM,
      backgroundColor: Colors.amber[700],
      colorText: Colors.white,
      icon: const Icon(
        Icons.timer,
        color: Colors.white,
      ),
    );
  }

  // 带进度条的Snackbar
  void _showProgressSnackbar() {
    // 创建一个进度控制器
    final RxDouble progress = 0.0.obs;

    // 显示带进度条的Snackbar
    Get.snackbar(
      '下载中',
      '正在下载文件...',
      isDismissible: false,
      duration: const Duration(seconds: 10),
      backgroundColor: Colors.blue,
      colorText: Colors.white,
      icon: const Icon(
        Icons.download,
        color: Colors.white,
      ),
      mainButton: TextButton(
        onPressed: () {
          Get.closeCurrentSnackbar();
        },
        child: const Text(
          '取消',
          style: TextStyle(color: Colors.white),
        ),
      ),
      snackPosition: SnackPosition.BOTTOM,
      // 使用Obx观察进度变化
      messageText: Obx(() => Column(
            crossAxisAlignment: CrossAxisAlignment.start,
            children: [
              Text(
                '正在下载文件... ${(progress.value * 100).toInt()}%',
                style: const TextStyle(color: Colors.white),
              ),
              const SizedBox(height: 8),
              LinearProgressIndicator(
                value: progress.value,
                backgroundColor: Colors.white.withOpacity(0.3),
                valueColor: const AlwaysStoppedAnimation<Color>(Colors.white),
              ),
            ],
          )),
    );

    // 模拟下载进度
    for (int i = 1; i <= 10; i++) {
      Future.delayed(Duration(milliseconds: i * 300), () {
        progress.value = i / 10;
        if (i == 10) {
          Get.closeCurrentSnackbar();
          Get.snackbar(
            '下载完成',
            '文件已成功下载',
            backgroundColor: Colors.green,
            colorText: Colors.white,
            icon: const Icon(
              Icons.check_circle,
              color: Colors.white,
            ),
            snackPosition: SnackPosition.BOTTOM,
          );
        }
      });
    }
  }
}

3.1 第一个flutter应用-计数器

使用flutter创建项目后,就是这个样子

image-20250213110438461

导入包

dart
import 'package:flutter/material.dart';

此行代码作用是导入了 Material UI 组件库。Material (opens new window)是一种标准的移动端和web端的视觉设计语言, Flutter 默认提供了一套丰富的 Material 风格的UI组件。

应用入口

dart
void main() => runApp(MyApp());
  • 与 C/C++、Java 类似,Flutter 应用中 main 函数为应用程序的入口。main 函数中调用了runApp 方法,它的功能是启动Flutter应用。runApp它接受一个 Widget参数,在本示例中它是一个MyApp对象,MyApp()是 Flutter 应用的根组件。

    读者现在只需知道 runApp 是 Flutter 应用的入口即可,关于 Flutter 应用的启动流程,我们会在本书后面原理篇中做详细介绍。

  • main函数使用了(=>)符号,这是 Dart 中单行函数或方法的简写。

应用结构

dart
class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      //应用名称  
      title: 'Flutter Demo', 
      theme: ThemeData(
        //蓝色主题  
        primarySwatch: Colors.blue,
      ),
      //应用首页路由  
      home: MyHomePage(title: 'Flutter Demo Home Page'),
    );
  }
}
  • MyApp类代表 Flutter 应用,它继承了 StatelessWidget类,这也就意味着应用本身也是一个widget。
  • 在 Flutter 中,大多数东西都是 widget(后同“组件”或“部件”),包括对齐(Align)、填充(Padding)、手势处理(GestureDetector)等,它们都是以 widget 的形式提供。
  • Flutter 在构建页面时,会调用组件的build方法,widget 的主要工作是提供一个 build() 方法来描述如何构建 UI 界面(通常是通过组合、拼装其他基础 widget )。
  • MaterialApp 是Material 库中提供的 Flutter APP 框架,通过它可以设置应用的名称、主题、语言、首页及路由列表等。MaterialApp也是一个 widget。
  • home 为 Flutter 应用的首页,它也是一个 widget。

初识widget

dart
class MyHomePage extends StatefulWidget {
  MyHomePage({Key? key, required this.title}) : super(key: key);
  final String title;
  
  @override
  _MyHomePageState createState() => _MyHomePageState();
}

class _MyHomePageState extends State<MyHomePage> {
 ...
}

MyHomePage 是应用的首页,它继承自StatefulWidget类,表示它是一个有状态的组件(Stateful widget)。现在我们只需简单认为有状态的组件(Stateful widget) 和无状态的组件(Stateless widget)有两点不同:

  1. Stateful widget 可以拥有状态,这些状态在 widget 生命周期中是可以变的,而 Stateless widget 是不可变的。

  2. Stateful widget 至少由两个类组成:

    • 一个StatefulWidget类。
    • 一个 State类; StatefulWidget类本身是不变的,但是State类中持有的状态在 widget 生命周期中可能会发生变化。

    _MyHomePageState类是MyHomePage类对应的状态类。

State类

_MyHomePageState 类解析

接下来,我们看看_MyHomePageState中都包含哪些东西:

  • 组件的状态。

    由于我们只需要维护一个点击次数计数器,所以定义一个_counter状态:

  • dart
    int _counter = 0; //用于记录按钮点击的总次数

    _counter 为保存屏幕右下角带“+”号按钮点击次数的状态。

  • 设置状态的自增函数。

    dart
    void _incrementCounter() {
      setState(() {
         _counter++;
      });
    }
  • 当按钮点击时,会调用此函数,该函数的作用是先自增_counter,然后调用setState 方法。setState方法的作用是通知 Flutter 框架,有状态发生了改变,Flutter 框架收到通知后,会执行 build 方法来根据新的状态重新构建界面, Flutter 对此方法做了优化,使重新执行变的很快,所以你可以重新构建任何需要更新的东西,而无需分别去修改各个 widget。

  • 构建UI界面的build方法

    构建UI界面的逻辑在 build 方法中,当MyHomePage第一次创建时,_MyHomePageState类会被创建,当初始化完成后,Flutter框架会调用 widget 的build方法来构建 widget 树,最终将 widget 树渲染到设备屏幕上。所以,我们看看_MyHomePageStatebuild方法中都干了什么事:

    dart
    Widget build(BuildContext context) {
      return Scaffold(
        appBar: AppBar(
          title: Text(widget.title),
        ),
        body: Center(
          child: Column(
            mainAxisAlignment: MainAxisAlignment.center,
            children: <Widget>[
              Text('You have pushed the button this many times:'),
              Text(
                '$_counter',
                style: Theme.of(context).textTheme.headline4,
              ),
            ],
          ),
        ),
        floatingActionButton: FloatingActionButton(
          onPressed: _incrementCounter,
          tooltip: 'Increment',
          child: Icon(Icons.add),
        ), 
      );
    }
    • Scaffold 是 Material 库中提供的页面脚手架,它提供了默认的导航栏、标题和包含主屏幕 widget 树(后同“组件树”或“部件树”)的body属性,组件树可以很复杂。本书后面示例中,路由默认都是通过Scaffold创建。
    • body的组件树中包含了一个Center 组件,Center 可以将其子组件树对齐到屏幕中心。此例中, Center 子组件是一个Column 组件,Column的作用是将其所有子组件沿屏幕垂直方向依次排列; 此例中Column子组件是两个 Text,第一个Text 显示固定文本 “You have pushed the button this many times:”,第二个Text 显示_counter状态的数值。
    • floatingActionButton是页面右下角的带“+”的悬浮按钮,它的onPressed属性接受一个回调函数,代表它被点击后的处理器,本例中直接将_incrementCounter方法作为其处理函数。

现在,我们将整个计数器执行流程串起来:当右下角的floatingActionButton按钮被点击之后,会调用_incrementCounter方法。在_incrementCounter方法中,首先会自增_counter计数器(状态),然后setState会通知 Flutter 框架状态发生变化,接着,Flutter 框架会调用build方法以新的状态重新构建UI,最终显示在设备屏幕上。

为什么要将 build 方法放在 State 中,而不是放在StatefulWidget中?

  1. 状态访问不便。

试想一下,如果我们的StatefulWidget有很多状态,而每次状态改变都要调用build方法,由于状态是保存在 State 中的,如果build方法在StatefulWidget中,那么build方法和状态分别在两个类中,那么构建时读取状态将会很不方便!试想一下,如果真的将build方法放在 StatefulWidget 中的话,由于构建用户界面过程需要依赖 State,所以build方法将必须加一个State参数,大概是下面这样:

dart
  Widget build(BuildContext context, State state){
      //state.counter
      ...
  }

这样的话就只能将State的所有状态声明为公开的状态,这样才能在State类外部访问状态!但是,将状态设置为公开后,状态将不再具有私密性,这就会导致对状态的修改将会变的不可控。但如果将build()方法放在State中的话,构建过程不仅可以直接访问状态,而且也无需公开私有状态,这会非常方便。

3.2 入门

widget接口

在 Flutter 中, widget 的功能是“描述一个UI元素的配置信息”,它就是说, Widget 其实并不是表示最终绘制在设备屏幕上的显示元素,所谓的配置信息就是 Widget 接收的参数,比如对于 Text 来讲,文本的内容、对齐方式、文本样式都是它的配置信息。下面我们先来看一下 Widget 类的声明:

dart
@immutable // 不可变的
abstract class Widget extends DiagnosticableTree {
  const Widget({ this.key });

  final Key? key;

  @protected
  @factory
  Element createElement();

  @override
  String toStringShort() {
    final String type = objectRuntimeType(this, 'Widget');
    return key == null ? type : '$type-$key';
  }

  @override
  void debugFillProperties(DiagnosticPropertiesBuilder properties) {
    super.debugFillProperties(properties);
    properties.defaultDiagnosticsTreeStyle = DiagnosticsTreeStyle.dense;
  }

  @override
  @nonVirtual
  bool operator ==(Object other) => super == other;

  @override
  @nonVirtual
  int get hashCode => super.hashCode;

  static bool canUpdate(Widget oldWidget, Widget newWidget) {
    return oldWidget.runtimeType == newWidget.runtimeType
        && oldWidget.key == newWidget.key;
  }
  ...
}
  • @immutable 代表 Widget 是不可变的,这会限制 Widget 中定义的属性(即配置信息)必须是不可变的(final),为什么不允许 Widget 中定义的属性变化呢?这是因为,Flutter 中如果属性发生变化则会重新构建Widget树,即重新创建新的 Widget 实例来替换旧的 Widget 实例,所以允许 Widget 的属性变化是没有意义的,因为一旦 Widget 自己的属性变了自己就会被替换。这也是为什么 Widget 中定义的属性必须是 final 的原因。
  • widget类继承自DiagnosticableTreeDiagnosticableTree即“诊断树”,主要作用是提供调试信息。
  • Key: 这个key属性类似于 React/Vue 中的key,主要的作用是决定是否在下一次build时复用旧的 widget ,决定的条件在canUpdate()方法中。
  • createElement():正如前文所述“一个 widget 可以对应多个Element”;Flutter 框架在构建UI树时,会先调用此方法生成对应节点的Element对象。此方法是 Flutter 框架隐式调用的,在我们开发过程中基本不会调用到。
  • debugFillProperties(...) 复写父类的方法,主要是设置诊断树的一些特性。
  • canUpdate(...)是一个静态方法,它主要用于在 widget 树重新build时复用旧的 widget ,其实具体来说,应该是:是否用新的 widget 对象去更新旧UI树上所对应的Element对象的配置;通过其源码我们可以看到,只要newWidgetoldWidgetruntimeTypekey同时相等时就会用new widget去更新Element对象的配置,否则就会创建新的Element

另外Widget类本身是一个抽象类,其中最核心的就是定义了createElement()接口,在 Flutter 开发中,我们一般都不用直接继承Widget类来实现一个新组件,相反,我们通常会通过继承StatelessWidgetStatefulWidget来间接继承widget类来实现。StatelessWidgetStatefulWidget都是直接继承自Widget类,而这两个类也正是 Flutter 中非常重要的两个抽象类,它们引入了两种 widget 模型,

既然 Widget 只是描述一个UI元素的配置信息,那么真正的布局、绘制是由谁来完成的呢?Flutter 框架的处理流程是这样的:

  1. 根据 Widget 树生成一个 Element 树,Element 树中的节点都继承自 Element 类。
  2. 根据 Element 树生成 Render 树(渲染树),渲染树中的节点都继承自RenderObject 类。
  3. 根据渲染树生成 Layer 树,然后上屏显示,Layer 树中的节点都继承自 Layer 类。

真正的布局和渲染逻辑在 Render 树中,Element 是 Widget 和 RenderObject 的粘合剂,可以理解为一个中间代理。我们通过一个例子来说明,假设有如下 Widget 树:

比如创建

dart
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';

class Echo extends StatelessWidget  {
  const Echo({
    Key? key,
    required this.text,
    this.backgroundColor = Colors.white, //默认为灰色
  }):super(key:key);

  final String text;
  final Color backgroundColor;

  @override
  Widget build(BuildContext context) {
    return Center(
      child: Container(
        color: backgroundColor,
        child: Text(text),
      ),
    );
  }
}

修改一下homepage

dart
class _MyHomePageState extends State<MyHomePage> {
  int _counter = 0;

  void _incrementCounter() {
    setState(() {
      // This call to setState tells the Flutter framework that something has
      // changed in this State, which causes it to rerun the build method below
      // so that the display can reflect the updated values. If we changed
      // _counter without calling setState(), then the build method would not be
      // called again, and so nothing would appear to happen.
      _counter+=2;
    });
  }

  @override
  Widget build(BuildContext context) {
    return Echo(text: "hello world");
  }
}

image-20250213113724561