啥?Flutter也能整3D了吗?我靠,竟然是这样的操作?

简介: 当我看了这样一个节目之后,我发现,复杂的ui竟然这么简单就可以实现了!当时我就用Flutter整了这么个3D效果,快来围观!!
前言:在网上看到了一个3D展示的效果,属实强?,于是我准备拿Flutter复刻一个,可是晚上找了半天也没有发现Flutter怎么使用3D模型,但是直到我看了这个节目,话不多说,先上效果图!

灵感来源:https://www.youtube.com/watch?v=FCyoHclCqc8&t=149s

视频讲解了很多复杂UI的处理制作

屏幕截图 2021-10-28 124641.jpg

效果图:

tt0.top-433400.gif

先分析一下原理,分为3个部分:

1.背景的旋转处理

2.文字的旋转动画处理

3.吉他(贝斯,写文章的时候才发现不是吉他)的旋转处理与吉他背景阴影的处理

1.背景的旋转处理

在复杂的动画中,常用的便是:Transform,Stack,AnimationController

这里也是一样:

tt0.top-133272.gif

_buildBackground() => Positioned.fill(
      top: -_extraHeight,
      bottom: -_extraHeight,
      child: AnimatedBuilder(
        animation: _animator,
        builder: (context, widget) => Transform.translate(
          offset: Offset(_maxSlide * _animator.value, 0),
          //重点是这里
          child: Transform(
            transform: Matrix4.identity() //单位矩阵,倾斜的角度
              ..setEntry(3, 2, 0.001)
              ..rotateY((pi / 2 + 0.1) * -_animator.value),
            alignment: Alignment.centerLeft, //相对于坐标系原点的对齐方式
            child: widget, 
          ),
        ),
        //以下就是普通的Widget
        child: Container(
          color: Color(0xffe8dfce),
          child: Stack(
            overflow: Overflow.visible,
            children: <Widget>[
              //Fender word
              Positioned(
                top: _extraHeight + 0.1 * _screen.height,
                left: 80,
                child: Transform.rotate(
                  angle: 90 * (pi / 180),
                  alignment: Alignment.centerLeft,
                  child: Text(
                    "HELLO",
                    style: TextStyle(
                      fontSize: 100,
                      color: Color(0xFFc7c0b2),
                      shadows: [
                        Shadow(
                          color: Colors.black26,
                          blurRadius: 5,
                          offset: Offset(2.0, 0.0),
                        ),
                      ],
                      fontWeight: FontWeight.w900,
                    ),
                  ),
                ),
              ),
              //给切换时加上一个黑色的背景动画,使动画更加立体
              AnimatedBuilder(
                animation: _animator,
                builder: (_, __) => Container(
                  color: Colors.black.withAlpha(
                    (150 * _animator.value).floor(),
                  ),
                ),
              ),
            ],
          ),
        ),
      ),
    );

2.文字的旋转动画处理

1635400145(1).png

  • 顶部文字

    //适配异形屏
    SafeArea(
          child: AnimatedBuilder(
              animation: _animator,
              builder: (_, __) {
                return Transform.translate(
                    //平移动画
                  offset: Offset((_screen.width - 60) * _animator.value, 0),
                  child: ...);
        )
    )
  • 底部文字

    这里的平移动画需要跟随背景动画,在此基础上加了一个透明度,一个平移动画,更加立体

    Opacity(
        //透明度
      opacity: 1 - _animator.value,
        //与背景动画相同,在其基础上加了一个平移动画
      child: Transform.translate(
        offset: Offset((_maxSlide + 50) * _animator.value, 0),
        child: Transform(
          transform: Matrix4.identity()
            ..setEntry(3, 2, 0.001)
            ..rotateY((pi / 2 + 0.1) * -_animator.value),
          alignment: Alignment.centerLeft,
          child: widget,
        ),
      ),
    )
  • 左侧文字

    此处的文字动画与上面相差不大,只在参数上有区别

    Transform.translate(
      offset: Offset(_maxSlide * (_animator.value - 1), 0),
      child: Transform(
        transform: Matrix4.identity()
          ..setEntry(3, 2, 0.001)
          ..rotateY(pi * (1 - _animator.value) / 2),
        alignment: Alignment.centerRight,
        child: widget,
      ),
    )

    3.贝斯的旋转处理与贝斯背景阴影的处理

这部分是本文的重点,先说一下3D效果的原理。我在这里找了120张贝斯的图,分别是每移动一点所对应的图片,当移动时,传入移动的距离,显示不同的图片

在这里封装了一个ImageSequenceAnimator,有个插件叫image_sequence_animator,但是功能不满足我自定义的需求,就把它的源码拿出来改了改。

注意事项:图片的命名

屏幕截图 2021-10-28 141224.jpg

在这里只给大家分析最重要的点了,源代码中我注释的很全~

屏幕截图 2021-10-28 141122.jpg

  • 对于图片名称的补齐:

    String _getSuffix(String value) {
      while (value.length < suffixCount) value = "0" + value;
      return value;
    }
  • 获取到图片的路径:

    String _getDirectory() {
      return folderName +
          "/" +
          fileName +
          _getSuffix((suffixStart + _previousFrame).toString()) +
          "." +
          fileFormat;
    }
  • UI处理:

    @override
    Widget build(BuildContext context) {
      if (widget.frame != null) {
        if (currentFrame == null ||
            widget.frame != _previousFrame ||
            colorChanged) {
          colorChanged = false;
          _previousFrame = widget.frame;
          if (_previousFrame < frameCount)
            currentFrame = Image.asset(
              _getDirectory(),
              color: color,
              gaplessPlayback: true,
            );
        }
        return currentFrame;
      }
      return ValueListenableBuilder(
        builder: (BuildContext context, int change, Widget cachedChild) {
          if (currentFrame == null ||
              animationController.value.floor() != _previousFrame ||
              colorChanged) {
            colorChanged = false;
            _previousFrame = animationController.value.floor();
            if (_previousFrame < frameCount)
              currentFrame = Image.asset(
                _getDirectory(),
                color: color,
                gaplessPlayback: true,
              );
          }
    //currentFrame是Image
          return currentFrame;
        },
        valueListenable: changeNotifier,
      );
    }
    ?
  • 使用方法:

    ImageSequenceAnimator(
      "assets/guitarSequence", //folderName
      "", //fileName
      1, //suffixStart
      4, //suffixCount
      "png", //fileFormat
      120, //frameCount
      fps: 60, 
      isLooping: false,
      isBoomerang: true,
      isAutoPlay: false,
      frame: (_objAnimator.value * 120).ceil(),
      // fullPaths: [(_objAnimator.value * 120).ceil().toString()],//官方插件的使用
    )
  • 贝斯阴影处理

    Positioned(
        //无需动画,用Stack叠在贝斯照片下面就行
      top: _extraHeight + 0.13 * _screen.height,
      bottom: _extraHeight + 0.24 * _screen.height,
      left: _maxSlide - 0.41 * _screen.width,
      right: _screen.width * 1.06 - _maxSlide,
      child: Column(
        children: <Widget>[
            //绘制了一个贝斯的形状
          Flexible(
            child: FractionallySizedBox(
              widthFactor: 0.2,
              child: Container(
                decoration: BoxDecoration(
                  boxShadow: [
                    BoxShadow(
                      blurRadius: 50,
                      color: Colors.black38,
                    )
                  ],
                  borderRadius: BorderRadius.circular(50),
                ),
              ),
            ),
          ),
          Flexible(
            child: Container(
              decoration: BoxDecoration(
                boxShadow: [
                  BoxShadow(
                    blurRadius: 50,
                    color: Colors.black26,
                  )
                ],
                borderRadius: BorderRadius.circular(50),
              ),
            ),
          ),
        ],
      ),
    ),

Flutter复杂的3D就这样很简单就完成啦~

相关文章
|
Dart
【Flutter】HTTP 网络操作 ( 引入 http 插件 | 测试网站 | Get 请求 | Post 请求 | 将响应结果转为 Dart 对象 | Future 异步调用 )(二)
【Flutter】HTTP 网络操作 ( 引入 http 插件 | 测试网站 | Get 请求 | Post 请求 | 将响应结果转为 Dart 对象 | Future 异步调用 )(二)
244 0
【Flutter】HTTP 网络操作 ( 引入 http 插件 | 测试网站 | Get 请求 | Post 请求 | 将响应结果转为 Dart 对象 | Future 异步调用 )(二)
|
存储 JSON Dart
【Flutter】HTTP 网络操作 ( 引入 http 插件 | 测试网站 | Get 请求 | Post 请求 | 将响应结果转为 Dart 对象 | Future 异步调用 )(一)
【Flutter】HTTP 网络操作 ( 引入 http 插件 | 测试网站 | Get 请求 | Post 请求 | 将响应结果转为 Dart 对象 | Future 异步调用 )(一)
393 0
【Flutter】HTTP 网络操作 ( 引入 http 插件 | 测试网站 | Get 请求 | Post 请求 | 将响应结果转为 Dart 对象 | Future 异步调用 )(一)
|
Dart 开发者
【Flutter】底部导航栏页面框架 ( BottomNavigationBar 底部导航栏 | PageView 滑动页面 | 底部导航与滑动页面关联操作 )(二)
【Flutter】底部导航栏页面框架 ( BottomNavigationBar 底部导航栏 | PageView 滑动页面 | 底部导航与滑动页面关联操作 )(二)
134 0
【Flutter】底部导航栏页面框架 ( BottomNavigationBar 底部导航栏 | PageView 滑动页面 | 底部导航与滑动页面关联操作 )(二)
|
索引
【Flutter】底部导航栏页面框架 ( BottomNavigationBar 底部导航栏 | PageView 滑动页面 | 底部导航与滑动页面关联操作 )(一)
【Flutter】底部导航栏页面框架 ( BottomNavigationBar 底部导航栏 | PageView 滑动页面 | 底部导航与滑动页面关联操作 )(一)
305 0
|
Dart Java
flutter中list相关操作汇总(有这一篇就够啦)
要说,List在我的开发使用中,确实是最为频繁的了,那么如何使用list,也就成了一个问题,list提供的方法又有哪些 这些都是需要掌握理解的。
2021 0
|
1天前
|
缓存 监控 前端开发
【Flutter 前端技术开发专栏】Flutter 应用的启动优化策略
【4月更文挑战第30天】本文探讨了Flutter应用启动优化策略,包括理解启动过程、资源加载优化、减少初始化工作、界面布局简化、异步初始化、预加载关键数据、性能监控分析以及案例和未来优化方向。通过这些方法,可以缩短启动时间,提升用户体验。使用Flutter DevTools等工具可助于识别和解决性能瓶颈,实现持续优化。
【Flutter 前端技术开发专栏】Flutter 应用的启动优化策略
|
1天前
|
开发框架 Dart 前端开发
【Flutter前端技术开发专栏】Flutter与React Native的对比与选择
【4月更文挑战第30天】对比 Flutter(Dart,强类型,Google支持,快速热重载,高性能渲染)与 React Native(JavaScript,庞大生态,热重载,依赖原生渲染),文章讨论了开发语言、生态系统、性能、开发体验、学习曲线、社区支持及项目选择因素。两者各有优势,选择取决于项目需求、团队技能和长期维护考虑。参考文献包括官方文档和性能比较文章。
【Flutter前端技术开发专栏】Flutter与React Native的对比与选择
|
1天前
|
Dart 前端开发 测试技术
【Flutter前端技术开发专栏】Flutter开发中的代码质量与重构实践
【4月更文挑战第30天】随着Flutter在跨平台开发的普及,保证代码质量成为开发者关注的重点。优质代码能确保应用性能与稳定性,提高开发效率。关键策略包括遵循最佳实践,编写可读性强的代码,实施代码审查和自动化测试。重构实践在项目扩展时尤为重要,适时重构能优化结构,降低维护成本。开发者应重视代码质量和重构,以促进项目成功。
【Flutter前端技术开发专栏】Flutter开发中的代码质量与重构实践
|
1天前
|
存储 缓存 监控
【Flutter前端技术开发专栏】Flutter中的列表滚动性能优化
【4月更文挑战第30天】本文探讨了Flutter中优化列表滚动性能的策略。建议使用`ListView.builder`以节省内存,避免一次性渲染所有列表项。为防止列表项重建,可使用`UniqueKey`或`ObjectKey`。缓存已渲染项、减少不必要的重绘和异步加载大数据集也是关键。此外,选择轻量级组件,如`StatelessWidget`,并利用Flutter DevTools监控性能以识别和解决瓶颈。持续测试和调整以提升用户体验。
【Flutter前端技术开发专栏】Flutter中的列表滚动性能优化
|
1天前
|
Dart 前端开发 安全
【Flutter前端技术开发专栏】Flutter中的线程与并发编程实践
【4月更文挑战第30天】本文探讨了Flutter中线程管理和并发编程的关键性,强调其对应用性能和用户体验的影响。Dart语言提供了`async`、`await`、`Stream`和`Future`等原生异步支持。Flutter采用事件驱动的单线程模型,通过`Isolate`实现线程隔离。实践中,可利用`async/await`、`StreamBuilder`和`Isolate`处理异步任务,同时注意线程安全和性能调优。参考文献包括Dart异步编程、Flutter线程模型和DevTools文档。
【Flutter前端技术开发专栏】Flutter中的线程与并发编程实践

热门文章

最新文章

http://www.vxiaotou.com