How to make "text flying in the screen" animation in flutter - flutter

I have a page in my app which if opened, displays 3 text flying in from the left of the screen, one after the other at a duration of 1 sec, to sit in the center of the page. How can I achieve this animation and what flutter package must I use?

I made the most basic Animation components, I hope useful to you
import 'dart:math';
import 'package:flutter/material.dart';
enum AnimationType {
ROTATION,
OFFSET,
}
enum OffsetType {
UP,
DOWN,
LEFT,
RIGHT,
}
typedef AnimationSwich = bool Function();
class BaseAnimationWidget extends StatefulWidget {
const BaseAnimationWidget(
{Key? key,
required this.type,
required this.body,
this.animationSwich,
this.rotationValue,
this.offset,
this.duration,
this.offsetType})
: assert(type == AnimationType.ROTATION
? rotationValue != null
: type == AnimationType.OFFSET
? offset != null && offsetType != null
: true),
super(key: key);
final AnimationSwich? animationSwich;
final Widget body;
final Offset? offset;
final double? rotationValue;
final AnimationType type;
final Duration? duration;
final OffsetType? offsetType;
#override
State<BaseAnimationWidget> createState() => _BaseAnimationWidgetState();
}
class _BaseAnimationWidgetState extends State<BaseAnimationWidget>
with SingleTickerProviderStateMixin {
late AnimationController _animationController;
#override
void initState() {
super.initState();
_animationController = AnimationController(
duration: widget.duration ?? Duration(milliseconds: 300), vsync: this);
}
#override
void dispose() {
_animationController.dispose();
super.dispose();
}
Offset get _offset => widget.offsetType == OffsetType.UP ||
widget.offsetType == OffsetType.DOWN
? Offset(
widget.offset!.dx, widget.offset!.dy * _animationController.value)
: Offset(
widget.offset!.dx * _animationController.value, widget.offset!.dy);
#override
Widget build(BuildContext context) {
if (widget.animationSwich == null) {
DoNothingAction();
} else if (mounted && widget.animationSwich!()) {
_animationController.forward();
} else if (mounted && !widget.animationSwich!()) {
_animationController.reverse();
}
return AnimatedBuilder(
animation: _animationController,
builder: (context, _) {
return widget.type == AnimationType.ROTATION
? Transform.rotate(
angle: widget.rotationValue! * _animationController.value,
child: widget.body)
: Transform.translate(
offset: _offset,
child: widget.body,
);
});
}
}
class MyWidget extends StatefulWidget {
const MyWidget({super.key});
#override
State<MyWidget> createState() => _MyWidgetState();
}
class _MyWidgetState extends State<MyWidget> {
var _animationSwich = true;
#override
Widget build(BuildContext context) {
var width = MediaQuery.of(context).size.width;
var height = MediaQuery.of(context).size.height;
return Scaffold(
body: SafeArea(
child: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.center,
children: [
// Left
BaseAnimationWidget(
type: AnimationType.OFFSET,
duration: Duration(seconds: 1),
body: Text('Test'),
offset: Offset(-width, 0),
offsetType: OffsetType.LEFT,
animationSwich: () => _animationSwich,
),
// Right
BaseAnimationWidget(
type: AnimationType.OFFSET,
duration: Duration(seconds: 1),
body: Text('Test'),
offset: Offset(width, 0),
offsetType: OffsetType.RIGHT,
animationSwich: () => _animationSwich,
),
// Up
BaseAnimationWidget(
type: AnimationType.OFFSET,
duration: Duration(seconds: 1),
body: Text('Test'),
offset: Offset(0, -height),
offsetType: OffsetType.UP,
animationSwich: () => _animationSwich,
),
// Down
BaseAnimationWidget(
type: AnimationType.OFFSET,
duration: Duration(seconds: 1),
body: Text('Test'),
offset: Offset(0, height),
offsetType: OffsetType.DOWN,
animationSwich: () => _animationSwich,
),
// Rotation
BaseAnimationWidget(
type: AnimationType.ROTATION,
duration: Duration(seconds: 1),
body: Text('Test'),
offset: Offset(-width, 0),
offsetType: OffsetType.LEFT,
rotationValue: pi,
animationSwich: () => _animationSwich,
),
TextButton(
onPressed: () {
_animationSwich = !_animationSwich;
setState(() {});
},
child: Text('Action!'))
],
))),
);
}
}

Related

Scroll and controller priority flutter

i have a CupertinoModalBottomSheet that is closing by scrolling down. And in this page i have image that i can pinch by two fingers. Is there any way to change priority to pinching when two fingers on screen. Because when i put two fingers on screen i can still close the page by scrolling down until i start pinching.
class PinchZoom extends StatefulWidget {
final Widget child;
PinchZoom({
Key? key,
required this.child,
}) : super(key: key);
#override
State<PinchZoom> createState() => _PinchZoomState();
}
class _PinchZoomState extends State<PinchZoom>
with SingleTickerProviderStateMixin {
late TransformationController controller;
late AnimationController animationController;
Animation<Matrix4>? animation;
Map<int, Offset> touchPositions = <int, Offset>{};
final double minScale = 1;
final double maxScale = 2;
double scale = 1;
OverlayEntry? entry;
#override
void initState() {
super.initState();
controller = TransformationController();
animationController = AnimationController(
vsync: this,
duration: Duration(milliseconds: 200),
)
..addListener(() => controller.value = animation!.value)
..addStatusListener((status) {
if (status == AnimationStatus.completed) {
removeOverlay();
}
});
}
#override
void dispose() {
controller.dispose();
animationController.dispose();
super.dispose();
}
#override
Widget build(BuildContext context) =>
BlocBuilder<DishInfoCubit, DishInfoState>(
builder: (_, _state) {
final dishCubit = BlocProvider.of<DishInfoCubit>(context);
return Builder(
builder: (context) => Listener(
onPointerMove: (opm) {
savePointerPosition(opm.pointer, opm.position);
if (touchPositions.length > 1) {
dishCubit.setIsPinchInProgress(true);
}
},
onPointerDown: (opm) {
savePointerPosition(opm.pointer, opm.position);
if (touchPositions.length > 1) {
dishCubit.setIsPinchInProgress(true);
}
},
onPointerCancel: (opc) {
clearPointerPosition(opc.pointer);
if (touchPositions.length < 2) {
dishCubit.setIsPinchInProgress(false);
}
},
onPointerUp: (opc) {
clearPointerPosition(opc.pointer);
if (touchPositions.length < 2) {
dishCubit.setIsPinchInProgress(false);
}
},
child: InteractiveViewer(
transformationController: controller,
clipBehavior: Clip.none,
panEnabled: false,
minScale: minScale,
maxScale: maxScale,
onInteractionStart: (details) {
print("trying to start anim");
if (details.pointerCount < 2) return;
animationController.stop();
},
onInteractionEnd: (details) {
if (details.pointerCount != 1) return;
resetAnimation();
dishCubit.setIsPinchInProgress(false);
},
child: AspectRatio(
aspectRatio: 1,
child: widget.child,
),
),
),
);
},
);
void removeOverlay() {
entry?.remove();
entry = null;
}
void resetAnimation() {
animation = Matrix4Tween(
begin: controller.value,
end: Matrix4.identity(),
).animate(
CurvedAnimation(parent: animationController, curve: Curves.easeInOut),
);
animationController.forward(from: 0);
}
void savePointerPosition(int index, Offset position) {
setState(() {
touchPositions[index] = position;
});
}
void clearPointerPosition(int index) {
setState(() {
touchPositions.remove(index);
});
}
}

Flutter - No BorderRadius assignment

When I build the application, I encounter such an error.
lib/input_page/pacman_slider.dart:41:7: Error: A value of type 'Animation<BorderRadius?>' can't be assigned to a variable of type 'Animation' because 'BorderRadius?' is nullable and 'BorderRadius' isn't.
'Animation' is from 'package:flutter/src/animation/animation.dart' ('../../Dev/flutter/packages/flutter/lib/src/animation/animation.dart').
'BorderRadius' is from 'package:flutter/src/painting/border_radius.dart' ('../../Dev/flutter/packages/flutter/lib/src/painting/border_radius.dart').
).animate(CurvedAnimation(
^
Error Code:
void initState() {
super.initState();
pacmanMovementController =
AnimationController(vsync: this, duration: Duration(milliseconds: 400));
_bordersAnimation = BorderRadiusTween(
begin: BorderRadius.circular(8.0),
end: BorderRadius.circular(50.0),
).animate(CurvedAnimation(
parent: widget.submitAnimationController,
curve: Interval(0.0, 0.07),
));
}
Full Code:
const double _pacmanWidth = 21.0;
const double _sliderHorizontalMargin = 24.0;
const double _dotsLeftMargin = 8.0;
class PacmanSlider extends StatefulWidget {
final VoidCallback onSubmit;
final AnimationController submitAnimationController;
const PacmanSlider(
{Key? key,
required this.onSubmit,
required this.submitAnimationController})
: super(key: key);
#override
_PacmanSliderState createState() => _PacmanSliderState();
}
class _PacmanSliderState extends State<PacmanSlider>
with TickerProviderStateMixin {
double _pacmanPosition = 24.0;
late Animation<BorderRadius> _bordersAnimation;
late Animation<double> _submitWidthAnimation;
late AnimationController pacmanMovementController;
late Animation<double> pacmanAnimation;
#override
void initState() {
super.initState();
pacmanMovementController =
AnimationController(vsync: this, duration: Duration(milliseconds: 400));
_bordersAnimation = BorderRadiusTween(
begin: BorderRadius.circular(8.0),
end: BorderRadius.circular(50.0),
).animate(CurvedAnimation(
parent: widget.submitAnimationController,
curve: Interval(0.0, 0.07),
));
}
#override
void dispose() {
pacmanMovementController.dispose();
super.dispose();
}
// double get width => _submitWidthAnimation?.value ?? 0.0;
double get width => _submitWidthAnimation.value;
#override
Widget build(BuildContext context) {
return LayoutBuilder(
builder: (context, constraints) {
_submitWidthAnimation = Tween<double>(
begin: constraints.maxWidth,
end: screenAwareSize(52.0, context),
).animate(CurvedAnimation(
parent: widget.submitAnimationController,
curve: Interval(0.05, 0.15),
));
return AnimatedBuilder(
animation: widget.submitAnimationController,
builder: (context, child) {
Decoration decoration = BoxDecoration(
borderRadius: _bordersAnimation.value,
color: Theme.of(context).primaryColor,
);
return Center(
child: Container(
height: screenAwareSize(52.0, context),
width: width,
decoration: decoration,
child: _submitWidthAnimation.isDismissed
? GestureDetector(
behavior: HitTestBehavior.translucent,
onTap: () => _animatePacmanToEnd(),
child: Stack(
alignment: Alignment.centerRight,
children: <Widget>[
AnimatedDots(),
_drawDotCurtain(decoration),
_drawPacman(),
],
),
)
: Container(),
),
);
},
);
},
);
}
Widget _drawDotCurtain(Decoration decoration) {
if (width == 0.0) {
return Container();
}
double marginRight =
width - _pacmanPosition - screenAwareSize(_pacmanWidth / 2, context);
return Positioned.fill(
right: marginRight,
child: Container(decoration: decoration),
);
}
Widget _drawPacman() {
pacmanAnimation = _initPacmanAnimation();
return Positioned(
left: _pacmanPosition,
child: GestureDetector(
onHorizontalDragUpdate: (details) => _onPacmanDrag(width, details),
onHorizontalDragEnd: (details) => _onPacmanEnd(width, details),
child: PacmanIcon(),
),
);
}
Animation<double> _initPacmanAnimation() {
Animation<double> animation = Tween(
begin: _pacmanMinPosition(),
end: _pacmanMaxPosition(width),
).animate(pacmanMovementController);
animation.addListener(() {
setState(() {
_pacmanPosition = animation.value;
});
if (animation.status == AnimationStatus.completed) {
_onPacmanSubmit();
}
});
return animation;
}
_onPacmanSubmit() {
widget.onSubmit();
Future.delayed(Duration(seconds: 1), () => _resetPacman());
}
_onPacmanDrag(double width, DragUpdateDetails details) {
setState(() {
_pacmanPosition += details.delta.dx;
_pacmanPosition = math.max(_pacmanMinPosition(),
math.min(_pacmanMaxPosition(width), _pacmanPosition));
});
}
_onPacmanEnd(double width, DragEndDetails details) {
bool isOverHalf =
_pacmanPosition + screenAwareSize(_pacmanWidth / 2, context) >
0.5 * width;
if (isOverHalf) {
_animatePacmanToEnd();
} else {
_resetPacman();
}
}
_animatePacmanToEnd() {
pacmanMovementController.forward(
from: _pacmanPosition / _pacmanMaxPosition(width));
}
_resetPacman() {
if (this.mounted) {
setState(() => _pacmanPosition = _pacmanMinPosition());
}
}
double _pacmanMinPosition() =>
screenAwareSize(_sliderHorizontalMargin, context);
double _pacmanMaxPosition(double sliderWidth) =>
sliderWidth -
screenAwareSize(_sliderHorizontalMargin / 2 + _pacmanWidth, context);
}
class AnimatedDots extends StatefulWidget {
#override
_AnimatedDotsState createState() => _AnimatedDotsState();
}
class _AnimatedDotsState extends State<AnimatedDots>
with TickerProviderStateMixin {
final int numberOfDots = 10;
final double minOpacity = 0.1;
final double maxOpacity = 0.5;
late AnimationController hintAnimationController;
#override
void initState() {
super.initState();
_initHintAnimationController();
hintAnimationController.forward();
}
#override
void dispose() {
hintAnimationController.dispose();
super.dispose();
}
void _initHintAnimationController() {
hintAnimationController = AnimationController(
vsync: this,
duration: Duration(milliseconds: 800),
);
hintAnimationController.addStatusListener((status) {
if (status == AnimationStatus.completed) {
Future.delayed(Duration(milliseconds: 800), () {
if (this.mounted) {
hintAnimationController.forward(from: 0.0);
}
});
}
});
}
#override
Widget build(BuildContext context) {
return Padding(
padding: EdgeInsets.only(
left: screenAwareSize(
_sliderHorizontalMargin + _pacmanWidth + _dotsLeftMargin,
context),
right: screenAwareSize(_sliderHorizontalMargin, context)),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: List.generate(numberOfDots, _generateDot)
..add(Opacity(
opacity: maxOpacity,
child: Dot(size: 14.0),
)),
),
);
}
Widget _generateDot(int dotNumber) {
Animation animation = _initDotAnimation(dotNumber);
return AnimatedBuilder(
animation: animation,
builder: (context, child) => Opacity(
opacity: animation.value,
child: child,
),
child: Dot(size: 9.0),
);
}
Animation<double> _initDotAnimation(int dotNumber) {
double lastDotStartTime = 0.4;
double dotAnimationDuration = 0.5;
double begin = lastDotStartTime * dotNumber / numberOfDots;
double end = begin + dotAnimationDuration;
return SinusoidalAnimation(min: minOpacity, max: maxOpacity).animate(
CurvedAnimation(
parent: hintAnimationController,
curve: Interval(begin, end),
),
);
}
}
class SinusoidalAnimation extends Animatable<double> {
SinusoidalAnimation({required this.min, required this.max});
final double min;
final double max;
#override
double transform(double t) {
return min + (max - min) * math.sin(math.pi * t);
}
}
class Dot extends StatelessWidget {
final double size;
const Dot({Key? key, required this.size}) : super(key: key);
#override
Widget build(BuildContext context) {
return Container(
height: screenAwareSize(size, context),
width: screenAwareSize(size, context),
decoration: BoxDecoration(
shape: BoxShape.circle,
color: Colors.white,
),
);
}
}
class PacmanIcon extends StatelessWidget {
#override
Widget build(BuildContext context) {
return Padding(
padding: EdgeInsets.only(
right: screenAwareSize(16.0, context),
),
child: SvgPicture.asset(
'images/pacman.svg',
height: screenAwareSize(25.0, context),
width: screenAwareSize(21.0, context),
),
);
}
}
As you see in the attached capture, BorderRadiusTween has a nullable value of type BorderRadius?
I'd suggest to either declare _bordersAnimation as Animation<BorderRadius?>or just using an Animation<double> instead

ValueKey does not work properly with the animatedbuilder - flutter

So, I am making the sport application with the bookmark articles functionality. The bookmark article widget has an animated icon with a simple size animation. After clicking on the icon that removes the article, the icons in other widgets refresh in a few moments. I put the link with the screen video below.
https://firebasestorage.googleapis.com/v0/b/footballapp-2fb40.appspot.com/o/Screen_Recording_20210727-122200.mp4?alt=media&token=794fb85b-659a-4127-a6cb-e784bde7ff72
class BookmarkArticleIcon extends StatefulWidget {
final Color iconColor;
final double iconSize;
final Article article;
final ValueKey key;
BookmarkArticleIcon({this.iconColor, this.iconSize, this.article, this.key})
: super(key: key);
#override
_BookmarkArticleIconState createState() => _BookmarkArticleIconState();
}
class _BookmarkArticleIconState extends State<BookmarkArticleIcon>
with SingleTickerProviderStateMixin {
AnimationController _animationController;
Animation<double> _animation;
#override
void initState() {
super.initState();
BlocProvider.of<BookmarkArticleBloc>(context)
.add(FetchInitialArticleState(article: widget.article));
_animationController =
AnimationController(vsync: this, duration: Duration(milliseconds: 100))
..addListener(() {
if (_animationController.isCompleted) {
_animationController.reverse();
}
});
_animation =
Tween<double>(begin: 1.0, end: 1.2).animate(_animationController);
}
#override
void dispose() {
_animationController.dispose();
super.dispose();
}
#override
Widget build(BuildContext context) {
return BlocBuilder<BookmarkArticleBloc, BookmarkArticleState>(
buildWhen: _buildWhenFunction,
builder: (context, state) {
if (state is BookmarkArticleResult) {
return AnimatedBuilder(
animation: _animationController,
builder: (context, _) {
return Transform.scale(
scale: _animation.value,
child: InkWell(
child: Icon(
(state.isBookmarked)
? Icons.bookmark
: Icons.bookmark_outline,
color: widget.iconColor,
size: widget.iconSize,
),
onTap: () {
_onTapFunction(state);
}),
);
});
} else {
return LoadingWidget(
iconColor: widget.iconColor,
);
}
},
);
}
void _onTapFunction(BookmarkArticleResult state) {
final BookmarkArticleBloc _bookmarkArticleBloc =
BlocProvider.of<BookmarkArticleBloc>(context);
_animationController.forward();
if (state.isBookmarked) {
_bookmarkArticleBloc
.add(RemoveBookmarkedArticle(article: widget.article));
} else {
_bookmarkArticleBloc.add(AddBookmarkArticle(article: widget.article));
}
}
bool _buildWhenFunction(previous, current) {
if ((current is BookmarkArticleResult &&
current.articleID == widget.article.id) ||
(current is LoadingBookmarkArticle &&
current.articleID == widget.article.id)) {
return true;
} else {
return false;
}
}
}
class LoadingWidget extends StatelessWidget {
final Color iconColor;
LoadingWidget({this.iconColor});
#override
Widget build(BuildContext context) {
return Container(
key: UniqueKey(),
margin: EdgeInsets.all(4),
width: 16,
height: 16,
child: CircularProgressIndicator(
color: iconColor,
strokeWidth: 2.0,
),
);
}
}

how to stop last element jumps when adding element to Animated list

I am trying to recreate RobinHood slide number animation, I am treating every digit as an Item in n animated list, When I am adding an digit to the start of the list the last Item in the list receives a jump and I cant quite figure out how to fix it.
this is the code for every digit
class NumberColView extends StatefulWidget {
final int animateTo;
final bool comma;
final TextStyle textStyle;
final Duration duration;
final Curve curve;
NumberColView(
{#required this.animateTo,
#required this.textStyle,
#required this.duration,
this.comma = false,
#required this.curve})
: assert(animateTo != null && animateTo >= 0 && animateTo < 10);
#override
_NumberColState createState() => _NumberColState();
}
class _NumberColState extends State<NumberColView>
with SingleTickerProviderStateMixin {
ScrollController _scrollController;
double _elementSize = 0.0;
#override
void initState() {
super.initState();
print(_elementSize);
_scrollController = new ScrollController();
WidgetsBinding.instance.addPostFrameCallback((_) {
_elementSize = _scrollController.position.maxScrollExtent / 10;
setState(() {});
});
}
#override
void didUpdateWidget(NumberColView oldWidget) {
if (oldWidget.animateTo != widget.animateTo) {
_scrollController.animateTo(_elementSize * widget.animateTo,
duration: widget.duration, curve: widget.curve);
}
super.didUpdateWidget(oldWidget);
}
#override
Widget build(BuildContext context) {
// print(widget.animateTo);
return Row(
mainAxisSize: MainAxisSize.min,
children: [
IgnorePointer(
child: ConstrainedBox(
constraints: BoxConstraints(maxHeight: _elementSize),
child: SingleChildScrollView(
controller: _scrollController,
child: Column(
children: List.generate(10, (position) {
return Text(position.toString(), style: widget.textStyle);
}),
),
),
),
),
widget.comma
? Container(
child: Text(', ',
style:
TextStyle(fontSize: 16, fontWeight: FontWeight.bold)))
: Container(),
],
);
}
}
This the code to build the whole number
class _NumberSlideAnimationState extends State<NumberSlideAnimation> {
#override
void didUpdateWidget(oldWidget) {
if (oldWidget.number.length != widget.number.length) {
int newLen = widget.number.length - oldWidget.number.length;
if(newLen > 0) {
widget.listKey.currentState.insertItem(0,
duration: const Duration(milliseconds: 200));
}
// setState(() {
// animateTo = widget.animateTo;
// });
}
super.didUpdateWidget(oldWidget);
}
Widget digitSlide(BuildContext context, int position, animation) {
int item = int.parse(widget.number[position]);
return SlideTransition(
position: Tween<Offset>(
begin: const Offset(0, 1),
end: Offset(0, 0),
).animate(animation),
child:
// TestCol(animateTo: item, style: widget.textStyle)
NumberColView(
textStyle: widget.textStyle,
duration: widget.duration,
curve: widget.curve,
comma: (widget.number.length - 1) != position &&
(widget.number.length - position) % 3 == 1 ??
true,
animateTo: item,
),
);
}
#override
Widget build(BuildContext context) {
return Container(
height: 40,
child: AnimatedList(
shrinkWrap: true,
scrollDirection: Axis.horizontal,
key: widget.listKey,
initialItemCount: widget.number.length,
itemBuilder: (context, position, animation) {
return digitSlide(context, position, animation);
},
),
);
}
}
I don't know the right solution for your problem, however I know another way and I am just sharing it.
Here the full code If you want to use this method:
update: now 0 will come form bottom as what it should be and the uncharged values will be in there palaces.
class Count extends StatefulWidget {
const Count({Key key}) : super(key: key);
#override
_CountState createState() => _CountState();
}
class _CountState extends State<Count> {
int count = 10;
int count2 = 10;
int count3 = 10;
Alignment alignment = Alignment.topCenter;
#override
Widget build(BuildContext context) {
return Scaffold(
body: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
ClipRRect(
child: Align(
alignment: Alignment.center,
heightFactor: 0.2,
child: SizedBox(
height: 100,
child: Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
...List.generate(count.toString().length, (index) {
final num2IndexInRange =
count2.toString().length - 1 >= index;
final num3IndexInRange =
count3.toString().length - 1 >= index;
final num1 = int.parse(count.toString()[index]),
num2 = num2IndexInRange
? int.parse(count2.toString()[index])
: 9,
num3 = num3IndexInRange
? int.parse(count3.toString()[index])
: 1;
return AnimatedAlign(
key: Key("${num2} ${index}"),
duration: Duration(milliseconds: 500),
alignment: (num1 != num2 || num1 != num3)
? alignment
: Alignment.center,
child: Text(
num3.toString(),
style: TextStyle(
fontSize: 20,
),
),
);
}),
],
),
),
),
),
SizedBox(height: 200),
RaisedButton(
onPressed: () async {
setState(() {
alignment = Alignment.topCenter;
count += 10;
});
await Future.delayed(Duration(milliseconds: 250));
setState(() {
alignment = Alignment.bottomCenter;
count2 = count;
});
await Future.delayed(Duration(milliseconds: 250));
setState(() {
count3 = count;
});
},
),
],
),
);
}
}
if you don't like how the spacing between the numbers changed you can warp the text widget with a sized box and give it a fixed width.

Slide child widget in Row layout container from its current position

I have a Row that consists x number of Button widgets. So when user clicks on one of the Button I need to fade out the other buttons and the slide the clicked button to the starting position(0,0) of the Row layout. I managed to get the fade out animation to work.
final _opacityTween = Tween<double>(begin: 1, end: 0);
_opacityAnimation = _opacityTween.animate(CurvedAnimation(
parent: _controller,
curve: new Interval(0.00, 0.50, curve: Curves.linear)
// curve: Curves.ease
));
// And using the opacityAnimation value in Opacity widget.
return Opacity(
opacity: _opacityAnimation.value,
child: child,
);
However I have been unsuccessful to using Animation classes and widgets to slide the child widget to the start position of the Row. Basically I am not sure what kind of classes could be useful to achieve such animation. I can use the Tween and set the end position to (0, 0) and use Translation to achieve it, however I am not sure how to set the begin position of the Animation when I initialise it as the position of a child is not known.
Try this solution. I thing it is almost what you need. You just have to add SizeTransition widget.
Ask if you have any question.
class _MyHomePageState extends State<MyHomePage> with SingleTickerProviderStateMixin {
int _pressedButton;
Animation<double> _animation;
AnimationController _controller;
#override
void initState() {
super.initState();
_controller = AnimationController(
duration: const Duration(milliseconds: 500),
vsync: this,
);
_animation = Tween<double>(begin: 1.0, end: 0.0)
.animate(_controller)
..addListener(() {
setState(() { });
});
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text("Test"),
),
body: Column(
children: <Widget>[
Row(
children: <Widget>[
_animatedButton(1),
_animatedButton(2),
_animatedButton(3),
_animatedButton(4)
]
),
Text(_pressedButton != null ? "$_pressedButton button pressed" : "No button pressed"),
RaisedButton(
child: Text("Reset"),
onPressed: () {
_controller.reset();
}
)
]
)
);
}
Widget _animatedButton(int button) {
if (button != _pressedButton) {
return SizeTransition(
sizeFactor: _animation,
axis: Axis.horizontal,
child: Opacity(
opacity: _animation.value,
child: _button(button)
)
);
} else {
return _button(button);
}
}
Widget _button(int button) {
return RaisedButton(
onPressed: () => onButtonClick(button),
child: Text("Button $button")
);
}
void onButtonClick(int button) {
setState(() {
_pressedButton = button;
});
_controller.forward();
}
}
UPDATE 1
Check one more way to do what you need. There are few different things in code below.
Change SingleTickerProviderStateMixin to TickerProviderStateMixin.
Use two animation controllers and animations (for fading out of unselected buttons and sliding left selected button).
Run animations sequentially (see animation tweens listeners).
Add bool _buttonSelected to track completion of animations.
Use _buttonSelected to build correct widget (Widget _buttonsWidget())
class _MyHomePageState extends State<MyHomePage> with TickerProviderStateMixin {
int _pressedButton = -1;
bool _buttonSelected = false;
Animation<double> _animationFadeOut;
AnimationController _controllerFadeOut;
Animation<double> _animationSlideLeft;
AnimationController _controllerSlideLeft;
#override
void initState() {
super.initState();
_initFadeOutAnimation();
_initSlideLeftAnimation();
}
void _initFadeOutAnimation() {
_controllerFadeOut = AnimationController(
duration: const Duration(milliseconds: 500),
vsync: this,
);
_animationFadeOut = Tween<double>(begin: 1.0, end: 0.0)
.animate(_controllerFadeOut)
..addListener(() {
setState(() {
if (_controllerFadeOut.isCompleted && !_controllerSlideLeft.isAnimating) {
_controllerSlideLeft.forward();
}
});
});
}
void _initSlideLeftAnimation() {
_controllerSlideLeft = AnimationController(
duration: const Duration(milliseconds: 500),
vsync: this,
);
_animationSlideLeft = Tween<double>(begin: 1.0, end: 0.0)
.animate(_controllerSlideLeft)
..addListener(() {
setState(() {
if (_controllerSlideLeft.isCompleted) {
_buttonSelected = true;
}
});
});
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text("Test"),
),
body: Column(
children: <Widget>[
_buttonsWidget(),
Text(_pressedButton != null ? "$_pressedButton button pressed" : "No button pressed"),
RaisedButton(
child: Text("Reset"),
onPressed: () {
_controllerFadeOut.reset();
_controllerSlideLeft.reset();
_pressedButton = -1;
_buttonSelected = false;
}
)
]
)
);
}
Widget _buttonsWidget() {
if (_buttonSelected) {
return Row(
mainAxisAlignment: MainAxisAlignment.start,
children: <Widget>[_buttonWidget(_pressedButton)]
);
} else {
return Row(
children: <Widget>[
_animatedButtonWidget(1),
_animatedButtonWidget(2),
_animatedButtonWidget(3),
_animatedButtonWidget(4)
]
);
}
}
Widget _animatedButtonWidget(int button) {
if (button == _pressedButton) {
return _buttonWidget(button);
} else {
return SizeTransition(
sizeFactor: _animationSlideLeft,
axis: Axis.horizontal,
child: Opacity(
opacity: _animationFadeOut.value,
child: _buttonWidget(button)
)
);
}
}
Widget _buttonWidget(int button) {
return RaisedButton(
onPressed: () => _onButtonClick(button),
child: Text("Button $button")
);
}
void _onButtonClick(int button) {
setState(() {
_pressedButton = button;
});
_controllerFadeOut.forward();
}
}