Related
I'm trying to make a FAB expand to become the bottom sheet similar to the "Within screen" example in material.io
So that when I tap on
this FAB
animates to become
this modal bottom sheet
Here is the code for this example:
import 'package:flutter/material.dart';
class HomePage extends StatelessWidget {
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('Example'),
),
body: Center(child: Text('Hello, World!')),
floatingActionButton: FloatingActionButton(
child: Icon(Icons.add),
onPressed: () async {
showModalBottomSheet(
context: context,
backgroundColor: Colors.transparent,
barrierColor: Colors.transparent,
builder: (context) {
return AddEntryBottomSheet();
},
);
},
),
);
}
}
class AddEntryBottomSheet extends StatelessWidget {
#override
Widget build(BuildContext context) {
return Card(
margin: EdgeInsets.all(8.0),
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
Padding(
padding: const EdgeInsets.all(32.0),
child: Text('Hello, world!'),
),
Padding(
padding: const EdgeInsets.all(32.0),
child: Text('More content'),
),
],
),
);
}
}
I managed to get the result I wanted with the example below. Just use ExpandingFab as a FAB in a Scaffold
import 'package:flutter/material.dart';
class ExpandingFab extends StatefulWidget {
#override
_ExpandingFabState createState() => _ExpandingFabState();
}
class _ExpandingFabState extends State<ExpandingFab> {
bool _cardIsOpen = false;
double get cardWidth => MediaQuery.of(context).size.width - 32;
double cardHeight = 200;
Widget _renderFab() {
return InkWell(
onTap: () {
setState(() {
_cardIsOpen = true;
});
},
child: Icon(Icons.add, color: Colors.white),
);
}
Widget _renderUpsertEntryCard() {
return Container(
width: cardWidth,
height: cardHeight,
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: [
Text('Hello, World!'),
ElevatedButton(
onPressed: () {
setState(() {
_cardIsOpen = false;
});
},
child: Text('Close'),
),
],
),
);
}
#override
Widget build(BuildContext context) {
double w = _cardIsOpen ? cardWidth : 56;
double h = _cardIsOpen ? cardHeight : 56;
return AnimatedContainer(
curve: Curves.ease,
constraints: BoxConstraints(
minWidth: w, maxWidth: w,
minHeight: h, maxHeight: h,
),
duration: Duration(milliseconds: 300),
decoration: BoxDecoration(
color: _cardIsOpen ? Colors.blueGrey[100] : Colors.blue,
boxShadow: kElevationToShadow[1],
borderRadius: _cardIsOpen
? BorderRadius.all(Radius.circular(0.0))
: BorderRadius.all(Radius.circular(50)),
),
child: AnimatedSwitcher(
duration: Duration(milliseconds: 200),
transitionBuilder: (child, animation) => ScaleTransition(
scale: animation,
child: child,
),
child: !_cardIsOpen ? _renderFab() : _renderUpsertEntryCard(),
),
);
}
}
I have 2 pages (Page1 and Page2) To navigate between them I have a custom PrimaryMenu widget that is the same for both pages. The PrimaryMenu is contained in BodyPage1 and BodyPage2 respectively. I also have a custom Header widget. Both pages have animation as does the header.
What I am look to do it press one of the InkWell widgets then the animation is reversed on the current page, then the new page is called. I know how to call the new page, I have a rough understanding on how to use GlobalKey but im starting to think this cant be done with GlobalKey. Below I will show the individual widgets i have: dartpad link just in case - https://dartpad.dev/88b8536ea7888b5621d7d80acdcd2887
class Header extends StatefulWidget {
#override
_HeaderState createState() => _HeaderState();
}
class _HeaderState extends State<Header> with SingleTickerProviderStateMixin {
AnimationController transitionAnimation;
#override
void initState() {
super.initState();
transitionAnimation = AnimationController(
duration: const Duration(seconds: 3),
vsync: this,
);
transitionAnimation.forward();
}
#override
Widget build(BuildContext context) {
return Container(
color: Color(0x88dddddd),
height: MediaQuery.of(context).size.height,
width: MediaQuery.of(context).size.width,
child: Row(
crossAxisAlignment: CrossAxisAlignment.center,
children: [
SizedBox(
width: 8,
),
AnimatedBuilder(
animation: transitionAnimation,
builder: (context, child) {
return SlideTransition(
position: Tween<Offset>(
begin: const Offset(-2, 0), end: const Offset(0, 0))
.animate(CurvedAnimation(
curve: const Interval(0, 0.3,
curve: Curves.easeInOutBack),
parent: transitionAnimation)),
child: child,
);
},
child: Container(
height: 100,
width: MediaQuery.of(context).size.width * 0.135,
color: Colors.pink,
),
),
Expanded(
child: Container(),
),
Container(
width: 100,
height: 50,
color: Colors.purple,
),
SizedBox(
width: 8,
),
Container(
height: 50,
width: 50,
color: Colors.amber,
),
SizedBox(
width: 8,
)
],
));
}
}
class Page1 extends StatefulWidget {
#override
_Page1State createState() => _Page1State();
}
class _Page1State extends State<Page1> {
#override
Widget build(BuildContext context) {
return Scaffold(
extendBodyBehindAppBar: true,
appBar: PreferredSize(
preferredSize: Size(MediaQuery.of(context).size.width, 120),
child: Header(),
),
body: BodyPage1(),
);
}
}
class PrimaryMenu extends StatefulWidget {
#override
_PrimaryMenuState createState() => _PrimaryMenuState();
}
class _PrimaryMenuState extends State<PrimaryMenu> {
#override
Widget build(BuildContext context) {
return Container(
height: MediaQuery.of(context).size.height,
width: MediaQuery.of(context).size.width * 0.15,
color: Colors.blue,
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Container(
height: 40,
width: MediaQuery.of(context).size.width * 0.135,
color: Colors.grey,
child: InkWell(
onTap: () {
print('1');
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => Page1(),
));
},
)),
SizedBox(height: 16),
Container(
height: 40,
width: MediaQuery.of(context).size.width * 0.135,
color: Colors.black,
child: InkWell(
onTap: () {
print('2');
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => Page2(),
));
},
)),
],
));
}
}
class BodyPage1 extends StatefulWidget {
#override
_BodyPage1State createState() => _BodyPage1State();
}
class _BodyPage1State extends State<BodyPage1>
with SingleTickerProviderStateMixin {
AnimationController transitionAnimation;
#override
void initState() {
super.initState();
transitionAnimation = AnimationController(
duration: const Duration(seconds: 3),
vsync: this,
);
transitionAnimation.forward();
}
#override
Widget build(BuildContext context) {
return Row(
children: [
AnimatedBuilder(
animation: transitionAnimation,
builder: (context, child) {
return SlideTransition(
position: Tween<Offset>(
begin: const Offset(-2, 0), end: const Offset(0, 0))
.animate(CurvedAnimation(
curve:
const Interval(0, 0.3, curve: Curves.easeInOutBack),
parent: transitionAnimation)),
child: child,
);
},
child: PrimaryMenu()),
AnimatedBuilder(
animation: transitionAnimation,
builder: (context, child) {
return SlideTransition(
position: Tween<Offset>(
begin: const Offset(-2, 0), end: const Offset(0, 0))
.animate(CurvedAnimation(
curve: const Interval(0.3, 1, curve: Curves.easeIn),
parent: transitionAnimation)),
child: child);
},
child: Padding(
padding: EdgeInsets.only(top: 140, left: 20, right: 20, bottom: 20),
child: Container(
height: MediaQuery.of(context).size.height - 140,
width: (MediaQuery.of(context).size.width * .85) - 40,
color: Colors.grey,
),
),
)
],
);
}
}
class Page2 extends StatefulWidget {
#override
_Page2State createState() => _Page2State();
}
class _Page2State extends State<Page2> {
#override
Widget build(BuildContext context) {
return Scaffold(
extendBodyBehindAppBar: true,
appBar: PreferredSize(
preferredSize: Size(MediaQuery.of(context).size.width, 120),
child: Header(),
),
body: BodyPage2(),
);
}
}
class BodyPage2 extends StatefulWidget {
#override
_BodyPage2State createState() => _BodyPage2State();
}
class _BodyPage2State extends State<BodyPage2>
with SingleTickerProviderStateMixin {
AnimationController transitionAnimation;
#override
void initState() {
super.initState();
transitionAnimation = AnimationController(
duration: const Duration(seconds: 3),
vsync: this,
);
transitionAnimation.forward();
}
#override
Widget build(BuildContext context) {
return Row(
children: [
AnimatedBuilder(
animation: transitionAnimation,
builder: (context, child) {
return SlideTransition(
position: Tween<Offset>(
begin: const Offset(-2, 0), end: const Offset(0, 0))
.animate(CurvedAnimation(
curve:
const Interval(0, 0.3, curve: Curves.easeInOutBack),
parent: transitionAnimation)),
child: child,
);
},
child: PrimaryMenu()),
AnimatedBuilder(
animation: transitionAnimation,
builder: (context, child) {
return SlideTransition(
position: Tween<Offset>(
begin: const Offset(-2, 0), end: const Offset(0, 0))
.animate(CurvedAnimation(
curve: const Interval(0.3, 1, curve: Curves.easeIn),
parent: transitionAnimation)),
child: child);
},
child: Padding(
padding: EdgeInsets.only(top: 140, left: 20, right: 20, bottom: 20),
child: Container(
height: MediaQuery.of(context).size.height - 140,
width: (MediaQuery.of(context).size.width * .85) - 40,
color: Colors.black,
),
),
)
],
);
}
}
Also I have set up a dartpad showing what i have managed to do with GlobalKey, I can only get it to work if i use a FloatingActionButton on the Scaffold - https://dartpad.dev/5979f44ecaa9cf2e22b4ce0cc9c23aa8
Sorry that there is a lot of code, I have tried to condense it as much as possible
All you need to do is to pass the AnimationController from respective BodyPage to your PrimaryMenu which is responsible to call Page1 and Page2. Once the AnimationController is available to PrimaryMenu it will reverse the animation first and .then call the respective page. Please see the working code below :
import 'package:flutter/material.dart';
void main() {
runApp(MyApp());
}
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
theme: ThemeData(visualDensity: VisualDensity.adaptivePlatformDensity),
debugShowCheckedModeBanner: false,
home: Page1(),
);
}
}
class Header extends StatefulWidget {
#override
_HeaderState createState() => _HeaderState();
}
class _HeaderState extends State<Header> with SingleTickerProviderStateMixin {
AnimationController transitionAnimation;
#override
void initState() {
super.initState();
transitionAnimation = AnimationController(
duration: const Duration(seconds: 3),
vsync: this,
);
transitionAnimation.forward();
}
#override
Widget build(BuildContext context) {
return Container(
color: const Color(0x88dddddd),
height: MediaQuery.of(context).size.height,
width: MediaQuery.of(context).size.width,
child: Row(
crossAxisAlignment: CrossAxisAlignment.center,
children: [
const SizedBox(
width: 8,
),
AnimatedBuilder(
animation: transitionAnimation,
builder: (context, child) {
return SlideTransition(
position: Tween<Offset>(
begin: const Offset(-2, 0), end: const Offset(0, 0))
.animate(CurvedAnimation(
curve: const Interval(0, 0.3,
curve: Curves.easeInOutBack),
parent: transitionAnimation)),
child: child,
);
},
child: Container(
height: 100,
width: MediaQuery.of(context).size.width * 0.135,
color: Colors.pink,
),
),
Expanded(
child: Container(),
),
Container(
width: 100,
height: 50,
color: Colors.purple,
),
const SizedBox(
width: 8,
),
Container(
height: 50,
width: 50,
color: Colors.amber,
),
const SizedBox(
width: 8,
)
],
));
}
}
class Page1 extends StatefulWidget {
#override
_Page1State createState() => _Page1State();
}
class _Page1State extends State<Page1> {
#override
Widget build(BuildContext context) {
return Scaffold(
extendBodyBehindAppBar: true,
appBar: PreferredSize(
preferredSize: Size(MediaQuery.of(context).size.width, 120),
child: Header(),
),
body: BodyPage1(),
);
}
}
class PrimaryMenu extends StatefulWidget {
final AnimationController controller;
const PrimaryMenu({Key key, this.controller}) : super(key: key);
#override
_PrimaryMenuState createState() => _PrimaryMenuState();
}
class _PrimaryMenuState extends State<PrimaryMenu> {
#override
Widget build(BuildContext context) {
return Container(
height: MediaQuery.of(context).size.height,
width: MediaQuery.of(context).size.width * 0.15,
color: Colors.blue,
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Container(
height: 40,
width: MediaQuery.of(context).size.width * 0.135,
color: Colors.grey,
child: InkWell(
onTap: () async {
print('1');
await widget.controller
.reverse()
.then((value) => Navigator.push(
context,
MaterialPageRoute(
builder: (context) => Page1(),
)));
},
)),
const SizedBox(height: 16),
Container(
height: 40,
width: MediaQuery.of(context).size.width * 0.135,
color: Colors.black,
child: InkWell(
onTap: () async {
print('2');
await widget.controller
.reverse()
.then((value) => Navigator.push(
context,
MaterialPageRoute(
builder: (context) => Page2(),
)));
},
)),
],
));
}
}
class BodyPage1 extends StatefulWidget {
#override
_BodyPage1State createState() => _BodyPage1State();
}
class _BodyPage1State extends State<BodyPage1>
with SingleTickerProviderStateMixin {
AnimationController transitionAnimation;
#override
void initState() {
super.initState();
transitionAnimation = AnimationController(
duration: const Duration(seconds: 3),
vsync: this,
);
transitionAnimation.forward();
}
#override
Widget build(BuildContext context) {
return Row(
children: [
AnimatedBuilder(
animation: transitionAnimation,
builder: (context, child) {
return SlideTransition(
position: Tween<Offset>(
begin: const Offset(-2, 0), end: const Offset(0, 0))
.animate(CurvedAnimation(
curve:
const Interval(0, 0.3, curve: Curves.easeInOutBack),
parent: transitionAnimation)),
child: child,
);
},
child: PrimaryMenu(
controller: transitionAnimation,
)),
AnimatedBuilder(
animation: transitionAnimation,
builder: (context, child) {
return SlideTransition(
position: Tween<Offset>(
begin: const Offset(-2, 0), end: const Offset(0, 0))
.animate(CurvedAnimation(
curve: const Interval(0.3, 1, curve: Curves.easeIn),
parent: transitionAnimation)),
child: child);
},
child: Padding(
padding: const EdgeInsets.only(
top: 140, left: 20, right: 20, bottom: 20),
child: Container(
height: MediaQuery.of(context).size.height - 140,
width: (MediaQuery.of(context).size.width * .85) - 40,
color: Colors.grey,
),
),
)
],
);
}
}
class Page2 extends StatefulWidget {
#override
_Page2State createState() => _Page2State();
}
class _Page2State extends State<Page2> {
#override
Widget build(BuildContext context) {
return Scaffold(
extendBodyBehindAppBar: true,
appBar: PreferredSize(
preferredSize: Size(MediaQuery.of(context).size.width, 120),
child: Header(),
),
body: BodyPage2(),
);
}
}
class BodyPage2 extends StatefulWidget {
#override
_BodyPage2State createState() => _BodyPage2State();
}
class _BodyPage2State extends State<BodyPage2>
with SingleTickerProviderStateMixin {
AnimationController transitionAnimation;
#override
void initState() {
super.initState();
transitionAnimation = AnimationController(
duration: const Duration(seconds: 3),
vsync: this,
);
transitionAnimation.forward();
}
#override
Widget build(BuildContext context) {
return Row(
children: [
AnimatedBuilder(
animation: transitionAnimation,
builder: (context, child) {
return SlideTransition(
position: Tween<Offset>(
begin: const Offset(-2, 0), end: const Offset(0, 0))
.animate(CurvedAnimation(
curve:
const Interval(0, 0.3, curve: Curves.easeInOutBack),
parent: transitionAnimation)),
child: child,
);
},
child: PrimaryMenu(
controller: transitionAnimation,
)),
AnimatedBuilder(
animation: transitionAnimation,
builder: (context, child) {
return SlideTransition(
position: Tween<Offset>(
begin: const Offset(-2, 0), end: const Offset(0, 0))
.animate(CurvedAnimation(
curve: const Interval(0.3, 1, curve: Curves.easeIn),
parent: transitionAnimation)),
child: child);
},
child: Padding(
padding: const EdgeInsets.only(
top: 140, left: 20, right: 20, bottom: 20),
child: Container(
height: MediaQuery.of(context).size.height - 140,
width: (MediaQuery.of(context).size.width * .85) - 40,
color: Colors.black,
),
),
)
],
);
}
}
in flutter after showing dialog i want to change height of that, for example make full screen dialog height, my implemented code don't work correctly and after show again i have full screen. how can i change height of the dialog real time?
void main() => runApp(MaterialApp(home: DialogCustomHeight(),));
class DialogCustomHeight extends StatefulWidget {
#override
State<StatefulWidget> createState() => DialogCustomHeightState();
}
class DialogCustomHeightState extends State<DialogCustomHeight> {
bool fullScreenDialog = false;
#override
Widget build(BuildContext context) {
return Scaffold(
body: Center(
child: RaisedButton(
color: Colors.white,
child: Text(
'show dialog'
),
onPressed: () => _showDialog(),
),
),
);
}
_showDialog() {
bool _fromTop = true;
return showGeneralDialog(
barrierLabel: "Label",
barrierDismissible: true,
barrierColor: Colors.black.withOpacity(0.5),
transitionDuration: Duration(milliseconds: 200),
context: context,
pageBuilder: (context, anim1, anim2) {
return Align(
alignment: _fromTop ? Alignment.topCenter : Alignment.bottomCenter,
child: Container(
height: fullScreenDialog ? MediaQuery.of(context).size.height : MediaQuery.of(context).size.height * 0.500,
child: SizedBox.expand(
child: ClipRRect(
borderRadius: BorderRadius.only(
bottomRight: Radius.circular(10.0),
bottomLeft: Radius.circular(10.0),
),
child: Container(
color:Colors.green,
child: Center(
child: RaisedButton(
color: Colors.white,
child: Text(
'change height'
),
onPressed: () {
setState(() {
fullScreenDialog = !fullScreenDialog;
});
},
),
),
))),
),
);
},
transitionBuilder: (context, anim1, anim2, child) {
return FadeTransition(
opacity: new CurvedAnimation(parent: anim1, curve: Curves.easeOut),
child: SlideTransition(
position: Tween(begin: Offset(0, _fromTop ? -0.1 : 0.1), end: Offset(0, 0)).animate(anim1),
child: child,
),
);
},
);
}
}
Output (From top):
Output (From bottom)
void main() => runApp(MaterialApp(home: DialogCustomHeight()));
class DialogCustomHeight extends StatefulWidget {
#override
State<StatefulWidget> createState() => DialogCustomHeightState();
}
class DialogCustomHeightState extends State<DialogCustomHeight> {
bool fullScreenDialog = false;
#override
Widget build(BuildContext context) {
return Scaffold(
body: Center(
child: RaisedButton(
color: Colors.white,
child: Text('show dialog'),
onPressed: () => _showDialog(),
),
),
);
}
_showDialog() {
bool _fromTop = false;
return showGeneralDialog(
barrierLabel: "Label",
barrierDismissible: true,
barrierColor: Colors.black.withOpacity(0.5),
transitionDuration: Duration(milliseconds: 200),
context: context,
pageBuilder: (context, anim1, anim2) {
return MyDialog(fromTop: _fromTop, fullScreen: fullScreenDialog);
},
transitionBuilder: (context, anim1, anim2, child) {
return FadeTransition(
opacity: new CurvedAnimation(parent: anim1, curve: Curves.easeOut),
child: SlideTransition(
position: Tween(begin: Offset(0, _fromTop ? -0.1 : 0.1), end: Offset(0, 0)).animate(anim1),
child: child,
),
);
},
);
}
}
class MyDialog extends StatefulWidget {
final bool fromTop;
final bool fullScreen;
const MyDialog({Key key, this.fromTop, this.fullScreen}) : super(key: key);
#override
_MyDialogState createState() => _MyDialogState();
}
class _MyDialogState extends State<MyDialog> {
bool _fromTop, _fullScreen;
#override
void initState() {
super.initState();
_fromTop = widget.fromTop;
_fullScreen = widget.fullScreen;
}
#override
Widget build(BuildContext context) {
return Align(
alignment: _fromTop ? Alignment.topCenter : Alignment.bottomCenter,
child: Container(
height: _fullScreen ? MediaQuery.of(context).size.height : MediaQuery.of(context).size.height * 0.500,
child: SizedBox.expand(
child: ClipRRect(
borderRadius: BorderRadius.only(
bottomRight: Radius.circular(10.0),
bottomLeft: Radius.circular(10.0),
),
child: Container(
color: Colors.green,
child: Center(
child: RaisedButton(
color: Colors.white,
child: Text('change height'),
onPressed: () {
setState(() {
_fullScreen = !_fullScreen;
});
},
),
),
),
),
),
),
);
}
}
By this simple code I can show dialog on bottom of screen like with this screenshot:
But I have three simple issue:
set margin on bottom of dialog such as 20.0 on showing dialog
using controller.reverse() on dismiss dialog
dismiss dialog on click on outside of dialog
Full source code:
import 'package:flutter/material.dart';
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
// This widget is the root of your application.
#override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Demo',
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: MyHomePage(title: 'Flutter Demo Home Page'),
);
}
}
class MyHomePage extends StatefulWidget {
MyHomePage({Key key, this.title}) : super(key: key);
final String title;
#override
_MyHomePageState createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
void showPopup() {
showDialog(
context: context,
builder: (_) => PopUp(),
);
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text(widget.title),
),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
Text(
'You have pushed the button this many times:',
),
],
),
),
floatingActionButton: FloatingActionButton(
onPressed: showPopup,
tooltip: 'Increment',
child: Icon(Icons.add),
), // This trailing comma makes auto-formatting nicer for build methods.
);
}
}
class PopUp extends StatefulWidget {
#override
State<StatefulWidget> createState() => PopUpState();
}
class PopUpState extends State<PopUp> with SingleTickerProviderStateMixin {
AnimationController controller;
Animation<double> opacityAnimation;
Tween<double> opacityTween = Tween<double>(begin: 0.0, end: 1.0);
Tween<double> marginTopTween = Tween<double>(begin: 300, end: 280);
Animation<double> marginTopAnimation;
#override
void initState() {
super.initState();
controller = AnimationController(duration: const Duration(milliseconds: 300), vsync: this);
marginTopAnimation = marginTopTween.animate(controller)
..addListener(() {
setState(() {});
});
controller.forward();
}
#override
Widget build(BuildContext context) {
return FadeTransition(
opacity: opacityTween.animate(controller),
child: Material(
color: Colors.transparent,
child: Container(
margin: EdgeInsets.only(
top: marginTopAnimation.value,
left:20.0,
right:20.0,
),
color: Colors.red,
child: Text("Container"),
),
),
);
}
#override
void dispose() {
controller.dispose();
super.dispose();
}
}
Screenshot:
Code:
floatingActionButton: FloatingActionButton(
onPressed: () {
showGeneralDialog(
barrierLabel: "Label",
barrierDismissible: true,
barrierColor: Colors.black.withOpacity(0.5),
transitionDuration: Duration(milliseconds: 700),
context: context,
pageBuilder: (context, anim1, anim2) {
return Align(
alignment: Alignment.bottomCenter,
child: Container(
height: 300,
child: SizedBox.expand(child: FlutterLogo()),
margin: EdgeInsets.only(bottom: 50, left: 12, right: 12),
decoration: BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.circular(40),
),
),
);
},
transitionBuilder: (context, anim1, anim2, child) {
return SlideTransition(
position: Tween(begin: Offset(0, 1), end: Offset(0, 0)).animate(anim1),
child: child,
);
},
);
},
)
import 'package:flutter/material.dart';
void main() {
runApp(const MaterialApp(home: CityPage()));
}
class CityPage extends StatelessWidget {
const CityPage({Key? key}) : super(key: key);
#override
Widget build(BuildContext context) {
return Scaffold(
body: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Center(
child: TextButton(
child: const Text('Press me'),
onPressed: () => BottomDialog().showBottomDialog(context),
),
),
],
),
);
}
}
class BottomDialog {
void showBottomDialog(BuildContext context) {
showGeneralDialog(
barrierLabel: "showGeneralDialog",
barrierDismissible: true,
barrierColor: Colors.black.withOpacity(0.6),
transitionDuration: const Duration(milliseconds: 400),
context: context,
pageBuilder: (context, _, __) {
return Align(
alignment: Alignment.bottomCenter,
child: _buildDialogContent(),
);
},
transitionBuilder: (_, animation1, __, child) {
return SlideTransition(
position: Tween(
begin: const Offset(0, 1),
end: const Offset(0, 0),
).animate(animation1),
child: child,
);
},
);
}
Widget _buildDialogContent() {
return IntrinsicHeight(
child: Container(
width: double.maxFinite,
clipBehavior: Clip.antiAlias,
padding: const EdgeInsets.all(16),
decoration: const BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.only(
topLeft: Radius.circular(16),
topRight: Radius.circular(16),
),
),
child: Material(
child: Column(
children: [
const SizedBox(height: 16),
_buildImage(),
const SizedBox(height: 8),
_buildContinueText(),
const SizedBox(height: 16),
_buildEmapleText(),
const SizedBox(height: 16),
_buildTextField(),
const SizedBox(height: 16),
_buildContinueButton(),
],
),
),
),
);
}
Widget _buildImage() {
const image =
'https://user-images.githubusercontent.com/47568606/134579553-da578a80-b842-4ab9-ab0b-41f945fbc2a7.png';
return SizedBox(
height: 88,
child: Image.network(image, fit: BoxFit.cover),
);
}
Widget _buildContinueText() {
return const Text(
'Continue with account',
style: TextStyle(
fontSize: 22,
fontWeight: FontWeight.w500,
),
);
}
Widget _buildEmapleText() {
return const Text(
'example.com',
style: TextStyle(
fontSize: 16,
fontWeight: FontWeight.w500,
),
);
}
Widget _buildTextField() {
const iconSize = 40.0;
return Container(
height: 60,
padding: const EdgeInsets.symmetric(horizontal: 16),
decoration: BoxDecoration(
color: Colors.white,
border: Border.all(width: 1, color: Colors.grey.withOpacity(0.4)),
borderRadius: const BorderRadius.all(Radius.circular(8)),
),
child: Row(
children: [
Container(
width: iconSize,
height: iconSize,
decoration: BoxDecoration(
shape: BoxShape.circle,
color: Colors.grey[200],
),
child: const Center(
child: Text('Е'),
),
),
const SizedBox(height: 16),
Column(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
crossAxisAlignment: CrossAxisAlignment.start,
children: const [
Text(
'elisa.g.beckett#gmail.com',
style: TextStyle(
fontWeight: FontWeight.w600,
),
),
Text('**********'),
],
)
],
),
);
}
Widget _buildContinueButton() {
return Container(
height: 40,
width: double.maxFinite,
decoration: const BoxDecoration(
color: Color(0xFF3375e0),
borderRadius: BorderRadius.all(Radius.circular(8)),
),
child: RawMaterialButton(
onPressed: () {
Navigator.of(context, rootNavigator: true).pop();
},
child: const Center(
child: Text(
'Continue',
style: TextStyle(
color: Colors.white,
fontWeight: FontWeight.w500,
),
),
),
),
);
}
}
From top to bottom you can use
bool _fromTop = true;
#override
Widget build(BuildContext context) {
return Scaffold(
floatingActionButton: FloatingActionButton(
onPressed: () {
showGeneralDialog(
barrierLabel: "Label",
barrierDismissible: true,
barrierColor: Colors.black.withOpacity(0.5),
transitionDuration: Duration(milliseconds: 700),
context: context,
pageBuilder: (context, anim1, anim2) {
return Align(
alignment: _fromTop ? Alignment.topCenter : Alignment.bottomCenter,
child: Container(
height: 300,
child: SizedBox.expand(child: FlutterLogo()),
margin: EdgeInsets.only(top: 50, left: 12, right: 12, bottom: 50),
decoration: BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.circular(40),
),
),
);
},
transitionBuilder: (context, anim1, anim2, child) {
return SlideTransition(
position: Tween(begin: Offset(0, _fromTop ? -1 : 1), end: Offset(0, 0)).animate(anim1),
child: child,
);
},
);
},
),
);
}
Output:
Not sure if I got your question clearly, if this is what you are looking for, replace your PopUp class with mine.
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
// This widget is the root of your application.
#override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Demo',
theme: ThemeData(primarySwatch: Colors.blue),
home: MyHomePage(title: 'Flutter Demo Home Page'),
);
}
}
class MyHomePage extends StatefulWidget {
MyHomePage({Key key, this.title}) : super(key: key);
final String title;
#override
_MyHomePageState createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
void showPopup() {
showDialog(
context: context,
builder: (_) => PopUp(),
);
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text(widget.title),
),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
Text(
'You have pushed the button this many times:',
),
],
),
),
floatingActionButton: FloatingActionButton(
onPressed: showPopup,
tooltip: 'Increment',
child: Icon(Icons.add),
), // This trailing comma makes auto-formatting nicer for build methods.
);
}
}
class PopUp extends StatefulWidget {
#override
State<StatefulWidget> createState() => PopUpState();
}
class PopUpState extends State<PopUp> with TickerProviderStateMixin {
AnimationController controller;
double _bottom = 0, _fromTop = 300, _screenHeight, _containerHeight = 300;
#override
void initState() {
super.initState();
controller = AnimationController(duration: const Duration(milliseconds: 300), vsync: this)
..addListener(() {
Timer.periodic(Duration(milliseconds: 15), (timer) {
if (_bottom < _screenHeight - _fromTop - _containerHeight) {
_bottom += 1;
setState(() {});
}
});
});
controller.forward();
}
#override
Widget build(BuildContext context) {
_screenHeight = MediaQuery.of(context).size.height;
return SizedBox(
width: double.infinity,
height: double.infinity,
child: Stack(
children: <Widget>[
Positioned(
bottom: _bottom,
left: 0,
right: 0,
child: Container(height: _containerHeight, color: Colors.green),
),
],
),
);
}
#override
void dispose() {
controller.dispose();
super.dispose();
}
}
I think showModalBottomSheet function does this out of the box.
this below code is simple dialog implementation to show and hide Container after click on button to show, that work fine, after click on dialog to hide container work too, now i want to show again container with Navigator.of(context).overlay, it doesn't work
full source code:
import 'package:flutter/material.dart';
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(home: Home());
}
}
class Home extends StatelessWidget {
#override
Widget build(BuildContext context) {
return Scaffold(
body: Directionality(
textDirection: TextDirection.rtl,
child: Center(
child: RaisedButton.icon(
icon: Icon(Icons.notifications_active),
label: Text('Notify!'),
onPressed: () {
Navigator.of(context).overlay.insert(OverlayEntry(builder: (BuildContext context) {
return SlidingLayer();
}));
},
),
),
),
);
}
}
class SlidingLayer extends StatefulWidget {
#override
State<StatefulWidget> createState() => SlidingLayerState();
}
class SlidingLayerState extends State<SlidingLayer> with SingleTickerProviderStateMixin {
AnimationController controller;
Animation<Offset> position;
#override
void initState() {
super.initState();
controller = AnimationController(vsync: this, duration: Duration(milliseconds: 750));
position = Tween<Offset>(begin: Offset(0.0, -14.0), end: Offset.zero).animate(CurvedAnimation(parent: controller, curve: Curves.ease));
controller.forward();
}
#override
Widget build(BuildContext context) {
return Material(
color: Colors.transparent,
child: Align(
alignment: Alignment.topCenter,
child: Padding(
padding: EdgeInsets.only(top: 90.0),
child: SlideTransition(
position: position,
child: Container(
margin: EdgeInsets.all(10.0),
height: 150.0,
width: double.infinity,
decoration: BoxDecoration(color: Colors.white, borderRadius: BorderRadius.circular(10.0), boxShadow: [
BoxShadow(
color: Colors.black26,
blurRadius: 0.9,
spreadRadius: 0.5,
offset: Offset(0.0, 0.0),
),
]),
child: InkWell(
onTap: (){
controller.reverse();
},
child: Center(
child: Text(
'ssss',
style: TextStyle(
fontSize: 26.0,
),
),
),
),
)),
),
),
);
}
}