How to create animated gender selection page? - flutter

I have built an app using Flutter. I want to create a gender selection page for it, currently I have a plain drop-down type button. I want to have an effect like this but I have no idea how to get this can anyone help?
GIF of Sample
#override
initState() {
pageController;
super.initState();
}
PageController? pageController =
PageController(initialPage: 1, viewportFraction: .5);
#override
Widget build(BuildContext context) {
var width = MediaQuery.of(context).size.width;
TabController tabController = TabController(length: 2, vsync:this);
List genders = [
Image.asset("assets/fitness_app/area1.png"),
Image.asset("assets/fitness_app/area3.png")];
return Scaffold(
floatingActionButton: FloatingActionButton(
onPressed: () {
premiumDialogue();
},
backgroundColor: primary,
child: const Icon(
Icons.add,
),
),
backgroundColor: bgcolor,
appBar: AppBar(
toolbarHeight: 70,
leadingWidth: 65,
backgroundColor: Colors.transparent,
centerTitle: true,
title: const Text(
"Home Workouts",
style: h2black,
),
elevation: 0,
),
body: Column(
mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.center,
children: [
Expanded(
child: PageView.builder(
controller: pageController,
clipBehavior: Clip.antiAlias,
physics: const BouncingScrollPhysics(),
itemCount: genders.length,
itemBuilder: (context, index) {
return genders[index];
},
),
),
),
],
);
I have implemented this code but I was able to achieve this only.
What I achieved

There are many approaches to achieving this you can your the pageview inside page view you can use the AnimatedContainer widget to animate the change in size and color of the selected image.
bool _isMaleSelected = false;
bool _isFemaleSelected = false;
void _selectMale() {
setState(() {
_isMaleSelected = true;
_isFemaleSelected = false;
});
}
void _selectFemale() {
setState(() {
_isMaleSelected = false;
_isFemaleSelected = true;
});
}
return Scaffold(
body: Container(
padding: padding as per your requirement,
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
AnimatedContainer(
duration: Duration(milliseconds: as per your requirement),
height: _isMaleSelected ? height: height,
width: _isMaleSelected ? width: width:
decoration: BoxDecoration(
color: _isMaleSelected ? Colors as per your requirement: Colors as per your requirement,
and decoration as per your need
),
child: InkWell(
onTap: _selectMale,
child: Image.asset(
"your image path",
height: height as per your requirement,
width: width as per your requirement,
),
),
),
SizedBox(
height: height as per your requirement,
),
AnimatedContainer(
duration: Duration(milliseconds: as per your requirement),
height: _isFemaleSelected ? height as per your requirement: height as per your requirement,
width: _isFemaleSelected ? width as per your requirement: width as per your requirement,
decoration: BoxDecoration(
color: _isFemaleSelected ? Colors as per your requirement: Colors as per your requirement,
decoration as per your need
),
child: InkWell(
onTap: _selectFemale,

Related

Transition from large to small image smooth animation - Flutter

I have a login-page where I want the transition of the logo from large to small (and back) to be smoother. As it is now, it just pops between the two.
When the keyboard opens, I would like the logo to animate to the smaller position, instead of just instantly changing.
Does anyone know a way to do this?
#override
Widget build(BuildContext context) {
Size size = MediaQuery.of(context).size;
return Scaffold(
resizeToAvoidBottomInset: true,
backgroundColor: AppColors.yellowLight,
// appBar: MainAppBar(),
body: SingleChildScrollView(
physics: BouncingScrollPhysics(),
child: Center(
child: Column(
mainAxisSize: MainAxisSize.min,
children: <Widget>[
Container(
padding: EdgeInsets.symmetric(vertical: 90),
child: Image(
image: ExactAssetImage('assets/icons/app_logo.png'),
height: MediaQuery.of(context).viewInsets.bottom == 0
? size.height * 0.3
: size.height * 0.15,
fit: BoxFit.fitHeight,
alignment: Alignment.center,
),
),
Padding(
padding: EdgeInsets.symmetric(horizontal: 50),
child: MainTextField(
controller: emailController,
labelText: 'E-post',
hintText: 'Skriv e-post som abc#epost.no'),
),
Padding(
padding: const EdgeInsets.only(
left: 50.0, right: 50.0, top: 30, bottom: 30),
child: MainTextField(
controller: passwordController,
obscureText: true,
labelText: 'Passord',
hintText: 'Skriv inn sikkert passord',
),
),
LoginButton(
text: 'Logg inn',
isLoading: isLoading,
onPressed: _login,
),
// Text('New User? Create Account')
],
),
),
),
);
}
AnimatedContainer(
height: MediaQuery.of(context).viewInsets.bottom != 0 ? 100.0 : 200.0,
width: MediaQuery.of(context).viewInsets.bottom != 0 ? 100 : 200.0,
duration:const Duration(milliseconds: 750),
curve: Curves.fastOutSlowIn,
child: Image.network("https://picsum.photos/200/300"),
),
but MediaQuery.of(context).viewInsets.bottom it won't give you accuracy as if it's real time when changing the keyboard state, so I advice you to use another method to know the current state of the keyboard like the code below
you can use this package to listen for keyboard change flutter_keyboard_visibility here is the code
class _MyHomePageState extends State<MyHomePage> {
late StreamSubscription<bool> keyboardSubscription;
bool _isVisible = false;
#override
void initState() {
super.initState();
var keyboardVisibilityController = KeyboardVisibilityController();
// Subscribe
keyboardSubscription = keyboardVisibilityController.onChange.listen((bool visible) {
if(mounted) {
setState(() {
_isVisible = visible;
});
}
});
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
// Here we take the value from the MyHomePage object that was created by
title: Text(widget.title),
),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
AnimatedContainer(
height: _isVisible ? 100.0 : 200.0,
width: _isVisible ? 100 : 200.0,
duration:const Duration(milliseconds: 750),
curve: Curves.fastOutSlowIn,
child: Image.network("https://picsum.photos/200/300"),
),
Spacer(),
const Text(
'You have pushed the button this many times:',
),
TextFormField(
),
Spacer(),
],
),
),
floatingActionButton: FloatingActionButton(
onPressed: (){},
tooltip: 'Increment',
child: const Icon(Icons.add),
), // This trailing comma makes auto-formatting nicer for build methods.
);
}
#override
void dispose() {
keyboardSubscription.cancel();
super.dispose();
}
}
if you don't want to use package for keyboard visibility so you can use this code
class _MyHomePageState extends State<MyHomePage> {
final FocusNode _myNode = FocusNode();
bool _isVisible = false;
#override
void initState() {
super.initState();
_myNode.addListener(_listener);
}
void _listener(){
if(_myNode.hasFocus){
// keyboard appeared
if(mounted){
setState(() {
_isVisible = true;
});
}
}else{
// keyboard dismissed
if(mounted){
setState(() {
_isVisible = false;
});
}
}
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
// Here we take the value from the MyHomePage object that was created by
title: Text(widget.title),
),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
AnimatedContainer(
height: _isVisible ? 100.0 : 200.0,
width: _isVisible ? 100 : 200.0,
duration:const Duration(milliseconds: 750),
curve: Curves.fastOutSlowIn,
child: Image.network("https://picsum.photos/200/300"),
),
Spacer(),
const Text(
'You have pushed the button this many times:',
),
TextFormField(
focusNode: _myNode,
),
Spacer(),
],
),
),
floatingActionButton: FloatingActionButton(
onPressed: (){},
tooltip: 'Increment',
child: const Icon(Icons.add),
), // This trailing comma makes auto-formatting nicer for build methods.
);
}
#override
void dispose() {
_myNode.removeListener(_listener);
super.dispose();
}
}
I found a way that kind of works. At least the animation does. But as it is based on MediaQuery.of(context).viewInsets.bottom == 0 still, the animation does not start until the keyboard is all the way out of view.
AnimatedContainer(
duration: Duration(milliseconds: 400),
curve: Curves.fastOutSlowIn,
padding: MediaQuery.of(context).viewInsets.bottom == 0
? EdgeInsets.symmetric(vertical: 100)
: EdgeInsets.only(top: 75, bottom: 10),
height: MediaQuery.of(context).viewInsets.bottom == 0
? _height.height * 0.5
: _height.height * 0.25,
child: Image(
image: ExactAssetImage('assets/icons/app_logo.png'),
),
),

flutter app freezes if animation is added inside futureBuilder

I have this application where I will have futurebuilder to get data first, and then return a scaffold with a separate splash screen to simulate the screen darken effect with alertdialog while enabling setstate (I tested, they dont allow setstate with alert dialog). However, when I used Animated Opacity, Color Tween or Animated Switcher, as soon as the screen loads, the app freezes.
code:
class MainPageState extends State<MainPage> with TickerProviderStateMixin {
GlobalKey imageKey = GlobalKey();
late Animation splashAnimation;
late AnimationController splashController;
double splashOpacity = 0;
#override
void initState() {
splashController =
AnimationController(vsync: this, duration: Duration(milliseconds: 400));
splashAnimation =
ColorTween(begin: Colors.transparent, end: Color.fromARGB(155, 0, 0, 0))
.animate(splashController);
super.initState();
}
#override
Widget build(BuildContext context) {
double height = MediaQuery.of(context).size.height;
double width = MediaQuery.of(context).size.width;
PageLoader pageLoader = context.watch<PageLoader>();
TabProperty tabProperty = context.watch<TabProperty>();
return WillPopScope(
onWillPop: () async {
//check if user logged in ? disable back btn : exit app
bool login = false;
await StorageManager.readData("loggedIn")
.then((value) => login = value);
if (login) {
return false;
} else {
SystemNavigator.pop();
}
return false;
},
child: FutureBuilder(
future: getMenuAndAccountInfo(pageLoader),
builder: ((context, snapshot) {
//placeholder
if (!snapshot.hasData) {
return Stack(children: [
SafeArea(
child: Scaffold(
body: pageLoader.currentPage,
),
),
Container(
height: MediaQuery.of(context).size.height,
width: MediaQuery.of(context).size.width,
color: Color.fromARGB(100, 0, 0, 0),
),
Center(child: CircularProgressIndicator()),
]);
} else {
List<dynamic> menuData =
(snapshot.data as Map<String, dynamic>)["Menu"];
return SafeArea(
child: RepaintBoundary(
key: imageKey,
child: Stack(
children: [
Scaffold(
body: pageLoader.currentPage,
bottomNavigationBar: Container(
height: height * 0.07,
color: Colors.blue,
child: Row(
crossAxisAlignment: CrossAxisAlignment.end,
children: [
Expanded(
child: MaterialButton(
padding: EdgeInsets.zero,
splashColor: Colors.orange,
highlightColor: Colors.transparent,
onPressed: () {
setState(() {});
splashOpacity = 1;
},
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Icon(
Icons.bookmark,
color: Colors.white,
size: height * 0.035,
),
Text(
"Bookmark",
style: TextStyle(
color: Colors.white,
fontSize: height * 0.02),
),
],
)),
),
Expanded(
child: MaterialButton(
padding: EdgeInsets.zero,
splashColor: Colors.orange,
highlightColor: Colors.transparent,
onPressed: () {
setState(() {});
splashOpacity = 1;
},
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Icon(
Icons.account_circle_rounded,
color: Colors.white,
size: height * 0.035,
),
Text(
"Profile",
style: TextStyle(
color: Colors.white,
fontSize: height * 0.02),
)
],
)),
),
],
),
),
),
Positioned.fill(
child: AnimatedOpacity(
opacity: splashOpacity,
duration: Duration(milliseconds: 400),
child: Container(
color: Color.fromARGB(155, 0, 0, 0),
),
),
)
],
),
),
);
}
}),
),
);
}
NEVER build the future as the future: parameter to the FutureBuilder!
future: getMenuAndAccountInfo(pageLoader),
No!
Instead, you want to declare a variable to hold the future in State, and in initState, initialize that variable, and in your FutureBuilder, reference that variable.
For details, see the first few paragraphs of the FutureBuilder documentation, or see my video on that.

onTap function not working for Align widgets in Flutter

I have a widget with a GestureDetector inside of Stack having Align as a child(where onTap is registered)
The layout code is,
body: Stack(
children: [
Padding(
padding: EdgeInsets.only(top: 28.0.vs),
child: PageView.builder(
controller: _pageController,
onPageChanged: _changeCurrentSlide,
itemCount: 2,
itemBuilder: (context, index) => Align(
alignment: Alignment.topCenter,
child: Container(
height: 700.h,
child: Card(
elevation: 20,
color: AppColors.black,
child: Container(
width: 350.w,
child: Stack(
overflow: Overflow.visible,
children: [
Text('Hai'),
GestureDetector(
behavior: HitTestBehavior.opaque,
onTap: () { print('hello'); },
child: Align(
alignment: Alignment(0, 1.1),
child: Material(
elevation: 20,
color: AppColors.transparent,
child: Container(
width: 70,
height: 40,
color: AppColors.goldenYellow,
child: Center(
child: Text(
'Next'
),
),
),
),
),
),
],
),
),
),
),
),
),
),
],
),
I need to call a function on tap of Align widget but only half the area of GestureDetector is working and on other half, onTap is not detected
Image for GestureDector, not working
This behavior occurs because the stack checks whether the pointer is inside its bounds before checking whether a child got hit:
If you notice the part of the button which is overflowing or not within the constraints of the Stack does not receive the tap. This is because the Stack widgets first checks whether the hit was inside its bounds before checking whether its child got tapped.
As a workaround you may wrap the parent widget of the Stack with Gesture detector and then check if the hit was within the bounds of your button or the intended widget. Please check the code below, I had to make some changes to your code in order to make my code work. Note if 'clipBehavior: Clip.none,' in my code gives you an error then you may comment it out and use 'overflow: Overflow.visible,' instead. Please check if this code works for you.
import 'package:flutter/material.dart';
import 'package:flutter/rendering.dart';
final Color darkBlue = const Color.fromARGB(255, 18, 32, 47);
void main() {
runApp(
MaterialApp(
theme: ThemeData.dark().copyWith(scaffoldBackgroundColor: darkBlue),
debugShowCheckedModeBanner: false,
home: MyApp(),
),
);
}
class MyApp extends StatefulWidget {
#override
_MyAppState createState() => _MyAppState();
}
class _MyAppState extends State<MyApp> {
final List<GlobalKey> _key = <GlobalKey>[];
final PageController _controller = PageController();
int _currentPage = 0;
void onTapDown(
BuildContext context,
TapDownDetails details,
) {
final RenderBox box = context.findRenderObject();
final Offset localOffset = box.globalToLocal(details.globalPosition);
final RenderBox containerBox =
_key[_currentPage].currentContext.findRenderObject();
final Offset containerOffset = containerBox.globalToLocal(localOffset);
final onTap = containerBox.paintBounds.contains(containerOffset);
if (onTap) {
print("hello");
}
}
#override
Widget build(BuildContext context) {
return Scaffold(
body: GestureDetector(
onTapDown: (TapDownDetails details) {
onTapDown(context, details);
},
child: Container(
width: 500,
height: MediaQuery.of(context).size.height,
color: Colors.white,
child: Stack(
children: [
Padding(
padding: EdgeInsets.only(
top: 28.0), // EdgeInsets.only(top: 28.0.vs),
child: PageView.builder(
controller: _controller, //_pageController,
onPageChanged: (val) {
_currentPage = val;
//_changeCurrentSlide();
},
itemCount: 2,
itemBuilder: (context, index) {
_key.add(GlobalKey());
return Align(
alignment: Alignment.topCenter,
child: Container(
height: 600, //700.h,
child: Card(
elevation: 20,
color: Colors.white, // AppColors.black,
child: Container(
width: 350, //350.w,
child: Stack(
//overflow: Overflow.visible,
clipBehavior: Clip.none,
children: [
Text('Hai'),
//GestureDetector(
// behavior: HitTestBehavior.opaque,
// onTap: () { print('hello'); },
// child:
Align(
alignment: Alignment(0, 1.1),
child: Material(
elevation: 20,
color: Colors
.transparent, // AppColors.transparent,
child: Container(
key: _key[index],
width: 70,
height: 40,
color: Colors
.black, //AppColors.goldenYellow,
child: Center(
child: Text('Next'),
),
),
),
),
//),
],
),
),
),
),
);
},
),
),
],
),
),
),
);
}
}

Flutter : how to hide and show button on last and first index in listView

i set two buttons(left and right Button) on top of ListView. buttons work for scrolling on click. now i want to hide the left button when index is 0 and the right button when index is last. more explain to clear, the left button will be hidden in first index and the right button will be hidden in last index. please help me.
class ScrollingLeftAndRightButtonHide extends StatefulWidget {
#override
_ScrolllingOnClickButtonState createState() =>
_ScrolllingOnClickButtonState();}
class _ScrolllingOnClickButtonState
extends State<ScrollingLeftAndRightButtonHide> {
final _controller = ScrollController();
var _width = 100.0;
#override
Widget build(BuildContext context) {
var sizeDevice = MediaQuery.of(context).size;
this._width = sizeDevice.width;
var recentIndexIncrease = 0;
var recentIndexDecrease = 0;
return MaterialApp(
debugShowCheckedModeBanner: false,
home: Scaffold(
body: Column(
children: <Widget>[
Expanded(
flex: 1,
child: Container(
color: Colors.green,
)),
Expanded(
flex: 2,
child: Row(
children: <Widget>[
Padding(
padding: const EdgeInsets.only(left: 8.0),
child: ClipOval(
child: Material(
color: Colors.blue, // button color
child: InkWell(
splashColor: Colors.red, // inkwell color
child: SizedBox(
width: 56,
height: 56,
child: Icon(Icons.arrow_back)),
onTap: () {
var recentIndexDecreaseMinus =
recentIndexDecrease--;
_animateToIndex(recentIndexDecrease);
},
),
),
),
),
Expanded(
flex: 2,
child: Container(
color: Colors.transparent,
)),
Padding(
padding: const EdgeInsets.only(right: 8),
child: ClipOval(
child: Material(
color: Colors.blue, // button color
child: InkWell(
splashColor: Colors.red, // inkwell color
child: SizedBox(
width: 56,
height: 56,
child: Icon(Icons.arrow_forward)),
onTap: () {
_animateToIndex(recentIndexIncrease);
},
),
),
),
),
],
)),
Expanded(
flex: 16,
child: Container(
// height: 400,
child: ListView.builder(
controller: _controller,
scrollDirection: Axis.horizontal,
physics: PageScrollPhysics(),
itemCount: word_data.drink.length,
itemBuilder: (BuildContext context, int index) {
recentIndexIncrease = index;
recentIndexDecrease = index;
var wordDataReplace = word_data.drink[index]
.replaceAll(" ", "_")
.toLowerCase();
return Container(
child: Column(
children: <Widget>[
Expanded(
flex: 6,
child: GestureDetector(
child: Container(
color: Colors.purple,
child: Padding(
padding: const EdgeInsets.all(10.0),
child: Image.asset(
"asset/drink_images/" +
wordDataReplace +
".png",
fit: BoxFit.contain,
),
),
),
)),
],
),
width: sizeDevice.width,
);
}),
color: Colors.yellow,
),
),
],
),
),
);
}
_animateToIndex(i) => _controller.animateTo(_width * i,
duration: Duration(seconds: 1), curve: Curves.fastOutSlowIn);
}
this image of (ListView with top two Button)
I think it might be easier for you to replace ListView.builder by a Flutter_Swiper it will make your life a lot easier. Or maybe you can add a listner to your ScrollController in the initState where it handles the value of two Boolean variables leftButtonEnabled and rightButtonEnabled and set them to true or false depending on the position of the Controller
EDIT :
here's an example of using Flutter swiper in your code, I tried to make it simple and in the same time adding multiple features that can help you ( like SwiperControl ) I hope it helps you a little bit.
void main() {
runApp(
MaterialApp(
debugShowCheckedModeBanner: false,
home: ScrollingLeftAndRightButtonHide(),
),
);
}
class ScrollingLeftAndRightButtonHide extends StatefulWidget {
#override
_ScrolllingOnClickButtonState createState() =>
_ScrolllingOnClickButtonState();
}
class _ScrolllingOnClickButtonState
extends State<ScrollingLeftAndRightButtonHide> {
SwiperController _controller = SwiperController();
SwiperControl _control = SwiperControl(color: Colors.white);
double get _width => MediaQuery.of(context).size.width;
double get _height => MediaQuery.of(context).size.height;
bool inFirstPage = true;
bool inLastPage = false;
List<String> word_data = [
"First",
"Second",
"Third",
"Fourth",
"Fifth",
"Sixth",
"Last",
];
#override
Widget build(BuildContext context) {
return Scaffold(
backgroundColor: Colors.white,
body: SafeArea(
child: Column(
children: <Widget>[
Container(
color: Colors.white,
child: Row(
children: <Widget>[
if (!inFirstPage)
IconButton(
color: Colors.indigoAccent,
onPressed: () {
_controller.previous();
},
icon: Icon(Icons.arrow_back),
),
Spacer(),
if (!inLastPage)
IconButton(
color: Colors.indigoAccent,
onPressed: () {
_controller.next();
},
icon: Icon(Icons.arrow_forward),
),
],
),
),
Expanded(
child: Container(
color: Colors.white,
child: Swiper(
controller: _controller,
control: _control,
loop: false,
scrollDirection: Axis.horizontal,
itemCount: word_data.length,
onIndexChanged: (value) {
if (value == word_data.length - 1)
setState(() {
inLastPage = true;
});
else if (value == 0)
setState(() {
inFirstPage = true;
});
else {
setState(() {
inFirstPage = false;
inLastPage = false;
});
}
},
itemBuilder: (BuildContext context, int index) {
return Container(
child: Column(
children: <Widget>[
Expanded(
child: GestureDetector(
child: Container(
width: _width,
alignment: Alignment.center,
color: Colors.indigoAccent,
child: Text(word_data[index]),
),
),
),
],
),
);
},
),
),
),
],
),
),
);
}
}
Add two variables in your state as
class _ScrolllingOnClickButtonState
extends State<ScrollingLeftAndRightButtonHide> {
bool leftEnabled = false; //this is initial visibility of left button
bool rightEnabled = true; //this is initial visibility of right button
........
Then in your build function where you are displaying left and right button add if statement
#override
Widget build(BuildContext context) {
var sizeDevice = MediaQuery.of(context).size;
this._width = sizeDevice.width;
var recentIndexIncrease = 0;
var recentIndexDecrease = 0;
return MaterialApp(
debugShowCheckedModeBanner: false,
home: Scaffold(
body: Column(
.............
if(leftEnabled)
Padding(
padding: const EdgeInsets.only(left: 8.0),
child: ClipOval(
child: Material(
color: Colors.blue, // button color
child: InkWell(
splashColor: Colors.red, // inkwell color
child: SizedBox(
width: 56,
height: 56,
child: Icon(Icons.arrow_back)),
onTap: () {
var recentIndexDecreaseMinus =
recentIndexDecrease--;
_animateToIndex(recentIndexDecrease);
if (index == 0) { //Your logic to detect start of the list.
leftEnabled = false; //if it is the start make left invisible
}
if(list.size > 1)
rightEnabled = true; //whenever left button is pressed and your data has more than 1 element make right visible
setState(() {});
},
),
),
),
),
...............
Same code for the right button.
You cannot do it through your current structure of code. To achieve it you will have to move those arrow button Icons inside of the listView like this:
return ListView.builder(
scrollDirection: Axis.horizontal,
physics: PageScrollPhysics(),
itemCount: 5,
itemBuilder: (BuildContext context, int index) {
recentIndexIncrease = index;
recentIndexDecrease = index;
var wordDataReplace = word_data.drink[index].replaceAll(" ", "_").toLowerCase();
return Column(
children: <Widget>[
Row(
children: [
//Left arrow is the button indicating left arrow
if (index != 0) LeftArrow,
//Rightarrow is the button indicating right arrow
if (index != 4) RightArrow
],
),
Expanded(
child: GestureDetector(
child: Container(
color: Colors.purple,
padding: const EdgeInsets.all(10.0),
child: Image.asset("asset/drink_images/" +
wordDataReplace +
".png",
fit: BoxFit.contain,
),
),
)),
],
);
});

Animated container : RenderFlex overflowed by 154 pixels on the bottom

I have a problem when resizing an animated container while setting different content height.
It throws exception while resizing :
════════ Exception caught by rendering library ════════
The following assertion was thrown during layout:
A RenderFlex overflowed by 154 pixels on the bottom.
Here is a minimal example to reproduce the problem (much more complex in my real app but you get the point) :
bool expanded;
initState() {
super.initState();
expanded = false;
}
void _toggleMode() {
setState(() {
expanded = !expanded;
});
}
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text("Test")),
body: AnimatedContainer(
height: expanded ? 230 : 70,
duration: Duration(milliseconds: 800),
child: Column(
children: <Widget>[
Expanded(
child: PageView.builder(
itemBuilder: (context, position) {
return expanded
? Column(
children: <Widget>[
Container(height: 40, color: Colors.blue),
Container(height: 40, color: Colors.blue[400]),
Container(height: 40, color: Colors.blue[300]),
Container(height: 40, color: Colors.blue[200]),
Container(height: 40, color: Colors.blue[100]),
],
)
: Column(
children: <Widget>[
Container(height: 40, color: Colors.blue),
],
);
},
),
),
InkWell(onTap: _toggleMode, child: expanded ? Icon(Icons.keyboard_arrow_up) : Icon(Icons.keyboard_arrow_down))
],
),
),
);
}
In both mode (expanded or not), the content fits the container (no overflow), problem appears only during resizing.
Of course problem does not happen with a basic container without animation.
How can I deal with this ?
This happening because you check for expanded and then immediately return the containers before the container takes its final size
A workaround will be to change the bigger column with a ListView with NeverScrollableScrollPhysics()
Edit: you don't even need to check for expanded any more and you will get the correct animation
bool expanded;
initState() {
super.initState();
expanded = false;
}
void _toggleMode() {
setState(() {
expanded = !expanded;
});
}
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text("Test")),
body: AnimatedContainer(
height: expanded ? 230 : 70,
duration: Duration(milliseconds: 800),
child: Column(
children: <Widget>[
Expanded(
child: PageView.builder(
itemBuilder: (context, position) {
return ListView(
physics: NeverScrollableScrollPhysics(),
children: <Widget>[
Column(
children: <Widget>[
Container(height: 40, color: Colors.blue),
Container(height: 40, color: Colors.blue[400]),
Container(height: 40, color: Colors.blue[300]),
Container(height: 40, color: Colors.blue[200]),
Container(height: 40, color: Colors.blue[100]),
],
),
],
);
// : Column(
// children: <Widget>[
// Container(height: 40, color: Colors.blue),
// ],
// );
},
),
),
InkWell(onTap: _toggleMode, child: expanded ? Icon(Icons.keyboard_arrow_up) : Icon(Icons.keyboard_arrow_down))
],
),
),
);
}