Gyakenji Gyakenji -3 years ago 225
Dart Question

Flutter animation how to fade in/out gradually

I'm starting out on Flutter and trying to make an animation which rotates while fading in and out continuously. So far rotation works, but I'm having difficulties with the fading effect. The widget will gradually become transparent but right after one rotation, it jumps back into opaque before turning transparent again. I'm trying to fix this but I can't seem to find out how. Using

.forward()
and
.reverse()
doesn't work, but it's possible I may have implemented the opaque animation incorrectly.

class AnimatedLoader extends AnimatedWidget {
static final _opacityTween = new Tween<double>(begin: 1.0, end: 0.3);

AnimatedLoader({
Key key,
this.alignment: FractionalOffset.center,
Animation<double> turns,
Animation<double> animation,
this.child,
}) : super(key: key, listenable: turns);

Animation<double> get turns => listenable;

final FractionalOffset alignment;
final Widget child;

@override
Widget build(BuildContext context) {
final Animation<double> animation = listenable;
final double turnsValue = turns.value;
final Matrix4 transform = new Matrix4.rotationZ(turnsValue * math.PI * 2.0);
return new Transform(
alignment: alignment,
transform: transform,
child: new Opacity(
opacity: _opacityTween.evaluate(animation),
child: child,
)
);
}
}

class AppLoader extends StatefulWidget {
AppLoaderState createState() => new AppLoaderState();
}

class AppLoaderState extends State<AppLoader> with TickerProviderStateMixin {
AnimationController _controller;
AnimationController _controllerOp;
Animation<double> animation;

@override initState(){
super.initState();
_controller = new AnimationController(
duration: const Duration(milliseconds: 1500),
vsync: this,
)..repeat();

_controllerOp = new AnimationController(
duration: const Duration(milliseconds: 800),
vsync: this,
);
animation = new Tween(begin: 0.0, end: 300.0).animate(_controllerOp);

animation.addStatusListener((status) {
if (status == AnimationStatus.completed) {
_controllerOp.reverse();
} else if (status == AnimationStatus.dismissed) {
_controllerOp.forward();
}
});
_controllerOp.forward();
}

@override
Widget build(BuildContext context) {
return new Center (
child: new AnimatedLoader(
turns: _controller,
alignment: FractionalOffset.center,
animation: _controllerOp,
child: new Container(
margin: new EdgeInsets.symmetric(vertical: 10.0),
height: 150.0,
width: 150.0,
child: new FlutterLogo(),
)
),
);
}


Sorry for the big chunk of code, I'm unsure which part I could've made a mistake in.

Answer Source

I think you're on the right track, but you should only use one AnimationController per AnimatedWidget. I fixed some bugs in your code.

video of pulsing flutter logo

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

void main() {
  runApp(new MyApp());
}

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return new MaterialApp(
      home: new MyHomePage(),
    );
  }
}

class MyHomePage extends StatelessWidget {
  Widget build(BuildContext context) {
    return new Scaffold(
      body: new AppLoader(),
    );
  }
}

class PulsateCurve extends Curve {
  @override
  double transform(double t) {
    if (t == 0 || t == 1)
      return 0.3;
    return math.sin(t * math.PI) * 0.35 + 0.65;
  }
}

class AnimatedLoader extends AnimatedWidget {
  static final _opacityTween = new CurveTween(curve: new PulsateCurve());

  AnimatedLoader({
    Key key,
    this.alignment: FractionalOffset.center,
    Animation<double> animation,
    this.child,
  }) : super(key: key, listenable: animation);

  final FractionalOffset alignment;
  final Widget child;

  @override
  Widget build(BuildContext context) {
    final Animation<double> animation = listenable;
    final Matrix4 transform = new Matrix4.rotationZ(animation.value * math.PI * 2.0);
    return new Transform(
        alignment: alignment,
        transform: transform,
        child: new Opacity(
          opacity: _opacityTween.evaluate(animation),
          child: child,
        )
    );
  }
}

class AppLoader extends StatefulWidget {
  AppLoaderState createState() => new AppLoaderState();
}

class AppLoaderState extends State<AppLoader> with TickerProviderStateMixin {
  AnimationController _controller;

  @override initState() {
    super.initState();
    _controller = new AnimationController(
      duration: const Duration(milliseconds: 1500),
      vsync: this,
    )..repeat();
  }

  @override
  Widget build(BuildContext context) {
    return new Center (
      child: new AnimatedLoader(
          animation: _controller,
          alignment: FractionalOffset.center,
          child: new Container(
            margin: new EdgeInsets.symmetric(vertical: 10.0),
            height: 150.0,
            width: 150.0,
            child: new FlutterLogo(),
          )
      ),
    );
  }
}
Recommended from our users: Dynamic Network Monitoring from WhatsUp Gold from IPSwitch. Free Download