Flutter学习笔记:03. Flutter 基础

193 阅读21分钟

image.png

1. 初始化项目

  • 项目目录 image.png
  • 入口文件与入口方法
  • Material Design (Google 推出的前端 UI解决方案)
  • Flutter 中一切内容都是组件(Widget)
    • 无状态组件(StatelessWidget)
    • 有状态组件(StatefulWidget)

1.1. 体验 Flutter

import 'package:flutter/material.dart';

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

class MyApp extends StatelessWidget {
  const MyApp({super.key});

  @override
  Widget build(BuildContext context) {
    return Container(
      child: const Center(
        child: Text('Hello Flutter', textDirection: TextDirection.ltr),
      ),
    );
  }
}

1.2. APP 结构

  • MaterialApp
    • title (任务管理器中的标题)
    • home(主内容)
    • debugShowCheckedModeBanner(是否显示左上角调试标记)
  • Scaffold
    • appBar (应用头部)
    • body (应用主体)
    • floatingActionButton(浮动按钮)
    • drawer (左侧抽屉菜单)
    • endDrawer (右侧抽屉菜单)

image.png

image.png

image.png

image.png

// main.dart

import 'package:flutter/material.dart';
import '01_basic/01_Hello.dart';

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

class MyApp extends StatelessWidget {
  const MyApp({super.key});

  @override
  Widget build(BuildContext context) {
    return const MaterialApp(
      title: '学习 Flutter',
      home: Home(),
      // 是否显示调试标记
      debugShowCheckedModeBanner: false,
    );
  }
}
// 01_basic/01_Hello.dart

import 'package:flutter/material.dart';

class Home extends StatelessWidget {
  const Home({super.key});

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      // 头部导航条
      appBar: AppBar(
        // 标题
        title: const Text('首页'),
        // 左侧插槽
        leading: const Icon(Icons.menu),
        // 右侧插槽
        actions: const [
          Icon(Icons.add_circle_outline),
        ],
        // 设置下边阴影
        elevation: 0.0,
        // 设置标题居中
        centerTitle: true,
      ),
      body: const HelloFlutter(),
    );
  }
}

class HelloFlutter extends StatelessWidget {
  const HelloFlutter({super.key});

  @override
  Widget build(BuildContext context) {
    return Container(
      child: const Center(
        child: Text('Hello Flutter', textDirection: TextDirection.ltr),
      ),
    );
  }
}

2. 基础组件

2.1. 常用

2.1.1. 文本 Text

  • Text
    • TextDirection (文本方向)
    • TextStyle (文本样式)
      • Colors (颜色)
      • FontWeight (字体粗细)
      • FontStyle (字体样式)
    • TextAlign (文本对齐)
    • TextOverflow (文本溢出)
    • maxLines (指定显示的行数)
  • RichText 与 TextSpan
import 'package:flutter/material.dart';

class Home extends StatelessWidget {
  const Home({super.key});

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      // 头部导航条
      appBar: AppBar(
        // 标题
        title: const Text('Text 文本'),
        // 左侧插槽
        leading: const Icon(Icons.menu),
        // 右侧插槽
        actions: const [
          Icon(Icons.add_circle_outline),
        ],
        // 设置下边阴影
        elevation: 0.0,
        // 设置标题居中
        centerTitle: true,
      ),
      body: const TextDemo(),
    );
  }
}

class TextDemo extends StatelessWidget {
  const TextDemo({super.key});

  @override
  Widget build(BuildContext context) {
    return Column(
      children: [
        const Text(
          '知识有两种,一种是你知道的,一种是你知道在哪里能找到的!',
          textDirection: TextDirection.ltr,
          // 指定文本样式
          style: TextStyle(
            // 指定文字大小
            fontSize: 30.0,
            // 指定文字颜色
            color: Colors.red,
            // 文字粗细
            fontWeight: FontWeight.w500,
            // 斜体字体
            fontStyle: FontStyle.italic,
            // 文本修饰,中划线
            decoration: TextDecoration.lineThrough,
            // 修饰颜色
            decorationColor: Colors.blue,
          ),
          // 文本对齐方式
          textAlign: TextAlign.center,
          // 显示的行数
          maxLines: 2,
          // 文本溢出
          overflow: TextOverflow.ellipsis,
          // 文本缩放比例
          textScaleFactor: 1.5,
        ),
        // RichText 里面可以包含多个不同的文本样式
        RichText(
            // TextSpan 类似与 html 中的 span 标签
            text: const TextSpan(
          text: 'Hello',
          style: TextStyle(
            fontSize: 40.0,
            color: Colors.red,
          ),
          children: [
            TextSpan(
              text: ' Flutter!',
              style: TextStyle(
                fontSize: 40.0,
                color: Colors.blue,
              ),
            ),
            TextSpan(
              text: '你好世界',
              style: TextStyle(
                fontSize: 30.0,
                color: Colors.black45,
              ),
            ),
          ],
        )),
      ],
    );
  }
}

2.1.2. 设置自定义字体

  • 下载并导入字体
  • pubspec.yaml 中声明字体
  • 使用
    • 为整个应用设置默认自定义字体
    • 为某个组件设置自定义字体

image.png

// pubspec.yaml

flutter:
    fonts:
        - family: CustomFamily
          fonts:
            - asset: fonts/FZSJ-QINGYAXK.TTF
              weight: 300
        - family: MyFamily
          fonts:
            - asset: fonts/FZZJ-DHTCSJW.TTF
              weight: 300
import 'package:flutter/material.dart';
// import '01_basic/01_Hello.dart';
import '01_basic/02_text.dart';

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

class MyApp extends StatelessWidget {
  const MyApp({super.key});

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: '学习 Flutter',
      home: const Home(),
      // 设置全局主题
      theme: ThemeData(
        // 自定义文本样式
        fontFamily: 'CustomFamily',
      ),
      // 是否显示调试标记
      debugShowCheckedModeBanner: false,
    );
  }
}
TextSpan(
  text: '你好世界',
  style: TextStyle(
    fontSize: 30.0,
    color: Colors.black45,
    // 给组件设置自定义字体
    fontFamily: 'MyFamily',
  ),
),

2.1.3. Icon

2.1.4. Color

  • Color (自定义颜色)
    • Flutter 中通过 ARGB 来声明颜色
    • const Color(0xFF42A5F5); // 16进制的ARGB = 透明度+六位十六进制颜色
    • const Color.fromARGB(0xFF, Ox42, 0xA5, 0xF5);
    • const Color.fromARGB(255, 66,165,245);
    • const Color.fromRGBO(66, 165, 245, 1.0); // O = Opacity
  • Colors (英文字母声明的颜色)
    • Colors.red
RichText(
    // TextSpan 类似与 html 中的 span 标签
    text: const TextSpan(
  text: 'Hello',
  style: TextStyle(
    fontSize: 40.0,
    color: Color.fromARGB(255, 0, 0, 255),
  ),
  children: [
    TextSpan(
      text: ' Flutter!',
      style: TextStyle(
        fontSize: 40.0,
        color: Color.fromRGBO(255, 0, 255, 0.5),
      ),
    ),
    TextSpan(
      text: '你好世界',
      style: TextStyle(
        fontSize: 30.0,
        color: Colors.black45,
        // 给组件设置自定义字体
        fontFamily: 'MyFamily',
      ),
    ),
  ],
)),

2.2. 布局

2.2.1. Container(类似于 div)

  • child (声明子组件)
  • padding(margin)
    • Edgelnsets(all()、 fromLTRB()、 only())
  • decoration
    • BoxDecoration(边框、圆角、渐变、阴影、背景色、背景图片)
  • alignment
    • Alignment(内容对齐)
  • transform
    • Matrix4 (平移-translate、旋转-rotate、缩放-scale、斜切-skew)
class ContainerDemo extends StatelessWidget {
  const ContainerDemo({super.key});

  @override
  Widget build(BuildContext context) {
    return Container(
      width: double.infinity,
      height: double.infinity,
      // 设置内边距
      padding: const EdgeInsets.all(10.0),
      margin: const EdgeInsets.fromLTRB(10.0, 30.0, 0.0, 5.0),
      // 内容的对齐方式
      alignment: Alignment.center,
      // 平移
      // transform: Matrix4.translationValues(100.0, 0, 0),
      // 旋转
      // transform: Matrix4.rotationZ(-0.1),
      // 斜切
      transform: Matrix4.skew(0.2, 0.1),
      decoration: BoxDecoration(
        // 设置边框
        // border: Border(
        //   top: BorderSide(
        //     width: 10.0,
        //     color: Colors.red,
        //   ),
        //   bottom: BorderSide(
        //     width: 10.0,
        //     color: Colors.red,
        //   ),
        //   right: BorderSide(
        //     width: 10.0,
        //     color: Colors.red,
        //   ),
        //   left: BorderSide(
        //     width: 10.0,
        //     color: Colors.red,
        //   ),
        // ),
        border: Border.all(
          width: 10.0,
          color: Colors.blue,
        ),
        // 设置边框圆角
        // borderRadius: BorderRadius.all(Radius.circular(30.0)),
        borderRadius: const BorderRadius.only(
          topLeft: Radius.circular(30.0),
        ),
        // 设置背景颜色
        color: Colors.lightGreen[100],
        // 设置渐变后,背景色会失效
        gradient: const LinearGradient(
          colors: [
            Colors.lightBlue,
            Colors.white12,
          ]
        )
      ),
      child: const Text(
        '此诗是高适与董大久别重逢,经过短暂的聚会以后,又各奔他方的赠别之作。作品勾勒了送别时晦暗寒冷的愁人景色,表现了诗人当时处在困顿不达的境遇之中,但没有因此沮丧、沉沦,既表露出诗人对友人远行的依依惜别之情,也展现出诗人豪迈豁达的胸襟。',
        style: TextStyle(
          fontSize: 20,
        ),
      ),
    );
  }
}

2.2.2. 线性布局

  • Column
    • Column 中的主轴方向是垂直方向
    • mainAxisAlignment: MainAxisAlignment 主轴对产方式
    • crossAxisAlignment: CrossAxisAlignment 交叉抽对产方式
    • children: 内容
  • Row
    • Row 中的主轴方向是水平方向 (其他属性与 Column 一致)
class ColumnRowDemo extends StatelessWidget {
  const ColumnRowDemo({super.key});

  @override
  Widget build(BuildContext context) {
    return Container(
      width: double.infinity,
      height: double.infinity,
      // 设置盒子的背景色
      color: Colors.lightGreen,
      child: Column(
        // 主轴的对齐方式
        mainAxisAlignment: MainAxisAlignment.spaceEvenly,
        // 交叉轴的对齐方式
        crossAxisAlignment: CrossAxisAlignment.center,
        children: [
          const Icon(Icons.access_alarm, size: 50),
          const Icon(Icons.search, size: 50),
          const Icon(Icons.settings, size: 50),
          const Icon(Icons.add_a_photo, size: 50),
          Container(
            height: 140,
            color: Colors.red[100],
            child: const Row(
              mainAxisAlignment: MainAxisAlignment.spaceAround,
              crossAxisAlignment: CrossAxisAlignment.center,
              children: [
                Icon(Icons.access_alarm, size: 50),
                Icon(Icons.search, size: 50),
                Icon(Icons.settings, size: 50),
                Icon(Icons.add_a_photo, size: 50),
              ],
            ),
          ),
        ],
      ),
    );
  }
}

2.2.3. 弹性布局

  • Flex
    • direction (声明主轴方向)
    • mainAxisAlignment (声明主轴对齐方式)
    • textDirection (声明水平方向的排列顺序)
    • crossAxisAlignment (声明交叉轴对产方式)
    • verticalDirection (声明垂直方向的排列顺序)
    • children (声明子组件)
  • Expanded (可伸缩组件)
    • flex (声明弹性布局所占比例)
    • child (声明子组件)
class FlexDemo extends StatelessWidget {
  const FlexDemo({super.key});

  @override
  Widget build(BuildContext context) {
    return Column(
      children: [
        // 验证 Expanded
        Row(
          children: [
            Container(
              width: 100,
              height: 50,
              color: Colors.lightBlue,
            ),
            Expanded(child: Container(
              height: 50,
              color: Colors.lightGreen,
            )),
          ],
        ),
        const Flex(
          direction: Axis.horizontal,
          mainAxisAlignment: MainAxisAlignment.spaceAround,
          // 声明水平方向的排列方式
          textDirection: TextDirection.rtl,
          children: [
            Icon(Icons.access_alarm, size: 50),
            Icon(Icons.search, size: 50),
            Icon(Icons.settings, size: 50),
            Icon(Icons.add_a_photo, size: 50),
          ],
        ),
        Flex(
          direction: Axis.horizontal,
          children: [
            Expanded(
              flex: 2,
              child: Container(
                height: 50,
                color: Colors.amber,
              ),
            ),
            Expanded(
              flex: 1,
              child: Container(
                  height: 50,
                  color: Colors.deepOrangeAccent,
                ),
            )
          ],
        ),
        Container(
          height: 100,
          margin: const EdgeInsets.all(50),
          child: Flex(
            direction: Axis.vertical,
            // 声明垂直方向的排列顺序
            verticalDirection: VerticalDirection.up,
            children: [
              Expanded(
                flex: 2,
                child: Container(
                  height: 50,
                  color: Colors.amber,
                ),
              ),
              // Spacer 间隙组件
              const Spacer(
                flex: 1,
              ),
              Expanded(
                flex: 1,
                child: Container(
                  height: 50,
                  color: Colors.deepOrangeAccent,
                ),
              )
            ],
          ),
        ),
      ],
    );
  }
}

2.2.4. 流式布局

  • Wrap (解决内容溢出问题)
    • spacing (主轴方向子组件的间距)
    • alignment (主轴方向的对齐方式)
    • runSpacing (纵轴方向子组件的间距)
    • runAlignment (纵轴方向的对产方式)
  • chip (标签)
  • CircleAvatar (圆形头像)
class WrapDemo extends StatelessWidget {
  const WrapDemo({super.key});

  @override
  Widget build(BuildContext context) {
    List<String> _list = ['曹操', '司马懿', '曹仁', '曹洪', '张辽', '许褚'];

    List<Widget> _weiguo() {
      return _list.map((item) => Chip(
        avatar: const CircleAvatar(
          backgroundColor: Colors.pink,
          child: Text('魏'),
        ),
        label: Text(item),
      )).toList();
    }

    return Column(
      mainAxisAlignment: MainAxisAlignment.spaceEvenly,
      children: [
        Wrap(
          spacing: 18.0, // 水平方向的间隙
          runSpacing: 10.0, // 垂直方向的间隙
          alignment: WrapAlignment.spaceAround, // 主轴方向的对齐方式
          runAlignment: WrapAlignment.spaceAround, // 交叉轴的对齐方式
          children: _weiguo(),
        ),
        const Wrap(
          children: [
            Chip(
              avatar: CircleAvatar(
                backgroundColor: Colors.blue,
                child: Text('蜀'),
              ),
              label: Text('刘备'),
            ),
            Chip(
              avatar: CircleAvatar(
                backgroundColor: Colors.blue,
                child: Text('蜀'),
              ),
              label: Text('关羽'),
            ),
            Chip(
              avatar: CircleAvatar(
                backgroundColor: Colors.blue,
                child: Text('蜀'),
              ),
              label: Text('张飞'),
            ),
            Chip(
              avatar: CircleAvatar(
                backgroundColor: Colors.blue,
                child: Text('蜀'),
              ),
              label: Text('赵云'),
            ),
            Chip(
              avatar: CircleAvatar(
                backgroundColor: Colors.blue,
                child: Text('蜀'),
              ),
              label: Text('诸葛亮'),
            ),
            Chip(
              avatar: CircleAvatar(
                backgroundColor: Colors.blue,
                child: Text('蜀'),
              ),
              label: Text('黄忠'),
            ),
          ],
        ),
      ],
    );
  }
}

2.2.5. 层叠布局

  • Stack (层叠组件 - 类似 css 中的 z-index)
    • alignment (声明未定位子组件的对齐方式)
    • textDirection (声明未定位子组件的排列顺序)
  • Positioned (绝对定位组件)
    • child (声明子组件)
    • lefttoprightbottom
    • widthheight
  • Networklmage (网络图片组件)
    • NetworkImage('图片地址')
    • <uses-permission android:name="android.permission.INTERNET" />
class StackDemo extends StatelessWidget {
  const StackDemo({super.key});

  @override
  Widget build(BuildContext context) {
    return Container(
      color: Colors.grey[300],
      child: Stack(
        // 声明未定位的子组件的排序方式
        textDirection: TextDirection.rtl,
        // 声明未定位的子组件的对齐方式
        alignment: AlignmentDirectional.bottomCenter,
        children: [
          const CircleAvatar(
            backgroundImage: NetworkImage(
                'https://i2.hdslb.com/bfs/sycp/creative_img/202206/fe08712b4ba1e3aa7bbf2b8af6c0c6c9.jpg'),
            radius: 200,
          ),
          Positioned(
            top: 50,
            right: 40,
            child: Container(
              padding: const EdgeInsets.all(10),
              decoration: BoxDecoration(
                color: Colors.red,
                borderRadius: BorderRadius.circular(10),
              ),
              child: const Text(
                '热卖',
                style: TextStyle(
                  color: Colors.white,
                  fontSize: 20,
                ),
              ),
            ),
          ),
          const Text(
            'Hello',
            style: TextStyle(
              fontSize: 20,
            ),
          ),
        ],
      ),
    );
  }
}

2.2.6. Card

  • card (卡片)
    • child 子组件
    • color 背景色
    • shadowColor 阴影色
    • elevation 阴影高度
    • shape 边框样式
    • margin 外边距
  • ListTile (列表瓦片)
    • leading (头部组件)
    • title (标题)
    • subtitle (子标题)
class CardDemo extends StatelessWidget {
  const CardDemo({super.key});

  @override
  Widget build(BuildContext context) {
    return Column(
      children: [
        Card(
          margin: const EdgeInsets.all(14),
          color: Colors.lightBlue,
          // 阴影颜色
          shadowColor: Colors.grey,
          // 阴影高度
          elevation: 20,
          // 设置卡片边框
          shape: RoundedRectangleBorder(
            // 边框圆角
            borderRadius: BorderRadius.circular(30),
            side: const BorderSide(
              color: Colors.pink,
              width: 10,
            ),
          ),
          child: const Column(
            children: [
              ListTile(
                leading: Icon(Icons.verified_user_outlined, size: 50),
                title: Text('张三',
                    style: TextStyle(
                      fontSize: 20,
                    )),
                subtitle: Text('董事长',
                    style: TextStyle(
                      fontSize: 12,
                    )),
              ),
              // 分割线组件
              Divider(),
              ListTile(
                title: Text('电话:133333333333',
                    style: TextStyle(
                      fontSize: 20,
                    )),
              ),
              ListTile(
                title: Text('地址:xxxxxx',
                    style: TextStyle(
                      fontSize: 20,
                    )),
              ),
            ],
          ),
        ),
        const Card(
          margin: EdgeInsets.all(14),
          child: Column(
            children: [
              ListTile(
                leading: Icon(Icons.verified_user_outlined, size: 50),
                title: Text('李四',
                    style: TextStyle(
                      fontSize: 20,
                    )),
                subtitle: Text('董事长',
                    style: TextStyle(
                      fontSize: 12,
                    )),
              ),
              // 分割线组件
              Divider(),
              ListTile(
                title: Text('电话:133333333333',
                    style: TextStyle(
                      fontSize: 20,
                    )),
              ),
              ListTile(
                title: Text('地址:xxxxxx',
                    style: TextStyle(
                      fontSize: 20,
                    )),
              ),
            ],
          ),
        ),
      ],
    );
  }
}

2.3. 按钮

  • Flutter 1.22 之前

    • FlatButton (扁平按钮)
    • RaisedButton (凸起按钮)
    • OutlineButton (轮廓按钮)
  • Flutter 1.22 之后

    • TextButton (文本按钮-用来替换 FlatButton)
    • ElevatedButton (凸起按钮-用来替换 RaisedButton)
    • OutlinedButton (轮廓按钮-用来替换 OutlineButton)
  • 按钮主题 image.png

  • 按钮类型

    • 图标按钮
      • IconButton
      • TextButton.icon()
      • ElevatedButton.icon()
      • OutlinedButton.icon()
    • ButtonBar (按钮组)
    • FloatingActionButton (浮动按钮)
    • BackButton (回退按钮)
    • CloseButton (关闭按钮)
class ButtonDemo extends StatelessWidget {
  const ButtonDemo({super.key});

  @override
  Widget build(BuildContext context) {
    return Container(
      padding: const EdgeInsets.all(10),
      child: Wrap(
        children: [
          TextButton(
            onPressed: () {
              print('点击了 TextButton');
            },
            onLongPress: () {
              print('长按 onLongPress');
            },
            child: const Text('TextButton'),
          ),
          ElevatedButton(
            onPressed: () {
              print('点击了 ElevatedButton');
            },
            onLongPress: () {
              print('长按 onLongPress');
            },
            child: const Text('ElevatedButton'),
          ),
          OutlinedButton(
            onPressed: () {
              print('点击了 OutlinedButton');
            },
            onLongPress: () {
              print('长按 onLongPress');
            },
            child: const Text('OutlinedButton'),
          ),
        ],
      ),
    );
  }
}

2.3.1. 按钮的相关样式

OutlinedButton(
    onPressed: () {
      print('点击了 OutlinedButton');
    },
    onLongPress: () {
      print('长按 onLongPress');
    },
    style: ButtonStyle(
      textStyle: MaterialStateProperty.all(
        const TextStyle(
          fontSize: 30,
        ),
      ),
      foregroundColor: MaterialStateProperty.resolveWith((states) {
        if (states.contains(MaterialState.pressed)) {
          // 按下按钮的前景色
          return Colors.red;
        }

        // 默认状态的颜色
        return Colors.blue;
      }),
      backgroundColor: MaterialStateProperty.resolveWith((states) {
        if (states.contains(MaterialState.pressed)) {
          // 按下按钮的背景色
          return Colors.yellow;
        }

        // 默认状态的颜色
        return Colors.white;
      }),
      shadowColor: MaterialStateProperty.all(Colors.yellow),
      elevation: MaterialStateProperty.all(20),
      side: MaterialStateProperty.all(
        const BorderSide(
          color: Colors.green,
          width: 2,
        ),
      ),
      // 声明按钮形状
      shape: MaterialStateProperty.all(
        const StadiumBorder(
          side: BorderSide(
            color: Colors.green,
            width: 2,
          ),
        ),
      ),
      // 设置按钮大小
      minimumSize: MaterialStateProperty.all(const Size(200, 100)),
      // 设置水波纹的颜色
      overlayColor: MaterialStateProperty.all(Colors.amber),
    ),
    child: const Text('轮廓按钮'),
  )

2.3.2. 其他按钮

import 'package:flutter/material.dart';

class Home extends StatelessWidget {
  const Home({super.key});

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      // 头部导航条
      appBar: AppBar(
        // 标题
        title: const Text('卡片'),
        // 左侧插槽
        leading: const Icon(Icons.menu),
        // 右侧插槽
        actions: const [
          Icon(Icons.add_circle_outline),
        ],
        // 设置下边阴影
        elevation: 0.0,
        // 设置标题居中
        centerTitle: true,
      ),
      body: const ButtonDemo(),
      // 浮动按钮
      floatingActionButton: FloatingActionButton(
        onPressed: () {},
        tooltip: 'Increment',
        child: const Icon(Icons.add),
        backgroundColor: Colors.pink[100],
        elevation: 0,
      ),
    );
  }
}

class ButtonDemo extends StatelessWidget {
  const ButtonDemo({super.key});

  @override
  Widget build(BuildContext context) {
    return Container(
      padding: const EdgeInsets.all(10),
      child: Wrap(
        children: [
          TextButton(
            onPressed: () {
              print('点击了 TextButton');
            },
            onLongPress: () {
              print('长按 onLongPress');
            },
            child: const Text('TextButton'),
          ),
          ElevatedButton(
            onPressed: () {
              print('点击了 ElevatedButton');
            },
            onLongPress: () {
              print('长按 onLongPress');
            },
            child: const Text('ElevatedButton'),
          ),
          OutlinedButton(
            onPressed: () {
              print('点击了 OutlinedButton');
            },
            onLongPress: () {
              print('长按 onLongPress');
            },
            style: ButtonStyle(
              textStyle: MaterialStateProperty.all(
                const TextStyle(
                  fontSize: 30,
                ),
              ),
              foregroundColor: MaterialStateProperty.resolveWith((states) {
                if (states.contains(MaterialState.pressed)) {
                  // 按下按钮的前景色
                  return Colors.red;
                }

                // 默认状态的颜色
                return Colors.blue;
              }),
              backgroundColor: MaterialStateProperty.resolveWith((states) {
                if (states.contains(MaterialState.pressed)) {
                  // 按下按钮的背景色
                  return Colors.yellow;
                }

                // 默认状态的颜色
                return Colors.white;
              }),
              shadowColor: MaterialStateProperty.all(Colors.yellow),
              elevation: MaterialStateProperty.all(20),
              side: MaterialStateProperty.all(
                const BorderSide(
                  color: Colors.green,
                  width: 2,
                ),
              ),
              // 声明按钮形状
              shape: MaterialStateProperty.all(
                const StadiumBorder(
                  side: BorderSide(
                    color: Colors.green,
                    width: 2,
                  ),
                ),
              ),
              // 设置按钮大小
              minimumSize: MaterialStateProperty.all(const Size(200, 100)),
              // 设置水波纹的颜色
              overlayColor: MaterialStateProperty.all(Colors.amber),
            ),
            child: const Text('轮廓按钮'),
          ),
          // 通过 OutlinedButtonTheme 给按钮设置样式
          OutlinedButtonTheme(
              data: OutlinedButtonThemeData(
                style: ButtonStyle(
                  overlayColor: MaterialStateProperty.all(Colors.red),
                ),
              ),
              child: OutlinedButton(
                onPressed: () {
                  print('点击了 OutlinedButton');
                },
                onLongPress: () {
                  print('长按了 OutlinedButton');
                },
                // 内部的样式会覆盖 buttonTheme 的样式
                style: ButtonStyle(
                  overlayColor: MaterialStateProperty.all(Colors.blue),
                ),
                child: const Text('轮廓按钮'),
              )),
          //   IconButton
          IconButton(
              // 图标颜色
              color: Colors.red,
              // 水波纹颜色
              splashColor: Colors.lightBlue,
              // 高亮时的颜色
              highlightColor: Colors.purple,
              tooltip: '长按提示文字',
              onPressed: () {
                print('点击了 IconButton');
              },
              icon: const Icon(Icons.add_a_photo)),
          TextButton.icon(
              onPressed: () {
                print('点击了 TextButton.icon');
              },
              icon: const Icon(Icons.add_circle),
              label: const Text('文本按钮')
          ),
          ElevatedButton.icon(
              onPressed: () {
                print('点击了 TextButton.icon');
              },
              icon: const Icon(Icons.add_circle),
              label: const Text('凸起按钮')
          ),
          OutlinedButton.icon(
              onPressed: () {
                print('点击了 TextButton.icon');
              },
              icon: const Icon(Icons.add_circle),
              label: const Text('轮廓按钮')
          ),
          //   按钮组
          Container(
            color: Colors.pink[100],
            width: double.infinity,
            // ButtonBar 水平方向显示不全时,会纵向排列
            child: ButtonBar(
              children: [
                ElevatedButton(
                    onPressed: () {},
                    child: const Text('按钮一'),
                ),
                ElevatedButton(
                  onPressed: () {},
                  child: const Text('按钮二'),
                ),
                ElevatedButton(
                  onPressed: () {},
                  child: const Text('按钮二'),
                ),
                ElevatedButton(
                  onPressed: () {},
                  child: const Text('按钮二'),
                ),
                ElevatedButton(
                  onPressed: () {},
                  child: const Text('按钮二'),
                ),
              ],
            ),
          ),
          // 返回按钮
          BackButton(
            color: Colors.red,
            onPressed: () {},
          ),
        //   关闭按钮
          CloseButton(
            color: Colors.red,
            onPressed: () {},
          ),
        ],
      ),
    );
  }
}

2.4. 图片

  • lmage.asset (加载本地图片)
    • Flutter 项目下,创建图片存储目录
    • 在 pubspec.yaml中的 flutter 部分添加图片配置
    • 在代码中加载图片
  • lmage.network (加载网络图片)
    • 保证网络畅通
    • 设置网络访问权限
    • 允许 http 协议访问

image.png

class ImageDemo extends StatelessWidget {
  const ImageDemo({super.key});

  @override
  Widget build(BuildContext context) {
    return Column(
      children: [
        // 加载本地图片
        Image.asset(
          'images/1.jpg',
          width: 200,
          height: 200,
          fit: BoxFit.cover,
        ),
        // 加载网络图片
        Image.network(
            'https://pic.netbian.com/uploads/allimg/230922/164533-16953723335065.jpg',
          repeat: ImageRepeat.repeat,
          // 颜色混合模式
          colorBlendMode: BlendMode.colorDodge,
          color: Colors.red,
        ),
        Image.asset('images/2.webp'),
        // Image.asset('images/客厅.png'),
      ],
    );
  }
}

2.5. 列表

2.5.1. SingleChildScrollView

  • SingleChildScrollView (类似Android 中的 Scrollview)
    • child (子组件)
    • padding (内边距)
    • scrollDirection (滚动方向: Axis.horizontal Axis.vertical)
    • reverse (初始滚动位置,false 头部、true 尾部)
    • physics
      • ClampingScrollPhysics: Android 下微光效果
      • BouncingScrollPhysics: ios 下弹性效果
class SingleChildScrollViewDemo extends StatelessWidget {
  const SingleChildScrollViewDemo({super.key});

  @override
  Widget build(BuildContext context) {
    return Stack(
      children: [
        // 验证水平滚动
        SingleChildScrollView(
          // 设置滚动方向
          scrollDirection: Axis.horizontal,
          // 设置内边距
          padding: const EdgeInsets.all(10),
          // 从尾部开始滚动
          reverse: true,
          child: Row(
            children: [
              OutlinedButton(
                onPressed: () {},
                child: const Text('按钮一'),
              ),
              OutlinedButton(
                onPressed: () {},
                child: const Text('按钮二'),
              ),
              OutlinedButton(
                onPressed: () {},
                child: const Text('按钮三'),
              ),
              OutlinedButton(
                onPressed: () {},
                child: const Text('按钮四'),
              ),
              OutlinedButton(
                onPressed: () {},
                child: const Text('按钮五'),
              ),
              OutlinedButton(
                onPressed: () {},
                child: const Text('按钮六'),
              ),
            ],
          ),
        ),
        // 验证垂直滚动
        SingleChildScrollView(
          // 设置滚动方向
          scrollDirection: Axis.vertical,
          // 设置内边距
          padding: const EdgeInsets.all(10),
          // 从头部开始滚动
          reverse: false,
          // 弹性下拉效果
          physics: const BouncingScrollPhysics(),
          child: Column(
            children: List.generate(100, (index) => OutlinedButton(
              onPressed: () {},
              child: Text('按钮$index'),
            )),
          ),
        ),
      ],
    );
  }
}

2.5.2. ListView

  • ListView
    • 加载列表的组件 (加载所有 Widgets,适用 Widget 较少的场景)
    • ListTile (leadingtitlesubtitletrailingselected)
  • ListView.builder
    • 性能比默认构造函数高,适用 widget 较多的场景按需加载 Widget,
  • ListView.separated
    • 也可以按需加载,比 ListView.builder 多了分隔器
import 'package:flutter/material.dart';

class Home extends StatelessWidget {
  const Home({super.key});

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      // 头部导航条
      appBar: AppBar(
        // 标题
        title: const Text('ListView'),
        // 左侧插槽
        leading: const Icon(Icons.menu),
        // 右侧插槽
        actions: const [
          Icon(Icons.add_circle_outline),
        ],
        // 设置下边阴影
        elevation: 0.0,
        // 设置标题居中
        centerTitle: true,
      ),
      body: const ListViewDemo(),
    );
  }
}

class ListViewDemo extends StatelessWidget {
  const ListViewDemo({super.key});

  @override
  Widget build(BuildContext context) {
    return const SingleChildScrollView(
      child: Column(
        children: [
          ListViewBasic(),
          ListViewHorizontal(),
        ],
      ),
    );
  }
}

class ListViewBasic extends StatelessWidget {
  const ListViewBasic({super.key});

  @override
  Widget build(BuildContext context) {
    return SizedBox(
      height: 200,
      child: ListView(
        // 设置滚动方向
        scrollDirection: Axis.vertical,
        children: [
          ListTile(
            leading: const Icon(
              Icons.access_alarm,
              size: 50,
            ),
            title: const Text('标题'),
            subtitle: const Text('子标题'),
            trailing: const Icon(Icons.keyboard_arrow_right),
            selected: true,
            selectedTileColor: Colors.blue[100],
          ),
          const ListTile(
            leading: Icon(
              Icons.audio_file,
              size: 50,
            ),
            title: Text('标题'),
            subtitle: Text('子标题'),
            trailing: Icon(Icons.keyboard_arrow_right),
          ),
          const ListTile(
            leading: Icon(
              Icons.notifications,
              size: 50,
            ),
            title: Text('标题'),
            subtitle: Text('子标题'),
            trailing: Icon(Icons.keyboard_arrow_right),
          ),
          const ListTile(
            leading: Icon(
              Icons.chat,
              size: 50,
            ),
            title: Text('标题'),
            subtitle: Text('子标题'),
            trailing: Icon(Icons.keyboard_arrow_right),
          ),
          const ListTile(
            leading: Icon(
              Icons.person,
              size: 50,
            ),
            title: Text('标题'),
            subtitle: Text('子标题'),
            trailing: Icon(Icons.keyboard_arrow_right),
          ),
        ],
      ),
    );
  }
}

class ListViewHorizontal extends StatelessWidget {
  const ListViewHorizontal({super.key});

  @override
  Widget build(BuildContext context) {
    return Container(
      height: 100,
      color: Colors.white,
      child: ListView(
        scrollDirection: Axis.horizontal,
        children: [
          Container(
            width: 160,
            color: Colors.amber,
          ),
          Container(
            width: 160,
            color: Colors.red,
          ),
          Container(
            width: 160,
            color: Colors.green,
          ),
          Container(
            width: 160,
            color: Colors.pink,
          ),
          Container(
            width: 160,
            color: Colors.purple,
          ),
        ],
      ),
    );
  }
}
import 'package:flutter/material.dart';

class Home extends StatelessWidget {
  const Home({super.key});

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      // 头部导航条
      appBar: AppBar(
        // 标题
        title: const Text('ListView'),
        // 左侧插槽
        leading: const Icon(Icons.menu),
        // 右侧插槽
        actions: const [
          Icon(Icons.add_circle_outline),
        ],
        // 设置下边阴影
        elevation: 0.0,
        // 设置标题居中
        centerTitle: true,
      ),
      body: const ListViewDemo(),
    );
  }
}

class ListViewDemo extends StatelessWidget {
  const ListViewDemo({super.key});

  @override
  Widget build(BuildContext context) {
    return SingleChildScrollView(
      child: Column(
        children: [
          const ListViewBasic(),
          const ListViewHorizontal(),
          ListViewBuilderDemo(),
          ListViewSeperatedDemo(),
        ],
      ),
    );
  }
}

class ListViewBasic extends StatelessWidget {
  const ListViewBasic({super.key});

  @override
  Widget build(BuildContext context) {
    return SizedBox(
      height: 200,
      child: ListView(
        // 设置滚动方向
        scrollDirection: Axis.vertical,
        children: [
          ListTile(
            leading: const Icon(
              Icons.access_alarm,
              size: 50,
            ),
            title: const Text('标题'),
            subtitle: const Text('子标题'),
            trailing: const Icon(Icons.keyboard_arrow_right),
            selected: true,
            selectedTileColor: Colors.blue[100],
          ),
          const ListTile(
            leading: Icon(
              Icons.audio_file,
              size: 50,
            ),
            title: Text('标题'),
            subtitle: Text('子标题'),
            trailing: Icon(Icons.keyboard_arrow_right),
          ),
          const ListTile(
            leading: Icon(
              Icons.notifications,
              size: 50,
            ),
            title: Text('标题'),
            subtitle: Text('子标题'),
            trailing: Icon(Icons.keyboard_arrow_right),
          ),
          const ListTile(
            leading: Icon(
              Icons.chat,
              size: 50,
            ),
            title: Text('标题'),
            subtitle: Text('子标题'),
            trailing: Icon(Icons.keyboard_arrow_right),
          ),
          const ListTile(
            leading: Icon(
              Icons.person,
              size: 50,
            ),
            title: Text('标题'),
            subtitle: Text('子标题'),
            trailing: Icon(Icons.keyboard_arrow_right),
          ),
        ],
      ),
    );
  }
}

class ListViewHorizontal extends StatelessWidget {
  const ListViewHorizontal({super.key});

  @override
  Widget build(BuildContext context) {
    return Container(
      height: 100,
      color: Colors.white,
      child: ListView(
        scrollDirection: Axis.horizontal,
        children: [
          Container(
            width: 160,
            color: Colors.amber,
          ),
          Container(
            width: 160,
            color: Colors.red,
          ),
          Container(
            width: 160,
            color: Colors.green,
          ),
          Container(
            width: 160,
            color: Colors.pink,
          ),
          Container(
            width: 160,
            color: Colors.purple,
          ),
        ],
      ),
    );
  }
}

class ListViewBuilderDemo extends StatelessWidget {
  ListViewBuilderDemo({super.key});

  final List<Widget> users = List<Widget>.generate(
      20,
      (index) => OutlinedButton(
            onPressed: () {},
            child: Text('姓名 $index'),
          ));

  @override
  Widget build(BuildContext context) {
    return Container(
      height: 150,
      color: Colors.white,
      child: ListView.builder(
        itemCount: users.length,
        // 当前元素的高度
        itemExtent: 30,
        padding: const EdgeInsets.all(10),
        itemBuilder: (context, index) {
          return users[index];
        },
      ),
    );
  }
}

class ListViewSeperatedDemo extends StatelessWidget {
  ListViewSeperatedDemo({super.key});

  final List<Widget> products = List.generate(
      20,
      (index) => ListTile(
            leading: Image.asset('images/1.jpg'),
            title: Text('商品标题 $index'),
            subtitle: const Text('子标题'),
            trailing: const Icon(Icons.keyboard_arrow_right),
          ));

  @override
  Widget build(BuildContext context) {
    Widget dividerOdd = const Divider(
      color: Colors.blue,
      thickness: 2,
    );

    Widget dividerEven = const Divider(
      color: Colors.red,
      thickness: 2,
    );

    return Column(
      children: [
        const ListTile(
          title: Text('商品列表'),
        ),
        Container(
          height: 200,
          color: Colors.white,
          child: ListView.separated(
            itemBuilder: (context, index) {
              return products[index];
            },
            itemCount: products.length,
            // 分割器的构造器
            separatorBuilder: (context, index) {
              return index % 2 == 0 ? dividerEven : dividerOdd;
            },
          ),
        ),
      ],
    );
  }
}

2.5.3. GridView

  • GridView (网格布局)
    • children (子组件)
    • scrollDirection (滚动方向).
    • gridDelegate
      • SliverGridDelegateWithFixedCrossAxisCount (指定列数 - 子组件宽度自适应)
      • SliverGridDelegateWithMaxCrossAxisExtent (指定子组件宽度 - 列数自适应)
  • GridView.count (列数固定)
  • GridView.extend (子组件宽度固定)
  • GridView.builder (动态网格布局)

image.png

class GridViewDemo extends StatelessWidget {
  const GridViewDemo({super.key});

  @override
  Widget build(BuildContext context) {
    return Container(
      padding: const EdgeInsets.all(10),
      color: Colors.grey[200],
      child:
          // 固定列数
          // GridView(
          //   gridDelegate: const SliverGridDelegateWithFixedCrossAxisCount(
          //     crossAxisCount: 2, // 指定列数
          //     mainAxisSpacing: 20, // 主轴方向的间距
          //     crossAxisSpacing: 20, // 交叉轴方向的间距
          //     childAspectRatio: 1.5, // 子组件的宽高比例
          //   ),
          //   children: [
          //     Container(color: Colors.cyan),
          //     Container(color: Colors.blueAccent),
          //     Container(color: Colors.purpleAccent),
          //     Container(color: Colors.purple),
          //     Container(color: Colors.yellow),
          //     Container(color: Colors.redAccent),
          //     Container(color: Colors.purpleAccent),
          //     Container(color: Colors.purple),
          //     Container(color: Colors.yellow),
          //     Container(color: Colors.redAccent),
          //     Container(color: Colors.purpleAccent),
          //     Container(color: Colors.purple),
          //     Container(color: Colors.yellow),
          //     Container(color: Colors.redAccent),
          //     Container(color: Colors.purpleAccent),
          //     Container(color: Colors.purple),
          //     Container(color: Colors.yellow),
          //     Container(color: Colors.redAccent),
          //   ],
          // ),
          // 子元素固定宽度
          GridView(
        gridDelegate: const SliverGridDelegateWithMaxCrossAxisExtent(
          maxCrossAxisExtent: 200, // 子组件的宽度
          mainAxisSpacing: 10,
          crossAxisSpacing: 10,
          childAspectRatio: 0.8,
        ),
        children: [
          Container(color: Colors.cyan),
          Container(color: Colors.blueAccent),
          Container(color: Colors.purpleAccent),
          Container(color: Colors.purple),
          Container(color: Colors.yellow),
          Container(color: Colors.redAccent),
          Container(color: Colors.purpleAccent),
          Container(color: Colors.purple),
          Container(color: Colors.yellow),
          Container(color: Colors.redAccent),
          Container(color: Colors.purpleAccent),
          Container(color: Colors.purple),
          Container(color: Colors.yellow),
          Container(color: Colors.redAccent),
          Container(color: Colors.purpleAccent),
          Container(color: Colors.purple),
          Container(color: Colors.yellow),
          Container(color: Colors.redAccent),
        ],
      ),
    );
  }
}
  • ScrollPhysics physics (确定可滚动控件的物理特性)
    • BouncingScrollPhysics (允许超出边界 - 反弹效果)
    • ClampingScrollPhysics (防止超出边界 - 夹住效果)
    • AlwaysScrollableScrollPhysics (始终响应滚动)
    • NeverScrollableScrollPhysics (不响应滚动)
class GridViewCountDemo extends StatelessWidget {
  const GridViewCountDemo({super.key});

  @override
  Widget build(BuildContext context) {
    return Container(
      color: Colors.grey[100],
      child: GridView.count(
        crossAxisCount: 2, // 指定列数
        mainAxisSpacing: 20,
        crossAxisSpacing: 20,
        // childAspectRatio: 1.5,
        padding: const EdgeInsets.symmetric(horizontal: 20),
        children: List.generate(10, (index) => Image.asset('images/1.jpg')),
      ),
    );
  }
}

class GridViewExtendDemo extends StatelessWidget {
  const GridViewExtendDemo({super.key});

  @override
  Widget build(BuildContext context) {
    return Container(
      color: Colors.grey[100],
      child: GridView.extent(
        maxCrossAxisExtent: 200, // 子元素宽度
        mainAxisSpacing: 20,
        crossAxisSpacing: 20,
        padding: const EdgeInsets.symmetric(horizontal: 20),
        children: List.generate(10, (index) => Image.asset('images/1.jpg')),
      ),
    );
  }
}

class GridViewBuilderDemo extends StatelessWidget {
  GridViewBuilderDemo({super.key});

  final List<dynamic> _titles = [
    Container(color: Colors.cyan),
    Container(color: Colors.blueAccent),
    Container(color: Colors.purpleAccent),
    Container(color: Colors.purple),
    Container(color: Colors.yellow),
    Container(color: Colors.redAccent),
    Container(color: Colors.purpleAccent),
    Container(color: Colors.purple),
    Container(color: Colors.yellow),
    Container(color: Colors.redAccent),
    Container(color: Colors.purpleAccent),
    Container(color: Colors.purple),
    Container(color: Colors.yellow),
    Container(color: Colors.redAccent),
    Container(color: Colors.purpleAccent),
    Container(color: Colors.purple),
    Container(color: Colors.yellow),
    Container(color: Colors.redAccent),
  ];

  @override
  Widget build(BuildContext context) {
    return Container(
      color: Colors.grey[100],
      padding: const EdgeInsets.all(20),
      child: GridView.builder(
        gridDelegate: const SliverGridDelegateWithFixedCrossAxisCount(
          crossAxisCount: 2,
          mainAxisSpacing: 20,
          crossAxisSpacing: 20,
          childAspectRatio: 1.0,
        ),
        itemCount: _titles.length,
        itemBuilder: (context, index) {
          return _titles[index];
        },
        physics: const BouncingScrollPhysics(), // 反弹效果
        // physics: const ClampingScrollPhysics(), // 夹住的效果
        // physics: const AlwaysScrollableScrollPhysics(), // 滚动
        // physics: const NeverScrollableScrollPhysics(), // 禁止滚动
      ),
    );
  }
}

2.6. 其他

2.6.1. Cupertino

  • Material
    • 安卓风格的组件
    • import 'package:flutter/material.dart'
  • Cupertino
import 'package:flutter/material.dart';
import 'package:flutter/cupertino.dart';
import 'dart:io';

class Home extends StatelessWidget {
  const Home({super.key});

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      // 头部导航条
      appBar: AppBar(
        // 标题
        title: const Text('Cupertino'),
        // 左侧插槽
        leading: const Icon(Icons.menu),
        // 右侧插槽
        actions: const [
          Icon(Icons.add_circle_outline),
        ],
        // 设置下边阴影
        elevation: 0.0,
        // 设置标题居中
        centerTitle: true,
      ),
      body: const MyBody(),
    );
  }
}

class MyBody extends StatelessWidget {
  const MyBody({super.key});

  @override
  Widget build(BuildContext context) {
    dynamic dialogBox;
    // 判断当前平台的信息
    if (Platform.isIOS) {
      // 加载IOS风格的组件
      dialogBox = const CupertinoDemo();
    } else if (Platform.isAndroid) {
      // 加载安卓风格的组件
      dialogBox = const MaterialDemo();
    }

    return Container(
      color: Colors.grey[100],
      child: Column(
        mainAxisAlignment: MainAxisAlignment.spaceAround,
        children: [
          dialogBox,

          // 安卓风格的组件
          const Text('Material - 安卓风格'),
          const MaterialDemo(),

          // IOS风格的组件
          const Text('Cupertino - IOS风格'),
          const CupertinoDemo(),
        ],
      ),
    );
  }
}

class MaterialDemo extends StatelessWidget {
  const MaterialDemo({super.key});

  @override
  Widget build(BuildContext context) {
    return Container(
      color: Colors.green,
      child: AlertDialog(
        title: const Text('提示'),
        content: const Text('您确定要删除吗?'),
        actions: [
          TextButton(
            child: const Text('取消'),
            onPressed: () {
              print('取消的逻辑');
            },
          ),
          TextButton(
            child: const Text('确认'),
            onPressed: () {
              print('确认的逻辑');
            },
          ),
        ],
      ),
    );
  }
}

class CupertinoDemo extends StatelessWidget {
  const CupertinoDemo({super.key});

  @override
  Widget build(BuildContext context) {
    return Container(
      color: Colors.blue,
      child: CupertinoAlertDialog(
        title: const Text('提示'),
        content: const Text('您确定要删除吗?'),
        actions: [
          CupertinoDialogAction(
            child: const Text('取消'),
            onPressed: () {
              print('取消的逻辑');
            },
          ),
          CupertinoDialogAction(
            child: const Text('确认'),
            onPressed: () {
              print('确认的逻辑');
            },
          ),
        ],
      ),
    );
  }
}

2.6.2. SafeArea

  • SafeArea 可以有效解决异形屏的问题 (刘海屏)

image.png

image.png

3. 第三方组件

3.1. dio

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

class Home extends StatelessWidget {
  const Home({super.key});

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      // 头部导航条
      appBar: AppBar(
        // 标题
        title: const Text('Dio'),
        // 左侧插槽
        leading: const Icon(Icons.menu),
        // 右侧插槽
        actions: const [
          Icon(Icons.add_circle_outline),
        ],
        // 设置下边阴影
        elevation: 0.0,
        // 设置标题居中
        centerTitle: true,
      ),
      body: const DioDemo(),
    );
  }
}

class DioDemo extends StatelessWidget {
  const DioDemo({super.key});

  @override
  Widget build(BuildContext context) {
    return Center(
      child: ElevatedButton(
        child: const Text('点击发送请求'),
        onPressed: () {
          // 调用 HTTP 请求
          getIpAdress();
        },
      ),
    );
  }

  void getIpAdress() async {
    try {
      const url = 'https://httpbin.org/ip';
      Response response = await Dio().get(url);
      String ip = response.data['origin'];
      print(ip);
    } catch (e) {
      print(e);
    }
  }
}

3.2. flutter_swiper

  • Flutter 中最好的轮播组件,适配 Android 和 ios
  • 使用步骤
    • pubsepc.yaml 中添加 flutter _swiper 依赖
    • 安装依赖 (pub get| flutter packages get | VS Code 中保存配置,自动下载)
    • 引入 import 'package:flutter_swiper/flutter_swiper.dart';
    • 使用
import 'package:flutter/material.dart';
import 'package:flutter_swiper_null_safety/flutter_swiper_null_safety.dart';

class Home extends StatelessWidget {
  const Home({super.key});

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      // 头部导航条
      appBar: AppBar(
        // 标题
        title: const Text('Flutter Swiper'),
        // 左侧插槽
        leading: const Icon(Icons.menu),
        // 右侧插槽
        actions: const [
          Icon(Icons.add_circle_outline),
        ],
        // 设置下边阴影
        elevation: 0.0,
        // 设置标题居中
        centerTitle: true,
      ),
      body: FlutterSwiperDemo(),
    );
  }
}

class FlutterSwiperDemo extends StatelessWidget {
  FlutterSwiperDemo({super.key});

  final List<String> imgs = [
    'images/1.jpg',
    'images/2.webp',
    'images/客厅.png',
  ];

  @override
  Widget build(BuildContext context) {
    return ListView(
      children: [
        Container(
          height: 200,
          color: Colors.grey[100],
          child: Swiper(
            itemCount: imgs.length,
            pagination: const SwiperPagination(), // 轮播图的指示点
            control: const SwiperControl(), // 左右箭头控制器
            itemBuilder: (context, index) {
              return Image.asset(imgs[index], fit: BoxFit.cover);
            },
          ),
        ),
        Container(
          height: 200,
          color: Colors.blue[100],
          child: Swiper(
            itemCount: imgs.length,
            viewportFraction: 0.7,
            scale: 0.7,
            itemBuilder: (context, index) {
              return Image.asset(imgs[index], fit: BoxFit.cover);
            },
          ),
        ),
        Container(
          height: 200,
          color: Colors.red[100],
          child: Swiper(
            itemCount: imgs.length,
            itemWidth: 300,
            layout: SwiperLayout.STACK,
            itemBuilder: (context, index) {
              return Image.asset(imgs[index], fit: BoxFit.cover);
            },
          ),
        ),
        Container(
          height: 200,
          color: Colors.yellow[100],
          child: Swiper(
            itemCount: imgs.length,
            itemWidth: 300,
            itemHeight: 200,
            layout: SwiperLayout.TINDER,
            itemBuilder: (context, index) {
              return Image.asset(imgs[index], fit: BoxFit.cover);
            },
          ),
        ),
      ],
    );
  }
}

3.3. shared_preferences

  • shared_preferences 是一个本地数据缓存库 (类似 AsyncStorage)

  • 使用步骤

    • pubsepc.yaml 中添加 shared _preferences 依赖
    • 安装依赖 (pub get| flutter packages get | VS Code 中保存配置,自动下载)
    • 引入
    • import 'package:shared_preferences/shared_preferences.dart';
    • 使用 SharedPreferences prefs = await SharedPreferences.getlnstance();
    • setString(key, value)
    • remove(key) | clear()
    • 更改就是重新设置数据
    • setString(key, value)
    • getString(key)
import 'package:flutter/material.dart';
import 'package:shared_preferences/shared_preferences.dart';

class Home extends StatelessWidget {
  const Home({super.key});

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      // 头部导航条
      appBar: AppBar(
        // 标题
        title: const Text('shared_preferences'),
        // 左侧插槽
        leading: const Icon(Icons.menu),
        // 右侧插槽
        actions: const [
          Icon(Icons.add_circle_outline),
        ],
        // 设置下边阴影
        elevation: 0.0,
        // 设置标题居中
        centerTitle: true,
      ),
      body: SharedPreferencesDemo(),
    );
  }
}

class SharedPreferencesDemo extends StatelessWidget {
  const SharedPreferencesDemo({super.key});

  @override
  Widget build(BuildContext context) {
    return Container(
      width: double.infinity,
      color: Colors.black,
      child: Column(
        mainAxisAlignment: MainAxisAlignment.spaceAround,
        crossAxisAlignment: CrossAxisAlignment.center,
        children: [
          ElevatedButton(
            onPressed: _incrementCounter,
            child: const Text('递增'),
          ),
          ElevatedButton(
            onPressed: _decrementCounter,
            child: const Text('递减'),
          ),
          ElevatedButton(
            onPressed: _removeCounter,
            child: const Text('删除'),
          ),
          ElevatedButton(
            onPressed: _addMyContent,
            child: const Text('设置字符串'),
          ),
          ElevatedButton(
            onPressed: _getMyContent,
            child: const Text('获取字符串'),
          ),
          ElevatedButton(
            onPressed: _clearContent,
            child: const Text('清空'),
          ),
        ],
      ),
    );
  }

  _incrementCounter() async {
    // 获取保存实例
    final SharedPreferences prefs = await SharedPreferences.getInstance();
    int counter = (prefs.getInt('counter') ?? 0) + 1;
    print('Pressed: $counter times.');
    await prefs.setInt('counter', counter);
  }

  _decrementCounter() async {
    final SharedPreferences prefs = await SharedPreferences.getInstance();
    int counter = prefs.getInt('counter') ?? 0;
    if (counter > 0) {
      counter--;
    }
    print('Pressed: $counter times.');
    await prefs.setInt('counter', counter);
  }

  _removeCounter() async {
    final SharedPreferences prefs = await SharedPreferences.getInstance();
    await prefs.remove('counter');
    int counter = (prefs.getInt('counter') ?? 0) + 1;
    print('Pressed: $counter times.');
  }

  _addMyContent() async {
    final SharedPreferences prefs = await SharedPreferences.getInstance();
    await prefs.setString('name', '张三');
    String name = prefs.getString('name') ?? '';
    print('设置的name: $name');
  }

  _getMyContent() async {
    final SharedPreferences prefs = await SharedPreferences.getInstance();
    String name = prefs.getString('name') ?? '';
    print('获取的name: $name');
  }

  _clearContent() async {
    final SharedPreferences prefs = await SharedPreferences.getInstance();
    await prefs.clear();
  }
}

4. 状态管理

4.1. StatefulWidget

  • Flutter 中的组件,按状态划分
    • StatelessWidget(无状态组件)
    • Statefulwidget (状态组件)
  • 按状态作用域划分
    • 组件内私有状态(StatefulWidget)
    • 跨组件状态共享 (InheritedWidgetProvider)
    • 全局状态 (Reduxfish-reduxMobx .....)
  • 状态组件的组成
    • StatefulWidget (组件本身不可变 - @immutable)
    • state (将变化的状态放到 state 中维护)
import 'package:flutter/material.dart';

class Home extends StatelessWidget {
  const Home({super.key});

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      // 头部导航条
      appBar: AppBar(
        // 标题
        title: const Text('StatefulWidget'),
        // 左侧插槽
        leading: const Icon(Icons.menu),
        // 右侧插槽
        actions: const [
          Icon(Icons.add_circle_outline),
        ],
        // 设置下边阴影
        elevation: 0.0,
        // 设置标题居中
        centerTitle: true,
      ),
      body: const MyState(),
    );
  }
}

class MyState extends StatefulWidget {
  const MyState({super.key});

  @override
  State<MyState> createState() => _MyStateState();
}

class _MyStateState extends State<MyState> {
  int _num = 0;

  void _increment() {
    setState(() {
      _num++;
    });
  }

  void _decrement() {
    setState(() {
      _num--;
    });
  }

  @override
  Widget build(BuildContext context) {
    return Center(
      child: Column(
        children: [
          ElevatedButton(
            onPressed: _decrement,
            child: const Icon(Icons.exposure_minus_1),
          ),
          Padding(
            padding: const EdgeInsets.all(20.0),
            child: Text('$_num'),
          ),
          ElevatedButton(
            onPressed: _increment,
            child: const Icon(Icons.add),
          ),
        ],
      ),
    );
  }
}

4.2. DataTable

  • DataTable 是 Flutter 中的表格
    • columns (声明表头列表)
      • DataColumn (表头单元格)
    • rows (声明数据列表)
      • DataRow (一行数据)
        • DataCell (数据单元格)
    • 其他属性

image.png

import 'package:flutter/material.dart';

class Home extends StatelessWidget {
  const Home({super.key});

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      // 头部导航条
      appBar: AppBar(
        // 标题
        title: const Text('DataTable'),
        // 左侧插槽
        leading: const Icon(Icons.menu),
        // 右侧插槽
        actions: const [
          Icon(Icons.add_circle_outline),
        ],
        // 设置下边阴影
        elevation: 0.0,
        // 设置标题居中
        centerTitle: true,
      ),
      body: const UserListDemo(),
    );
  }
}

class User {
  String name;
  int age;
  bool selected;

  User(this.name, this.age, {this.selected = false});
}

class UserListDemo extends StatefulWidget {
  const UserListDemo({super.key});

  @override
  State<UserListDemo> createState() => _UserListDemoState();
}

class _UserListDemoState extends State<UserListDemo> {
  List<User> data = [
    User('张三', 18),
    User('张三丰', 180, selected: true),
    User('张翠山', 30),
    User('张无忌', 40),
  ];

  var _sortAscending = true;

  List<DataRow> _getUserRows() {
    List<DataRow> dataRows = [];
    for (int i = 0; i < data.length; i++) {
      dataRows.add(DataRow(
        selected: data[i].selected,
        onSelectChanged: (selected) {
          setState(() {
            data[i].selected = selected!;
          });
        },
        cells: [
          DataCell(Text(data[i].name)),
          DataCell(Text('${data[i].age}')),
          const DataCell(Text('男')),
          const DataCell(Text('---')),
        ],
      ));
    }

    return dataRows;
  }

  @override
  Widget build(BuildContext context) {
    return Container(
      width: double.infinity,
      height: double.infinity,
      color: Colors.pink[100],
      child: SingleChildScrollView(
        scrollDirection: Axis.horizontal,
        child: DataTable(
          // 对那一列进行排序
          sortColumnIndex: 1,
          sortAscending: _sortAscending,
          // 行高
          dataRowHeight: 100,
          // 水平方向外边距
          horizontalMargin: 20,
          // 列间距
          columnSpacing: 100,
          columns: [
            const DataColumn(label: Text('姓名')),
            DataColumn(
              label: const Text('年龄'),
              // 可排序
              numeric: true,
              onSort: (int columnIndex, bool asscending) {
                setState(() {
                  _sortAscending = asscending;
                  if (asscending) {
                    data.sort((a, b) => a.age.compareTo(b.age));
                  } else {
                    data.sort((a, b) => b.age.compareTo(a.age));
                  }
                });
              },
            ),
            const DataColumn(label: Text('性别')),
            const DataColumn(label: Text('简介')),
          ],
          rows: _getUserRows(),
          // const [
          //   DataRow(
          //     cells: [
          //       DataCell(Text('张三')),
          //       DataCell(Text('20')),
          //       DataCell(Text('男')),
          //       DataCell(Text('无敌是多么的寂寞')),
          //     ],
          //   ),
          //   DataRow(
          //     cells: [
          //       DataCell(Text('李四')),
          //       DataCell(Text('18')),
          //       DataCell(Text('男')),
          //       DataCell(Text('无敌')),
          //     ],
          //   ),
          // ],
        ),
      ),
    );
  }
}

4.3. InheritedWidget

  • what: 提供了沿树向下,共享数据的功能
    • 即子组件可以获取父组件(Inheritedwidget 的子类)的数据
  • Why:
    • 依赖构造函数传递数据的方式不能满足业务需求
    • 所以,需要一个新的,更好的跨组件数据传输方案
  • How:
    • BuildContext.dependOnlnheritedWidgetOfExactType<MylnheritedWidget>()

image.png

image.png

import 'package:flutter/material.dart';

class Home extends StatelessWidget {
  const Home({super.key});

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      // 头部导航条
      appBar: AppBar(
        // 标题
        title: const Text('StatefulWidget'),
        // 左侧插槽
        leading: const Icon(Icons.menu),
        // 右侧插槽
        actions: const [
          Icon(Icons.add_circle_outline),
        ],
        // 设置下边阴影
        elevation: 0.0,
        // 设置标题居中
        centerTitle: true,
      ),
      body: const MyState(),
    );
  }
}

class MyState extends StatefulWidget {
  const MyState({super.key});

  @override
  State<MyState> createState() => _MyStateState();
}

class _MyStateState extends State<MyState> {
  int _num = 0;

  void _increment() {
    setState(() {
      _num++;
    });
  }

  void _decrement() {
    setState(() {
      _num--;
    });
  }

  @override
  Widget build(BuildContext context) {
    return ShareDataWidget(
      num: _num,
      child: Center(
        child: Column(
          children: [
            ElevatedButton(
              onPressed: _decrement,
              child: const Icon(Icons.exposure_minus_1),
            ),
            const Padding(
              padding: EdgeInsets.all(20.0),
              // child: Text('$_num'),
              // 跨组件访问数据
              child: MyCounter(),
            ),
            ElevatedButton(
              onPressed: _increment,
              child: const Icon(Icons.add),
            ),
          ],
        ),
      ),
    );
  }
}

class MyCounter extends StatefulWidget {
  const MyCounter({super.key});

  @override
  State<MyCounter> createState() => _MyCounterState();
}

class _MyCounterState extends State<MyCounter> {
  @override
  Widget build(BuildContext context) {
    // 获取共享数据
    return Text('${ShareDataWidget.of(context)!.num}');
  }
}

// 创建数据共享组件
class ShareDataWidget extends InheritedWidget {
  final int num;
  final Widget child;

  const ShareDataWidget({super.key, required this.child, required this.num})
      : super(child: child);

  static ShareDataWidget? of(BuildContext context) {
    return context.dependOnInheritedWidgetOfExactType<ShareDataWidget>();
  }

  @override
  bool updateShouldNotify(ShareDataWidget oldWidget) {
    return true;
  }
}

4.4. 生命周期

  • initState() 组件对象插入到元素树中时
  • didchangeDependencies() 当前状态对象的依赖改变时
  • build() 组件渲染时
  • setState() 组件对象的内部状态变更时
  • didUpdateWidget() 组件配置更新时
  • deactivate() 组件对象在元素树中暂时移除时
  • dispose() 组件对象在元素树中永远移除时

image.png

image.png

import 'package:flutter/material.dart';

class Home extends StatelessWidget {
  const Home({super.key});

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      // 头部导航条
      appBar: AppBar(
        // 标题
        title: const Text('Life Cycle'),
        // 左侧插槽
        leading: const Icon(Icons.menu),
        // 右侧插槽
        actions: const [
          Icon(Icons.add_circle_outline),
        ],
        // 设置下边阴影
        elevation: 0.0,
        // 设置标题居中
        centerTitle: true,
      ),
      body: const MyState(),
    );
  }
}

class MyState extends StatefulWidget {
  const MyState({super.key});

  @override
  State<MyState> createState() => _MyStateState();
}

class _MyStateState extends State<MyState> {
  var _num;

  // 状态初始化时调用,通常根据后台接口的返回数据对状态进行初始化
  @override
  void initState() {
    super.initState();
    print('initState');
    _num = 1;
  }

  @override
  void didChangeDependencies() {
    // TODO: implement didChangeDependencies
    super.didChangeDependencies();
    print('didChangeDependencies');
  }

  @override
  void didUpdateWidget(covariant MyState oldWidget) {
    // TODO: implement didUpdateWidget
    super.didUpdateWidget(oldWidget);
    print('didUpdateWidget');
  }

  @override
  void deactivate() {
    // TODO: implement deactivate
    super.deactivate();
    print('deactivate');
  }

  @override
  void dispose() {
    // TODO: implement dispose
    super.dispose();
    print('dispose');
  }

  void _increment() {
    setState(() {
      print('setState');
      _num++;
    });
  }

  void _decrement() {
    setState(() {
      print('setState');
      _num--;
    });
  }

  @override
  Widget build(BuildContext context) {
    print('build');

    return Center(
      child: Column(
        children: [
          ElevatedButton(
            onPressed: _decrement,
            child: const Icon(Icons.exposure_minus_1),
          ),
          Padding(
            padding: const EdgeInsets.all(20.0),
            child: Text('$_num'),
          ),
          ElevatedButton(
            onPressed: _increment,
            child: const Icon(Icons.add),
          ),
        ],
      ),
    );
  }
}

4.5. Provider

  • Provider 是对 Inheritedwidget 的封装
  • 优点:
    • 简化资源的分配与处置
    • 懒加载
  • Provider 的实现原理?

image.png

image.png

使用

  • 安装 Provider (第三方库)
  • 创建数据模型 (T extends ChangeNotifier)
  • 创建 Provider(注册数据模型)
    • Provider() // 不会被要求随着变动而变动
    • ChangeNotifierProvider() // 随着某些数据改变而被通知更新
  • 获取数据模型并更新 UI
    • 通过上下文 (BuildContext)
    • 通过静态方法 (Provider.of<T>(context))

image.png

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

class Home extends StatelessWidget {
  const Home({super.key});

  @override
  Widget build(BuildContext context) {
    // 2. 创建 Provider (注册数据模型)
    return ChangeNotifierProvider(
      create: (BuildContext context) => LikesModel(),
      child: Scaffold(
        // 头部导航条
        appBar: AppBar(
          // 标题
          title: const Text('Provider'),
          // 左侧插槽
          leading: const Icon(Icons.menu),
          // 右侧插槽
          actions: const [
            Icon(Icons.add_circle_outline),
          ],
          // 设置下边阴影
          elevation: 0.0,
          // 设置标题居中
          centerTitle: true,
        ),
        body: MyHomePage(),
      ),
    );
  }
}

// 1. 创建数据模型
class LikesModel extends ChangeNotifier {
  int _count = 0;

  int get count => _count;

  incrementCounter() {
    // 累加
    _count++;

    // 通过 UI 更新
    notifyListeners();
  }
}

class MyHomePage extends StatelessWidget {
  const MyHomePage({super.key});

  @override
  Widget build(BuildContext context) {
    return Container(
      width: double.infinity,
      padding: const EdgeInsets.only(top: 100),
      color: Colors.blue[100],
      child: Column(
        children: [
          // 3. 在子组件中使用数据模型
          Text('${context.watch<LikesModel>().count}'),
          TextButton(
            // 3. 在子组件中使用数据模型
            onPressed: Provider.of<LikesModel>(context).incrementCounter,
            child: const Icon(Icons.thumb_up),
          ),
        ],
      ),
    );
  }
}

5. 路由与导航

5.1. 路由简介

  • Route
    • 一个路由是一个屏幕或页面的抽象
  • Navigator
    • 管理路由的组件。Navigator 可以通过路由入栈和出栈来实现页面之间的跳转
    • 常用属性
      • initialRoute:初始路由,即默认页面
      • onGenerateRoute:动态路由 (根据规则,匹配动态路由)
      • onUnknownRoute: 未知路由,也就是 404
      • routes: 路由集合

5.2. 匿名路由

  • Navigator
    • push (跳转到指定组件)
Navigator.push(
    context,
    MaterialPageRoute(builder:(context) => 组件名称())
)
  • pop (回退)
    • Navigator.pop(context)
import 'package:flutter/material.dart';

class Home extends StatelessWidget {
  const Home({super.key});

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      // 头部导航条
      appBar: AppBar(
        // 标题
        title: const Text('匿名路由'),
        // 左侧插槽
        leading: const Icon(Icons.menu),
        // 右侧插槽
        actions: const [
          Icon(Icons.add_circle_outline),
        ],
        // 设置下边阴影
        elevation: 0.0,
        // 设置标题居中
        centerTitle: true,
      ),
      body: const HomePage(),
    );
  }
}

class HomePage extends StatelessWidget {
  const HomePage({super.key});

  @override
  Widget build(BuildContext context) {
    return Container(
      child: Center(
        child: ElevatedButton(
          onPressed: () {
            Navigator.push(
              context,
              MaterialPageRoute(builder: (context) => const Product()),
            );
          },
          child: const Text('跳转到商品页面'),
        ),
      ),
    );
  }
}

class Product extends StatelessWidget {
  const Product({super.key});

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      // 头部导航条
      appBar: AppBar(
        // 标题
        title: const Text('商品页面'),
        // 左侧插槽
        leading: const Icon(Icons.menu),
        // 右侧插槽
        actions: const [
          Icon(Icons.add_circle_outline),
        ],
        // 设置下边阴影
        elevation: 0.0,
        // 设置标题居中
        centerTitle: true,
      ),
      body: Container(
        child: Center(
          child: ElevatedButton(
            onPressed: () => Navigator.pop(context),
            child: const Text('返回'),
          ),
        ),
      ),
    );
  }
}

5.3. 命名路由

  • 声明路由
    • routes 路由表 (Map 类型)
    • initialRoute (初始路由)
    • onUnknownRoute (未知路由 - 404)
  • 跳转到命名路由
    • NavigatorpushNamed(context,路由名称');

image.png

// main.dart

import 'package:flutter/material.dart';
import '09_navigation/02_namedRoute.dart';

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

class MyApp extends StatelessWidget {
  const MyApp({super.key});

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: '学习 Flutter',
      // home: const Home(),
      // 声明路由
      routes: {
        'home': (context) => const Home(),
        'product': (context) => const Product(),
      },
      // 默认加载的路由
      initialRoute: 'home',
      onUnknownRoute: (RouteSettings settings) => MaterialPageRoute(
        builder: (context) => const UnkonwPage(),
      ),
      // 设置全局主题
      theme: ThemeData(
          // 自定义文本样式
          // fontFamily: 'CustomFamily',
          ),
      // 是否显示调试标记
      debugShowCheckedModeBanner: false,
    );
  }
}
import 'package:flutter/material.dart';

class Home extends StatelessWidget {
  const Home({super.key});

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      // 头部导航条
      appBar: AppBar(
        // 标题
        title: const Text('首页'),
        // 左侧插槽
        leading: const Icon(Icons.menu),
        // 右侧插槽
        actions: const [
          Icon(Icons.add_circle_outline),
        ],
        // 设置下边阴影
        elevation: 0.0,
        // 设置标题居中
        centerTitle: true,
      ),
      body: Center(
        child: Column(
          children: [
            ElevatedButton(
              onPressed: () => Navigator.pushNamed(context, 'product'),
              child: const Text('跳转'),
            ),
            ElevatedButton(
              onPressed: () => Navigator.pushNamed(context, 'user'),
              child: const Text('未知路由'),
            ),
          ],
        ),
      ),
    );
  }
}

class Product extends StatelessWidget {
  const Product({super.key});

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      // 头部导航条
      appBar: AppBar(
        // 标题
        title: const Text('商品页面'),
        // 左侧插槽
        leading: const Icon(Icons.menu),
        // 右侧插槽
        actions: const [
          Icon(Icons.add_circle_outline),
        ],
        // 设置下边阴影
        elevation: 0.0,
        // 设置标题居中
        centerTitle: true,
      ),
      body: Center(
        child: Column(
          children: [
            ElevatedButton(
              onPressed: () => Navigator.pop(context),
              child: const Text('返回'),
            ),
          ],
        ),
      ),
    );
  }
}

class UnkonwPage extends StatelessWidget {
  const UnkonwPage({super.key});

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      // 头部导航条
      appBar: AppBar(
        // 标题
        title: const Text('404'),
        // 左侧插槽
        leading: const Icon(Icons.menu),
        // 右侧插槽
        actions: const [
          Icon(Icons.add_circle_outline),
        ],
        // 设置下边阴影
        elevation: 0.0,
        // 设置标题居中
        centerTitle: true,
      ),
      body: Center(
        child: Column(
          children: [
            ElevatedButton(
              onPressed: () => Navigator.pop(context),
              child: const Text('返回'),
            ),
          ],
        ),
      ),
    );
  }
}

5.4. 动态路由

  • 动态路由是指,通过 onGenerateRoute 属性指定的路由

image.png

// main.dart

import 'package:flutter/material.dart';
import '09_navigation/03_onGenerateRoute.dart';

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

class MyApp extends StatelessWidget {
  const MyApp({super.key});

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: '学习 Flutter',
      // home: const Home(),
      // 声明命名路由
      // routes: {
      //   'home': (context) => const Home(),
      //   'product': (context) => const Product(),
      // },
      // // 默认加载的路由
      // initialRoute: 'home',
      // onUnknownRoute: (RouteSettings settings) => MaterialPageRoute(
      //   builder: (context) => const UnkonwPage(),
      // ),
      onGenerateRoute: (RouteSettings setting) {
        print(setting);
        // 匹配首页 /
        if (setting.name == '/') {
          return MaterialPageRoute(builder: (context) => const Home());
        }

        if (setting.name == '/product') {
          return MaterialPageRoute(builder: (context) => const Product());
        }

        // 匹配 /product/:id
        var uri = Uri.parse(setting.name!);
        print(uri.pathSegments);
        if (uri.pathSegments.length == 2 && uri.pathSegments[0] == 'product') {
          String id = uri.pathSegments[1];
          return MaterialPageRoute(
            builder: (context) => ProductDetail(id: id),
          );
        }

        return MaterialPageRoute(builder: (context) => const UnkonwPage());
      },
      // 设置全局主题
      theme: ThemeData(
          // 自定义文本样式
          // fontFamily: 'CustomFamily',
          ),
      // 是否显示调试标记
      debugShowCheckedModeBanner: false,
    );
  }
}
import 'package:flutter/material.dart';

class Home extends StatelessWidget {
  const Home({super.key});

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      // 头部导航条
      appBar: AppBar(
        // 标题
        title: const Text('首页'),
        // 左侧插槽
        leading: const Icon(Icons.menu),
        // 右侧插槽
        actions: const [
          Icon(Icons.add_circle_outline),
        ],
        // 设置下边阴影
        elevation: 0.0,
        // 设置标题居中
        centerTitle: true,
      ),
      body: Center(
        child: Column(
          children: [
            ElevatedButton(
              onPressed: () => Navigator.pushNamed(context, '/product'),
              child: const Text('跳转'),
            ),
            ElevatedButton(
              onPressed: () => Navigator.pushNamed(context, '/product/1'),
              child: const Text('商品1'),
            ),
            ElevatedButton(
              onPressed: () => Navigator.pushNamed(context, '/product/2'),
              child: const Text('商品2'),
            ),
            ElevatedButton(
              onPressed: () => Navigator.pushNamed(context, 'user'),
              child: const Text('未知路由'),
            ),
          ],
        ),
      ),
    );
  }
}

class Product extends StatelessWidget {
  const Product({super.key});

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      // 头部导航条
      appBar: AppBar(
        // 标题
        title: const Text('商品页面'),
        // 左侧插槽
        leading: const Icon(Icons.menu),
        // 右侧插槽
        actions: const [
          Icon(Icons.add_circle_outline),
        ],
        // 设置下边阴影
        elevation: 0.0,
        // 设置标题居中
        centerTitle: true,
      ),
      body: Center(
        child: Column(
          children: [
            ElevatedButton(
              onPressed: () => Navigator.pop(context),
              child: const Text('返回'),
            ),
          ],
        ),
      ),
    );
  }
}

class ProductDetail extends StatelessWidget {
  // /product/1
  final String id;

  const ProductDetail({super.key, required this.id});

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      // 头部导航条
      appBar: AppBar(
        // 标题
        title: const Text('商品详情页面'),
        // 左侧插槽
        leading: const Icon(Icons.menu),
        // 右侧插槽
        actions: const [
          Icon(Icons.add_circle_outline),
        ],
        // 设置下边阴影
        elevation: 0.0,
        // 设置标题居中
        centerTitle: true,
      ),
      body: Center(
        child: Column(
          children: [
            Text('当前商品的id是:$id'),
            ElevatedButton(
              onPressed: () => Navigator.pop(context),
              child: const Text('返回'),
            ),
          ],
        ),
      ),
    );
  }
}

class UnkonwPage extends StatelessWidget {
  const UnkonwPage({super.key});

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      // 头部导航条
      appBar: AppBar(
        // 标题
        title: const Text('404'),
        // 左侧插槽
        leading: const Icon(Icons.menu),
        // 右侧插槽
        actions: const [
          Icon(Icons.add_circle_outline),
        ],
        // 设置下边阴影
        elevation: 0.0,
        // 设置标题居中
        centerTitle: true,
      ),
      body: Center(
        child: Column(
          children: [
            ElevatedButton(
              onPressed: () => Navigator.pop(context),
              child: const Text('返回'),
            ),
          ],
        ),
      ),
    );
  }
}

5.5. 路由传参

5.5.1. 路由传参 -匿名路由

  • 路由中声明参数
    • Navigator.push
  • 组件中接收参数

image.png

5.5.2. 路由传参-命名路由

  • 路由中声明参数
    • Navigator.pushNamed(context, routename, (arguments))
  • 组件中接收参数
    • ModalRoute.of(context).settings.arguments

image.png

// main.dart

import 'package:flutter/material.dart';
import '09_navigation/04_arguments.dart';

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

class MyApp extends StatelessWidget {
  const MyApp({super.key});

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: '学习 Flutter',
      // home: const Home(),
      // 声明命名路由
      routes: {
        'home': (context) => const Home(),
        'product': (context) => const Product(),
        'productDetail': (context) => const ProductDetail(),
      },
      // 默认加载的路由
      initialRoute: 'home',
      onUnknownRoute: (RouteSettings settings) => MaterialPageRoute(
        builder: (context) => const UnkonwPage(),
      ),
      // onGenerateRoute: (RouteSettings setting) {
      //   print(setting);
      //   // 匹配首页 /
      //   if (setting.name == '/') {
      //     return MaterialPageRoute(builder: (context) => const Home());
      //   }

      //   if (setting.name == '/product') {
      //     return MaterialPageRoute(builder: (context) => const Product());
      //   }

      //   // 匹配 /product/:id
      //   var uri = Uri.parse(setting.name!);
      //   print(uri.pathSegments);
      //   if (uri.pathSegments.length == 2 && uri.pathSegments[0] == 'product') {
      //     String id = uri.pathSegments[1];
      //     return MaterialPageRoute(
      //       builder: (context) => ProductDetail(id: id),
      //     );
      //   }

      //   return MaterialPageRoute(builder: (context) => const UnkonwPage());
      // },
      // 设置全局主题
      theme: ThemeData(
          // 自定义文本样式
          // fontFamily: 'CustomFamily',
          ),
      // 是否显示调试标记
      debugShowCheckedModeBanner: false,
    );
  }
}
import 'package:flutter/material.dart';

class Home extends StatelessWidget {
  const Home({super.key});

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      // 头部导航条
      appBar: AppBar(
        // 标题
        title: const Text('首页'),
        // 左侧插槽
        leading: const Icon(Icons.menu),
        // 右侧插槽
        actions: const [
          Icon(Icons.add_circle_outline),
        ],
        // 设置下边阴影
        elevation: 0.0,
        // 设置标题居中
        centerTitle: true,
      ),
      body: Center(
        child: Column(
          children: [
            ElevatedButton(
              onPressed: () => Navigator.pushNamed(
                context,
                'product',
                arguments: {
                  'title': '我是主页传过来的参数',
                },
              ),
              child: const Text('跳转到商品页面'),
            ),
            ElevatedButton(
              onPressed: () => Navigator.pushNamed(
                context,
                'productDetail',
                arguments: {
                  'id': 1,
                },
              ),
              child: const Text('商品1'),
            ),
            ElevatedButton(
              onPressed: () => Navigator.pushNamed(
                context,
                'productDetail',
                arguments: {
                  'id': 2,
                },
              ),
              child: const Text('商品2'),
            ),
            ElevatedButton(
              onPressed: () => Navigator.pushNamed(context, 'user'),
              child: const Text('未知路由'),
            ),
          ],
        ),
      ),
    );
  }
}

class Product extends StatelessWidget {
  const Product({super.key});

  @override
  Widget build(BuildContext context) {
    final Map arguments = ModalRoute.of(context)?.settings.arguments as Map;

    return Scaffold(
      // 头部导航条
      appBar: AppBar(
        // 标题
        title: const Text('商品页面'),
        // 左侧插槽
        leading: const Icon(Icons.menu),
        // 右侧插槽
        actions: const [
          Icon(Icons.add_circle_outline),
        ],
        // 设置下边阴影
        elevation: 0.0,
        // 设置标题居中
        centerTitle: true,
      ),
      body: Center(
        child: Column(
          children: [
            Text('接收的参数是:${arguments['title']}'),
            ElevatedButton(
              onPressed: () => Navigator.pop(context),
              child: const Text('返回'),
            ),
          ],
        ),
      ),
    );
  }
}

class ProductDetail extends StatelessWidget {
  const ProductDetail({super.key});

  @override
  Widget build(BuildContext context) {
    final Map arguments = ModalRoute.of(context)?.settings.arguments as Map;

    return Scaffold(
      // 头部导航条
      appBar: AppBar(
        // 标题
        title: const Text('商品详情页面'),
        // 左侧插槽
        leading: const Icon(Icons.menu),
        // 右侧插槽
        actions: const [
          Icon(Icons.add_circle_outline),
        ],
        // 设置下边阴影
        elevation: 0.0,
        // 设置标题居中
        centerTitle: true,
      ),
      body: Center(
        child: Column(
          children: [
            Text('当前商品的id是:${arguments['id']}'),
            ElevatedButton(
              onPressed: () => Navigator.pop(context),
              child: const Text('返回'),
            ),
          ],
        ),
      ),
    );
  }
}

class UnkonwPage extends StatelessWidget {
  const UnkonwPage({super.key});

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      // 头部导航条
      appBar: AppBar(
        // 标题
        title: const Text('404'),
        // 左侧插槽
        leading: const Icon(Icons.menu),
        // 右侧插槽
        actions: const [
          Icon(Icons.add_circle_outline),
        ],
        // 设置下边阴影
        elevation: 0.0,
        // 设置标题居中
        centerTitle: true,
      ),
      body: Center(
        child: Column(
          children: [
            ElevatedButton(
              onPressed: () => Navigator.pop(context),
              child: const Text('返回'),
            ),
          ],
        ),
      ),
    );
  }
}

5.6. Drawer 导航

image.png

  • Scaffold
    • drawer (左侧抽菜单)
    • endDrawer (右侧抽菜单)
  • UserAccountsDrawerHeader
    • 抽屉菜单头部组件
  • AboutListTile
    • 关于弹窗
import 'package:flutter/material.dart';

class Home extends StatelessWidget {
  const Home({super.key});

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      // 头部导航条
      appBar: AppBar(
        // 标题
        title: const Text('Drawer'),
        // 左侧插槽
        leading: const Icon(Icons.menu),
        // 右侧插槽
        actions: const [
          Icon(Icons.add_circle_outline),
        ],
        // 设置下边阴影
        elevation: 0.0,
        // 设置标题居中
        centerTitle: true,
      ),
      body: const HomePage(),
      drawer: const DrawerList(),
      endDrawer: const DrawerList(),
    );
  }
}

class HomePage extends StatelessWidget {
  const HomePage({super.key});

  @override
  Widget build(BuildContext context) {
    return Container(
      child: const Center(
        child: Text('Home'),
      ),
    );
  }
}

class DrawerList extends StatelessWidget {
  const DrawerList({super.key});

  @override
  Widget build(BuildContext context) {
    return Drawer(
      child: ListView(
        // 取消内边距
        padding: const EdgeInsets.all(0),
        children: [
          const UserAccountsDrawerHeader(
            accountName: Text('小笑残虹'),
            accountEmail: Text('2504862082@qq.com'),
            decoration: BoxDecoration(
              image: DecorationImage(
                image: AssetImage('images/2.webp'),
                fit: BoxFit.cover,
              ),
            ),
            // 用户头像
            currentAccountPicture: CircleAvatar(
              backgroundImage: AssetImage('images/1.jpg'),
            ),
          ),
          const ListTile(
            leading: Icon(Icons.settings),
            title: Text('设置'),
            trailing: Icon(Icons.arrow_forward_ios),
          ),
          const Divider(
            thickness: 1,
          ),
          const ListTile(
            leading: Icon(Icons.account_balance_wallet),
            title: Text('余额'),
            trailing: Icon(Icons.arrow_forward_ios),
          ),
          const Divider(
            thickness: 1,
          ),
          const ListTile(
            leading: Icon(Icons.person),
            title: Text('我的'),
            trailing: Icon(Icons.arrow_forward_ios),
          ),
          const Divider(
            thickness: 1,
          ),
          ListTile(
            leading: const Icon(Icons.keyboard_return),
            title: const Text('回退'),
            onTap: () => Navigator.pop(context),
            trailing: const Icon(Icons.arrow_forward_ios),
          ),
          AboutListTile(
            applicationName: '我的APP',
            applicationIcon: Image.asset(
              'images/1.jpg',
              width: 50,
              height: 50,
            ),
            applicationVersion: '1.0.0',
            applicationLegalese: '应用法律条例',
            aboutBoxChildren: const [
              Text('条例一:XXXX'),
              Text('条例二:XXXX'),
            ],
            icon: const CircleAvatar(
              child: Text('aaa'),
            ),
            child: const Text('关于'),
          ),
          // Text('Drawer'),
        ],
      ),
    );
  }
}

5.7. BottomNavigationBar 导航

  • items
    • 包含导航 (BottomNavigationBarltem) 的列表
  • currentIndex
    • 当前导航索引
  • type
    • 导航类型(BottomNavigationBarType)
  • onTap()
    • 导航的点击事件(一般会更新导航索引)
import 'package:flutter/material.dart';

class Home extends StatefulWidget {
  const Home({super.key});

  @override
  State<Home> createState() => _HomeState();
}

class _HomeState extends State<Home> {
  final List<BottomNavigationBarItem> bottomNavItems = [
    const BottomNavigationBarItem(
      icon: Icon(Icons.home),
      label: '首页',
      backgroundColor: Colors.blue,
    ),
    const BottomNavigationBarItem(
      icon: Icon(Icons.message),
      label: '消息',
      backgroundColor: Colors.green,
    ),
    const BottomNavigationBarItem(
      icon: Icon(Icons.shopping_cart),
      label: '购物车',
      backgroundColor: Colors.amber,
    ),
    const BottomNavigationBarItem(
      icon: Icon(Icons.person),
      label: '我',
      backgroundColor: Colors.red,
    ),
  ];

  final List<Widget> pages = [
    const Center(
      child: Text(
        '首页',
        style: TextStyle(fontSize: 50),
      ),
    ),
    const Center(
      child: Text(
        '消息',
        style: TextStyle(fontSize: 50),
      ),
    ),
    const Center(
      child: Text(
        '购物车',
        style: TextStyle(fontSize: 50),
      ),
    ),
    const Center(
      child: Text(
        '我的',
        style: TextStyle(fontSize: 50),
      ),
    ),
  ];

  late int currentIndex;

  @override
  void initState() {
    // TODO: implement initState
    super.initState();
    currentIndex = 0;
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      // 头部导航条
      appBar: AppBar(
        // 标题
        title: const Text('底部导航'),
        // 左侧插槽
        leading: const Icon(Icons.menu),
        // 右侧插槽
        actions: const [
          Icon(Icons.add_circle_outline),
        ],
        // 设置下边阴影
        elevation: 0.0,
        // 设置标题居中
        centerTitle: true,
      ),
      bottomNavigationBar: BottomNavigationBar(
        items: bottomNavItems,
        currentIndex: currentIndex,
        // type: BottomNavigationBarType.fixed,
        type: BottomNavigationBarType.shifting,
        onTap: (index) {
          _changePage(index);
        },
      ),
      body: pages[currentIndex],
    );
  }

  void _changePage(int index) {
    if (index != currentIndex) {
      setState(() {
        currentIndex = index;
      });
    }
  }
}

5.8. Tab 导航

  • DefaultTabController (整个 Tab 导航的容器)
    • length (声明导航数量)
    • child (指定子组件)
  • TabBar (导航菜单)
    • tabs (导航菜单数组)
  • TabBarView (导航页面)
    • children (多个导航页面内容)
import 'package:flutter/material.dart';

class Home extends StatelessWidget {
  Home({super.key});

  // 菜单数组
  final List<Widget> _tabs = [
    const Tab(
      text: '首页',
      icon: Icon(Icons.home),
    ),
    const Tab(
      text: '添加',
      icon: Icon(Icons.add),
    ),
    const Tab(
      text: '搜索',
      icon: Icon(Icons.search),
    ),
  ];

  // 页面数组
  final List<Widget> _tabViews = [
    const Icon(
      Icons.home,
      size: 120,
      color: Colors.red,
    ),
    const Icon(
      Icons.add,
      size: 120,
      color: Colors.green,
    ),
    const Icon(
      Icons.search,
      size: 120,
      color: Colors.black,
    ),
  ];

  @override
  Widget build(BuildContext context) {
    return DefaultTabController(
      length: _tabs.length,
      child: Scaffold(
        // 头部导航条
        appBar: AppBar(
          // 标题
          title: const Text('Tab'),
          // 左侧插槽
          leading: const Icon(Icons.menu),
          // 右侧插槽
          actions: const [
            Icon(Icons.add_circle_outline),
          ],
          // 设置下边阴影
          elevation: 0.0,
          // 设置标题居中
          centerTitle: true,
          bottom: TabBar(
            tabs: _tabs,
            labelColor: Colors.yellow,
            unselectedLabelColor: Colors.black45,
            indicatorSize: TabBarIndicatorSize.tab,
            indicatorColor: Colors.yellow,
            indicatorWeight: 10,
          ),
        ),
        body: TabBarView(
          children: _tabViews,
        ),
        bottomNavigationBar: TabBar(
          tabs: _tabs,
          labelColor: Colors.blue,
          unselectedLabelColor: Colors.black45,
        ),
      ),
    );
  }
}