I'm trying to create an animation in a CustomPainter in which the animation starts from the bottom to up, but it's starting at the top.
When clicking on the FloatActionButton the rectangle should rise to the maximum height of the screen, and when tap again go back to the minimum size.
I can get the size of the screen but I'm not able to insert this animation from the bottom to up. can you help me?
import 'package:flutter/material.dart';
import 'dart:ui' as ui;
void main() {
runApp(new MaterialApp(home: new HomePage()));
}
class HomePage extends StatefulWidget {
#override
HomePageState createState() => new HomePageState();
}
class HomePageState extends State<HomePage> with TickerProviderStateMixin {
AnimationController _controller;
Animation<double> _animation;
bool upDown = true;
#override
void initState() {
_controller = new AnimationController(
vsync: this,
duration: const Duration(milliseconds: 180),
);
_animation = new CurvedAnimation(
parent: _controller,
curve: new Interval(0.0, 1.0, curve: Curves.linear),
);
}
#override
Widget build(BuildContext context) {
final ui.Size logicalSize = MediaQuery.of(context).size;
final double _width = logicalSize.width;
final double _height = logicalSize.height;
void _up(){
setState((){
if(upDown) {
upDown = false;
_controller.forward(from: 0.0);
} else {
upDown = true;
_controller.reverse(from: 1.0);
}
});
}
return new Scaffold(
body: new Stack(
children: <Widget>[
new Positioned(
bottom: 0.0,
child: new AnimatedBuilder(
animation: _animation,
builder: (BuildContext context, Widget child) {
return new Container(
height: _height,
child: new CustomPaint(
painter: new Sky(_width, _height * _animation.value),
//child: new Text('$_height '+ _animation.value.toString()),
),
);
},
),
),
new Positioned(
bottom: 16.0,
right: 16.0,
child: new FloatingActionButton(
backgroundColor: new Color(0xFFE57373),
child: new Icon(Icons.add),
onPressed: (){
_up();
},
)
)
]
)
);
}
}
class Sky extends CustomPainter {
final double _width;
double _rectHeight;
Sky(this._width, this._rectHeight);
#override
void paint(Canvas canvas, Size size) {
canvas.drawRect(
new Rect.fromLTRB(
0.0, 0.0, this._width, _rectHeight
),
new Paint()..color = new Color(0xFF0099FF),
);
}
#override
bool shouldRepaint(Sky oldDelegate) {
return _width != oldDelegate._width || _rectHeight != oldDelegate._rectHeight;
}
}
You can use an AnimatedBuilder to do this. Also, make sure that you provide a working implementation of shouldRepaint. Your upDown member variable should be a member of the State rather than part of the build function.
import 'package:flutter/material.dart';
import 'dart:ui' as ui;
void main() {
runApp(new MaterialApp(home: new HomePage()));
}
class HomePage extends StatefulWidget {
#override
HomePageState createState() => new HomePageState();
}
class HomePageState extends State<HomePage> with TickerProviderStateMixin {
AnimationController _controller;
Animation<double> _animation;
bool upDown = true;
#override
void initState() {
_controller = new AnimationController(
vsync: this,
duration: const Duration(milliseconds: 180),
);
_animation = new CurvedAnimation(
parent: _controller,
curve: new Interval(0.0, 1.0, curve: Curves.linear),
);
}
#override
Widget build(BuildContext context) {
final ui.Size logicalSize = MediaQuery.of(context).size;
final double _width = logicalSize.width;
final double _height = logicalSize.height;
void _up(){
setState((){
if(upDown) {
upDown = false;
_controller.forward(from: 0.0);
} else {
upDown = true;
_controller.reverse(from: 1.0);
}
});
}
return new Scaffold(
body: new Stack(
children: <Widget>[
new Positioned(
bottom: 0.0,
child: new AnimatedBuilder(
animation: _animation,
builder: (BuildContext context, Widget child) {
return new Container(
height: _height,
child: new CustomPaint(
painter: new Sky(_width, _height * _animation.value),
),
);
},
),
),
new Positioned(
bottom: 16.0,
right: 16.0,
child: new FloatingActionButton(
backgroundColor: new Color(0xFFE57373),
child: new Icon(Icons.add),
onPressed: (){
_up();
},
)
)
]
)
);
}
}
class Sky extends CustomPainter {
final double _width;
double _rectHeight;
Sky(this._width, this._rectHeight);
#override
void paint(Canvas canvas, Size size) {
canvas.drawRect(
new Rect.fromLTRB(
0.0, size.height - _rectHeight, this._width, size.height
),
new Paint()..color = new Color(0xFF0099FF),
);
}
#override
bool shouldRepaint(Sky oldDelegate) {
return _width != oldDelegate._width || _rectHeight != oldDelegate._rectHeight;
}
}
Related
I want to show the animation of feedback of draggable when it is not accepted by the DropTarget.Flutter doesn't show the feedback. Is there any way we can show that or control it. Like this example, I want to achieve this effect. I somehow achieve this effect but it is not proper accurate returning to the original offset. It is moving ahead to its original position.
Animation effect I want.
Here is my Code, I have one drag box when I lift it to a certain position and leave him from there and it should animate back to original position, but it is returning to some other Offset like this.
import 'package:flutter/material.dart';
import 'package:flutter/rendering.dart';
void main() {
runApp(MyApp());
}
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: Scaffold(body: DragBox()),
);
}
}
class DragBox extends StatefulWidget {
DragBox({
Key key,
}) : super(key: key);
#override
State<StatefulWidget> createState() {
return new _MyDragBox();
}
}
class _MyDragBox extends State<DragBox> with TickerProviderStateMixin {
GlobalKey _globalKey = new GlobalKey();
AnimationController _controller;
Offset begin;
Offset cancelledOffset;
Offset _offsetOfWidget;
#override
void initState() {
WidgetsBinding.instance.addPostFrameCallback((s) {
_afeteLayout();
});
super.initState();
_controller = AnimationController(
vsync: this,
duration: Duration(milliseconds: 1000),
);
}
void _afeteLayout() {
final RenderBox box = _globalKey.currentContext.findRenderObject();
Offset offset = -box.globalToLocal(Offset(0.0, 0.0));
_offsetOfWidget = offset;
}
#override
void dispose() {
_controller.dispose();
super.dispose();
}
#override
Widget build(BuildContext context) {
return Stack(
children: <Widget>[
Center(
child: Draggable(
key: _globalKey,
onDraggableCanceled: (v, o) {
setState(() {
cancelledOffset = o;
});
_controller.forward();
},
feedback: Container(
color: Colors.red,
height: 50,
width: 50,
),
child: Container(
color: Colors.red,
height: 50,
width: 50,
),
),
),
_controller.isAnimating
? SlideTransition(
position: Tween<Offset>(
begin: cancelledOffset * 0.01,
end: _offsetOfWidget * 0.01)
.animate(_controller)
..addStatusListener((status) {
if (status == AnimationStatus.completed) {
_controller.stop();
} else {
_controller.reverse();
}
}),
child: Container(
color: Colors.red,
height: 50,
width: 50,
),
)
: Container(
child: Text('data'),
)
],
);
}
}
I think, this documentation about "Animate a widget using a physics simulation" is the closest example suited for what you are trying to achieve.
This recipe demonstrates how to move a widget from a dragged point
back to the center using a spring simulation.
To appreciate it in action, take a look on the example:
import 'package:flutter/material.dart';
import 'package:flutter/physics.dart';
void main() {
runApp(MaterialApp(home: PhysicsCardDragDemo()));
}
class PhysicsCardDragDemo extends StatelessWidget {
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(),
body: DraggableCard(
child: FlutterLogo(
size: 128,
),
),
);
}
}
/// A draggable card that moves back to [Alignment.center] when it's
/// released.
class DraggableCard extends StatefulWidget {
final Widget child;
DraggableCard({required this.child});
#override
_DraggableCardState createState() => _DraggableCardState();
}
class _DraggableCardState extends State<DraggableCard>
with SingleTickerProviderStateMixin {
late AnimationController _controller;
/// The alignment of the card as it is dragged or being animated.
///
/// While the card is being dragged, this value is set to the values computed
/// in the GestureDetector onPanUpdate callback. If the animation is running,
/// this value is set to the value of the [_animation].
Alignment _dragAlignment = Alignment.center;
late Animation<Alignment> _animation;
/// Calculates and runs a [SpringSimulation].
void _runAnimation(Offset pixelsPerSecond, Size size) {
_animation = _controller.drive(
AlignmentTween(
begin: _dragAlignment,
end: Alignment.center,
),
);
// Calculate the velocity relative to the unit interval, [0,1],
// used by the animation controller.
final unitsPerSecondX = pixelsPerSecond.dx / size.width;
final unitsPerSecondY = pixelsPerSecond.dy / size.height;
final unitsPerSecond = Offset(unitsPerSecondX, unitsPerSecondY);
final unitVelocity = unitsPerSecond.distance;
const spring = SpringDescription(
mass: 30,
stiffness: 1,
damping: 1,
);
final simulation = SpringSimulation(spring, 0, 1, -unitVelocity);
_controller.animateWith(simulation);
}
#override
void initState() {
super.initState();
_controller = AnimationController(vsync: this);
_controller.addListener(() {
setState(() {
_dragAlignment = _animation.value;
});
});
}
#override
void dispose() {
_controller.dispose();
super.dispose();
}
#override
Widget build(BuildContext context) {
final size = MediaQuery.of(context).size;
return GestureDetector(
onPanDown: (details) {
_controller.stop();
},
onPanUpdate: (details) {
setState(() {
_dragAlignment += Alignment(
details.delta.dx / (size.width / 2),
details.delta.dy / (size.height / 2),
);
});
},
onPanEnd: (details) {
_runAnimation(details.velocity.pixelsPerSecond, size);
},
child: Align(
alignment: _dragAlignment,
child: Card(
child: widget.child,
),
),
);
}
}
Sample output:
A simple way to solve this problem is to create a widget that overrides Draggable and make it a child of an AnimatedPositioned. Here is the example:
import 'package:flutter/material.dart';
class XDraggable extends StatefulWidget {
const XDraggable(
{Key? key,
required this.child,
required this.original_x,
required this.original_y,
this.animation_speed = 200})
: super(key: key);
final Widget child;
final double original_x;
final double original_y;
final double animation_speed;
#override
_XDraggableState createState() => _XDraggableState();
}
class _XDraggableState extends State<XDraggable> {
double x = 200;
double y = 200;
int animation_speed = 0;
#override
void initState() {
x = widget.original_x;
y = widget.original_y;
super.initState();
}
#override
Widget build(BuildContext context) {
return AnimatedPositioned(
left: x,
top: y,
duration: Duration(milliseconds: animation_speed),
child: Draggable(
onDragUpdate: (details) => {
setState(() {
animation_speed = 0;
x = x + details.delta.dx;
y = y + details.delta.dy;
}),
},
onDragEnd: (details) {
setState(() {
animation_speed = 200;
x = widget.original_x;
y = widget.original_y;
});
},
child: widget.child,
feedback: SizedBox(),
),
);
}
}
Then, just use the widget as a Stack child:
...
child: Stack(
fit: StackFit.expand,
children: [
XDraggable(
original_x: 20,
original_y: 20,
child: Container(
height: 50.0,
width: 50.0,
color: Colors.green,
),
)
],
),
...
I am trying to move the container on the screen by giving begin and end offset like from Offset(0.0,0.0) to Offset(400.0,300.0). I am using Slide Transition to animate the container I am using Tween<Offset>(begin: const Offset(3.0, 4.0), end: Offset(0.0, 0.0)) to move it on the screen I want to pass these Offset(400.0,300.0) and animate it.
Here is my code
class MoveContainer extends StatefulWidget {
MoveContainer({Key key, }) : super(key: key);
#override
State<StatefulWidget> createState() {
return new _MyMoveContainer();
}
}
class _MyMoveContainer extends State<MoveContainer>
with TickerProviderStateMixin {
GlobalKey _globalKey = new GlobalKey();
AnimationController _controller;
Animation<Offset> _offset;
Offset local;
#override
void initState() {
super.initState();
_controller = AnimationController(
vsync: this,
duration: const Duration(seconds: 3),
);
_offset =
Tween<Offset>(begin: const Offset(3.0, 4.0), end: Offset(0.0, 0.0))
.animate(_controller);
_offset.addListener(() {
setState(() {});
});
_controller.forward();
}
#override
void dispose() {
_controller.dispose();
super.dispose();
}
#override
Widget build(BuildContext context) {
return SlideTransition(
position: _offset,
child: GestureDetector(
onPanStart: (start) {
RenderBox getBox = context.findRenderObject();
local = getBox.localToGlobal(start.globalPosition);
print('point are $local');
},
child: Container(
color: Colors.cyan,
height: 200.0,
width: 200.0,
child: Text("hello ")),
),
);
}
}
Probably this question is not actual for the author. (Asked 7 months ago).
But maybe my answer will help someone else.
Usually Slide Transition is used for transitions between pages. That is why, one unit of position value here is the size of one page. When you put there Offset(400.0,300.0) it's equal 400 screen right, and 300 pages down.
For your case it better to use AnimatedPositioned Widget.
import 'package:flutter/material.dart';
import 'package:flutter/rendering.dart';
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
debugShowCheckedModeBanner: false,
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: Scaffold(
backgroundColor: Colors.blue,
body: MoveContainer(),
),
);
}
}
class MoveContainer extends StatefulWidget {
#override
_MoveContainerState createState() => _MoveContainerState();
}
class _MoveContainerState extends State<MoveContainer> {
Offset offset = Offset.zero;
final double height = 200;
final double width = 200;
#override
Widget build(BuildContext context) {
return GestureDetector(
onPanStart: (details) {
RenderBox getBox = context.findRenderObject();
setState(() {
offset = getBox.localToGlobal(details.globalPosition);
});
},
child: Stack(
children: <Widget>[
AnimatedPositioned(
duration: Duration(milliseconds: 300),
top: offset.dy - (height / 2),
left: offset.dx - (width / 2),
child: Container(
color: Colors.cyan,
height: height,
width: width,
child: Text("hello "),
),
),
],
),
);
}
}
I want to create a screen for the coach mark. Idea is to blur and make darker everything except the region where is my icon located.
I could cut a circle with feather edges. But the icon on the background is also blurred.
import 'dart:ui' as ui;
import 'package:flutter/material.dart';
void main() => runApp(new MyApp());
class MyApp extends StatefulWidget {
#override
State<StatefulWidget> createState() => MyAppState();
}
class MyAppState extends State<MyApp> {
#override
Widget build(BuildContext context) {
return new MaterialApp(
title: 'Flutter Demo',
home: HomeScreen(),
);
}
}
class HomeScreen extends StatefulWidget {
#override
_HomeScreenState createState() => new _HomeScreenState();
}
class _HomeScreenState extends State<HomeScreen> {
#override
Widget build(BuildContext context) {
return Stack(children: <Widget>[
_buildScaffold(),
CustomPaint(
child: Container(
constraints: BoxConstraints.expand(),
child: BackdropFilter(
filter: new ui.ImageFilter.blur(sigmaX: 2.0, sigmaY: 2.0),
child: Container(
decoration: new BoxDecoration(
color: Colors.grey[900].withOpacity(0.7)),
))),
foregroundPainter: CoachMarksPainter(),
),
]);
}
Widget _buildScaffold() {
return Scaffold(
appBar: AppBar(
title: Text("Hello"),
actions: <Widget>[
new IconButton(
onPressed: () => print("press"),
icon: new Icon(Icons.calendar_today),
),
PopupMenuButton<String>(
itemBuilder: (BuildContext context) {},
),
],
),
body: new Container(
decoration: new BoxDecoration(
image: new DecorationImage(
image: new NetworkImage(
"http://www.mobileswall.com/wp-content/uploads/2015/03/640-Sunset-Beach-2-l.jpg"),
fit: BoxFit.cover))));
}
}
class CoachMarksPainter extends CustomPainter {
void paint(Canvas canvas, Size size) {
print("Paint size=$size canvas=${canvas.getSaveCount()}");
canvas.save();
Path path = Path()
..addOval(Rect.fromCircle(center: Offset(287.0, 52.0), radius: 25.0))
..addRect(new Rect.fromLTWH(
-10.0, -10.0, size.width + 20.0, size.height + 20.0))
//to have rect a bit larger than screen, so blurred edges won't be seen
..fillType = PathFillType.evenOdd;
Paint paint = Paint()
..blendMode = BlendMode.dstOut
..color = Colors.white.withOpacity(0.4)
..maskFilter = new MaskFilter.blur(
BlurStyle.normal, 2.0); //BoxShadow.convertRadiusToSigma(25.0)
canvas.drawPath(path, paint);
canvas.restore();
}
#override
bool shouldRepaint(CoachMarksPainter oldDelegate) => false;
#override
bool shouldRebuildSemantics(CoachMarksPainter oldDelegate) => false;
}
blurred background with a highlighted icon in a circle
Is it possible to use ImageFilter.blur for Canvas? I use MaskFilter, but it does not blur canvas as much as ImageFilter for BackdropFilter widget.
Ideally, I want to get a semitransparent blurring layer with a hole with soft edges.
P.S. I read this question but I need to invert it.
#Marica Hopefully this is doing what you want.
https://gist.github.com/slightfoot/76043f8f3fc4a8b20fc24c5a6f22b0a0
import 'dart:async';
import 'dart:ui' as ui;
import 'package:flutter/material.dart';
import 'package:shared_preferences/shared_preferences.dart';
void main() => runApp(MyApp());
class MyApp extends StatefulWidget {
#override
State<StatefulWidget> createState() => MyAppState();
}
class MyAppState extends State<MyApp> {
#override
Widget build(BuildContext context) {
return MaterialApp(
debugShowCheckedModeBanner: false,
title: 'Coach Mark Demo',
home: HomeScreen(),
);
}
}
class HomeScreen extends StatefulWidget {
#override
_HomeScreenState createState() => _HomeScreenState();
}
class _HomeScreenState extends State<HomeScreen> {
final GlobalKey<ScaffoldState> _scaffold = GlobalKey();
final GlobalKey<CoachMarkState> _calendarMark = GlobalKey();
#override
Widget build(BuildContext context) {
return Scaffold(
key: _scaffold,
appBar: AppBar(
title: Text("Hello"),
actions: <Widget>[
CoachMark(
key: _calendarMark,
id: 'calendar_mark',
text: 'Tap here to use the Calendar!',
child: GestureDetector(
onLongPress: () => _calendarMark.currentState.show(),
child: IconButton(
onPressed: () => print('calendar'),
icon: Icon(Icons.calendar_today),
),
),
),
PopupMenuButton<String>(
itemBuilder: (BuildContext context) {
return <PopupMenuEntry<String>>[
PopupMenuItem<String>(
value: 'reset',
child: Text('Reset'),
),
];
},
onSelected: (String value) {
if (value == 'reset') {
_calendarMark.currentState.reset();
_scaffold.currentState.showSnackBar(SnackBar(
content: Text('Hot-restart the app to see the coach-mark again.'),
));
}
},
),
],
),
body: Container(
decoration: BoxDecoration(
image: DecorationImage(
image: NetworkImage("http://www.mobileswall.com/wp-content/uploads/2015/03/640-Sunset-Beach-2-l.jpg"),
fit: BoxFit.cover),
),
),
);
}
}
class CoachMark extends StatefulWidget {
const CoachMark({
Key key,
#required this.id,
#required this.text,
#required this.child,
}) : super(key: key);
final String id;
final String text;
final Widget child;
#override
CoachMarkState createState() => CoachMarkState();
}
typedef CoachMarkRect = Rect Function();
class CoachMarkState extends State<CoachMark> {
_CoachMarkRoute _route;
String get _key => 'mark_${widget.id}';
#override
void initState() {
super.initState();
test().then((bool seen) {
if (seen == false) {
show();
}
});
}
#override
void didUpdateWidget(CoachMark oldWidget) {
super.didUpdateWidget(oldWidget);
_rebuild();
}
#override
void reassemble() {
super.reassemble();
_rebuild();
}
#override
void dispose() {
dismiss();
super.dispose();
}
#override
Widget build(BuildContext context) {
_rebuild();
return widget.child;
}
void show() {
if (_route == null) {
_route = _CoachMarkRoute(
rect: () {
final box = context.findRenderObject() as RenderBox;
return box.localToGlobal(Offset.zero) & box.size;
},
text: widget.text,
padding: EdgeInsets.all(4.0),
onPop: () {
_route = null;
mark();
},
);
Navigator.of(context).push(_route);
}
}
void _rebuild() {
if (_route != null) {
WidgetsBinding.instance.addPostFrameCallback((_) {
_route.changedExternalState();
});
}
}
void dismiss() {
if (_route != null) {
_route.dispose();
_route = null;
}
}
Future<bool> test() async {
return (await SharedPreferences.getInstance()).getBool(_key) ?? false;
}
void mark() async {
(await SharedPreferences.getInstance()).setBool(_key, true);
}
void reset() async {
(await SharedPreferences.getInstance()).remove(_key);
}
}
class _CoachMarkRoute<T> extends PageRoute<T> {
_CoachMarkRoute({
#required this.rect,
#required this.text,
this.padding,
this.onPop,
this.shadow = const BoxShadow(color: const Color(0xB2212121), blurRadius: 8.0),
this.maintainState = true,
this.transitionDuration = const Duration(milliseconds: 450),
RouteSettings settings,
}) : super(settings: settings);
final CoachMarkRect rect;
final String text;
final EdgeInsets padding;
final BoxShadow shadow;
final VoidCallback onPop;
#override
final bool maintainState;
#override
final Duration transitionDuration;
#override
bool didPop(T result) {
onPop();
return super.didPop(result);
}
#override
Widget buildPage(BuildContext context, Animation<double> animation, Animation<double> secondaryAnimation) {
Rect position = rect();
if (padding != null) {
position = padding.inflateRect(position);
}
position = Rect.fromCircle(center: position.center, radius: position.longestSide * 0.5);
final clipper = _CoachMarkClipper(position);
return Material(
type: MaterialType.transparency,
child: GestureDetector(
behavior: HitTestBehavior.opaque,
onTapDown: (d) => Navigator.of(context).pop(),
child: IgnorePointer(
child: FadeTransition(
opacity: animation,
child: Stack(
children: <Widget>[
ClipPath(
clipper: clipper,
child: BackdropFilter(
filter: ui.ImageFilter.blur(sigmaX: 2.0, sigmaY: 2.0),
child: Container(
color: Colors.transparent,
),
),
),
CustomPaint(
child: SizedBox.expand(
child: Center(
child: Text(text,
style: const TextStyle(
fontSize: 22.0,
fontStyle: FontStyle.italic,
color: Colors.white,
)),
),
),
painter: _CoachMarkPainter(
rect: position,
shadow: shadow,
clipper: clipper,
),
),
],
),
),
),
),
);
}
#override
bool get opaque => false;
#override
Color get barrierColor => null;
#override
String get barrierLabel => null;
}
class _CoachMarkClipper extends CustomClipper<Path> {
final Rect rect;
_CoachMarkClipper(this.rect);
#override
Path getClip(Size size) {
return Path.combine(PathOperation.difference, Path()..addRect(Offset.zero & size), Path()..addOval(rect));
}
#override
bool shouldReclip(_CoachMarkClipper old) => rect != old.rect;
}
class _CoachMarkPainter extends CustomPainter {
_CoachMarkPainter({
#required this.rect,
#required this.shadow,
this.clipper,
});
final Rect rect;
final BoxShadow shadow;
final _CoachMarkClipper clipper;
void paint(Canvas canvas, Size size) {
final circle = rect.inflate(shadow.spreadRadius);
canvas.saveLayer(Offset.zero & size, Paint());
canvas.drawColor(shadow.color, BlendMode.dstATop);
canvas.drawCircle(circle.center, circle.longestSide * 0.5, shadow.toPaint()..blendMode = BlendMode.clear);
canvas.restore();
}
#override
bool shouldRepaint(_CoachMarkPainter old) => old.rect != rect;
#override
bool shouldRebuildSemantics(_CoachMarkPainter oldDelegate) => false;
}
I'm not sure I understand the question. It seems that what you want would be achievable by using 3 layers in a stack. Lowest is your background, second is the darker frosted blur and put your icon on top.
Am I misunderstanding something?
I'm trying to make a flip card, what would be the best way to get the effect
I would use an AnimatedBuilder or AnimatedWidget to animate the values of a Transform widget. ScaleTransition almost does this for you, but it scales both directions, and you only want one.
import 'package:flutter/material.dart';
void main() {
runApp(new MyApp());
}
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return new MaterialApp(
home: new MyHomePage(),
);
}
}
class MyHomePage extends StatefulWidget {
MyHomePageState createState() => new MyHomePageState();
}
class MyCustomCard extends StatelessWidget {
MyCustomCard({ this.colors });
final MaterialColor colors;
Widget build(BuildContext context) {
return new Container(
alignment: FractionalOffset.center,
height: 144.0,
width: 360.0,
decoration: new BoxDecoration(
color: colors.shade50,
border: new Border.all(color: new Color(0xFF9E9E9E)),
),
child: new FlutterLogo(size: 100.0, colors: colors),
);
}
}
class MyHomePageState extends State<MyHomePage> with TickerProviderStateMixin {
AnimationController _controller;
Animation<double> _frontScale;
Animation<double> _backScale;
#override
void initState() {
super.initState();
_controller = new AnimationController(
vsync: this,
duration: const Duration(seconds: 1),
);
_frontScale = new Tween(
begin: 1.0,
end: 0.0,
).animate(new CurvedAnimation(
parent: _controller,
curve: new Interval(0.0, 0.5, curve: Curves.easeIn),
));
_backScale = new CurvedAnimation(
parent: _controller,
curve: new Interval(0.5, 1.0, curve: Curves.easeOut),
);
}
#override
void dispose() {
_controller.dispose();
super.dispose();
}
#override
Widget build(BuildContext context) {
ThemeData theme = Theme.of(context);
return new Scaffold(
appBar: new AppBar(),
floatingActionButton: new FloatingActionButton(
child: new Icon(Icons.flip_to_back),
onPressed: () {
setState(() {
if (_controller.isCompleted || _controller.velocity > 0)
_controller.reverse();
else
_controller.forward();
});
},
),
body: new Center(
child: new Stack(
children: <Widget>[
new AnimatedBuilder(
child: new MyCustomCard(colors: Colors.orange),
animation: _backScale,
builder: (BuildContext context, Widget child) {
final Matrix4 transform = new Matrix4.identity()
..scale(1.0, _backScale.value, 1.0);
return new Transform(
transform: transform,
alignment: FractionalOffset.center,
child: child,
);
},
),
new AnimatedBuilder(
child: new MyCustomCard(colors: Colors.blue),
animation: _frontScale,
builder: (BuildContext context, Widget child) {
final Matrix4 transform = new Matrix4.identity()
..scale(1.0, _frontScale.value, 1.0);
return new Transform(
transform: transform,
alignment: FractionalOffset.center,
child: child,
);
},
),
],
),
),
);
}
}
I used simple approach, rotated it on X axis. Here is the full code.
void main() => runApp(MaterialApp(home: HomePage()));
class HomePage extends StatefulWidget {
#override
_HomePageState createState() => _HomePageState();
}
class _HomePageState extends State<HomePage> with SingleTickerProviderStateMixin {
AnimationController _controller;
bool _flag = true;
Color _color = Colors.blue;
#override
void initState() {
super.initState();
_controller = AnimationController(vsync: this, duration: Duration(seconds: 1), value: 1);
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(),
floatingActionButton: FloatingActionButton(
child: Icon(Icons.crop_rotate),
onPressed: () async {
if (_flag) {
await _controller.reverse();
setState(() {
_color = Colors.orange;
});
await _controller.forward();
} else {
await _controller.reverse();
setState(() {
_color = Colors.blue;
});
await _controller.forward();
}
_flag = !_flag;
},
),
body: Center(
child: AnimatedBuilder(
animation: _controller,
builder: (context, child) {
return Transform(
transform: Matrix4.rotationX((1 - _controller.value) * math.pi / 2),
alignment: Alignment.center,
child: Container(
height: 100,
margin: EdgeInsets.symmetric(horizontal: 20),
padding: EdgeInsets.symmetric(vertical: 12),
alignment: Alignment.center,
decoration: BoxDecoration(color: _color.withOpacity(0.2), border: Border.all(color: Colors.grey)),
child: FlutterLogo(colors: _color, size: double.maxFinite),
),
);
},
),
),
);
}
}
You can use the flip_card Flutter package. It lets you define a front and back widget and can be flipped horizontally or vertically.
I am trying to create an animation that "pops" the widget to the front and returns to it
import "package:flutter/material.dart";
class ScoreCounter extends StatefulWidget {
#override
_ScoreCounter createState() => new _ScoreCounter();
}
class _ScoreCounter extends State<ScoreCounter> with SingleTickerProviderStateMixin{
AnimationController _controller;
#override
void initState() {
super.initState();
_controller = new AnimationController(
duration: const Duration(seconds: 10),
vsync: this,
)..forward();
}
#override
build(BuildContext context){
return new AnimatedBuilder(
animation: _controller,
child: new Container(width: 200.0, height: 200.0, color: Colors.green),
builder: (BuildContext context, Widget child) {
//What to return that scales the element
},
);
}
}
For rotating, I would use a Transform and return a Matrix. But what should I return to accomplish the scaling animation?
Thanks in advance
Just as a alternative, to set just scale for a specific Widget.
Transform.scale(
scale: 1.0,
child: Container(
height: 200.0,
width: 200.0,
color: Colors.pink,
),
),
If you're going to size your contents manually you could just read controller.value in your builder function and use that to set the size of the container.
Alternatively, you could consider a pair of SizeTransitions for each axis. The Align class may also be useful because you can set a sizeFactor in each dimension.
import 'package:flutter/material.dart';
void main() {
runApp(new MaterialApp(
home: new HomePage(),
));
}
class HomePage extends StatefulWidget {
HomePageState createState() => new HomePageState();
}
class HomePageState extends State<HomePage> {
#override
Widget build(BuildContext context) {
return new Scaffold(
floatingActionButton: new FloatingActionButton(
child: new Icon(Icons.sort),
onPressed: () {},
),
body: new Center(
child: new ScoreCounter(),
),
);
}
}
class ScoreCounter extends StatefulWidget {
#override
_ScoreCounter createState() => new _ScoreCounter();
}
class _ScoreCounter extends State<ScoreCounter>
with SingleTickerProviderStateMixin {
AnimationController _controller;
#override
void initState() {
super.initState();
_controller = new AnimationController(
duration: const Duration(seconds: 1),
vsync: this,
)
..forward();
}
#override
build(BuildContext context){
return new AnimatedBuilder(
animation: _controller,
builder: (BuildContext context, Widget child) {
double size = _controller.value * 200.0;
return new Container(width: size, height: size, color: Colors.green);
},
);
}
}
Another Approach is to create generalized Scale Transformation.
Simply add this method to your component
Matrix4 scaleXYZTransform({
double scaleX = 1.00,
double scaleY = 1.00,
double scaleZ = 1.00,
}) {
return Matrix4.diagonal3Values(scaleX, scaleY, scaleZ);
}
Now you can easily Scale any widget by wrapping it:
Transform(
transform: scaleXYZTransform(scaleX: .5, scaleY: .5),
child: Container(
child: myAwesomeWidget(),
));