Flutter: How manage transition with MediaQuery's values on animatedTransition Widget - flutter

first at all, i apologize for my english.
And second i have a trouble manage values for animateTransition. As you can see i have a card widget with a position at the right of the screen and MaterialButton in the center of the screen.
The positioned widget's values are give by MediaQuery Class and after save in the position variable, but when i try change the position's value and call setSate function, the build method redo and initialize the variables and again the new value is the same always because of that (i think that), and for that never will see a transition.
i show the code.
class _DashboardState extends State<Dashboard> {
final double blur = 30;
final double offset = 20;
final double top = 25;
#override
Widget build(BuildContext context) {
final size = MediaQuery.of(context).size;
double position = -(size.width) * 0.8;
return WillPopScope(
onWillPop: () async =>
SystemChannels.platform.invokeMethod('SystemNavigator.pop'),
child: MaterialApp(
home: Scaffold(
body: SafeArea(
child: Stack(
alignment: Alignment.center,
children: <Widget>[
Positioned(
right: position,
child: AnimatedContainer(
duration: Duration(milliseconds: 500),
curve: Curves.decelerate,
child: Card(
elevation: 8,
color: Colors.red,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(10),
),
child: Container(
width: size.width * 0.9,
height: size.height * 0.8,
),
),
),
),
Center(
child: MaterialButton(
color: Colors.black,
child: Text("mover"),
onPressed: () {
setState(() {
position = 0;
});
},
),
)
],
),
),
),
),
);
}
}
when i try to take out the variables i can't because de context dependency in mediaQuery and i can't initialize the request variables to get size of device, and put in the initial state. any advice for manage this type of problem.
Thanks for your attention

Related

Flutter animated sliver header

I am trying to create a profile header sliver that can animate.
If you consider above image, Section 1 is what we see in the fully expanded sliver, and Section 2 is what we want to see in pinned mode.
Now I would like transition to move the image - purple circle - to the side, shrink it slightly, and also move the name and the links.
I can achieve all of that but one thing: How to center them in the expanded view.
As I have to use transform to move widgets around, I cannot simply use a centring widget like column or center. And I didn't find a way to calculate the exact position to center the widget, as it needs the size of the widget, that I don't have.
Firstly I am using SliverPersistentHeaderDelegate and it provides shrinkOffset that will be used on linear interpolation(lerp method).
Then CompositedTransformTarget widget to follow the center widget.
On this example play with targetAnchor and followerAnchor and use t/shrinkOffset to maintain other animation.
class SFeb223 extends StatelessWidget {
const SFeb223({super.key});
#override
Widget build(BuildContext context) {
return Scaffold(
body: CustomScrollView(
slivers: [
SliverPersistentHeader(
delegate: MySliverPersistentHeaderDelegate(),
pinned: true,
),
SliverToBoxAdapter(
child: SizedBox(
height: 1333,
),
)
],
),
);
}
}
class MySliverPersistentHeaderDelegate extends SliverPersistentHeaderDelegate {
final LayerLink layerLink = LayerLink();
#override
Widget build(
BuildContext context, double shrinkOffset, bool overlapsContent) {
double t = shrinkOffset / maxExtent;
return Material(
color: Colors.cyanAccent.withOpacity(.2),
child: Stack(
children: [
Align(
alignment:
Alignment.lerp(Alignment.center, Alignment.centerLeft, t)!,
child: CompositedTransformTarget(
link: layerLink,
child: Padding(
padding: const EdgeInsets.all(8.0),
child: Container(
height: lerpDouble(100, kToolbarHeight - 10, t),
width: lerpDouble(100, kToolbarHeight - 10, t),
decoration: const ShapeDecoration(
shape: CircleBorder(),
color: Colors.deepPurple,
),
),
),
),
),
CompositedTransformFollower(
link: layerLink,
targetAnchor: Alignment.lerp(
Alignment.bottomCenter, Alignment.centerRight, t)!,
followerAnchor:
Alignment.lerp(Alignment.topCenter, Alignment.centerLeft, t)!,
child: Padding(
padding: const EdgeInsets.all(8.0),
child: Container(
child: Column(
children: [Text("Sheikh")],
),
),
),
),
],
),
);
}
#override
double get maxExtent => kToolbarHeight * 6;
#override
double get minExtent => kToolbarHeight;
#override
bool shouldRebuild(covariant SliverPersistentHeaderDelegate oldDelegate) =>
false;
}

Flutter color animation produces unwanted flashing

I want to animate from this white color with slight opacity to a black color with very high opacity.
Colors are
Color(0xB1FFFFFF) - White
Color(0x0B000000) - Black
This will produce an animation with a "flash". However, this should not happen.
What am I doing wrong? When doing the same animation with css, it does not "flash".
This is how it looks when using css and how I would expect it:
The jump to white is not important, it's just the animation restarting.
import 'dart:math';
import 'package:flutter/material.dart';
void main() => runApp(const AnimatedContainerApp());
class AnimatedContainerApp extends StatefulWidget {
const AnimatedContainerApp({Key? key}) : super(key: key);
#override
_AnimatedContainerAppState createState() => _AnimatedContainerAppState();
}
class _AnimatedContainerAppState extends State<AnimatedContainerApp> {
double _width = 50;
double _height = 50;
Color _color = Color(0xB1FFFFFF);
BorderRadiusGeometry _borderRadius = BorderRadius.circular(8);
#override
Widget build(BuildContext context) {
return MaterialApp(
home: Scaffold(
appBar: AppBar(
title: const Text('AnimatedContainer Demo'),
),
body: Center(
child: AnimatedContainer(
width: _width,
height: _height,
decoration: BoxDecoration(
color: _color,
borderRadius: _borderRadius,
),
duration: const Duration(seconds: 1),
curve: Curves.fastOutSlowIn,
),
),
floatingActionButton: FloatingActionButton(
onPressed: () {
setState(() {
_color = Color(0x0B000000);
});
},
child: const Icon(Icons.play_arrow),
),
),
);
}
}
I found an alternative of this using Stack widget.
class _AnimatedContainerAppState extends State<AnimatedContainerApp>{
double _width = 50;
double _height = 50;
Color colorA = Color(0xB1FFFFFF);
Color colorB = Color(0x9F000000); //change your color here
double opacity = 0;
#override
Widget build(BuildContext context) {
return MaterialApp(
home: Scaffold(
backgroundColor: Colors.pink,
appBar: AppBar(
title: const Text('AnimatedContainer Demo'),
),
body: Center(
child: ClipRRect(
borderRadius: BorderRadius.circular(8),
child: SizedBox(
key: const ValueKey("C"),
width: _width,
height: _height,
child: Stack(
fit: StackFit.expand,
children: [
//fixed color
AnimatedOpacity(
opacity: (1 - opacity).abs(),
duration: const Duration(seconds: 1),
child: ColoredBox(color: colorA),
),
AnimatedOpacity(
opacity: opacity,
duration: const Duration(seconds: 1),
child: ColoredBox(color: colorB),
),
],
),
),
),
),
floatingActionButton: FloatingActionButton(
onPressed: () {
setState(() {
opacity = opacity == 0 ? 1 : 0;
});
},
child: const Icon(Icons.play_arrow),
),
),
);
}
}
You can also try with others animated widget like AnimatedCrossFade , FadeTransition etc.
I now understand why this "issue" exists.
Since both colors vary a lot both in opacity and color, a basic interpolation by time over the rgba values produces this behavior.
Whilst the alpha is being interpolated, the color is so too, and this creates a temporary darker color in between.
Dart interpolates colors by mapping the difference in colors to the time in the rgba spectrum.
a + (b - a) * t
This article describes very good what happens and other possible solutions for interpolating colors.
Also using HSL instead of rgba might also solve the issue. However, converting the color to HSL every time is not near as performant as flat rgba interpolation. Dart is all about performance, so this might be why they went for this very basic approach.
My personal solution to the problem was a very basic one though. I asked the designer to bring the colors closer together :)

In Expandable Floating action button the gestures in the child widget is not detected in flutter

Trying to implement a floating action button that extends in two dimension and then show some more option of floating action button.
Somehow able to animated the child widget of the floating action button to their correct position, using the Transform Widget, but when I try to press on the child widget, i.e. the widgets that come out on pressing the floating action button, they do not respond to the onPressed handler.
Tried many different thing like IgnorePointer, stacked rows and Columns, AnimatedBuilder,etc. but was unable to find the correct solution.
It was like sometimes used to get the UI correct then the gesture was not detected and if the gesture were detected the UI got distorted.
And I am somewhat new to flutter. Any help in sorting out this issue would be appreciated.
Here is my Code:
main.dart
import "package:flutter/material.dart";
import 'myhome.dart';
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
theme: ThemeData(
primaryColor: Colors.blue,
),
title: "Custom Expandable FAB",
home: MyHome(),
);
}
}
myhome.dart
import 'package:flutter/material.dart';
import 'package:tester_project/customFAB.dart';
class MyHome extends StatelessWidget {
#override
Widget build(BuildContext context) {
return Scaffold(
extendBody: true,
backgroundColor: Colors.white,
floatingActionButton: CustomFab(),
floatingActionButtonLocation: FloatingActionButtonLocation.centerDocked,
bottomNavigationBar: BottomAppBar(
color: Colors.blue,
shape: CircularNotchedRectangle(),
child: Container(
height: 55,
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
IconButton(
icon: Icon(Icons.search),
onPressed: () {},
),
IconButton(
icon: Icon(Icons.menu),
onPressed: () {},
),
],
),
),
),
appBar: AppBar(
title: Text("Custom FAB"),
),
body: Container(
alignment: Alignment.center,
child: Text("Click on Fab to expand"),
color: Colors.white,
),
);
}
}
CustomFab.dart
import 'package:flutter/material.dart';
import 'package:permission_handler/permission_handler.dart';
class CustomFab extends StatefulWidget {
#override
_CustomFabState createState() => _CustomFabState();
}
class _CustomFabState extends State<CustomFab>
with SingleTickerProviderStateMixin {
AnimationController _animationController;
Animation<double> _translateAnimation;
Animation<double> _rotationAnimation;
Animation<double> _iconRotation;
bool _isExpanded = false;
void animate() {
if (!_isExpanded) {
_animationController.forward();
} else {
_animationController.reverse();
}
_isExpanded = !_isExpanded;
}
Widget fab1() {
return Container(
height: 60,
width: 60,
child: FittedBox(
child: FloatingActionButton(
heroTag: "btn3",
backgroundColor: Color(0xffFFC852),
elevation: 0,
onPressed: () {
print("pressed");
},
),
),
);
}
Widget fab2() {
return Container(
height: 60,
width: 60,
child: FittedBox(
child: FloatingActionButton(
heroTag: "btn4",
child: Transform.rotate(
angle: _iconRotation.value,
child: Icon(Icons.home),
),
elevation: _isExpanded ? 5 : 0,
backgroundColor: Color(0xffE5E4F4),
onPressed: () {
print("Pressed");
},
),
),
);
}
Widget fab3() {
return Container(
height: 60,
width: 60,
child: FittedBox(
child: FloatingActionButton(
heroTag: "btn5",
child: Transform.rotate(
angle: _rotationAnimation.value,
child: Icon(Icons.add),
),
backgroundColor: Color(0xffFFC852),
onPressed: () async {
await Permission.contacts.request();
if (await Permission.contacts.status.isGranted) {
animate();
}
},
),
),
);
}
#override
void initState() {
_animationController =
AnimationController(vsync: this, duration: Duration(milliseconds: 400))
..addListener(() {
setState(() {});
});
_translateAnimation = Tween<double>(begin: 0, end: 80)
.chain(
CurveTween(
curve: _isExpanded ? Curves.fastOutSlowIn : Curves.bounceOut,
),
)
.animate(_animationController);
_iconRotation = Tween<double>(begin: 3.14 / 2, end: 0)
.chain(
CurveTween(curve: Curves.bounceInOut),
)
.animate(_animationController);
_rotationAnimation = Tween<double>(begin: 0, end: 3 * 3.14 / 4)
.chain(
CurveTween(
curve: Curves.bounceInOut,
),
)
.animate(_animationController);
super.initState();
}
#override
void dispose() {
_animationController.dispose();
super.dispose();
}
#override
Widget build(BuildContext context) {
return Stack(
clipBehavior: Clip.none,
children: [
Transform(
transform:
Matrix4.translationValues(0, -_translateAnimation.value, 0),
child: fab1(),
),
Transform(
transform:
Matrix4.translationValues(-_translateAnimation.value, 0, 0),
child: fab2(),
),
fab3(),
],
);
}
}
Floating action button before and after expansion
Look at this. https://api.flutter.dev/flutter/widgets/Transform-class.html
Unlike RotatedBox, which applies a rotation prior to layout, this object applies its transformation just prior to painting, which means the transformation is not taken into account when calculating how much space this widget's child (and thus this widget) consumes.
So, your fab1(),fab2(),fab3() have the same position.
Although you animate them, it just move at painting, their real position wont change.
Just give a color to your fabs, you will know what I mean.
Container(
color:Colors.green,
child: Transform(
transform:
Matrix4.translationValues(-_translateAnimation!.value, 0, 0),
child: fab2(),
),
),
Now you know why, and you need to know how.
So I hope you can look at this. https://api.flutter.dev/flutter/animation/animation-library.html
You can use Stack&&Positioned,and with Tween, caculate each button's position, or other way. I will leave you to explore.
Here's some code of CustomFab.dart
#override
Widget build(BuildContext context) {
return Container(
// color: Colors.green, // Give this area a background color, then you know why.
height: 150,// You need to give your "action area" a bigger size.
width: 150,// make these bigger have a problem, your bar will have a bigger circle.
// if you need this effect, you need to change all your fabs "Stack on the appbar"
// or just remove `shape: CircularNotchedRectangle(),` in myhome.dart
child: Stack(
clipBehavior: Clip.none,
children: [
Positioned(// These numbers just for example, you can make your own size or position.
left: 150 / 2 - 30,
bottom: _translateAnimation.value + 40,
child: fab1(),
),
Positioned(
left: 150 / 2 - 30 -_translateAnimation.value,
bottom: 40,
child: fab2(),
),
Positioned(
left: 150 / 2 - 30,
bottom: 40,
child: fab3(),
),
],
),
);
}
Try Speed Dial using this package https://pub.dev/packages/flutter_speed_dial
SpeedDial(
marginBottom: 25,
marginEnd: 25,
backgroundColor: Colors.blue,
activeBackgroundColor: Colors.white,
activeForegroundColor: Colors.blue,
animatedIcon: AnimatedIcons.menu_close,
children: [
SpeedDialChild(
child: Icon(
Icons.filter_alt,
),
label: 'ABC',
onTap: () {
}),
SpeedDialChild(
labelBackgroundColor: Colors.white,
backgroundColor: Colors.blue,
foregroundColor: Colors.white,
child: Icon(Icons.add),
label: 'ABC',
onTap: () {
}),
],
),

Flutter Transform.scale for child widgets not scaling proportionately, why?

I have a basic flag widget, with code here:
import 'package:flutter/material.dart';
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Demo',
home: Scaffold(
body: Flag(),
),
);
}
}
class Flag extends StatelessWidget {
#override
Widget build(BuildContext context) {
var scaleFactor = 1.0;
final flag = Transform.scale(
scale: scaleFactor,
child: AspectRatio(
aspectRatio: 5 / 3,
child: Container(
decoration: BoxDecoration(
border: Border.all(width: 2, color: Colors.black),
),
child: Center(
child: Container(
height: 400,
width: 150,
color: Colors.purple,
),
),
),
),
);
return LayoutBuilder(
builder: (BuildContext context, BoxConstraints constraints) => flag,
);
}
}
It looks like this:
If I change scaleFactor to 0.5, it scales as expected:
But if I rotate my emulator to landscape, the proportions are off:
If I make the stripe proportional to the landscape view (e.g. change the stripe width to 250), it gets too big in portrait mode:
How do I ensure the purple stripe takes up the same proportion of space regardless of device size?
The flag is going to get way more complicated so I don't want to use MediaQuery.of(context).size to get the device width and calculate a percentage of that for every single child widget...
Do I need to tell my widget its "canonical" size? Do I need to pass a scaleFactor to every child widget?
Any ideas very appreciated :)
So, I see two ways to solve this, depending on what your eventual goal is. I think the first one is probably what you actually want.
1) Use a Row and an Expanded widget for each part of the flag, with a flex of one. The AspectRatio will keep the aspect ratio fixed, so the proportions should remain the same for the containers. Since the default flex factor is one anyhow, you can also just leave that out. If you need to the white parts to be transparent, just give Colors.transparent as the color.
class Flag extends StatelessWidget {
#override
Widget build(BuildContext context) {
var scaleFactor = 1.0;
final flag = Transform.scale(
scale: scaleFactor,
child: AspectRatio(
aspectRatio: 5 / 3,
child: Container(
decoration: BoxDecoration(
border: Border.all(width: 2, color: Colors.black),
),
child: Row(
children: <Widget>[
Expanded(
flex:1,
child: Container(
color: Colors.white,
),
),
Expanded(
flex:1,
child: Container(
color: Colors.purple,
),
),
Expanded(
flex:1,
child: Container(
color: Colors.white,
),
),
],
),
),
),
);
return LayoutBuilder(
builder: (BuildContext context, BoxConstraints constraints) => flag,
);
}
}
2) You could just scale the contents of the center with a Transform widget, but that will squish any content you put inside it, so that's probably not what you want.
class Flag extends StatelessWidget {
#override
Widget build(BuildContext context) {
var scaleFactor = 1.0;
final flag = Transform.scale(
scale: scaleFactor,
child: AspectRatio(
aspectRatio: 5 / 3,
child: Container(
decoration: BoxDecoration(
border: Border.all(width: 2, color: Colors.black),
),
child: Center(
child: Transform(
transform: Matrix4.diagonal3Values(0.33, 1.0, 1.0),
alignment: Alignment.bottomCenter,
child: Container(
color: Colors.purple,
),
),
),
),
),
);
return LayoutBuilder(
builder: (BuildContext context, BoxConstraints constraints) => flag,
);
}
}

How to draw container outside screen width in flutter?

How can I create a container with rounded corners as shown below?
I tried using container with width more than the screen width. But that constraints it inside the screen. I tried using an OverFlow box, but couldn't get the same result as well. I don't want to use clipRect to make this as I want to apply animation on the corners.
Edit: Added container snippet with the resulting outcome to clear doubts
Widget build(BuildContext context) {
return Scaffold(
backgroundColor: Colors.black,
body: Align(
alignment: Alignment.bottomCenter,
child: Container(
height: 500,
decoration: BoxDecoration(
color: Colors.green, borderRadius: BorderRadius.circular(500)),
),
),
);
}
I have managed to get similar to what I want by using scale transformation. Would like to see a different approach though.
Widget build(BuildContext context) {
return Scaffold(
backgroundColor: Colors.black,
body: Align(
alignment: Alignment.bottomCenter,
child: Transform.scale(
scale: 1.7,
child: Container(
height: 400,
decoration: BoxDecoration(
color: Colors.green, borderRadius: BorderRadius.circular(200)),
),
),
),
);
}
I have done this using clippath. If you change size of Container then Clippath size automatically change as per Container size.
You can modify Path for different shape as per your requirement so this is very useful.
Here, I just use ClipPath widget and I create MyCustomShape class for modify shape of child Container widget
class Example extends StatelessWidget {
#override
Widget build(BuildContext context) {
return Scaffold(
backgroundColor: Colors.black87,
body: ClipPath(
clipper: MyCustomShape(),
child: Container(
color: Colors.green[800],
height: double.infinity,
),
),
);
}
}
class MyCustomShape extends CustomClipper<Path> {
#override
Path getClip(Size size) {
Path path = new Path(); // use for shape your container
path.lineTo(0.0, 100);
path.quadraticBezierTo(size.width * 0.5, 0.0, size.width, 100);
path.lineTo(size.width, size.height);
path.lineTo(0.0, size.height);
path.close();
return path;
}
after reading your questions and comments many time i hope this what you are looking for
class dummy2 extends StatefulWidget {
#override
_dummy2State createState() => _dummy2State();
}
class _dummy2State extends State<dummy2> with TickerProviderStateMixin {
AnimationController controller;
Animation<double> animaton;
#override
initState() {
super.initState();
controller =
AnimationController(vsync: this, duration: Duration(seconds: 1));
animaton = Tween<double>(begin: 1, end: 2.5).animate(
CurvedAnimation(parent: controller, curve: Curves.fastOutSlowIn));
animaton.addListener(() {
setState(() {});
});
}
#override
Widget build(BuildContext context) {
return SafeArea(
child: Scaffold(
appBar: AppBar(
actions: <Widget>[
FlatButton(
onPressed: () {
controller.forward();
},
child: Text('forward')),
FlatButton(
onPressed: () {
controller.reverse();
},
child: Text('reverse'))
],
),
body: Transform.scale(
child: Container(
decoration: BoxDecoration(
color: Colors.deepPurple, shape: BoxShape.circle),
),
scale: animaton.value,
)));
}
}
I believe you should use SafeArea within a container to fill it. Like this:
Container(
color: Colors.blue,
child: SafeArea(child: ### // you nav or functions),
),
See: https://api.flutter.dev/flutter/widgets/SafeArea-class.html