As a common appbar widget how to change appbar color when page is scrolled Flutter - flutter

Good morning friends, I'm trying to make the appbar transparent or white in scrollable
parts.
For me, this solution An efficient way in Flutter to change appbar color when scrolled works, but as the person said, I don't want to use setState continuously and do it in every separate component, so I'm trying to do what is mentioned in the comment. For this reason, I created a common appbar widget so that I can use it in other components. I made the CustomAppBar widget statefull, but I don't know where to add the scrollController. Therefore, I see errors. If anyone has time, can you help?
The code below is the widget where I call CustomAppBar.
import ...
const ExtractionBody({Key? key, required this.goal}) : super(key: key);
final Objective goal;
#override
ExtractionBodyState createState() => ExtractionBodyState();
}
class ExtractionBodyState extends ConsumerState<ExtractionBody> {
#override
Widget build(BuildContext context) {
return Scaffold(
extendBodyBehindAppBar: true,
appBar: PreferredSize(
preferredSize: const Size.fromHeight(100),
child: CustomAppBar(
icon: IconButton(
icon: const Icon(PhosphorIcons.xBold),
onPressed: () => Navigator.of(context).pushNamedAndRemoveUntil(
HomePage.routeName,
(route) => false,
),
),
),
),
body: ExtractionRequestContent(
goal: widget.goal, scrollController: _scrollController),
);
}
}
Finally, this is my CustomAppBar code. Thank you very much in advance. and have a good weekend everyone
class CustomAppBar extends StatefulHookConsumerWidget {
static String routeName = "/extraction_body";
const CustomAppBar({Key? key, this.icon})
: preferredSize = const Size.fromWidth(50),
super(key: key);
final Widget? icon;
#override
final Size preferredSize; // default is 56.0
#override
CustomAppBarState createState() => CustomAppBarState();
}
class CustomAppBarState extends ConsumerState<CustomAppBar> {
bool isAppbarCollapsing = false;
final ScrollController _scrollController = ScrollController();
#override
void initState() {
super.initState();
_initializeController();
}
#override
void dispose() {
_scrollController.dispose();
super.dispose();
}
void _initializeController() {
_scrollController.addListener(() {
if (_scrollController.offset == 0.0 &&
!_scrollController.position.outOfRange) {
//Fully expanded situation
if (!mounted) return;
setState(() => isAppbarCollapsing = false);
}
if (_scrollController.offset >= 9.0 &&
!_scrollController.position.outOfRange) {
//Collapsing situation
if (!mounted) return;
setState(() => isAppbarCollapsing = true);
}
});
}
#override
Widget build(BuildContext context) {
return AppBar(
elevation: 0,
backgroundColor:
isAppbarCollapsing ? AppColors.monochromeWhite : Colors.transparent,
title: Text(context.l10n.buttonCancel),
titleSpacing: 4,
leading: widget.icon,
);
}
}
Thanks!

Instead of define ScrollController in CustomAppBar, pass it in constructor like this:
class CustomAppBar extends StatefulHookConsumerWidget {
static String routeName = "/extraction_body";
const CustomAppBar({Key? key, this.icon, required this.scrollController})
: preferredSize = const Size.fromWidth(50),
super(key: key);
final Widget? icon;
final ScrollController scrollController;
#override
final Size preferredSize; // default is 56.0
#override
CustomAppBarState createState() => CustomAppBarState();
}
and use it like this:
class MyApp extends StatelessWidget {
MyApp({Key? key}) : super(key: key);
final ScrollController scrollController = ScrollController(); //<---- define scrollController here
#override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Demo',
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: Scaffold(
appBar: PreferredSize(
preferredSize: const Size.fromHeight(100),
child: CustomAppbar(scrollController: scrollController)),
body: ListView.builder(
controller: scrollController,
itemCount: 10,
itemBuilder: (BuildContext context, int index) {
return Container(
height: 100,
width: 100,
color: Colors.red,
margin: EdgeInsets.all(12),
);
},
),
));
}
}

Related

Flutter - PageView - Don't change page if the user still touches the screen

How to update the PageView to trigger onPageChange only on specific conditions?
Here, I don't want to change the current page if the user is still touching the screen. Apart from that, everything should remain the same (ballistic scroll simulation, page limits)
It seems it has to deal with the ScrollPhysics object attached to PageView, but I don't know how to correctly extends it.
Let me know if you need some code, but the question is very general and can refer to any PageView, so you should not need any context.
Minimum Reproductible Example
Here is the translation in dart of the text above. Feel free to update this code to make it achieve the objective.
// main.dart
import 'package:flutter/material.dart';
void main() => runApp(const MyApp());
class MyApp extends StatelessWidget {
const MyApp({Key? key}) : super(key: key);
static const String _title = 'Flutter Code Sample';
#override
Widget build(BuildContext context) {
return const MaterialApp(title: _title, home: MyPageView());
}
}
class MyPageView extends StatefulWidget {
const MyPageView({Key? key}) : super(key: key);
#override
State<MyPageView> createState() => _MyPageViewState();
}
class _MyPageViewState extends State<MyPageView> {
#override
Widget build(BuildContext context) {
final PageController controller = PageController();
return Scaffold(
body: SafeArea(
child: PageView.builder(
onPageChanged: (int index) {
// TODO: Don't trigger this function if you still touch the screen
print('onPageChanged index $index, ${controller.page}');
},
allowImplicitScrolling: false,
controller: controller,
itemBuilder: (BuildContext context, int index) {
print('Build Sliver');
return Center(
child: Text('Page $index'),
);
},
)));
}
}
Example of a (bad) solution
import 'package:flutter/material.dart';
void main() => runApp(const MyApp());
class MyApp extends StatelessWidget {
const MyApp({Key? key}) : super(key: key);
static const String _title = 'Flutter Code Sample';
#override
Widget build(BuildContext context) {
return const MaterialApp(title: _title, home: MyPageView());
}
}
class MyPageView extends StatefulWidget {
const MyPageView({Key? key}) : super(key: key);
#override
State<MyPageView> createState() => _MyPageViewState();
}
class _MyPageViewState extends State<MyPageView> {
#override
Widget build(BuildContext context) {
final PageController controller = PageController();
return Scaffold(
body: SafeArea(
child: Listener(
onPointerUp: (PointerUpEvent event) {
if (controller.page == null) {
return;
}
if (controller.page! > 0.5) {
//TODO: update the time so it fits the end of the animation
Future.delayed(const Duration(milliseconds: 700), () {
print('Do your custom action onPageChange action here');
});
}
},
child: PageView.builder(
controller: controller,
itemBuilder: (BuildContext context, int index) {
print('Build Sliver');
return Center(
child: Text('Page $index'),
);
},
),
),
));
}
}
This solution triggers an action on the next page, 700ms after the user stops touching the screen.
It does work, but it is a lousy work.
How to account for different screen sizes? 700ms is the maximum amount of time to animate between 2 pages on an iPhone SE.
How to adjust this arbitrary number (700), so it varies according to controller.page (the closer to the next page, the smaller you have to wait).
It doesn't use onHorizontalDragEnd or a similar drag detector, which can result in unwanted behaviour.
You should disable the scrolling entirely on PageView with physics: NeverScrollableScrollPhysics() and detect the scroll left and right on your own with GestureDetector. The GestureDetector.onHorizontalDragEnd will tell which direction the user dragged, to the left or to the right, checking the parameter's DragEndDetails property primaryVelocity. If the value is negative the user dragged to the right and is positive if the user dragged to the left.
To change the page manually just use the PageController methods nextPage and previousPage.
Take a look at the screenshot below and the live demo on DartPad.
import 'package:flutter/gestures.dart';
import 'package:flutter/material.dart';
void main() {
runApp(const MyApp());
}
class MyApp extends StatelessWidget {
const MyApp({Key? key}) : super(key: key);
#override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Demo',
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: const MyHomePage(),
debugShowCheckedModeBanner: false,
scrollBehavior: MyCustomScrollBehavior(),
);
}
}
class MyHomePage extends StatefulWidget {
const MyHomePage({Key? key}) : super(key: key);
#override
State<MyHomePage> createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
late PageController _pageController;
#override
void initState() {
super.initState();
_pageController = PageController();
}
#override
Widget build(BuildContext context) {
return Scaffold(
body: GestureDetector(
onHorizontalDragEnd: (details) => (details.primaryVelocity ?? 0) < 0
? _pageController.nextPage(
duration: const Duration(seconds: 1), curve: Curves.easeInOut)
: _pageController.previousPage(
duration: const Duration(seconds: 1), curve: Curves.easeInOut),
child: PageView(
physics: const NeverScrollableScrollPhysics(),
controller: _pageController,
children: [
Container(
color: const Color.fromARGB(255, 0, 91, 187),
),
Container(
color: const Color.fromARGB(255, 255, 213, 0),
),
],
),
),
);
}
}
class MyCustomScrollBehavior extends MaterialScrollBehavior {
#override
Set<PointerDeviceKind> get dragDevices => {
PointerDeviceKind.touch,
PointerDeviceKind.mouse,
};
}
You can simply use physics: NeverScrollableScrollPhysics() inside PageView() to achieve this kind of behaviour
I struggled with the same solution and built a complex custom gesture controller with drag listeners.
However, your so called bad example seems like the right direction.\
Why have this 700ms at all?\
You already have the onPointerUp event, where you can check the current page by using controller.page.round().\
You can also check that there is a dragging going on at this pointerUp by comparing controller.page==controller.page.floor()

update item number in appbar from listview.builder

I am learning flutter and have some experience in javascript.
I want to add length of _suggestions to _appBar.
I know I need setState(), but I can't find the right place to insert setState().
When I add setState in build(), the flutter framework issues error.
I understand the setState requires the build to be called, so if setState() is in build(), the condition is recursive.
And the ListView.builder seems to have no event handler. If there is event handler, I can register setState() there.
// Copyright 2018 The Flutter team. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
import 'package:english_words/english_words.dart';
import 'package:flutter/material.dart';
void main() {
runApp(const MyApp());
}
class MyApp extends StatelessWidget {
const MyApp({Key? key}) : super(key: key);
#override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Welcome to Flutter',
debugShowCheckedModeBanner: false,
home: Scaffold(
appBar: AppBar(
title: const Text('Welcome to Flutter'),
toolbarHeight: 100.0,
),
body: const Center(
child: RandomWords(),
),
),
);
}
}
/*
https://stackoverflow.com/questions/60902203/flutter-update-the-text-in-appbar
http://fluttersamples.com/
https://bendyworks.com/blog/a-month-of-flutter-rendering-a-list-view-with-stream-builder
https://stackoverflow.com/questions/48481590/how-to-set-update-state-of-statefulwidget-from-other-statefulwidget-in-flutter
*/
class RandomWords extends StatefulWidget {
const RandomWords({Key? key}) : super(key: key);
#override
State<RandomWords> createState() => _RandomWordsState();
}
class _RandomWordsState extends State<RandomWords> {
final _suggestions = <WordPair>[];
final _biggerFont = const TextStyle(fontSize: 18.0);
String title = 'Startup Name Generator';
final _appBar = const CustomAppBar();
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: _appBar,
body: ListView.builder(
padding: const EdgeInsets.all(16.0),
itemBuilder: (context, i) {
if(i.isOdd) return const Divider();
final index = i ~/ 2;
if(index >= _suggestions.length) {
_suggestions.addAll(generateWordPairs().take(10));
}
return ListTile(
title: Text(
_suggestions[index].asPascalCase,
style: _biggerFont,
),
);
},
),
);
}
}
class CustomAppBar extends StatefulWidget implements PreferredSizeWidget {
const CustomAppBar({Key? key}) : super(key: key);
#override
final Size preferredSize = const Size.fromHeight(56); // default is 56.0
#override
State<CustomAppBar> createState() => _CustomAppBarState();
}
class _CustomAppBarState extends State<CustomAppBar> {
String title = "Title";
_changeTitle(String title) {
setState(() {
this.title = title;
});
}
#override
Widget build(BuildContext context) {
return AppBar(
title: Text(title),
);
}
}
flutter codelab
Tested this code. but not works.
// Copyright 2018 The Flutter team. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
import 'package:english_words/english_words.dart';
import 'package:flutter/material.dart';
void main() {
runApp(const MyApp());
}
class MyApp extends StatelessWidget {
const MyApp({Key? key}) : super(key: key);
#override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Welcome to Flutter',
debugShowCheckedModeBanner: false,
home: Scaffold(
appBar: AppBar(
title: const Text('Welcome to Flutter'),
toolbarHeight: 100.0,
),
body: const Center(
child: RandomWords(),
),
),
);
}
}
/*
https://stackoverflow.com/questions/60902203/flutter-update-the-text-in-appbar
http://fluttersamples.com/
https://bendyworks.com/blog/a-month-of-flutter-rendering-a-list-view-with-stream-builder
https://stackoverflow.com/questions/48481590/how-to-set-update-state-of-statefulwidget-from-other-statefulwidget-in-flutter
*/
class RandomWords extends StatefulWidget {
const RandomWords({Key? key}) : super(key: key);
#override
State<RandomWords> createState() => _RandomWordsState();
}
class _RandomWordsState extends State<RandomWords> {
final _suggestions = <WordPair>[];
final _biggerFont = const TextStyle(fontSize: 18.0);
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: CustomAppBar(totalSuggestions: _suggestions.length.toString()),
body: ListView.builder(
padding: const EdgeInsets.all(16.0),
itemBuilder: (context, i) {
if(i.isOdd) return const Divider();
final index = i ~/ 2;
if(index >= _suggestions.length) {
setState(()
{
_suggestions.addAll(generateWordPairs().take(10));
});
}
return ListTile(
title: Text(
_suggestions[index].asPascalCase,
style: _biggerFont,
),
);
},
),
);
}
}
class CustomAppBar extends StatefulWidget implements PreferredSizeWidget {
final String totalSuggestions;
const CustomAppBar({Key? key, required this.totalSuggestions}) : super(key: key);
#override
final Size preferredSize = const Size.fromHeight(56); // default is 56.0
#override
State<CustomAppBar> createState() => _CustomAppBarState();
}
class _CustomAppBarState extends State<CustomAppBar> {
#override
Widget build(BuildContext context) {
return AppBar(
title: Row(
children: [
Text("Startup Name Generator"),
Spacer(),
Text(widget.totalSuggestions),
],
),
);
}
}
class RandomWords extends StatefulWidget {
const RandomWords({Key? key}) : super(key: key);
#override
State<RandomWords> createState() => _RandomWordsState();
}
class _RandomWordsState extends State<RandomWords> {
final _suggestions = <WordPair>[];
final _biggerFont = const TextStyle(fontSize: 18.0);
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: CustomAppBar(totalSuggestions: _suggestions.length);,
body: ListView.builder(
padding: const EdgeInsets.all(16.0),
itemBuilder: (context, i) {
if(i.isOdd) return const Divider();
final index = i ~/ 2;
if(index >= _suggestions.length) {
setState((){
_suggestions.addAll(generateWordPairs().take(10));
})
}
return ListTile(
title: Text(
_suggestions[index].asPascalCase,
style: _biggerFont,
),
);
},
),
);
}
}
class CustomAppBar extends StatefulWidget implements PreferredSizeWidget {
final String totalSuggestions;
const CustomAppBar({Key? key, requiered this.totalSuggestions}) : super(key: key);
#override
final Size preferredSize = const Size.fromHeight(56); // default is 56.0
#override
State<CustomAppBar> createState() => _CustomAppBarState();
}
class _CustomAppBarState extends State<CustomAppBar> {
#override
Widget build(BuildContext context) {
return AppBar(
title:Row(
chidren: [
Text("Startup Name Generator"),
Spacer(),
Text(widget.totalSuggestions)
],
),
);
}
}
This may help you. Welcome to flutter

How to pass a drawer with a string inside to next screen

I have the next block of code where I'm getting the AppVersion using a library and after that I'm passing the AppVersion to a drawer. That drawer I send it to next screen but when I open the drawer on the next screen is showing the AppVersion as NULL. What can be the issue ?
I will provide below the full code source and maybe somebody can help me to figure out where is the bug.
import 'package:flutter/material.dart';
import 'package:package_info/package_info.dart';
void main() {
runApp(FirstPage());
}
class FirstPage extends StatefulWidget {
final String title;
FirstPage({Key key, this.title}) : super(key: key);
#override
_FirstPageState createState() => _FirstPageState();
}
class _FirstPageState extends State<FirstPage> {
String packageAppVersion = '';
#override
void initState() {
super.initState();
versionCheck();
}
Future<void> versionCheck() async {
PackageInfo packageInfo = await PackageInfo.fromPlatform();
setState(() {
packageAppVersion = packageInfo.version;
});
}
Widget buildDrawerForSecondPage(BuildContext context) {
return new Drawer(
child: Padding(
padding: const EdgeInsets.all(10.0),
child: new Column(
children: [
Flexible(
child: new ListView(
children: <Widget>[],
),
),
Flexible(
flex: 0,
child: Text("App version: $packageAppVersion"),
)
],
),
),
);
}
#override
Widget build(BuildContext context) {
return MaterialApp(
debugShowCheckedModeBanner: false,
title: 'Drawer Demo',
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: SecondPage(
title: 'Second Page',
drawer: buildDrawerForSecondPage(context),
),
);
}
}
class SecondPage extends StatefulWidget {
final String title;
final Drawer drawer;
SecondPage({Key key, this.title, this.drawer}) : super(key: key);
#override
_SecondPageState createState() => _SecondPageState(drawer);
}
class _SecondPageState extends State<SecondPage> {
String packageAppVersion = '';
final Drawer drawer;
_SecondPageState(this.drawer);
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text(widget.title)),
endDrawer: drawer,
body: Container(),
);
}
}
Thanks in advance.
That's because you are using a .then() syntax, the AppVersion actually gets updated but a bit later hence the null value. You could await the version before the run() method and then pass it down to MaterialApp, or you could try using a setState after the print inside then(). Let me know if this fixes your issue.
Initially, the value of packageAppVersion is null, that is what it is being shown in the UI. So to update the UI you need to use setState. Check the below code for a better understanding:
#override
void initState() {
super.initState();
versionCheck();
}
Future<void> versionCheck() async {
PackageInfo packageInfo = await PackageInfo.fromPlatform();
setState((){
packageAppVersion = packageInfo.version;
});
print('App version received: $packageAppVersion');
}
I found the fix for above code, but I don't understand why is working only like this (removed the drawer property and the constructor from the _SecondPageState) :
class SecondPage extends StatefulWidget {
final String title;
final Drawer drawer;
SecondPage({Key key, this.title, this.drawer}) : super(key: key);
#override
_SecondPageState createState() => _SecondPageState();
}
class _SecondPageState extends State<SecondPage> {
String packageAppVersion = '';
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text(widget.title)),
endDrawer: widget.drawer,
body: Container(),
);
}
}

setState() within StatefulWidget not working properly

The thing I'm trying to do is, to change the colour of a RawMaterialButton when the button is clicked. Read about StatefulWidget and it seemed like it should work, but for some reason it doesn't.
flutter: Another exception was thrown: setState() called in constructor: ButtonTest#1a93b(lifecycle state: created, no widget, not mounted)
ButtonTest class:
class ButtonState extends StatefulWidget {
#override
State createState() => ButtonTest();
}
class ButtonTest extends State<ButtonState> implements Cipher {
#override
String icon = '';
#override
String title = '';
bool enabled = false;
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text(this.title),
),
body: RawMaterialButton(
shape: CircleBorder(side: BorderSide(color: Colors.black)),
fillColor: enabled ? Colors.blue : Colors.red,
onPressed: () {
setState(() {
this.enabled = true;
});
},
padding: EdgeInsets.all(0)),
);
}
}
Cipher class:
abstract class Cipher {
String icon;
String title;
Widget build(BuildContext context);
}
getCiphers()
getCiphers() {
final List<Cipher> ciphers = new List();
ciphers.add(ButtonTest());
return ciphers;
}
Main class:
void main() => runApp(CipherTools());
class CipherTools extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
title: 'CipherTools',
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: CipherScreen(
ciphers: getCiphers(),
),
);
}
}
class CipherScreen extends StatelessWidget {
final List<Cipher> ciphers;
CipherScreen({Key key, #required this.ciphers}) : super(key: key);
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('Ciphers'),
),
body: ListView.builder(
itemCount: ciphers.length,
itemBuilder: (context, index) {
return ListTile(
title: Text(ciphers[index].title),
// When a user taps on the ListTile, navigate to the DetailScreen.
// Notice that we're not only creating a DetailScreen, we're
// also passing the current todo through to it!
onTap: () {
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => DetailScreen(cipher: ciphers[index]),
),
);
},
);
},
),
);
}
}
class DetailScreen extends StatelessWidget {
// Declare a field that holds the Todo
final Cipher cipher;
// In the constructor, require a Todo
DetailScreen({Key key, #required this.cipher}) : super(key: key);
#override
Widget build(BuildContext context) {
return cipher.build(context);
}
}
What am I doing wrong here?
Wrap setState() like this.
if(this.mounted) {
setState(() {
this.enabled = true;
});
}
A couple of things:
ButtonState should be called ButtonTest because this is the
StatefulWidget
ButtonTest should be called ButtonTestState because this is the State.
Then in DetailScreen, in the build() method, you could return the StatefulWidget (ButtonTest), like this:
#override
Widget build(BuildContext context) {
return ButtonTest();
}

Flutter add custom SlideTransition to ModalRoute

in this below implemented code i can show dialog on bottom of page with Fade animation and now, i want to add SlideTransition to ModalRoute of this implementation to slide dialog from bottom, but i can't to do that
for example, what i want to have:
source code:
import 'package:flutter/material.dart';
import 'package:flutter/services.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> {
int _counter = 0;
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text(widget.title),
),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
Text(
'Open the popup window',
),
Text(
'$_counter',
style: Theme.of(context).textTheme.display1,
),
],
),
),
floatingActionButton: FloatingActionButton(
onPressed: () {
showPopup(context, _popupBody(), 'Popup Demo');
},
tooltip: 'Open Popup',
child: Icon(Icons.add),
), // This trailing comma makes auto-formatting nicer for build methods.
);
}
showPopup(BuildContext context, Widget widget, String title, {BuildContext popupContext}) {
Navigator.push(
context,
PopupLayout(
top: MediaQuery.of(context).size.height * 0.300,
left: 0,
right: 0,
bottom: 0,
child: PopupContent(
content: Scaffold(
body: widget,
),
),
),
);
}
Widget _popupBody() {
return Container(
child: Text('This is a popup window'),
);
}
}
class PopupLayout extends ModalRoute {
double top;
double bottom;
double left;
double right;
Color bgColor;
final Widget child;
#override
Duration get transitionDuration => Duration(milliseconds: 200);
#override
bool get opaque => false;
#override
bool get barrierDismissible => false;
#override
Color get barrierColor => bgColor == null ? Colors.black.withOpacity(0.5) : bgColor;
#override
String get barrierLabel => null;
#override
bool get maintainState => false;
PopupLayout({Key key, this.bgColor, #required this.child, this.top, this.bottom, this.left, this.right});
#override
Widget buildPage(
BuildContext context,
Animation<double> animation,
Animation<double> secondaryAnimation,
) {
if (top == null) this.top = 10;
if (bottom == null) this.bottom = 20;
if (left == null) this.left = 20;
if (right == null) this.right = 20;
return GestureDetector(
onTap: () {
// call this method here to hide soft keyboard
SystemChannels.textInput.invokeMethod('TextInput.hide');
},
child: Material(
// This makes sure that text and other content follows the material style
type: MaterialType.transparency,
//type: MaterialType.canvas,
// make sure that the overlay content is not cut off
child: SafeArea(
bottom: true,
child: _buildOverlayContent(context),
),
),
);
}
Widget _buildOverlayContent(BuildContext context) {
return Container(
margin: EdgeInsets.only(bottom: this.bottom, left: this.left, right: this.right, top: this.top),
child: SlideTransition(child: child),
);
}
#override
Widget buildTransitions(BuildContext context, Animation<double> animation, Animation<double> secondaryAnimation, Widget child) {
return FadeTransition(opacity: animation, child: child);
}
}
class PopupContent extends StatefulWidget {
final Widget content;
PopupContent({
Key key,
this.content,
}) : super(key: key);
_PopupContentState createState() => _PopupContentState();
}
class _PopupContentState extends State<PopupContent> {
#override
void initState() {
super.initState();
}
#override
Widget build(BuildContext context) {
return Container(
child: widget.content,
);
}
}
Here is a working example
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>
with TickerProviderStateMixin {
void showPopup() {
AnimationController controller = AnimationController(
duration: const Duration(milliseconds: 400), vsync: this);
showDialog(
context: context,
builder: (_) => PopUp(
controller: controller,
),
);
}
#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 {
final AnimationController controller;
PopUp({this.controller});
#override
State<StatefulWidget> createState() => PopUpState();
}
class PopUpState extends State<PopUp> {
Animation<double> opacityAnimation;
Tween<double> opacityTween = Tween<double>(begin: 0.0, end: 1.0);
Tween<double> marginTopTween = Tween<double>(begin: 600, end: 200);
Animation<double> marginTopAnimation;
AnimationStatus animationStatus;
#override
void initState() {
super.initState();
marginTopAnimation = marginTopTween.animate(widget.controller)
..addListener(() {
animationStatus = widget.controller.status;
if (animationStatus == AnimationStatus.dismissed) {
Navigator.of(context).pop();
}
if(this.mounted) {
setState(() {
});
}
});
widget.controller.forward();
}
#override
Widget build(BuildContext context) {
return FadeTransition(
opacity: opacityTween.animate(widget.controller),
child: GestureDetector(
onTap: () {
widget.controller.reverse();
},
child: Material(
color: Colors.transparent,
child: Container(
margin: EdgeInsets.only(
top: marginTopAnimation.value,
),
color: Colors.red,
child: Text("Container"),
),
),
),
);
}
#override
void dispose() {
widget.controller.dispose();
super.dispose();
}
}
UPDATE 1: Added Material as a child of Container to fix the barrier not dismissing bug.
UPDATE 2: Made a few more changes which reverses the animation when the barrier is dismissed.
NOTE: The screenshot does not reflect the updated changes. It is a demo of the original answer.