美文网首页
从零开始用flutter写一个完整应用(14):动画3--物理模

从零开始用flutter写一个完整应用(14):动画3--物理模

作者: 逃离_102 | 来源:发表于2022-07-07 17:36 被阅读0次

说明

接上篇,这篇说说物理模拟动画效果。它是这几种动画最复杂的一种,也是能实现效果最丰富的一种,物理模拟能够让应用富有真实感和更好的交互性。例如,你可能会为一个 widget 添加动画,让它看起来就像安着弹簧,或是在随重力下落。下面以如何将 widget 从拖动的点移回到中心,并使用弹簧模拟效果,为示例演示它的使用。

创建一个动画控制器

_DraggableCardState 类继承至 SingleTickerProviderStateMixin。然后在 initState 中构造一个 AnimationController,并将其 vsync 属性设为 this。继承的 SingleTickerProviderStateMixin 让 state 对象为 AnimationController 提供了 TickerProvider 的能力。要获得更多信息,请查看 TickerProvider 文档

class _DraggableCardState extends State<DraggableCard> with SingleTickerProviderStateMixin {
  late AnimationController _controller;
   @override
   void initState() {
     super.initState();
     _controller = AnimationController(vsync: this, duration: const Duration(seconds: 1));
   }

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

使用手势移动 widget

让 widget 可以被拖拽,并为 _DraggableCardState 类添加一个 Alignment 范围。添加一个 GestureDetector 来捕获 onPanDownonPanUpdate,以及 onPanEnd 回调。为了调整对齐方式,请使用 MediaQuery 来获得 widget 的大小,然后除以 2。(这会将「拖动的像素」单位转为 Align使用的坐标。)然后,将 Align widget 的 alignmnt 属性设为 _dragAlignment

Alignment _dragAlignment = Alignment.center;

@override
  Widget build(BuildContext context) {
    return  GestureDetector(
     onPanDown: (details) {},
     onPanUpdate: (details) {
          setState(() {
            _dragAlignment += Alignment(
                details.delta.dx / (size.width / 2),
                details.delta.dy / (size.height / 2),
            );
        });
     },
    onPanEnd: (details) {},
    child: Align(
       alignment: _dragAlignment,
       child: Card(
          child: widget.child,
       ),
    ),
  );
 }

对 widget 进行动画,计算速度以模拟弹跳运动

当一个 widget 被释放,它应该就会弹回中心。
添加一个 Animation<Alignment>,以及 _runAnimation 方法。此方法定义了一个 Tween,它在 widget 被拖动到的点之间插入到中心点。引入 physics 这个 package,用于计算速度

void _runAnimation(Offset pixelsPerSecond, Size size) {
  _animation = _controller.drive(
    AlignmentTween(
      begin: _dragAlignment,
      end: Alignment.center,
    ),
  );
  
  final unitsPerSecondX = pixelsPerSecond.dx / size.width;
  final unitsPerSecondY = pixelsPerSecond.dy / size.height;
  final unitsPerSecond = Offset(unitsPerSecondX, unitsPerSecondY);
  final unitVelocity = unitsPerSecond.distance;

  const spring = SpringDescription(
    mass: 30,
    stiffness: 1,
    damping: 1,
  );

  final simulation = SpringSimulation(spring, 0, 1, -unitVelocity);

  _controller.animateWith(simulation);
}

完整示例

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

void main() {
  runApp(const MaterialApp(home: PhysicsCardDragDemo()));
}

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

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(),
      body: const DraggableCard(
        child: FlutterLogo(
          size: 128,
        ),
      ),
    );
  }
}

class DraggableCard extends StatefulWidget {
  const DraggableCard({required this.child, super.key});

  final Widget child;

  @override
  _DraggableCardState createState() => _DraggableCardState();
}

class _DraggableCardState extends State<DraggableCard>
    with SingleTickerProviderStateMixin {
  late AnimationController _controller;

  Alignment _dragAlignment = Alignment.center;

  late Animation<Alignment> _animation;

  void _runAnimation(Offset pixelsPerSecond, Size size) {
    _animation = _controller.drive(
      AlignmentTween(
        begin: _dragAlignment,
        end: Alignment.center,
      ),
    );
    
    final unitsPerSecondX = pixelsPerSecond.dx / size.width;
    final unitsPerSecondY = pixelsPerSecond.dy / size.height;
    final unitsPerSecond = Offset(unitsPerSecondX, unitsPerSecondY);
    final unitVelocity = unitsPerSecond.distance;

    const spring = SpringDescription(
      mass: 30,
      stiffness: 1,
      damping: 1,
    );

    final simulation = SpringSimulation(spring, 0, 1, -unitVelocity);

    _controller.animateWith(simulation);
  }

  @override
  void initState() {
    super.initState();
    _controller = AnimationController(vsync: this);

    _controller.addListener(() {
      setState(() {
        _dragAlignment = _animation.value;
      });
    });
  }

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

  @override
  Widget build(BuildContext context) {
    final size = MediaQuery.of(context).size;
    return GestureDetector(
      onPanDown: (details) {
        _controller.stop();
      },
      onPanUpdate: (details) {
        setState(() {
          _dragAlignment += Alignment(
            details.delta.dx / (size.width / 2),
            details.delta.dy / (size.height / 2),
          );
        });
      },
      onPanEnd: (details) {
        _runAnimation(details.velocity.pixelsPerSecond, size);
      },
      child: Align(
        alignment: _dragAlignment,
        child: Card(
          child: widget.child,
        ),
      ),
    );
  }
}

物理模拟动画效果就说到这,这需要慢慢理解一下,有的地方不太好理解,具体还是得多看看具体示例,大概就说到这了,如有遗漏欢迎留言,如有错误欢迎指正,谢谢

相关文章

网友评论

      本文标题:从零开始用flutter写一个完整应用(14):动画3--物理模

      本文链接:https://www.haomeiwen.com/subject/gsdrbrtx.html