flutter - 在动画上创建弹性效果

flutter - create an elastic effect on animation

我在高度为 100.0Stack 小部件中有一个容器。它使用 Positioned 小部件位于中心,如下所示

Container(
  width:100.0,
  height:100.0,
  child: Stack(
    fit: StackFit.expand,
    children: <Widget>[
      Positioned(
        top: 40.0,
        child: Container(
          width: 20.0,
          height: 20.0,
          color: Colors.red,
        ),
      )
    ],
  )
)

我想以这样的方式设置红色容器的动画,使其在单击时到达父容器的底部,在再次单击时弹回顶部然后返回中心。

我尝试使用 Curves.elasticOut,但这对我来说不够弹跳。 我如何实现这种效果

试试这个代码,让我知道我是否正确理解你的动画

已编辑:

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

const BOX_COLOR = Colors.cyan;

void main() => runApp(new MyApp());

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: "Spring Box",
      theme: ThemeData(
        primaryColor: Colors.red,
      ),
      home: HomePage(),
    );
  }
}

class HomePage extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
        body: Container(
          child: Padding(
            child: PhysicsBox(),
            padding: EdgeInsets.only(
              left: 20.0,
              right: 20.0,
              top: 20.0,
              bottom: 20.0,
            ),
          ),
        ));
  }
}

class PhysicsBox extends StatefulWidget {

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

class BoxState extends State<PhysicsBox> with TickerProviderStateMixin {

  AnimationController controller;
  AnimationController controller2;
  Animation<double> animation;
  SpringSimulation simulation;
  double _position;

  @override
  void initState() {
    super.initState();
    simulation = SpringSimulation(
      SpringDescription(
        mass: 1.0,
        stiffness: 100.0,
        damping: 5.0,
      ),
      400.0,
      208.0,
      -4000.0,
    );

    controller2 = AnimationController(vsync: this,duration: Duration(milliseconds: 70));
    animation = Tween(begin: 200.0, end: 400.0).animate(controller2)
      ..addListener((){
        if(controller2.status == AnimationStatus.completed){controller.reset();}
        setState(() {
          _position = animation.value;
        });
      });

    controller = AnimationController(vsync: this,duration: Duration(milliseconds: 700))..forward()
      ..addListener(() {
        if(controller.status == AnimationStatus.completed){controller2.reset();}
        setState(() {
          _position = simulation.x(controller.value);
        });
        print('${simulation.x(controller.value)}');
      });
  }

  @override
  Widget build(BuildContext context) {



    return Container(
      color: Colors.yellow,
        width:500.0,
        height:500.0,
        child: Stack(
          fit: StackFit.expand,
          children: <Widget>[
            Positioned(
              top: _position,
              child: GestureDetector(
                onTap: (){

                  if (controller.status == AnimationStatus.completed) {
                    controller2.forward();//controller.reset();
                  }else{

                  controller.forward();}
                },
                child: Container(
                  width: 100.0,
                  height: 100.0,
                  color: Colors.red,
                ),
              ),
            )
          ],
        )
    );
  }
}

对 Flutter 使用 TweenMax:https://pub.dartlang.org/packages/tweenmax

GestureDetector 包装容器以使用点击手势,然后用 TweenContainer:

替换红色容器
import 'package:flutter/material.dart';
import 'package:flutter/physics.dart';
import 'package:tweenmax/tweenmax.dart';

void main() => runApp(new MyApp());

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: "Spring Box",
      theme: ThemeData(
        primaryColor: Colors.red,
      ),
      home: HomePage(),
    );
  }
}

class HomePage extends StatefulWidget {
  @override
  HomePageState createState() => HomePageState();
}

class HomePageState extends State<HomePage> {

  bool isClicked = false;

  @override
  Widget build(BuildContext context) {
    TweenContainer redContainer = TweenContainer(
      data: TweenData(
        top: 40,
        width: 20.0,
        height: 20.0,
        color: Colors.red,
      ),
    );

    return Scaffold(
      body: GestureDetector(
        child: Container(
          width: 100.0,
          height: 100.0,
          margin: EdgeInsets.only(top: 100, left: 100),
          color: Colors.yellow,
          child: Stack(
            fit: StackFit.expand,
            children: <Widget>[
              redContainer
            ],
          )
        ),
        onTap: (){
          // click first time, animate "redContainer" to bottom:
          if(!isClicked){
            TweenMax.to(
              redContainer,
              duration: 0.3,
              ease: Curves.ease,
              data: TweenData(
                top: 80
              )
            );
          } else { // click second time, animate "redContainer" to top:
            TweenMax.to(
              redContainer,
              duration: 0.2,
              ease: Curves.easeIn,
              data: TweenData(
                top: 0
              ),
              onComplete: (redContainer){
                // animate it back to center position:
                TweenMax.to(
                  redContainer,
                  duration: 0.8,
                  ease: ElasticOutCurve(0.3),
                  data: TweenData(
                    top: 40
                  )
                );
              }
            );
          }
          isClicked = !isClicked;
        },
      )
    );
  }
}

观看此视频:https://drive.google.com/file/d/1i3BgxWiVna6kQMRKVgUEQDTW2QTlSXWo/view?usp=sharing