Flutter,确实提高了我们的开发功率,写Flutter代码,感觉行云流水,欢迎我们关注与提意见。

物理模仿能够让应用程序的交互感觉逼真和互动,例如,你或许希望为一个 Widget 设置动画,使其看起来像是附着在绷簧上或是重力下落。本文章完结了演示了怎么运用绷簧模仿将小部件从拖动的点移回中心。


Flutter文章目录 公众号 biglead


完结步骤如下

  1. 设置动画控制器
  2. 运用手势移动小部件
  3. 为小部件制作动画
  4. 核算速度以模仿绷簧运动

1 创立一个动画控制器

首页创立一个测试运用的Demo页面

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,
        ),
      ),
    );
  }
}

DraggableCard 是自定义的一个 StatefulWidget,代码如下:

class _DraggableCardState extends State<DraggableCard> {
  @override
  void initState() {
    super.initState();
  }
  @override
  void dispose() {
    super.dispose();
  }
  @override
  Widget build(BuildContext context) {
    return Align(
      child: Card(
        child: widget.child,
      ),
    );
  }
}

然后在 _DraggableCardState 中创立一个动画控制器,并在页面毁掉的时分开释动画控制器,代码如下:

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();
  }
  @override
  Widget build(BuildContext context) {
    return Align(
      child: Card(
        child: widget.child,
      ),
    );
  }
}

SingleTickerProviderStateMixin是用来在StatefulWidget中办理单个AnimationController的Mixin;它供给了一个TickerProvider,用于将AnimationController与TickerProviderStateMixin一同运用。

TickerProviderStateMixin供给了一个Ticker,它能够在每个frame中调用AnimationController的办法,这使得AnimationController能够在每个frame中更新动画。

2 运用手势移动Widget

在 _DraggableCardState 中,结合运用 Alignment 与 GestureDetector,代码如下:

class _DraggableCardState extends State<DraggableCard>
    with SingleTickerProviderStateMixin {
  late AnimationController _controller;
  Alignment _dragAlignment = const Alignment(0, 0);
  @override
  Widget build(BuildContext context) {
    var size = MediaQuery.of(context).size;
    return GestureDetector(
      onPanDown: (details) {},
      onPanUpdate: (details) {
        _dragAlignment += Alignment(
          details.delta.dx / (size.width / 2),
          details.delta.dy / (size.height / 2),
        );
        setState(() {
        });
      },
      onPanEnd: (details) {},
      child: Align(
        alignment: _dragAlignment,
        child: Card(
          child: widget.child,
        ),
      ),
    );
  }

GestureDetector用来检测手势,例如轻触、滑动、拖动等,能够用来完结各种交互作用。

Alignment用于控制子widget在父widget中的位置。能够通过Alignment的结构函数来指定子widget相对于父widget的位置,如Alignment.topLeft表明子widget坐落父widget的左上角。也能够通过FractionalOffset来指定子widget相对于父widget的位置,如FractionalOffset(0.5, 0.5)表明子widget坐落父widget的中心。Alignment还能够与Stack一同运用,完结多个子widget的定位。

Flutter 手指拖动实现弹簧动画交互

3 创立一个动画Widget

我们需要完结,当手指抬起时,被移动的 Widget 动画的办法弹回去。

在这里需要一个 Animation ,再定义一个 runAnimation 办法,一起为 第一步创立的动画控制器添加一个更新监听。

class _DraggableCardState extends State<DraggableCard>
    with SingleTickerProviderStateMixin {
  late AnimationController _controller;
  late Animation<Alignment> _animation;
  @override
  void initState() {
    super.initState();
    _controller =
        AnimationController(vsync: this, duration: const Duration(seconds: 1));
    _controller.addListener(() {
      setState(() {
        _dragAlignment = _animation.value;
      });
    });
  }
  void _runAnimation() {
    _animation = _controller.drive(
      AlignmentTween(
        begin: _dragAlignment,
        end: Alignment.center,
      ),
    );
    _controller.reset();
    _controller.forward();
  }
 }

然后在手指抬起的时分,履行动画,将被移动的 Widget (如这里的图片)以动画的办法移动回原位:

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

Flutter 手指拖动实现弹簧动画交互

4 核算速度以模仿绷簧运动

最终一步是做一些数学运算,核算小部件完结拖动后的速度。这是为了使小部件在被拍回之前能够以这种速度逼真地持续。(_runAnimation办法现已通过设置动画的开始和完毕对齐来设置方向。)

导入包如下:

import 'package:flutter/physics.dart';

onPanEnd回调供给了一个DragEndDetails对象。此对象供给指针中止触摸屏幕时的速度。速度以像素每秒为单位,但Align小部件不运用像素。它运用介于[-1.0,-1.0]和[1.0,1.0]之间的坐标值,其中[0.0,0.0]表明中心。步骤2中核算的大小用于将像素转换为该范围内的坐标值。

然后修改 runAnimation 履行动画函数如下:

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,
    );
    //SpringSimulation用来模仿一个绷簧的运动,能够用于创立具有弹性的动画作用。
    final simulation = SpringSimulation(spring, 0, 1, -unitVelocity);
    _controller.animateWith(simulation);
  }

然后在手指抬起的时分调用

onPanEnd: (details) {
    _runAnimation(details.velocity.pixelsPerSecond, size);
  },

Flutter 手指拖动实现弹簧动画交互
如果有兴趣能够关注一下公众号 biglead ,每周都会有 java、Flutter、小程序、js 、英语相关的内容分享

本文正在参与「金石方案」