I have created a custom class from the Floating Action Bubble package, to make it reusable. Now, if I declare the method customBubble as shown below, (which I am not sure it is the right way), everything works fine, apart the fact that I cannot access _animationController!.reverse() before the initState.
Else, if I declare the method customBubble inside class _FabAddCategoryState, the problem is that I am not able to access it from other class/screens.
Can you help me to write it in the right way?
the code:
class FabAddCategory extends StatefulWidget {
final List<Bubble>? items ;
const FabAddCategory({Key? key, this.items}) : super(key: key);
#override
State<FabAddCategory> createState() => _FabAddCategoryState();
Bubble customBubble({required IconData icon, TextStyle? titleStyle, Color? iconColor,
required String title, MaterialColor? bubbleColor, required Null Function() onPress, }) {
return Bubble(icon: icon, iconColor: Colors.white, title: title,
titleStyle: const TextStyle(fontSize: 16, color: Colors.white),
bubbleColor: Colors.amber,
onPress: (){
//_animationController!.reverse();
onPress.call();
});
}
}
class _FabAddCategoryState extends State<FabAddCategory> with SingleTickerProviderStateMixin{
Animation<double>? _animation;
AnimationController? _animationController;
#override
void initState() {
_animationController = AnimationController(
vsync: this,
duration: const Duration(milliseconds: 260),
);
final curvedAnimation = CurvedAnimation(curve: Curves.easeInOut, parent: _animationController!);
_animation = Tween<double>(begin: 0, end: 1).animate(curvedAnimation);
super.initState();
}
#override
Widget build(BuildContext context) {
return FloatingActionBubble(
items: widget.items!,
animation: _animation!,
onPress: () =>
_animationController!.isCompleted ?
_animationController!.reverse()
: _animationController!.forward(),
iconColor: Colors.white, backGroundColor: Theme.of(context).primaryColor,
iconData: Icons.menu,
);
}
}
Related
I am tring LED applciation.
My app can setting option use can change font-size , font-color etc.
I use AnimationController for control LED scroller by my self.
My problem.
When i key fill on text input for change message on led scroller . message movement by config of controller!.repeat(period: Duration(seconds: 10)); if short message movement it' slow but long message movement it quickly. one of reason because animation begin to end in 10 second. I don't want it. I want scrolling movement by duration fixed
import 'package:flutter/material.dart';
import 'package:get/get.dart';
import 'package:led_project2/controllers/setting_controller.dart';
class LedWidget extends StatefulWidget {
const LedWidget({Key? key}) : super(key: key);
#override
State<LedWidget> createState() => _LedWidgetState();
}
class _LedWidgetState extends State<LedWidget> with TickerProviderStateMixin {
SettingController c = Get.find();
AnimationController? controller;
late Animation<double> animation;
TextEditingController textEditingController = TextEditingController();
#override
void initState() {
super.initState();
controller = AnimationController(
vsync: this,
);
}
#override
void dispose() {
controller!.dispose();
super.dispose();
}
Size _textSize(String text, TextStyle style) {
final TextPainter textPainter = TextPainter(
text: TextSpan(text: text, style: style),
maxLines: 1,
textDirection: TextDirection.ltr)
..layout(minWidth: 0, maxWidth: double.infinity);
return textPainter.size;
}
#override
Widget build(BuildContext context) {
return Scaffold(
body: Container(
height: double.infinity,
width: double.infinity,
color: c.led.value.backgroundColor,
child: Stack(
alignment: Alignment.center,
children: <Widget>[
//
GetX<SettingController>(builder: (settingController) {
textEditingController.text = c.led.value.value ?? "";
final textValue = textEditingController.text;
final TextStyle textStyle = TextStyle(
color: c.led.value.fontColor, fontSize: c.led.value.fontSize);
final Size txtSize = _textSize(textValue, textStyle);
controller!.repeat(period: Duration(seconds: 10));
final Animation<double> curve = CurvedAnimation(
parent: controller!,
curve: Curves.linear,
);
Animation<int> alpha = IntTween(
begin: 0 - txtSize.width.toInt(),
end: Get.width.toInt() + txtSize.width.toInt())
.animate(curve);
return AnimatedBuilder(
animation: alpha,
builder: (context, child) {
return Positioned(
right: alpha.value.toDouble(),
child: Text(
textEditingController.text,
style: textStyle,
),
);
},
);
}),
],
),
));
}
}
I'm writting a small tamagotchi app using Flutter and now I'm learning how to use flutter_bloc lib.
When user tap on a pet image on a screen, it must redraw a CircularPercentIndicator widget, but it won't work. I'm trying to connect a view with a bloc using a BlocBuilder and BlocProvider classes, but it did not help.
After tapping a pet widget, animation is forwarded, but the state of saturationCount and CircularPercentIndicator hasn't been updated.
Here is my BLoC for pet feeding:
class PetFeedingBloc extends Bloc<SaturationEvent, SaturationState> {
PetFeedingBloc()
: super(const SaturationState(saturationCount: 40.0)) {
on<SaturationSmallIncrementEvent>((event, emit) => state.saturationCount + 15.0);
on<SaturationBigIncrementEvent>((event, emit) => state.saturationCount + 55.0);
on<SaturationDecrementEvent>((event, emit) => state.saturationCount - 2.0);
}
}
In SaturationBarWidget class I'm trying to connect a percent indicator in a widget with a BLoC, but it does not work. Here it is:
class SaturationBarWidget extends StatefulWidget {
const SaturationBarWidget({Key? key}) : super(key: key);
#override
State<SaturationBarWidget> createState() => SaturationBarWidgetState();
}
class SaturationBarWidgetState extends State<SaturationBarWidget> {
#override
void initState() {
Timer? timer;
timer = Timer.periodic(const Duration(milliseconds: 3000), (_) {
setState(() {
context.read<PetFeedingBloc>().add(SaturationDecrementEvent());
if (context.read<PetFeedingBloc>().state.saturationCount <= 0) {
timer?.cancel();
}
});
});
super.initState();
}
#override
Widget build(BuildContext context) {
return BlocBuilder<PetFeedingBloc, SaturationState>(builder: (context, state){
return CircularPercentIndicator(
radius: 50.0,
lineWidth: 20.0,
animateFromLastPercent: true,
percent: context.read<PetFeedingBloc>().state.saturationCount / 100,
center: const Icon(
Icons.emoji_emotions_outlined,
size: 50.0,
),
backgroundColor: Colors.blueGrey,
progressColor: Colors.blue,
);
});
}
}
And here it is my PetWidget class with image that need to be tapped:
class PetWidget extends StatefulWidget {
const PetWidget({Key? key}) : super(key: key);
#override
State<PetWidget> createState() => PetWidgetState();
}
class PetWidgetState extends State<PetWidget> with TickerProviderStateMixin {
late Animation<Offset> _animation;
late AnimationController _animationController;
static GlobalKey<SaturationBarWidgetState> key = GlobalKey();
bool reverse = true;
Image cat = Image.asset('images/cat.png');
#override
void initState() {
super.initState();
_animationController =
AnimationController(vsync: this, duration: const Duration(seconds: 4));
_animation = Tween<Offset>(begin: Offset.zero, end: const Offset(1, 0))
.animate(CurvedAnimation(
parent: _animationController, curve: Curves.elasticIn));
_animationController.addStatusListener((status) {
if (status == AnimationStatus.completed) {
_animationController.reverse();
}
});
//_animationController.forward();
}
#override
void dispose() {
_animationController.dispose();
super.dispose();
}
#override
Widget build(BuildContext context) {
return Center(child:
BlocBuilder<PetFeedingBloc, SaturationState>(builder: (context, state) {
return Center(
child: SizedBox(
width: 300,
height: 400,
child: SlideTransition(
position: _animation,
child: GestureDetector(
child: cat,
onDoubleTap: () {
context.read<PetFeedingBloc>().add(SaturationBigIncrementEvent());
_animationController.forward();
},
onTap: () {
context.read<PetFeedingBloc>().add(SaturationSmallIncrementEvent());
_animationController.forward();
},
),
),
)
);
})
);
}
}
I think you have to call the emit method in you
PetFeedingBloc
class PetFeedingBloc extends Bloc<SaturationEvent, SaturationState> {
PetFeedingBloc()
: super(const SaturationState(saturationCount: 40.0)) {
on<SaturationSmallIncrementEvent>((event, emit) => emit(SaturationState(saturationCount: state.saturationCount + 15.0)) );
...
}
}
I am just trying out flutter animations, and I know that the Animation controller needs vsync ( TickerProvider ) , so I used the SingleTickerProviertateMixin. Now I want to slow down this following animation.
class AnimatedGradientText extends StatefulWidget {
final String data;
final double size;
AnimatedGradientText(this.data, {this.size});
#override
_AnimatedGradientTextState createState() => _AnimatedGradientTextState();
}
class _AnimatedGradientTextState extends State<AnimatedGradientText> with SingleTickerProviderStateMixin {
List listOfColors = <Color>[
Colors.red[200],
Colors.blue[200],
Colors.green[200],
Colors.yellow[200],
// Colors.orange[200],
// Colors.teal[200],
// Colors.cyan[200],
// Colors.purple[200],
// Colors.brown[200],
// Colors.amber[200],
// Colors.pink[200],
];
AnimationController _controller;
void shift(){
Color color = listOfColors.removeAt(Random().nextInt(listOfColors.length));
listOfColors.add(color);
}
#override
void initState() {
_controller = AnimationController(vsync: this , duration: Duration(seconds: 5),upperBound: 10,lowerBound: 0);
_controller.forward(from: 0);
_controller.addListener((){
setState(() {
shift();
});
});
super.initState();
}
#override
void deactivate() {
_controller.dispose();
super.deactivate();
}
#override
Widget build(BuildContext context) {
return Text(
widget.data,
style: TextStyle(
fontSize: widget.size*10,
fontFamily: 'AudioWide',
foreground: Paint()..shader = LinearGradient(
colors: listOfColors
).createShader(Rect.fromLTWH(0.0, 0.0, 200.0, 70.0)),
),
);
}
}
Basically what I am trying to achieve with this is some text that has gradient and changes the gradient over time. But the animation is too fast that it doesnt interpolate smoothly.
Any suggestions regarding an easier way to achieve this will also be helpful. Thanks for your time !
As addListener call shift method every time when data is change, so we have to control is ourselves.
In Following code i add delay of one second to change color, you can play with await and change whatever you find best for you.
Following code will help you to understand more.
class AnimatedGradientText extends StatefulWidget {
final String data;
final double size;
AnimatedGradientText(this.data, {this.size});
#override
_AnimatedGradientTextState createState() => _AnimatedGradientTextState();
}
class _AnimatedGradientTextState extends State<AnimatedGradientText>
with SingleTickerProviderStateMixin {
List listOfColors = <Color>[
Colors.red[200],
Colors.blue[200],
Colors.green[200],
Colors.yellow[200],
// Colors.orange[200],
// Colors.teal[200],
// Colors.cyan[200],
// Colors.purple[200],
// Colors.brown[200],
// Colors.amber[200],
// Colors.pink[200],
];
AnimationController _controller;
bool canCall = true;
void shift() async {
await Future.delayed(Duration(seconds: 1));
Color color = listOfColors.removeAt(Random().nextInt(listOfColors.length));
listOfColors.add(color);
canCall = !canCall;
}
callme() {
if (canCall) {
canCall = !canCall;
shift();
}
}
#override
void initState() {
_controller = AnimationController(
vsync: this,
duration: Duration(seconds: 5),
upperBound: 10,
lowerBound: 0,
);
_controller.forward(from: 0);
_controller.addListener(() {
setState(() {
// shift();
callme();
});
});
super.initState();
}
#override
void deactivate() {
_controller.dispose();
super.deactivate();
}
#override
Widget build(BuildContext context) {
return Text(
widget.data,
style: TextStyle(
fontSize: widget.size * 10,
fontFamily: 'AudioWide',
foreground: Paint()
..shader = LinearGradient(colors: listOfColors)
.createShader(Rect.fromLTWH(0.0, 0.0, 200.0, 70.0)),
),
);
}
}
You can use a Tween to control how fast your slow you want your animation.
The code below shows you how to use a Tween that works perfectly well.
Check the code below, it works well.
Tween<Int>(
begin:0,
end: 255,
);
I hope this helps.
Expose timeDilation property and set it to higher value to slowdown
import 'package:flutter/scheduler.dart' show timeDilation;
timeDilation = 2.0;
I came across this on StackOverFlow some time back
For a flutter project I needed a tree structure in which when clicking on a node, not only the entries below it are displayed, but - as with the file manager in Windows - also the content: On a smartphone as a new screen and on a tablet as an additional area to the right of the list.
Unfortunately, the standard ExpansionTile does not have this capability.
Since I'm a newcomer to Flutter, I first looked at the source code and tried to understand the most important parts (I'm still at it ;-). Then I made the following changes:
A property 'AlignOpener' now decides whether the open/close icon is displayed on the left or right.
I added the properties 'onTap' and 'onLongPress' as callback. Once one of these properties is assigned in the calling widget, the '_isInAdvancedMode' property is set to true and an IconButton is inserted as leading or trailing.
Clicking this button will open/close the directory tree. A click on the remaining part is forwarded to the calling widget via callback.
Finally, I added a property 'indentListTile' to control the indent of each layer.
If none of the properties are assigned, the AvancedExpansionTile behaves like the standard ExpansionTile.
Since I'm a newbie, there's still a lot of uncertainty as to whether the code is really correct. As far as I can see, it works, but I would be happy if experienced developers among you could check the code and make suggestions for improvements if necessary.
Maybe the code solves a problem that others also had?
Her is the code:
master_list_item.dart
import 'package:meta/meta.dart';
class ItemMaster {
ItemMaster(
{this.id,
#required this.title,
this.subtitle,
this.children = const <ItemMaster>[]});
final int id;
final String title;
final String subtitle;
final List<ItemMaster> children;
}
master_list.dart
import 'master_list_item.dart';
class MasterList {
List<ItemMaster> get items {
return _items;
}
final List<ItemMaster> _items = <ItemMaster>[
ItemMaster(
title: 'Chapter Master List',
id: 1,
children: <ItemMaster>[
ItemMaster(
title: 'Scene A0',
id: 2,
children: <ItemMaster>[
ItemMaster(title: 'Item A0.1', id: 3),
ItemMaster(title: 'Item A0.2', id: 4),
ItemMaster(title: 'Item A0.3', id: 5),
],
),
ItemMaster(title: 'Scene A1', id: 6),
ItemMaster(title: 'Scene A2', id: 7),
],
),
ItemMaster(
title: 'Chapter B',
id: 8,
children: <ItemMaster>[
ItemMaster(title: 'Scene B0', id: 9),
ItemMaster(title: 'Scene B1', id: 10),
],
),
ItemMaster(
title: 'Chapter C',
id: 11,
children: <ItemMaster>[
ItemMaster(title: 'Scene C0', id: 12),
ItemMaster(title: 'Scene C1', id: 13),
ItemMaster(
title: 'Scene C2',
id: 14,
children: <ItemMaster>[
ItemMaster(title: 'Item C2.0', id: 15),
ItemMaster(title: 'Item C2.1', id: 16),
ItemMaster(title: 'Item C2.2', id: 17),
ItemMaster(title: 'Item C2.3', id: 18),
],
),
],
),
];
}
main.dart
import 'package:flutter/material.dart';
import 'advanced_expansion_tile.dart';
import 'master_list_item.dart';
import 'master_list.dart';
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
// This widget is the root of your application.
#override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Demo',
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: MyHomePage(title: 'Flutter Demo Home Page'),
);
}
}
class MyHomePage extends StatefulWidget {
MyHomePage({Key key, this.title}) : super(key: key);
final String title;
#override
_MyHomePageState createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
List<ItemMaster> items;
#override
void initState() {
// TODO: implement initState
super.initState();
items = MasterList().items;
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text(widget.title),
),
body: Center(
child: Container(
child: ListView.builder(
itemCount: items.length,
itemBuilder: (context, int index) => MasterListEntry(items[index]),
)),
),
);
}
}
class MasterListEntry extends StatelessWidget {
const MasterListEntry(this.entry);
final ItemMaster entry;
Widget _buildTiles(ItemMaster root) {
if (root.children.isEmpty)
return ListTile(
title: Text(root.title),
onTap: () => print("onTap listTile"),
);
return AdvancedExpansionTile(
key: PageStorageKey<ItemMaster>(root),
title: Text(root.title),
children: root.children.map(_buildTiles).toList(),
onTap: () => print("onTap AdvancedExpansionTile"),
alignOpener: AlignOpener.Right,
indentListTile: 15.0,
// isInAdvancedMode: true,
);
}
#override
Widget build(BuildContext context) {
return _buildTiles(entry);
}
}
advanced_expansion_tile.dart (based on the source code from the Flutter team)
// Copyright 2017 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
import 'package:flutter/widgets.dart';
import 'package:flutter/material.dart';
const Duration _kExpand = Duration(milliseconds: 200);
enum AlignOpener { Left, Right }
class AdvancedExpansionTile extends StatefulWidget {
const AdvancedExpansionTile({
Key key,
this.leading,
this.trailing,
#required this.title,
this.backgroundColor,
this.onExpansionChanged,
this.onTap,
this.onLongPress,
this.alignOpener,
this.indentListTile,
this.children = const <Widget>[],
this.initiallyExpanded = false,
}) : assert(initiallyExpanded != null),
super(key: key);
/// A widget to display before the title.
///
/// Typically a [CircleAvatar] widget.
final Widget leading;
/// The primary content of the list item.
///
/// Typically a [Text] widget.
final Widget title;
/// Called when the tile expands or collapses.
///
/// When the tile starts expanding, this function is called with the value
/// true. When the tile starts collapsing, this function is called with
/// the value false.
final ValueChanged<bool> onExpansionChanged;
/// The widgets that are displayed when the tile expands.
///
/// Typically [ListTile] widgets.
final List<Widget> children;
/// The color to display behind the sublist when expanded.
final Color backgroundColor;
/// Specifies if the list tile is initially expanded (true) or collapsed (false, the default).
final bool initiallyExpanded;
/// A widget to display instead of a rotating arrow icon.
final Widget trailing;
/// A callback for onTap and onLongPress on the listTile
final GestureTapCallback onTap;
final GestureLongPressCallback onLongPress;
/// The side where the Open/Close-Icon/IconButton will be placed
final AlignOpener alignOpener;
/// indent of listTile (left)
final indentListTile;
#override
_AdvancedExpansionTileState createState() => _AdvancedExpansionTileState();
}
class _AdvancedExpansionTileState extends State<AdvancedExpansionTile>
with SingleTickerProviderStateMixin {
static final Animatable<double> _easeOutTween =
CurveTween(curve: Curves.easeOut);
static final Animatable<double> _easeInTween =
CurveTween(curve: Curves.easeIn);
static final Animatable<double> _halfTween =
Tween<double>(begin: 0.0, end: 0.5);
final ColorTween _borderColorTween = ColorTween();
final ColorTween _headerColorTween = ColorTween();
final ColorTween _iconColorTween = ColorTween();
final ColorTween _backgroundColorTween = ColorTween();
AnimationController _controller;
Animation<double> _iconTurns;
Animation<double> _heightFactor;
Animation<Color> _borderColor;
Animation<Color> _headerColor;
Animation<Color> _iconColor;
Animation<Color> _backgroundColor;
bool _isExpanded = false;
/// If set to true an IconButton will be created. This button will open/close the children
bool _isInAdvancedMode;
AlignOpener _alignOpener;
double _indentListTile;
#override
void initState() {
super.initState();
_controller = AnimationController(duration: _kExpand, vsync: this);
_heightFactor = _controller.drive(_easeInTween);
_iconTurns = _controller.drive(_halfTween.chain(_easeInTween));
_borderColor = _controller.drive(_borderColorTween.chain(_easeOutTween));
_headerColor = _controller.drive(_headerColorTween.chain(_easeInTween));
_iconColor = _controller.drive(_iconColorTween.chain(_easeInTween));
_backgroundColor =
_controller.drive(_backgroundColorTween.chain(_easeOutTween));
_isExpanded =
PageStorage.of(context)?.readState(context) ?? widget.initiallyExpanded;
if (_isExpanded) _controller.value = 1.0;
/// OnTap or onLongPress are handled in the calling widget --> AdvancedExpansionTile is in Advanced Mode
if (widget.onTap != null || widget.onLongPress != null) {
_isInAdvancedMode = true;
} else {
_isInAdvancedMode = false;
}
/// fallback to standard behaviour if aligning isn't set
_alignOpener = widget.alignOpener ?? AlignOpener.Right;
/// if no indent is set the indent will be 0.0
_indentListTile = widget.indentListTile ?? 0.0;
}
#override
void dispose() {
_controller.dispose();
super.dispose();
}
void _handleTap() {
setState(() {
_isExpanded = !_isExpanded;
if (_isExpanded) {
_controller.forward();
} else {
_controller.reverse().then<void>((void value) {
if (!mounted) return;
setState(() {
// Rebuild without widget.children.
});
});
}
PageStorage.of(context)?.writeState(context, _isExpanded);
});
if (widget.onExpansionChanged != null)
widget.onExpansionChanged(_isExpanded);
}
Widget _buildChildren(BuildContext context, Widget child) {
final Color borderSideColor = _borderColor.value ?? Colors.transparent;
return Container(
decoration: BoxDecoration(
color: _backgroundColor.value ?? Colors.transparent,
border: Border(
top: BorderSide(color: borderSideColor),
bottom: BorderSide(color: borderSideColor),
),
),
child: Column(
mainAxisSize: MainAxisSize.min,
children: <Widget>[
ListTileTheme.merge(
iconColor: _iconColor.value,
textColor: _headerColor.value,
child: ListTile(
onTap: () {
_isInAdvancedMode ? widget.onTap() : _handleTap();
}, // in AdvancedMode a callback will handle the gesture inside the calling widget
onLongPress: () {
_isInAdvancedMode ? widget.onLongPress() : _handleTap();
}, // in AdvancedMode a callback will handle the gesture inside the calling widget
leading: getLeading(),
title: widget.title,
trailing: getTrailing(),
),
),
ClipRect(
child: Padding(
padding: EdgeInsets.only(left: _indentListTile), // set the indent
child: Align(
heightFactor: _heightFactor.value,
child: child,
),
)),
],
),
);
}
#override
void didChangeDependencies() {
final ThemeData theme = Theme.of(context);
_borderColorTween..end = theme.dividerColor;
_headerColorTween
..begin = theme.textTheme.subhead.color
..end = theme.accentColor;
_iconColorTween
..begin = theme.unselectedWidgetColor
..end = theme.accentColor;
_backgroundColorTween..end = widget.backgroundColor;
super.didChangeDependencies();
}
#override
Widget build(BuildContext context) {
final bool closed = !_isExpanded && _controller.isDismissed;
return AnimatedBuilder(
animation: _controller.view,
builder: _buildChildren,
child: closed ? null : Column(children: widget.children),
);
}
/// A method to decide what will be shown in the leading part of the lisTile
getLeading() {
if (_alignOpener.toString() == AlignOpener.Left.toString() &&
_isInAdvancedMode == true) {
return buildIcon(); //IconButton will be created
} else if (_alignOpener.toString() == AlignOpener.Left.toString() &&
_isInAdvancedMode == false) {
return widget.leading ??
RotationTransition(
turns: _iconTurns,
child: const Icon(Icons.expand_more),
);
} else {
return widget.leading;
}
}
/// A method to decide what will be shown in the trailing part of the lisTile
getTrailing() {
if (_alignOpener.toString() == AlignOpener.Right.toString() &&
_isInAdvancedMode == true) {
return buildIcon(); //IconButton will be created
} else if (_alignOpener.toString() == AlignOpener.Right.toString() &&
_isInAdvancedMode == false) {
return widget.trailing ??
RotationTransition(
turns: _iconTurns,
child: const Icon(Icons.expand_more),
);
} else {
return widget.leading;
}
}
/// A widget to build the IconButton for the leading or trailing part of the listTile
Widget buildIcon() {
return Container(
child: RotationTransition(
turns: _iconTurns,
child: IconButton(
icon: Icon(Icons.expand_more),
onPressed: () {
_handleTap();
//toDo: open/close is working but not the animation
},
),
));
}
}
I have a custom widget that has normal / animated state. Sometimes I want to be it animated, and sometimes static.
I have made a simple test project to demonstrate my problem: test page contains my custom widget (ScoreBoard) and 2 buttons to start / stop animating scoreboard. My problem, that ScoreBoard is not animated, even if I start animation.
Here is my code:
TestPage:
class TestPage extends StatefulWidget {
#override
_TestPageState createState() => _TestPageState();
}
class _TestPageState extends State<TestPage> {
bool _animate;
#override
void initState() {
_animate = false;
super.initState();
}
#override
Widget build(BuildContext context) {
return Scaffold(
body: Column(
mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.center,
children: <Widget>[
ScoreBoard(
text: "Hello, world!",
animate: _animate,
),
FlatButton(
child: Text("START animation"),
onPressed: () {
setState(() {
_animate = true;
});
},
),
FlatButton(
child: Text("STOP animation"),
onPressed: () {
setState(() {
_animate = false;
});
},
),
],
),
);
}
}
ScoreBoard widget:
class ScoreBoard extends StatefulWidget {
final String text;
final bool animate;
const ScoreBoard({Key key, this.text, this.animate}) : super(key: key);
#override
_ScoreBoardState createState() => _ScoreBoardState();
}
class _ScoreBoardState extends State<ScoreBoard>
with SingleTickerProviderStateMixin {
AnimationController _controller;
#override
void initState() {
super.initState();
_controller = new AnimationController(
duration: const Duration(seconds: 1),
vsync: this,
)..forward();
}
#override
void dispose() {
_controller.dispose();
super.dispose();
}
#override
Widget build(BuildContext context) {
return widget.animate
? ScaleTransition(
child:
Text(widget.text, style: Theme.of(context).textTheme.display1),
scale: new CurvedAnimation(
parent: _controller,
curve: Curves.easeIn,
),
)
: Container(
child:
Text(widget.text, style: Theme.of(context).textTheme.display1),
);
}
}
Would you be so kind to help me? Thanks in advance!
Answer
If you initialize an AnimationController widget and do not specify arguments for lowerBound and upperBound (which is the case here), your animation is going to start by default with lowerBound 0.0.
AnimationController({double value, Duration duration, String debugLabel, double lowerBound: 0.0, double upperBound: 1.0, AnimationBehavior animationBehavior: AnimationBehavior.normal, #required TickerProvider vsync })
Creates an animation controller. [...]
https://docs.flutter.io/flutter/animation/AnimationController-class.html
If you initialize the state of your widget ScoreBoard, the method forward gets called only once during the whole lifetime of your app. The method forward makes that your animation increases from the lowerBound (0.0) to the upperBound (1.0) within 1 second.
Starts running this animation forwards (towards the end).
https://docs.flutter.io/flutter/animation/AnimationController/forward.html
In our case, there is no way back once the method forward got called. We are only able to stop the animation.
Press Ctrl + F5 for a full restart of the app to see the animation.
To make it even clearer, change the duration of your animation to 10 seconds.
Btw. since Dart 2 you do not need to use the new keyword.
#override
void initState() {
super.initState();
_controller = AnimationController(
duration: const Duration(seconds: 10),
vsync: this,
)..forward();
}
Example
To see what happens you could add this to your build method:
#override
Widget build(BuildContext context) {
// Execute 'reverse' if the animation is completed
if (_controller.isCompleted)
_controller.reverse();
else if (_controller.isDismissed)
_controller.forward();
// ...
... and do not call the method forward in the initState method:
#override
void initState() {
super.initState();
_controller = new AnimationController(
duration: const Duration(seconds: 10),
vsync: this,
);
}