Flutter - DragBox 反馈动画到原始位置

Posted

技术标签:

【中文标题】Flutter - DragBox 反馈动画到原始位置【英文标题】:Flutter - DragBox Feedback animate to original position 【发布时间】:2019-06-06 05:37:05 【问题描述】:

我想在 DropTarget 不接受时显示可拖动的反馈动画。Flutter 不显示反馈。有什么方法可以证明或控制它。像这个例子,我想达到这个效果。我以某种方式实现了这种效果,但是返回到原始偏移量并不正确。它正在向前移动到原来的位置。

我想要的动画效果。

这是我的代码,当我将它提升到某个位置并让他从那里离开时,我有一个拖动框,它应该动画回到原始位置,但它正在返回到像这样的其他偏移量。

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

void main() 
  runApp(MyApp());


class MyApp extends StatelessWidget 
  @override
  Widget build(BuildContext context) 
    return MaterialApp(
      theme: ThemeData(
        primarySwatch: Colors.blue,
      ),
      home: Scaffold(body: DragBox()),
    );
  


class DragBox extends StatefulWidget 
  DragBox(
    Key key,
  ) : super(key: key);

  @override
  State<StatefulWidget> createState() 
    return new _MyDragBox();
  


class _MyDragBox extends State<DragBox> with TickerProviderStateMixin 
  GlobalKey _globalKey = new GlobalKey();
  AnimationController _controller;
  Offset begin;
  Offset cancelledOffset;
  Offset _offsetOfWidget;
  @override
  void initState() 
    WidgetsBinding.instance.addPostFrameCallback((s) 
      _afeteLayout();
    );
    super.initState();
    _controller = AnimationController(
      vsync: this,
      duration: Duration(milliseconds: 1000),
    );
  

  void _afeteLayout() 
    final RenderBox box = _globalKey.currentContext.findRenderObject();
    Offset offset = -box.globalToLocal(Offset(0.0, 0.0));
    _offsetOfWidget = offset;
  

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

  @override
  Widget build(BuildContext context) 
    return Stack(
      children: <Widget>[
        Center(
          child: Draggable(
            key: _globalKey,
            onDraggableCanceled: (v, o) 
              setState(() 
                cancelledOffset = o;
              );
              _controller.forward();
            ,
            feedback: Container(
              color: Colors.red,
              height: 50,
              width: 50,
            ),
            child: Container(
              color: Colors.red,
              height: 50,
              width: 50,
            ),
          ),
        ),
        _controller.isAnimating
            ? SlideTransition(
                position: Tween<Offset>(
                        begin: cancelledOffset * 0.01,
                        end: _offsetOfWidget * 0.01)
                    .animate(_controller)
                      ..addStatusListener((status) 
                        if (status == AnimationStatus.completed) 
                          _controller.stop();
                         else 
                          _controller.reverse();
                        
                      ),
                child: Container(
                  color: Colors.red,
                  height: 50,
                  width: 50,
                ),
              )
            : Container(
                child: Text('data'),
              )
      ],
    );
  

【问题讨论】:

这不是 Draggable 开箱即用的东西。你必须自己制作动画,使用OverlayEntry 和其他一些东西 @RémiRousselet 谢谢。如果您有任何示例,我会尝试一下,我会得到更多的想法? @RémiRousselet ***.com/questions/54194412/… 你能帮我解决这个问题吗? 你能达到想要的动画效果吗? 你能详细说明你是如何实现它的吗? 【参考方案1】:

我认为,关于 "Animate a widget using a physics simulation" 的文档是最适合您想要实现的目标的示例。

这个秘籍演示了如何从拖动点移动小部件 使用弹簧模拟回到中心。

要实际体验它,请查看example:

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

void main() 
  runApp(MaterialApp(home: PhysicsCardDragDemo()));


class PhysicsCardDragDemo extends StatelessWidget 
  @override
  Widget build(BuildContext context) 
    return Scaffold(
      appBar: AppBar(),
      body: DraggableCard(
        child: FlutterLogo(
          size: 128,
        ),
      ),
    );
  


/// A draggable card that moves back to [Alignment.center] when it's
/// released.
class DraggableCard extends StatefulWidget 
  final Widget child;
  DraggableCard(required this.child);

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


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

  /// The alignment of the card as it is dragged or being animated.
  ///
  /// While the card is being dragged, this value is set to the values computed
  /// in the GestureDetector onPanUpdate callback. If the animation is running,
  /// this value is set to the value of the [_animation].
  Alignment _dragAlignment = Alignment.center;

  late Animation<Alignment> _animation;

  /// Calculates and runs a [SpringSimulation].
  void _runAnimation(Offset pixelsPerSecond, Size size) 
    _animation = _controller.drive(
      AlignmentTween(
        begin: _dragAlignment,
        end: Alignment.center,
      ),
    );
    // Calculate the velocity relative to the unit interval, [0,1],
    // used by the animation controller.
    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,
        ),
      ),
    );
  

样本输出:

【讨论】:

嗨,这很好,但是如果您真的需要使用 Draggable 小部件,因为您有需要 Draggable 的数据的 DragTarget,该怎么办?【参考方案2】:

解决这个问题的一个简单方法是创建一个覆盖 Draggable 的小部件,并使其成为 AnimatedPositioned 的子级。示例如下:

import 'package:flutter/material.dart';

class XDraggable extends StatefulWidget 
  const XDraggable(
      Key? key,
      required this.child,
      required this.original_x,
      required this.original_y,
      this.animation_speed = 200)
      : super(key: key);
  final Widget child;
  final double original_x;
  final double original_y;
  final double animation_speed;

  @override
  _XDraggableState createState() => _XDraggableState();


class _XDraggableState extends State<XDraggable> 
  double x = 200;
  double y = 200;

  int animation_speed = 0;

  @override
  void initState() 
    x = widget.original_x;
    y = widget.original_y;

    super.initState();
  

  @override
  Widget build(BuildContext context) 
    return AnimatedPositioned(
      left: x,
      top: y,
      duration: Duration(milliseconds: animation_speed),
      child: Draggable(
        onDragUpdate: (details) => 
          setState(() 
            animation_speed = 0;
            x = x + details.delta.dx;
            y = y + details.delta.dy;
          ),
        ,
        onDragEnd: (details) 
          setState(() 
            animation_speed = 200;
            x = widget.original_x;
            y = widget.original_y;
          );
        ,
        child: widget.child,
        feedback: SizedBox(),
      ),
    );
  


然后,只需将小部件用作 Stack 子项:

...
child: Stack(
            fit: StackFit.expand,
            children: [
              XDraggable(
                original_x: 20,
                original_y: 20,
                child: Container(
                  height: 50.0,
                  width: 50.0,
                  color: Colors.green,
                ),
              )
            ],
          ),
...

【讨论】:

以上是关于Flutter - DragBox 反馈动画到原始位置的主要内容,如果未能解决你的问题,请参考以下文章

深入分析 Flutter 渲染性能

Flare Flutter 动画问题

将 Flutter 动画直接渲染到视频中

Flutter 之 动画1

从 Firebase 获取原始 JSON 到 Flutter

基于音量反馈的UIImageView动画