flutter / flutter

Flutter makes it easy and fast to build beautiful apps for mobile and beyond
https://flutter.dev
BSD 3-Clause "New" or "Revised" License
166.33k stars 27.53k forks source link

Add an example of a dynamically-updatable Simulation subclass #14397

Open cachapa opened 6 years ago

cachapa commented 6 years ago

I'm trying to build a simple app which animates a red circle following the user's finger as it's dragged vertically on the screen. The problem I'm having is that the animation stops completely while the finger is dragged across the screen. As soon as dragging stops, the animation resumes normally.

I believe the problem is that there is no way to update the current SpringSimulation with a new target value, but rather I'm forced to create a new one on each "drag" event. This resets the internal simulation timer to zero, causing the movement to stop. From quick experimentation I couldn't get tween animations to work either, so I believe they have the same problem.

flutter

Source code

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

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

class MyApp extends StatelessWidget {
  // This widget is the root of your application.
  @override
  Widget build(BuildContext context) {
    return new MaterialApp(
      title: 'Flutter Demo',
      theme: new ThemeData(
        primarySwatch: Colors.blue,
      ),
      home: new Signature(),
    );
  }
}

class Signature extends StatefulWidget {
  SignatureState createState() => new SignatureState();
}

class SignatureState extends State<Signature>
    with SingleTickerProviderStateMixin {
  double _y = 0.0;

  Widget build(BuildContext context) {
    return new GestureDetector(
      onPanUpdate: (DragUpdateDetails details) {
        RenderBox referenceBox = context.findRenderObject();
        Offset localPosition =
        referenceBox.globalToLocal(details.globalPosition);
        springSimulation = new SpringSimulation(
            spring, _y, localPosition.dy, animationController.velocity);
        animationController.animateWith(springSimulation);
      },
      child: new CustomPaint(painter: new SignaturePainter(_y)),
    );
  }

  SpringDescription spring = new SpringDescription(
      mass: 1.0, stiffness: 100.0, damping: 10.0);
  SpringSimulation springSimulation;
  AnimationController animationController;

  initState() {
    super.initState();
    animationController = new AnimationController(
      vsync: this,
      lowerBound: double.NEGATIVE_INFINITY,
      upperBound: double.INFINITY,
    );
    animationController.addListener(() {
      print(animationController.value);
      setState(() {
        print(animationController.value);
        _y = animationController.value;
      });
    });
  }
}

class SignaturePainter extends CustomPainter {
  final double y;

  SignaturePainter(this.y);

  void paint(Canvas canvas, Size size) {
    var paint = new Paint()
      ..color = Colors.red
      ..strokeCap = StrokeCap.round
      ..strokeWidth = 20.0;
    canvas.drawCircle(new Offset(size.width / 2, y), 20.0, paint);
  }

  bool shouldRepaint(SignaturePainter other) => other.y != y;
}

Flutter Doctor

[✓] Flutter (on Linux, locale en_GB.UTF-8, channel alpha)
    • Flutter version 0.0.21 at /home/cachapa/flutter
    • Framework revision 2e449f06f0 (2 days ago), 2018-01-29 14:26:51 -0800
    • Engine revision 6921873c71
    • Tools Dart version 2.0.0-dev.16.0
    • Engine Dart version 2.0.0-edge.da1f52592ef73fe3afa485385cb995b9aec0181a

[✓] Android toolchain - develop for Android devices (Android SDK 27.0.1)
    • Android SDK at /home/cachapa/Android/Sdk
    • Android NDK location not configured (optional; useful for native profiling support)
    • Platform android-27, build-tools 27.0.1
    • Java binary at: /opt/android-studio/jre/bin/java
    • Java version OpenJDK Runtime Environment (build 1.8.0_152-release-915-b01)

[✓] Android Studio (version 3.0)
    • Android Studio at /opt/android-studio
    • Java version OpenJDK Runtime Environment (build 1.8.0_152-release-915-b01)

[✓] Connected devices
    • Pixel 2 • FA79L1A10786 • android-arm • Android 8.1.0 (API 27)
Hixie commented 6 years ago

Yeah, the problem is that on the first frame of the animation, the AnimationController starts at the initial values. Since you're updating every frame, you keep resetting it to the initial values.

I recommend creating your own SpringSimulation subclass that you can adjust the parameters on dynamically without restarting the animation controller.

Hixie commented 6 years ago

We should probably give an example of doing this in the API docs somewhere. Maybe the Simulation class docs.

Hixie commented 6 years ago

Maybe also mention in animateWith that calling it every frame will prevent the animation from advancing.

cachapa commented 6 years ago

Android's SpringAnimation allows updating the target position in an existing object, which solves this issue.

I feel this is a common enough use case that it should be supported by SpringSimulation rather than forcing the developer to subclass it.

matthew-carroll commented 4 years ago

@Hixie I might be able to handle this. I'm trying to do something similar with springs. Do you have a sense for whether it's preferable to show a subclass doc example vs extend or introduce behavior as the previous commenter suggested?

MrHeer commented 3 years ago

Is there any progress now? I think a dynamically-updatable animation is very important, it will guarantee to be continuous as well.

There is a demo dynamic_animation