Flutter 笔记

Flutter 动画

概念

UI界面设计合理的动画,可以让用户觉得更加流畅、直观,可以极大提高和改善用户体验


从直观来说动画是

1. 动画就是动起来的画面
2. 视觉暂留:画面经视神经传入大脑后,不会立即消失,会存留一段时间
3. 帧(Frame):单个的画面,在学术上叫帧
4. 每秒中暂时的帧数简称 fps(Frame per Second)

针对动画的性质,分为两大类:

  • 补间(Tween)动画
  1. 在补间动画中我们定义开始点和结束点、时间线以及定义转换时间和速度曲线,
    然后由系统计算,从开始点运动到结束点。从而形成效果
  2. 例如:透明度从 0 到 1,颜色值从 0 到 255
  • 拟物动画
  1. 拟物动画是对真实世界的行为进行建模,使动画效果类似于现实中的物理效果
  2. 例如:弹簧,阻尼,重力,抛物线等
Animation 动画控制器

AnimationController 动画控制器 是一个类,是Animation的重要实现类,
主要是完成控制动画的各种操作,包括动画的启动(forward)、暂停(stop)、回滚(reverse)、以及反复(repeat)

它可设置在指定时间内,将组件属性值由初始值演变到终止值,从而形成动画效果

AnimationController 参数

  1. duration 动画的执行时间
  2. reverseDuration 动画反向执行时间
  3. lowerBound = 0.0 动画最小值
  4. upperBound = 1.0 动画最大值
  5. value 动画初始值 默认是 lowerBound
  6. vsync (TickerProvider 类型 的对象,用来创建 Ticker对象)

   当创建一个 AnimationController 时,需要传递一个 vsync 参数

  1. vaync 的作用是:防止屏幕外动画(动画页面切换到后台时)消耗不必要的资源(管理Ticker更新)
    (淡入淡出的过程是一个数值变化的过程,Ticker 在每一步骤上都会通知 UI组件 进行界面更新)
  2. 通过将 singleTickerProviderStateMixin 添加到类定义中,可以将 stateful 对象作为 vsync 的值

 AnimationController 具有控制动画的方法

  1. forward() 可以正向执行动画
  2. reverse() 可以反向执行动画
  3. dispose() 用来释放动画资源(在不使用时需要调用该方法,否则会造成资源泄露)
  4. stop() 用来停止动画
Dart
class _AnimateAppState extends State<AnimateApp>
    with SingleTickerProviderStateMixin {
  AnimationController? controller;

  @override
  void initState() {
    super.initState();
    // 创建 AnimationController 对象
    controller = AnimationController(
      /*
      this 指的是当前组件对象
      因为使用了混入 SingleTickerProviderStateMixin 类
      因此会自动调用 cerateTicker()方法
      去创建 Ticker 用于通知组件去一帧一帧创建动画
      */
      vsync: this,
      // 持续时间 {毫秒:1000}
      duration: const Duration(milliseconds: 2000),
    );
    ...
    // 执行动画
    controller!.forward();
  }
}

Tween 补间动画

animationController 动画生成值的默认区间是 0.0 到 1.0,如果希望使用不同的区间,
或者不同的数据类型,需要使用 Tween(补间动画)

实际上它也可以对  AnimationController 默认区值 的补充,可以设置新的阔值

Dart
animation = CurvedAnimation(
      // 动画控制器来源
      parent: controller!,
      // 动画曲线类型
      curve: Curves.bounceIn,
    );
    // 通过 Tween 对象 创建 Animation 对象
    animation = Tween(begin: 50.0, end: 400.0).animate(controller!)
      ..addListener(() {
        // 注意:这句不能少,否则 widget 不会重绘,也就看不到动画效果
        setState(() {});
      });
    // 执行动画
    controller!.forward();

  1. Tween 的唯一职责就是定义哦那个输入范围到输出范围的映射
  2. 例如:颜色区间是 0 到 255

Tween 组件中有:

1. IntTween(int 类型的动画
2. ColorTween(颜色渐变动画)
3. ReverseTween(反转动画)
4. SizeTween(size类型的动画)等

>>更多内容

针对不同的 Tween 组件,对应的写入参数也不同,例如:

  1. Tween(begin:起始值,end:终止值);
  2. ColorTween(begin:Colors.withe,end:Colors.black);
动画曲线

定义动画曲线的组件为 CurvedAnimation组件,它可控制动画从 开始到结束 的速度曲线

参数介绍:

  1. parent 动画控制器对象
  2. curve 正向执行的动画曲线
  3. reverseCurve 反向执行的动画曲线
    >>>更多内容
Dart
  Animation<double>? animation;
    
  @override
  void initState() {
    ...
    super.initState();
    // 声明动画曲线
    animation = CurvedAnimation(
      // 动画控制器来源
      parent: controller!,
      // 动画曲线类型
      curve: Curves.bounceIn,
    );
  };
动画销毁

当组件切换到后台时,我们理应当停止动画样式的继续渲染

使用 dispose() 用来释放动画资源 (在不使用时应调用该方法,否则会造成资源泄露)

Dart
  @override
  // 当前组件 切换到后台
  void dispose() {
    // TODO: implement dispose
    super.dispose();
    // 动画控制器 释放资源
    controller!.dispose();
}
创建动画

Dart 创建动画 一般创建动画需要以下几个步骤

  • 创建动画控制器
  1. controller = AnimationController(duration,vsync);
  • 创建动画
  1. 动画曲线(CurvedAnimation)
  2. 补间动画(Tween)
  • 监听动画
  1. addListener 监听动画生成值
  2. addStatusListener 监听动画状态
  • 执行动画
  1. controller.forward() 正向执行
  2. controller.reverse() 反向执行

下方为完整代码

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('动画'),
        centerTitle: true,
        elevation: 0.0,
        leading: const Icon(Icons.menu),
        actions: const [
          Icon(Icons.search),
        ],
        backgroundColor: Colors.blue,
      ),
      body: AnimateApp(),
    );
  }
}

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

  @override
  State<StatefulWidget> createState() {
    return _AnimateAppState();
  }
}

class _AnimateAppState extends State<AnimateApp>
    with SingleTickerProviderStateMixin {
  AnimationController? controller;
  Animation<double>? animation;

  @override
  void initState() {
    super.initState();
    // 创建 AnimationController 对象
    controller = AnimationController(
      /*
      this 指的是当前组件对象
      因为使用了混入 SingleTickerProviderStateMixin 类
      因此会自动调用 cerateTicker()方法
      去创建 Ticker 用于通知组件去一帧一帧创建动画
      */
      vsync: this,
      // 持续时间 {毫秒:1000}
      duration: const Duration(milliseconds: 2000),
    );
    // 声明动画曲线
    animation = CurvedAnimation(
      // 动画控制器来源
      parent: controller!,
      // 动画曲线类型
      curve: Curves.bounceIn,
    );
    // 通过 Tween 对象 创建 Animation 对象
    animation = Tween(begin: 50.0, end: 400.0).animate(controller!);
    // 监听 动画值变化过程
    animation!.addListener(() {
      // 注意:这句不能少,否则 widget 不会重绘,也就看不到动画效果
      setState(() {});
    });
    // 执行动画
    controller!.forward();
  }

  @override
  Widget build(BuildContext context) {
    return SizedBox(
      width: double.infinity,
      height: double.infinity,
      child: Column(
        mainAxisAlignment: MainAxisAlignment.center,
        crossAxisAlignment: CrossAxisAlignment.center,
        children: [
          Container(
            /*
              获取动画的值赋给 widget 的宽高
              当值发生变化时 会自动通知 Ticker 重绘界面
            */
            width: animation!.value,
            height: animation!.value,
            decoration: const BoxDecoration(
              color: Colors.redAccent,
            ),
            child: Center(
                child: Text(
              '${animation!.value}',
              style: const TextStyle(
                fontSize: 50,
                fontWeight: FontWeight.w800,
                color: Colors.white,
              ),
            )),
          ),
        ],
      ),
    );
  }

  @override
  void dispose() {
    // 组件 切换到后台
    super.dispose();
    // 动画 释放资源
    controller!.dispose();
  }
}
动画循环

动画循环就是让动画一直播放

如果需要让动画持续播放,可在监听动画结束状态时反向执行动画,并在动画回到开始状态时候正向执行。

Dart
    // 3. 让动画反复执行
    animation!.addStatusListener((status) {
      // 如果动画 已经 播放完整
      if (status == AnimationStatus.completed) {
        print(AnimationStatus.completed);
        // 反向执行动画
        controller!.reverse();
        // 如果动画 状态 为初始状态
      } else if (status == AnimationStatus.dismissed) {
        // 正向执行动画
        controller!.forward();
      }
    });
交织动画

交织动画是由多个单一动画叠加而成的复杂动画

例如:组件变化可能涉及高度、宽度、颜色、透明度、位置等

方法:需要给每个动画设置时间间隔 (Interval) ,并依次在规定时间里执行。
* 注意,它们都需要同时绑定在同一个动画控制器中 *

Dart
    // 2. 创建动画
    animation = CurvedAnimation(
      // 动画控制器
      parent: controller!,
      // 播放区间
      curve: const Interval(0.0, 0.5),
    )..addListener(() {
        setState(() {});
      });

例如,下方这段代码,在一个动画周期过程中声明了多个 动画参数

%title插图%num
Dart
    // 2. 创建动画
    animation = CurvedAnimation(
      // 动画控制器
      parent: controller!,
      // 播放区间
      curve: const Interval(0.0, 0.5),
    )
      // 动画执行期间 通知组件渲染界面
      ..addListener(() {
        // 只是监听 不执行任何操作
        setState(() {});
      });

    // 4.设置其他变化
    sizeAnimation = Tween(begin: 0.0, end: 200.0).animate(CurvedAnimation(
      // 动画控制器
      parent: controller!,
      // 播放区间
      curve: const Interval(0.0, 0.5),
    ));
    colorAnimation = ColorTween(begin: Colors.yellow, end: Colors.red).animate(
      CurvedAnimation(
        // 动画控制器
        parent: controller!,
        // 播放区间
        curve: const Interval(0.5, 0.8, curve: Curves.bounceIn),
      ),
    );
    rotationAnimation = Tween(begin: 0.0, end: 2 * Math.pi).animate(
      CurvedAnimation(
        // 动画控制器
        parent: controller!,
        // 动画控制器
        curve: const Interval(0.8, 1.0, curve: Curves.easeIn),
      ),
    );
  }

声明完成,再将各个 Animation动画组件 的 value 参数绑定到对应的位置,实现动画效果

Dart
// 透明组件
        Opacity(
          // opacity 为 0 时,使组件透明
          opacity: controller!.value,
          // 子组件 旋转组件
          child: Transform.rotate(
            // 旋转角度
            angle: rotationAnimation!.value,
            // 子组件 容器组件
            child: Container(
              // 容器 宽度
              width: sizeAnimation!.value,
              // 容器 高度
              height: sizeAnimation!.value,
              // 容器 颜色
              color: colorAnimation!.value,
            ),
          ),
        ),

完整代码

%title插图%num
Dart
import 'package:flutter/material.dart';
import 'dart:math' as Math;

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

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: const Text('Stagger Animation'),
        centerTitle: true,
        elevation: 0.0,
        leading: const Icon(Icons.menu),
        actions: const [
          Icon(Icons.search),
        ],
        backgroundColor: Colors.blue,
      ),
      body: const AnimationDemo(),
    );
  }
}

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

  @override
  State<AnimationDemo> createState() => _AnimationDemoState();
}

class _AnimationDemoState extends State<AnimationDemo>
    with SingleTickerProviderStateMixin {
  // 定义动画 控制器
  AnimationController? controller;
  // 声明 动画
  CurvedAnimation? animation;
  Animation? sizeAnimation;
  Animation? colorAnimation;
  Animation? rotationAnimation;

  @override
  // 组件初始化 状态
  void initState() {
    // TODO: implement initState
    super.initState();

    // 1. 创建 AnimationController
    controller = AnimationController(
      /*
      this 指的是当前组件对象
      因为使用了混入 SingleTickerProviderStateMixin 类
      因此会自动调用 cerateTicker()方法
      去创建 Ticker 用于通知组件去一帧一帧创建动画
      */
      vsync: this,
      // 动画执行时间 3秒
      duration: const Duration(seconds: 3),
    );

    // 2. 创建动画
    animation = CurvedAnimation(
      // 动画控制器
      parent: controller!,
      // 播放区间
      curve: const Interval(0.0, 0.5),
    )
      // 动画执行期间 通知组件渲染界面
      ..addListener(() {
        // 只是监听 不执行任何操作
        setState(() {});
      });

    // 3. 让动画反复执行
    animation!.addStatusListener((status) {
      // 如果动画 已经 播放完整
      if (status == AnimationStatus.completed) {
        print(AnimationStatus.completed);
        // 反向执行动画
        controller!.reverse();
        // 如果动画 状态 为初始状态
      } else if (status == AnimationStatus.dismissed) {
        // 正向执行动画
        controller!.forward();
      }
    });

    // 4.设置其他变化
    sizeAnimation = Tween(begin: 0.0, end: 200.0).animate(CurvedAnimation(
      // 动画控制器
      parent: controller!,
      // 播放区间
      curve: const Interval(0.0, 0.5),
    ));
    colorAnimation = ColorTween(begin: Colors.yellow, end: Colors.red).animate(
      CurvedAnimation(
        // 动画控制器
        parent: controller!,
        // 播放区间
        curve: const Interval(0.5, 0.8, curve: Curves.bounceIn),
      ),
    );
    rotationAnimation = Tween(begin: 0.0, end: 2 * Math.pi).animate(
      CurvedAnimation(
        // 动画控制器
        parent: controller!,
        // 动画控制器
        curve: const Interval(0.8, 1.0, curve: Curves.easeIn),
      ),
    );
  }

  @override
  Widget build(BuildContext context) {
    return Center(
      child: Column(children: [
        ElevatedButton(
            onPressed: () {
              // animation!.addStatusListener((status) {
              //   // 如果动画 已经 播放完整
              //   if (status == AnimationStatus.completed) {
              //     // 反向执行动画
              //     controller!.reverse();
              //   } else {
              //     // 正向执行动画
              //     controller!.forward();
              //   }
              // });
              // 正向执行动画
              controller!.forward();
            },
            child: const Text('重复')),
        ElevatedButton(
            onPressed: () {
              // animation!.addStatusListener((status) {
              //   // 如果动画 已经 播放完整
              //   if (status == AnimationStatus.completed) {
              //     // 反向执行动画
              //     controller!.reverse();
              //   } else {
              //     // 正向执行动画
              //     controller!.forward();
              //   }
              // });
              // 正向执行动画
              controller!.reverse();
            },
            child: const Text('返回')),
        ElevatedButton(
          onPressed: () {
            // 停止动画
            controller!.stop();
          },
          child: const Text('停止'),
        ),
        // 透明组件
        Opacity(
          // opacity 为 0 时,使组件透明
          opacity: controller!.value,
          // 子组件 旋转组件
          child: Transform.rotate(
            // 旋转角度
            angle: rotationAnimation!.value,
            // 子组件 容器组件
            child: Container(
              // 容器 宽度
              width: sizeAnimation!.value,
              // 容器 高度
              height: sizeAnimation!.value,
              // 容器 颜色
              color: colorAnimation!.value,
            ),
          ),
        ),
      ]),
    );
  }

  @override
  // 当前组件 切换到后台
  void dispose() {
    // TODO: implement dispose
    super.dispose();
    // 动画控制器 释放资源
    controller!.dispose();
  }
}
Hero动画

Hero动画 用来实现跨页面的动画效果

原理:

由于共享组件在不同页面中的位置、外观等不同,使用hero作为父级,路由切换时,两者之间形成动画效果

在两个组件同时定义 Hero 组件,使用 相同的 tag 的值 ( tag就是Hero控件的一个标签,用来判定唯一性)

实现:

  1. 在页面 A 中定义 起始 Hero组件 (source hero),声明 tag
  2. 在页面 B 中定义目标 Hero 组件 (destination hero),绑定相同的 tag
  3. 页面跳转时,通过 Navigator,传递 tag

hero 参数:

  1. tag (路由切换时,共享组件的标记)
  2. child (声明子组件)

代码演示

%title插图%num
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('Hero'),
        leading: const Icon(Icons.menu),
        actions: const [
          Icon(Icons.settings),
        ],
        elevation: 0.0,
        // 居中标题
        centerTitle: true,
        // AppBar 背景色
        backgroundColor: Colors.green,
      ),
      body: HeroAnimation(),
    );
  }
}

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

  @override
  Widget build(BuildContext context) {
    return Container(
      // 网格布局盒子
      child: GridView.extent(
        // 必填 副轴的最大长度
        maxCrossAxisExtent: 202.0,
        // 主轴边距
        mainAxisSpacing: 20,
        // 子元素 设定 20个成员 依次对每个下标成员布局
        children: List.generate(20, (index) {
          // 网络图片
          String imageUrl = 'https://picsum.photos/id/$index/300/400';
          // 返回 交互组件
          return GestureDetector(
            // 单击事件
            onTap: () {
              // 匿名路由
              Navigator.push(
                context,
                MaterialPageRoute(
                  builder: (BuildContext ctx) {
                    return ImageDetail(imageUrl);
                  },
                ),
              );
            },
            // Hero 动画组件
            child: Hero(
              tag: imageUrl,
              child: Image.network(imageUrl),
            ),
          );
        }),
      ),
    );
  }
}

class ImageDetail extends StatelessWidget {
  final String imageUrl;
  ImageDetail(this.imageUrl);

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      backgroundColor: Colors.black,
      body: Center(
        child: GestureDetector(
          onTap: () {
            // 返回上一个组件
            Navigator.pop(context);
          },
          // Hero 动画组件
          child: Hero(
            tag: imageUrl,
            child: Image.network(
              imageUrl,
              width: double.infinity,
              fit: BoxFit.cover,
            ),
          ),
        ),
      ),
    );
  }
}
Flutter 生命周期
%title插图%num
钩子函数

在 Flutter组件 初始化到销毁之间,这个过程是组件的生命周期,每个状态都对应下方的钩子函数

钩子函数描述
initState()组件对象插入到元素树中时
didChangeDependencies() 当前状态对象的依赖改变时
build()组件渲染时
setState()组件对象的内部状态变更时
didUpdateWidget()组件配置更新时
deactivate()组件对象在元素树中 暂时移除时 (切换界面)
dispose()组件对象在元素树中 永远被移除时 (关闭App时)
运行过程

statefulWidget(建立有状态组件) → createElement(创建元素对象) → widget.createState(创建状态组件)
→ initState(初始化状态组件) → didChangeDependencies(公共数据依赖发生变更?) → dirty(脏状态[列表]) → build(构建组件列表) → clean(完成构建)
→ deactivate(切换页面) → dispose(关闭App)

[同步] clean(完成构建) → setState(页面发生改变) → dirty(脏状态[列表])
[同步] clean(完成构建) → didUpdateWidget(组件配置更新时) → dirty(脏状态[列表])

下方为完整代码演示

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

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

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: const Text('life Cycle'),
        leading: const Icon(Icons.menu),
        actions: const [
          Icon(Icons.settings),
        ],
        elevation: 0.0,
        centerTitle: true,
      ),
      body: MyState(),
    );
  }
}

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

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

class _MyStateState extends State<MyState> {
  // 延时初始化
  late int _num;

  String _str = '';

  // 生命周期 初始化状态组件
  @override
  void initState() {
    // ignore: todo
    // TODO: implement initState
    super.initState();
    // ignore: avoid_print
    print('initState 初始化状态组件');
    _num = 1;
  }

  // 生命周期 公共数据依赖发生变更?
  @override
  void didChangeDependencies() {
    // ignore: todo
    // TODO: implement didChangeDependencies
    super.didChangeDependencies();
    // ignore: avoid_print
    print('didChangeDependencies 公共数据依赖发生变更?');
  }

  // 生命周期 组件配置更新时
  @override
  void didUpdateWidget(covariant MyState oldWidget) {
    // ignore: todo
    // TODO: implement didUpdateWidget
    super.didUpdateWidget(oldWidget);
    // ignore: avoid_print
    print('didUpdateWidget 组件配置更新');
  }

  @override
  void deactivate() {
    // ignore: todo
    // TODO: implement deactivate
    super.deactivate();
    // ignore: avoid_print
    print('deactivate 切换界面');
  }

  @override
  void dispose() {
    // ignore: todo
    // TODO: implement dispose
    super.dispose();
    // ignore: avoid_print
    print('dispose APP关闭');
  }

  void _increment() {
    setState(() {
      // ignore: avoid_print
      print('setState 页面发生改变');
      _num++;
      _str += 'setState 页面发生改变';
    });
  }

  void _decrement() {
    setState(() {
      // ignore: avoid_print
      print('setState 页面发生改变');
      _num--;
      _str += 'setState 页面发生改变';
    });
  }

  @override
  Widget build(BuildContext context) {
    // ignore: avoid_print
    print('build 页面重新构建');
    return Center(
      child: Column(
        children: [
          ElevatedButton(
            onPressed: _decrement,
            child: const Text('-'),
          ),
          Padding(
            padding: const EdgeInsets.all(20),
            child: Text('$_num'),
          ),
          ElevatedButton(
            onPressed: _increment,
            child: const Icon(Icons.add),
          ),
          Container(
            width: double.infinity,
            height: 300,
            decoration: BoxDecoration(
              border: Border.all(color: Colors.red, width: 1),
            ),
            child: SingleChildScrollView(
              // ignore: unnecessary_string_interpolations
              child: Text('$_str'),
            ),
          ),
        ],
      ),
    );
  }
}
Flutter 状态管理
inheritedWidget

inheritedWidget组件 提供了沿树向下,共享数据的功能。
解决了 组件传参 过程中多余的步骤,从沿树而上 的方式到直接 一对一绑定的方式传递(类似 Vue)

用途

  1. 依赖构造函数传递数据的方式不能满足业务需求
  2. 需要一个新的、更好的跨组件数据传输方式
%title插图%num

使用:

Dart
BuildContext.dependOnInheritedWidgetOfExactType<MyInheritedWidget>()

下方为一个完整代码,演示 inheritedWidget 如何作为参数传递方传递参数

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

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

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      // 头部区域
      appBar: AppBar(
        // 标题
        title: const Text('DataTable'),
        // 左侧图标
        leading: const Icon(Icons.assessment),
        // 右侧图标组
        actions: const <Widget>[
          Icon(Icons.home),
          Padding(
            padding: EdgeInsets.symmetric(horizontal: 16),
            child: Icon(Icons.move_down),
          ),
        ],
        // 下部阴影
        elevation: 0.0,
        // 居中元素
        centerTitle: true,
      ),
      body: ListView(
        children: [
          Card(
            child: MyState(),
          ),
        ],
      ),
    );
  }
}

class MyState extends StatefulWidget {
  MyState({Key? key}) : super(key: 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) {
    /*
    InheritedWidget 可以存储需要的数据,可共享给该组件下的所有组件的数据并统一在该组件中存放
    如若需要子组件传给父组件传数据时候,可以使用 InheritedWidget

    该组件解决了传参过程中多余的从上到下的传递参数方式 节约了传递的消耗和拥堵
    */
    return ShareDataWidfet(
      // 存入 num 属性值
      num: _num,
      child: Center(
        child: Column(children: [
          ElevatedButton(
            onPressed: _increment,
            child: const Text('-'),
          ),
          Padding(
            padding: const EdgeInsets.all(20),
            // child: Text('$_num'),
            // 跨组件访问数据
            child: MyCounter(),
          ),
          ElevatedButton(
            onPressed: _decrement,
            child: const Icon(Icons.add),
          ),
          /*
          在InheritedWidget 下的所有子级组件都可以直接使用共享的数据内容
          */
          Padding(
            padding: const EdgeInsets.all(20),
            child: TextDemo(),
          ),
        ]),
      ),
    );
  }
}

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

  @override
  State<TextDemo> createState() => _TextDemoState();
}

class _TextDemoState extends State<TextDemo> {
  @override
  Widget build(BuildContext context) {
    return Center(
      child: MyCounter(),
    );
  }
}

// 返回 Text组件 并使用 inheritedWidget 进行存值赋值
class MyCounter extends StatefulWidget {
  MyCounter({Key? key}) : super(key: key);

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

class _MyCounterState extends State<MyCounter> {
  @override
  Widget build(BuildContext context) {
    // 使用 InheritedWidget 中的共享数据 从中读取成员属性 num
    return Text(ShareDataWidfet.of(context)!.num.toString());
  }
}

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

  ShareDataWidfet({Key? key, required this.child, required this.num})
      : super(key: key, child: child);

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

  @override
  bool updateShouldNotify(ShareDataWidfet oldWidget) {
    // 每次值发生改变都传递
    return true;
  }
}
Provider

该组件为 第三方包,需要在 pubspec.yaml 配置安装

配置 pubspec.yaml

YAML
  # The following adds the Cupertino Icons font to your application.
  # Use with the CupertinoIcons class for iOS style icons.
  provider: ^4.3.3

说明

provider 是对 InheritedWidget 的封装 文档地址

provider是第三方包 它有一个 ChangeNotifierProvider 组件 存储了 ChangeNotifier 的 数据
 当调用数据的组件 ChangeNotifier 中的数据值发生改变 ChangeNotifierProvider 会通知 其他调用同样数据的Ui组件 将旧数据进行更新

优点

  1. 简化资源的分配和处置
  2. 懒加载

provider 组成

  • 数据模型 ChangeNotigier [被观察者]
  • 提供者(对组件提供数据) ChangeNotifierProvier [观察者]
  • 使用数据的组件 的 不同称呼 Producer [生产者] Listener(Consumer) [消费者]

业务逻辑上看 分为 数据层-存放各组件数据 [Model] 观察层-监控数据变化并通知Ui层更新 [ViewModel] 视图层-各个Flutter组件 [View]

probider 使用

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

下方是完整代码,实现由 Probider 管理数据

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

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

  @override
  Widget build(BuildContext context) {
    return ChangeNotifierProvider(
      // 2. 创建 Provider对象(注册数据模型) 赋值上下文内容
      create: (BuildContext context) => LikesModel(),
      child: Scaffold(
        appBar: AppBar(
          title: const Text('Provider'),
          leading: const Icon(Icons.menu),
          actions: const [
            Icon(Icons.settings),
          ],
          elevation: 0.0,
          centerTitle: true,
        ),
        body: const MyHomePage(),
      ),
    );
  }
}

// 1. 创建数据模型
class LikesModel extends ChangeNotifier {
  // 初始值
  int _counter = 0;
  // get 方法 取私有成员值方法
  int get counter => _counter;
  incrementCounter() {
    // 累加
    _counter++;
    // 通知UI更新函数 类似 setstate
    notifyListeners();
  }
}

/*
   probider 负责页面的更新后 因此即使无状态组件也可以更新页面
   因为 依赖 Probider组件 后 更新的任务不是委派给 setstate()
   而是第三方组件负责 数据的观察 数据的更新 和UI的重新绘制 等操作
*/
class MyHomePage extends StatelessWidget {
  const MyHomePage({super.key});

  @override
  Widget build(BuildContext context) {
    return SizedBox(
      width: double.infinity,
      height: double.infinity,
      child: Column(
        mainAxisAlignment: MainAxisAlignment.center,
        crossAxisAlignment: CrossAxisAlignment.center,
        children: [
          // 获取并使用 数据模型 中的 私有属性值 (依据 get函数)
          Text('${context.watch<LikesModel>().counter}'),
          TextButton(
            // 获取并使用 数据模型中的 方法
            onPressed: Provider.of<LikesModel>(context).incrementCounter,
            child: const Icon(Icons.thumb_up),
          ),
        ],
      ),
    );
  }
}
Flutter 路由
  • Route 一个路由是一个屏幕或页面的抽象
  • Navigator 管理路由的组件
    1. Navigator 可以通过路由入栈和出栈来实现页面之间的跳转
    2. 常用属性
    • initialRoute 初始路由 即默认页面
    • onGenerateRoute (根据规则,匹配动态路由) // 根据传过来的路径进行动态判断
    • onUnknownRoute 未知路由 也就是 404
    • routes 路由集合
匿名路由

在组件中 直接使用 Navigator.push() 跳转至目标组件

Navigator

  • push (跳转到指定组件)
Dart
 Navigator.push(
   context,MaterialPageRoute(builder:(context)=>组件名称()
 );

  • pop (回退)
Dart
Navigator.pop(context);

下方是完整代码,演示组件之间的跳转

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

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

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: const Text('匿名路由'),
        leading: const Icon(Icons.menu),
        actions: const [
          Icon(Icons.settings),
        ],
        elevation: 0.0,
        centerTitle: true,
      ),
      body: const HomePage(),
    );
  }
}

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

  @override
  Widget build(BuildContext context) {
    return SizedBox(
      width: double.infinity,
      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('商品页面'),
      ),
      body: SizedBox(
        width: double.infinity,
        child: Center(
            child: ElevatedButton(
            // 返回到上一个页面
          onPressed: () => Navigator.pop(context), 
          child: const Text('回退'),
        )),
      ),
    );
  }
}
命名路由

一般在根组件注册常用路由,并给路由设置命名,使用 Navigator.pushNamed(context,'RouteName'); 跳转。

声明路由

  1. routes 路由表 (Map类型)
  2. initialRoute (初始路由)
  3. onUnknownRoute (未知路由 -404)

跳转操作

Dart
Navigator.pushNamed(context,'RouteName');

定义操作

Dart
 MaterialApp(
   // ..... //
   routes:{ // 注册路由
     'first': (context) => FirstPage(),
     'second': (context) => SecondPage(),
   },
   initialRoute: 'first', // 初始路由
   onUnknownRoute: (RouteSettings setting) => MaterialPageRoute(
     builder: (context) => UnknownPage(), // 未知路由
   )
 )

使用命名路由操作

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

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

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: const Text('首页'),
        leading: const Icon(Icons.menu),
        actions: const [
          Icon(Icons.settings),
        ],
        elevation: 0.0,
        centerTitle: true,
      ),
      body: Center(
        child: Column(
          children: [
            ElevatedButton(
              // 命名路由跳转
              onPressed: () => Navigator.pushNamed(context, 'first'),
              child: const Text('第一个组件'),
            ),
            ElevatedButton(
              // 命名路由跳转
              onPressed: () => Navigator.pushNamed(context, 'second'), 
              child: const Text('第二个组件'),
            ),
            ElevatedButton(
              // 未定义的命名路由跳转
              onPressed: () => Navigator.pushNamed(context, 'smm'), 
              child: const Text('未知路由'),
            ),
          ],
        ),
      ),
    );
  }
}
Flutter 多语言

概念

多语言 internationalization (简称 i18n),在终端 (手机) 系统使用不同国家语言的文字时候,
应用理所应当切换为该终端 (手机) 系统使用的国家的语言

组件多语言

该组件为 官方包,需要在 pubspec.yaml 配置安装

介绍

终端(手机) 系统语言切换时候,Flutter应用的跟随切换,

  • 组件 widget 国际化 (已封装在 flutter 中)
    • 例如:日历,弹窗等常用组件的国际化
    • 需要依赖 flutter_localizations 包

配置第三方包

在 flutter 项目根目录中的 pubspec.yaml 中,对应的位置引用包

YAML
dependencies:
  flutter:
    sdk: flutter
  flutter_localizations:
    sdk: flutter

在 flutter 的入口组件 main.dart 中,启用 多语言组件

Dart
import 'package:flutter/material.dart';
// 多语言组件
import 'package:flutter_localizations/flutter_localizations.dart';
void main() {
  runApp(const MyApp());
}

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

  @override
  Widget build(BuildContext context) {
    return const MaterialApp(
      title: '初号机',
      home: Home(),
      // 国际化 多语言组件
      localizationsDelegates: [
        // 本地化代理 多语言组件
        GlobalMaterialLocalizations.delegate, // 安卓组件
        GlobalWidgetsLocalizations.delegate, // 基础组件
        GlobalWidgetsLocalizations.delegate, // IOS风格组件
      ],
      // 支持的语言 (按需添加) 多语言组件
      supportedLocales: [
        Locale('en', 'US'), // 美国英语
        Locale('zh', 'CN'), // 简体中文
      ],
    );
  }
}

配置完成后,localizationsDelegates 项对应的所有组件,一旦被 main.dart 所引用,就有了多语言的效果

效果展示

%title插图%num
Flutter 网络请求

概念

向服务器地址发起请求,当返回结果后执行对应的操作

演示

http包 为 第三方包,需要在 pubspec.yaml 配置安装

使用 http 第三方包 在 pubspec.yaml 中配置 引用包

YAML
dependencies:
  flutter:
    sdk: flutter
  http: ^0.12.0

在 dart 相关组件项目中实际使用