IT数码 购物 网址 头条 软件 日历 阅读 图书馆
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
图片批量下载器
↓批量下载图片,美女图库↓
图片自动播放器
↓图片自动播放器↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁
 
   -> 移动开发 -> Flutter旋转平移缩放动画实例 -- 手动实现底部FloatingActionButton弹入弹出动画 -> 正文阅读

[移动开发]Flutter旋转平移缩放动画实例 -- 手动实现底部FloatingActionButton弹入弹出动画

先看下完成后的效果:

?这个动画效果在app中很常见,由底部蓝色的FloatingActionButton旋转动画和另外的三个白色按钮的弹入弹出平移缩放动画组成。先看旋转动画的实现:

一.FloatingActionButton旋转动画

蓝色按钮在相邻次数的点击时分别对应了顺时针和逆时针的两次45度旋转,所以我们需要声明一个补间动画进行控制:

class _MyHomePageState extends State<MyHomePage> with TickerProviderStateMixin {

    ... ...

    /// 手动控制动画的控制器
    late final AnimationController floatButtonAnimController;

    /// 手动控制
    late final Animation<double> floatButtonAnimation;

    @override
    void initState() {
        super.initState();
        /// 不设置重复,使用代码控制进度
        floatButtonAnimController = AnimationController(
          duration: const Duration(milliseconds: 500),
          vsync: this,
        );
        floatButtonAnimation = Tween<double>(
            begin: 0,
            end: 0.5
        ).animate(floatButtonAnimController);
    }

    ... ...
}

声明动画控制器为500ms执行一次,插值器不特殊指定使用默认的线性插值器,并且不指定执行重复次数,由实际代码进行控制:

      floatingActionButton: Container(
        margin: const EdgeInsets.only(bottom: 16),
        child: RotationTransition(
          turns: floatButtonAnimation,
          child: FloatingActionButton(
            backgroundColor: const Color.fromARGB(255, 30, 136, 229),
            onPressed: () {//点击事件
                ... ...
                var animValue = floatButtonAnimController.value;
                if (animValue == 0.25) {
                  floatButtonAnimController.animateTo(0);//逆时针
                } else {
                  floatButtonAnimController.animateTo(0.25);//顺时针
                }
            },
            child: const Icon(Icons.add),
          ),
        ),
      ), // This trailing comma makes auto-formatting nicer for build methods.
      floatingActionButtonLocation: FloatingActionButtonLocation.centerDocked,

可以看到在布局中使用了一个旋转动画的Widget即RotationTransition,给他的turns属性指定动画实例即floatButtonAnimation,点击时会判断controller当前的value来执行顺时针/逆时针动画。整体上没有特殊的地方需要注意,很简单的一个补间动画实现。下面来主要说说平移和缩放的实现:

二.平移缩放动画

由于三个白色按钮的动画是同时执行和结束的,所以三个组合动画可以直接共用同一个控制器就可以了:

_controller = AnimationController(
    vsync: this,
    duration: const Duration(milliseconds: 200)
);

_animation = CurvedAnimation( //贝塞尔曲线动画插值器
    parent: _controller,
    curve: Curves.easeIn,
);

对于平移动画,最主要的事情就是确定Button移动的起点和终点。终点其实就是放大后三个按钮各自在坐标系中的位置,他们分别对应各自的三个位置,而至于起点是他们三个缩小后的位置,这个位置从理论上来说是同一个并且是蓝色按钮的中心位置,但是在实际绘制布局时仍然需要声明三个重叠的起点,因为他们各自的动画是不尽相同的且是同时执行的,我们无法对同一个Widget同时进行三个不同的动画。

确定Button移动的起点和终点我们需要用到flukit三方库(pubspec.yaml文件中引入flukit: ^3.0.1)中的一个AfterLayout组件,他是专门用来获取组件大小和相对于屏幕的坐标的:

AfterLayout(
  callback: (RenderAfterLayout ral) {
    print(ral.size); //子组件的大小
    print(ral.offset);// 子组件在屏幕中坐标
  },
  child: Text('flutter@wendux'),
),

首先我们在堆叠布局Stack下声明三个透明布局用来定位,并使用Positioned布局来调整位置:

body: Stack(
        alignment: Alignment.bottomCenter,
        children: <Widget> [
          _widgetOptions[_curIndex],
          Positioned( ///中间的
            bottom: 68,
            child: Opacity(
                opacity: 0,//设置为透明 这里是为了知道放大动画结束后icon应该摆放的位置,所以不需要展示也不需要响应事件
                child: AfterLayout(
                  callback: (v) => childBig1Rect = _getRect(v),
                  child: childBig1,
                )
            )
          ),
          Positioned( ///右边的
              bottom: 32,
              right: 80,
              child: Opacity(
                  opacity: 0,
                  child: AfterLayout(
                    callback: (v) => childBig2Rect = _getRect(v),
                    child: childBig2,
                  )
              )
          ),
          Positioned( ///左边的
              bottom: 32,
              left: 80,
              child: Opacity(
                  opacity: 0,
                  child: AfterLayout(
                    callback: (v) => childBig3Rect = _getRect(v),
                    child: childBig3,
                  )
              )
          ),

... ...


//我们需要获取的是AfterLayout子组件相对于Stack的Rect,通过_getRect方法转换一下
Rect _getRect(RenderAfterLayout renderAfterLayout) {
    return renderAfterLayout.localToGlobal(
      Offset.zero,
      ///找到Stack对应的 RenderObject 对象
      ancestor: context.findRenderObject(),
    ) & renderAfterLayout.size;
}

我们拿到定位后的RenderAfterLayout对象后需要通过_getRect方法来转为在Stack下的Rect,而这个Rect一定意义来说就是坐标。

接着还需要声明三个动画组件作为三个按钮的初始位置:

  //是否展示小图标
  bool showChild1 = !_animating && _lastAnimationStatus != AnimationStatus.forward;
  //执行动画时的目标组件;如果是从小图变为大图,则目标组件是大图;反之则是小图
  Widget targetWidget1;
  Widget targetWidget2;
  Widget targetWidget3;
  if (showChild1 || _controller.status == AnimationStatus.reverse) {
    targetWidget1 = childSmall1;
    targetWidget2 = childSmall2;
    targetWidget3 = childSmall3;
  } else {
    targetWidget1 = childBig1;
    targetWidget2 = childBig2;
    targetWidget3 = childBig3;
  }

    ... ...
          showChild1 ? AfterLayout(
              callback: (v) => child1Rect = _getRect(v),
              child: childSmall1
          ) : AnimatedBuilder(
            animation: _animation,
            builder: (context, child) {
              //rect 估值器
              final rect = Rect.lerp(
                child1Rect,
                childBig1Rect,
                _animation.value,
              );
              // 通过 Positioned 设置组件大小和位置
              return Positioned.fromRect(rect: rect!, child: child!);
            },
            child: targetWidget1,
          ),

          showChild1 ? AfterLayout(
              callback: (v) => child1Rect = _getRect(v),
              child: childSmall2
          ) : AnimatedBuilder(
            animation: _animation,
            builder: (context, child) {
              final rect = Rect.lerp(
                child1Rect,
                childBig2Rect,
                _animation.value,
              );
              return Positioned.fromRect(rect: rect!, child: child!);
            },
            child: targetWidget2,
          ),

          showChild1 ? AfterLayout(
              callback: (v) => child1Rect = _getRect(v),
              child: childSmall3
          ) : AnimatedBuilder(
            animation: _animation,
            builder: (context, child) {
              final rect = Rect.lerp(
                child1Rect,
                childBig3Rect,
                _animation.value,
              );
              return Positioned.fromRect(rect: rect!, child: child!);
            },
            child: targetWidget3,
          ),

通过布尔型变量showChild1来判断当前是应该显示起点还是应该展示动画,平移和缩放动画的执行是通过Rect自带的估值器完成的,最后用Positioned的fromRect方法刷新位置。

最后就是点击FloatingActionButton按钮执行弹出弹入动画:

      floatingActionButton: Container( 
        margin: const EdgeInsets.only(bottom: 16),
        child: RotationTransition(
          turns: floatButtonAnimation,
          child: FloatingActionButton(
            backgroundColor: const Color.fromARGB(255, 30, 136, 229),
            onPressed: () {
              /// 平移缩放
              setState(() {//通过setState方法重置动画状态完成逆向执行
                _animating = true;
                if (isSmallToBig) {
                  isSmallToBig = false;
                  _controller.forward();
                } else {
                  isSmallToBig = true;
                  _controller.reverse();
                }
              });
              /// 旋转
              var animValue = floatButtonAnimController.value;
              if (animValue == 0.25) {
                floatButtonAnimController.animateTo(0);
              } else {
                floatButtonAnimController.animateTo(0.25);
              }
            },
            child: const Icon(Icons.add),
          ),
        ),
      ), // This trailing comma makes auto-formatting nicer for build methods.
      floatingActionButtonLocation: FloatingActionButtonLocation.centerDocked,

代码自提入口:

https://github.com/HAND-jiaming/flutter_demo

  移动开发 最新文章
Vue3装载axios和element-ui
android adb cmd
【xcode】Xcode常用快捷键与技巧
Android开发中的线程池使用
Java 和 Android 的 Base64
Android 测试文字编码格式
微信小程序支付
安卓权限记录
知乎之自动养号
【Android Jetpack】DataStore
上一篇文章           查看所有文章
加:2022-08-19 19:17:25  更:2022-08-19 19:22:18 
 
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁

360图书馆 购物 三丰科技 阅读网 日历 万年历 2024年11日历 -2024/11/25 4:25:02-

图片自动播放器
↓图片自动播放器↓
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
图片批量下载器
↓批量下载图片,美女图库↓
  网站联系: qq:121756557 email:121756557@qq.com  IT数码