Flutter transform translate appbar leading Button to top on scrolling - flutter

I want translate leading Button in app bar when scrolling like below.
button will translate according with scrolled amount.
and this is what i tried and got. Same thing happened but its not changing with scrolled amount which is not smooth as well
How can i achieve this kind of smooth transtion based on scrolled amount??
This is my current code
class _HomePageState extends State<HomePage> with SingleTickerProviderStateMixin {
final scrollcontroller = ScrollController();
late AnimationController animationController;
#override
void initState() {
super.initState();
scrollcontroller.addListener(onScroll);
animationController = AnimationController(vsync: this, duration: Duration(milliseconds: 500));
animationController.addListener(() {
setState(() {});
});
}
onScroll() {
if (scrollcontroller.offset < 15) {
animationController.reverse();
print(animationController.value);
} else if (scrollcontroller.offset > 15) {
animationController.forward();
print(animationController.value);
}
}
#override
Widget build(context) {
return Scaffold(
appBar: AppBar(
elevation: 0,
leading: Transform.translate(
offset: Offset(0, -animationController.value * 35),
child: IconButton(
icon: Icon(Icons.menu),
onPressed: () {},
),
),
title: Text("test"),
),
backgroundColor: Color(0xFF51d3e3),
body: NotificationListener<UserScrollNotification>(
onNotification: (notification) {
if (notification.direction == ScrollDirection.forward) {
// print("Scrolling up");
} else if (notification.direction == ScrollDirection.reverse) {
// print("Scrolling down");
// print(scrollcontroller.offset);
}
return true;
},
child: Container(
padding: EdgeInsets.only(left: 20, right: 20, top: 20),
child: ListView.builder(
controller: scrollcontroller,
itemCount: 9,
itemBuilder: (content, index) {
return Container(
padding: EdgeInsets.symmetric(vertical: 12),
child: PlaceholderCardTall(
height: 200,
color: const Color(0xFF99D3F7),
backgroundColor: Color(0xFFC7EAFF)),
);
},
),
),
),
}
}

You can use SliverPersistentHeader, play with shrinkOffset.
class CSLVW extends StatelessWidget {
const CSLVW({Key? key}) : super(key: key);
#override
Widget build(BuildContext context) {
return Scaffold(
body: CustomScrollView(
slivers: [
SliverPersistentHeader(
pinned: true,
delegate: CustomAppBar(),
),
SliverToBoxAdapter(
child: Container(height: 1133),
),
],
),
);
}
}
class CustomAppBar extends SliverPersistentHeaderDelegate {
#override
Widget build(
BuildContext context, double shrinkOffset, bool overlapsContent) {
var factionDatabaseSnapshot;
return Container(
color: Colors.purple,
height: maxExtent,
child: Stack(
children: [
Positioned(
left: 20,
top: -shrinkOffset + 10,
child: IconButton(
onPressed: null,
icon: Icon(Icons.ac_unit),
),
)
],
),
);
}
#override
double get maxExtent => kToolbarHeight * 1.5;
#override
double get minExtent => kToolbarHeight;
#override
bool shouldRebuild(covariant SliverPersistentHeaderDelegate oldDelegate) =>
false;
}

Related

Persistent Navigation Bar only in some pages

Stackoverflowers!
I'm using an BottomAppBar inside the bottomNavigationBar section of the Scaffold. The problem is that it doesn't persists while I'm navigating. I used the persistent_bottom_nav_bar plugin, but it doesn't work with my custom navigation bar because it has a ripple animation in one button and a bottomSheet that is over the keyboard.
home_page.dart
This file has the CustomNavigationBar and the main pages for each item on it.
class HomePage extends StatefulWidget {
const HomePage({super.key});
#override
State<HomePage> createState() => _HomePageState();
}
class _HomePageState extends State<HomePage> with TickerProviderStateMixin {
NavigationProvider? navigationProvider;
AnimationController? rippleController;
AnimationController? scaleController;
Animation<double>? rippleAnimation;
Animation<double>? scaleAnimation;
#override
void initState() {
super.initState();
rippleController = AnimationController(
vsync: this, duration: const Duration(milliseconds: 500));
scaleController = AnimationController(
vsync: this, duration: const Duration(milliseconds: 500))
..addStatusListener((status) {
if (status == AnimationStatus.completed) {
scaleController!.reverse();
Navigator.push(
context,
PageTransition(
type: PageTransitionType.bottomToTop,
child: pages.elementAt(2),
childCurrent: widget,
fullscreenDialog: true,
)).whenComplete(() => setState(() {
buttonColor = Colors.black;
}));
}
});
rippleAnimation =
Tween<double>(begin: 80.0, end: 90.0).animate(rippleController!)
..addStatusListener((status) {
if (status == AnimationStatus.completed) {
rippleController!.reverse();
} else if (status == AnimationStatus.dismissed) {
rippleController!.forward();
}
});
scaleAnimation =
Tween<double>(begin: 1.0, end: 30.0).animate(scaleController!);
rippleController!.forward();
}
#override
void dispose() {
rippleController!.dispose();
scaleController!.dispose();
super.dispose();
}
#override
Widget build(BuildContext context) {
navigationProvider = Provider.of<NavigationProvider>(context);
return Scaffold(
body:
pages.elementAt(navigationProvider!.bottomNavigationBarSelectedIndex),
bottomNavigationBar: CustomNavigationBar(
rippleController: rippleController,
scaleController: scaleController,
rippleAnimation: rippleAnimation,
scaleAnimation: scaleAnimation),
);
}
}
custom_navigation_bar.dart
This file contains the properties of the CustomNavigationBar.
class CustomNavigationBar extends StatefulWidget {
const CustomNavigationBar({
super.key,
this.rippleController,
this.scaleController,
this.rippleAnimation,
this.scaleAnimation,
});
final AnimationController? rippleController;
final AnimationController? scaleController;
final Animation<double>? rippleAnimation;
final Animation<double>? scaleAnimation;
#override
State<CustomNavigationBar> createState() => _CustomNavigationBarState();
}
class _CustomNavigationBarState extends State<CustomNavigationBar> {
#override
Widget build(BuildContext context) {
final navigationProvider = Provider.of<NavigationProvider>(context);
return BottomAppBar(
child: IconTheme(
data: const IconThemeData(color: Colors.black, size: 36),
child: Padding(
padding: const EdgeInsets.fromLTRB(10, 5, 10, 5),
child: Row(
mainAxisSize: MainAxisSize.max,
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Wrap(
crossAxisAlignment: WrapCrossAlignment.center,
direction: Axis.vertical,
children: [
IconButton(
icon: ...,
padding: ...,
constraints: ...,
onPressed: () {
//Here I change the selected index with Provider.
...
},
),
Text(
title,
style: ...,
),
],
),
const Spacer(),
Wrap(...),
const Spacer(),
InkWell(
onTap: () {
setState(
() {
//Executes the ripple animation.
widget.scaleController!.forward();
},
);
},
child: AnimatedBuilder(
animation: widget.scaleAnimation!,
builder: (context, child) => Transform.scale(
scale: widget.scaleAnimation!.value,
child: Container(
width: 50,
height: 50,
margin: const EdgeInsets.all(10),
decoration: const BoxDecoration(
shape: BoxShape.circle, color: Colors.blue),
child: Icon(Icons.add,
color: widget.scaleAnimation!.value == 1.0
? Colors.white
: Colors.blue),
),
),
),
),
const Spacer(),
Wrap(...),
const Spacer(),
Wrap(...),
],
),
),
),
);
}
}
As you can see, I use Provider to manage the state of the CustomNavigationBar when it changes the index.
Example of what I want:
This app is Splitwise and it has some pages with the navigation bar and others without it. That ripple animation is similar to mine. Also the bottom sheet has the same effect in my app.
I'll wait for all your suggestions, thanks!

How can I properly remove an OverlayEntry in flutter?

In my main widget tree, I have a GestureDetector that when tapped, will launch an Overlay as follows:
OverlayState? _overlayState = Overlay.of(context);
_overlayState?.insert(
OverlayEntry(
builder: (BuildContext context) {
return ShowNotificationIcon();
},
)
);
SnowNotificationIcon is actually a StatefulWidget that houses the guts of the Overlay:
class ShowNotificationIcon extends ConsumerStatefulWidget {
const ShowNotificationIcon({Key? key}) : super(key: key);
#override
_ShowNotificationIconState createState() => _ShowNotificationIconState();
}
class _ShowNotificationIconState extends ConsumerState<ShowNotificationIcon> {
void initState(){
super.initState();
}
void dispose(){
super.dispose();
}
Positioned theDropDown(){
return
Positioned(
top: 50.0,
left: 50.0,
child: Material(
color: Colors.transparent,
child:
Column(children: [
Text('Test!'),
],)),
);
}
#override
Widget build(BuildContext context) {
return Stack(
children: [
Positioned.fill(
child: GestureDetector(
onTap: () {
/// I WANT TO REMOVE THE OVERLAY HERE
},
child: Container(
color: Colors.transparent,
),
)
),
theDropDown()
],
);
}
}
As I understand it, the overlay must be removed via a .remove() call, but since the overlay is all housed within a StatefulWidget, how can I make a .remove call on the overlay when it was opened outside of the StateWidget?
Am I missing something obvious here?
You can try this example I created for you
OverlayState? _overlayState = Overlay.of(context);
OverlayEntry? _overlayEntry;
_overlayEntry = OverlayEntry(
builder: (BuildContext context) {
return ShowNotificationIcon(entry: _overlayEntry);
},
);
_overlayState?.insert(_overlayEntry);
class ShowNotificationIcon extends ConsumerStatefulWidget {
final OverlayEntry? entry;
const ShowNotificationIcon({Key? key, this.entry}) : super(key: key);
#override
_ShowNotificationIconState createState() => _ShowNotificationIconState();
}
class _ShowNotificationIconState extends ConsumerState<ShowNotificationIcon> {
Positioned theDropDown(){
return
Positioned(
top: 50.0,
left: 50.0,
child: Material(
color: Colors.transparent,
child:
Column(children: [
Text('Test!'),
],)),
);
}
#override
Widget build(BuildContext context) {
return Stack(
children: [
Positioned.fill(
child: GestureDetector(
onTap: () {
if (widget.entry != null){
widget.entry.remove();
}
},
child: Container(
color: Colors.transparent,
),
)
),
theDropDown()
],
);
}
}
Here, A sample Toast class. I use close button in overlay. You can use similarly.
import 'package:flutter/material.dart';
class AppDialogs {
static final AppDialogs _instance = AppDialogs.internal();
AppDialogs.internal();
factory AppDialogs() => _instance;
static void appToast(
BuildContext context, {
required Widget title,
Widget? description,
Icon? toastIcon,
Color? toastColor,
double? height,
double? width,
bool dismissibleToast = true,
}) async {
final OverlayState? overlayState = Overlay.of(context);
late OverlayEntry overlayEntry;
overlayEntry = OverlayEntry(
builder: (content) => Positioned(
height: height ?? 80,
width: width ?? 200,
top: 0,
right: 0,
child: Card(
borderOnForeground: true,
elevation: 10,
child: Stack(
children: [
ListTile(
tileColor: toastColor,
title: title,
subtitle: description,
leading: toastIcon,
),
Positioned(
top: 0,
right: 0,
child: Padding(
padding: const EdgeInsets.all(8.0),
child: InkWell(
onTap: () => closeOverlay(overlayEntry),
child: const Icon(
Icons.close,
size: 14,
),
),
),
),
],
))),
);
overlayState!.insert(overlayEntry);
}
static void closeOverlay(OverlayEntry overlayEntry) {
{
overlayEntry.remove();
}
}
}
class _LoginViewState extends State<LoginView> {
#override
Widget build(BuildContext context) {
return Scaffold(
body: Column(
children: [
TextButton(
child: Text("Login"),
onPressed: (() {
AppDialogs.appToast(context, title: Text("Toast"));
}),
)
],
),
);
}

How to Navigate in Same the Page Master Detail page Flutter

I am building a master detail based app and I want to show in splitview. Trying to understand how to push data to another page in same view but couldn't. Want to cover details data in second page. How to push data?
It could be either responsive or not. But I don't want to resolve but only using set state and fill the blank in details 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> {
#override
void initState() {
super.initState();
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text(
widget.title,
style: TextStyle(color: Colors.black),
),
backgroundColor: Colors.transparent,
elevation: 0,
),
body: Padding(
padding: const EdgeInsets.all(8.0),
child: VerticalSplitView(
left: ListView.builder( itemCount: 12,
itemBuilder: (context, index) {
return Card(
child: InkWell(
splashColor: Colors.blue.withAlpha(30),
onTap: () {
//Navigator.push(context, MaterialPageRoute(builder: (context) => new yapiekle()) );
},
child: Container(
child: Padding(
padding: EdgeInsets.all(12.0),
child: Row(
mainAxisAlignment: MainAxisAlignment.center,
//Center Column contents vertically,
crossAxisAlignment: CrossAxisAlignment.center,
children: <Widget>[
Expanded(
child: ListTile(
leading: Image.network("https://picsum.photos/200/300"),
title: Text("Title"),
subtitle: Text("Subtitle")),
),
//Spacer(),
],
),
),
),
),
);
}
),
right: Container(
decoration: BoxDecoration(
border: Border.all(color: Colors.black),
),
child: Center(
child: FlutterLogo(
size: 256,
)),
),
),
),
);
}
}
class VerticalSplitView extends StatefulWidget {
final Widget left;
final Widget right;
final double ratio;
const VerticalSplitView(
{Key key, #required this.left, #required this.right, this.ratio = 0.5})
: assert(left != null),
assert(right != null),
assert(ratio >= 0),
assert(ratio <= 1),
super(key: key);
#override
_VerticalSplitViewState createState() => _VerticalSplitViewState();
}
class _VerticalSplitViewState extends State<VerticalSplitView> {
final _dividerWidth = 16.0;
//from 0-1
double _ratio;
double _maxWidth;
get _width1 => _ratio * _maxWidth;
get _width2 => (1 - _ratio) * _maxWidth;
#override
void initState() {
super.initState();
_ratio = widget.ratio;
}
#override
Widget build(BuildContext context) {
return LayoutBuilder(builder: (context, BoxConstraints constraints) {
assert(_ratio <= 1);
assert(_ratio >= 0);
if (_maxWidth == null) _maxWidth = constraints.maxWidth - _dividerWidth;
if (_maxWidth != constraints.maxWidth) {
_maxWidth = constraints.maxWidth - _dividerWidth;
}
return SizedBox(
width: constraints.maxWidth,
child: Row(
children: <Widget>[
SizedBox(
width: _width1,
child: widget.left,
),
GestureDetector(
behavior: HitTestBehavior.translucent,
child: SizedBox(
width: _dividerWidth,
height: constraints.maxHeight,
child: RotationTransition(
child: Icon(Icons.drag_handle),
turns: AlwaysStoppedAnimation(0.25),
),
),
onPanUpdate: (DragUpdateDetails details) {
setState(() {
_ratio += details.delta.dx / _maxWidth;
if (_ratio > 1)
_ratio = 1;
else if (_ratio < 0.0) _ratio = 0.0;
});
},
),
SizedBox(
width: _width2,
child: widget.right,
),
],
),
);
});
}
}
I assumed that you want to change the the right page by clicking on the left card widgets. I have been developed something like this. I am using IndexedStack for render on the right side of VerticalSplitView then use provider and consumer for controlling the page to display.
First of all you need to import provider dependency in pubspec.ymal
You can replace this code below for entire of main.dart.
In main.dart you can try to replace this code. The idea is we are going to create IndexedStack that contain the Widget (Page as you prefer). Then we are going to change the index of IndexedStack by using Provider and Consumer.
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
import 'package:test_indexed_stack/page_data.dart';
void main() {
runApp(MyApp());
}
class MyApp extends StatelessWidget {
// This widget is the root of your application.
#override
Widget build(BuildContext context) {
return MultiProvider(
providers: [
ChangeNotifierProvider(create: (context) {
var pageData = PageData();
return pageData;
}),
],
child: 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> {
// Set required page same as list length in left of VerticalSplitView
List<Widget> pages = [Text('Page1'), Text('Page2'), Text('Page3'),
Text('Page4'), Text('Page5'), Text('Page6'), Text('Page7'),
Text('Page8'), Text('Page9'), Text('Page10'), Text('Page11'),
Text('Page12'), ];
#override
void initState() {
super.initState();
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text(
widget.title,
style: TextStyle(color: Colors.black),
),
backgroundColor: Colors.transparent,
elevation: 0,
),
body: Padding(
padding: const EdgeInsets.all(8.0),
child: VerticalSplitView(
left: ListView.builder( itemCount: 12,
itemBuilder: (context, index) {
return Card(
child: InkWell(
splashColor: Colors.blue.withAlpha(30),
onTap: () {
// Set the current page for change page on the right side.
Provider.of<PageData>(context, listen: false).setCurrentTab(index);
},
child: Container(
child: Padding(
padding: EdgeInsets.all(12.0),
child: Row(
mainAxisAlignment: MainAxisAlignment.center,
//Center Column contents vertically,
crossAxisAlignment: CrossAxisAlignment.center,
children: <Widget>[
Expanded(
child: ListTile(
leading: Image.network("https://picsum.photos/200/300"),
title: Text("Title"),
subtitle: Text("Subtitle")),
),
//Spacer(),
],
),
),
),
),
);
}
),
right: Consumer<PageData>(
builder: (context, pageData, child) {
return Container(
decoration: BoxDecoration(
border: Border.all(color: Colors.black),
),
child: IndexedStack(
children: pages,
index: pageData.currentPage,
)
);
},
),
),
),
);
}
}
class VerticalSplitView extends StatefulWidget {
final Widget left;
final Widget right;
final double ratio;
const VerticalSplitView(
{Key key, #required this.left, #required this.right, this.ratio = 0.5})
: assert(left != null),
assert(right != null),
assert(ratio >= 0),
assert(ratio <= 1),
super(key: key);
#override
_VerticalSplitViewState createState() => _VerticalSplitViewState();
}
class _VerticalSplitViewState extends State<VerticalSplitView> {
final _dividerWidth = 16.0;
//from 0-1
double _ratio;
double _maxWidth;
get _width1 => _ratio * _maxWidth;
get _width2 => (1 - _ratio) * _maxWidth;
#override
void initState() {
super.initState();
_ratio = widget.ratio;
}
#override
Widget build(BuildContext context) {
return LayoutBuilder(builder: (context, BoxConstraints constraints) {
assert(_ratio <= 1);
assert(_ratio >= 0);
if (_maxWidth == null) _maxWidth = constraints.maxWidth - _dividerWidth;
if (_maxWidth != constraints.maxWidth) {
_maxWidth = constraints.maxWidth - _dividerWidth;
}
return SizedBox(
width: constraints.maxWidth,
child: Row(
children: <Widget>[
SizedBox(
width: _width1,
child: widget.left,
),
GestureDetector(
behavior: HitTestBehavior.translucent,
child: SizedBox(
width: _dividerWidth,
height: constraints.maxHeight,
child: RotationTransition(
child: Icon(Icons.drag_handle),
turns: AlwaysStoppedAnimation(0.25),
),
),
onPanUpdate: (DragUpdateDetails details) {
setState(() {
_ratio += details.delta.dx / _maxWidth;
if (_ratio > 1)
_ratio = 1;
else if (_ratio < 0.0) _ratio = 0.0;
});
},
),
SizedBox(
width: _width2,
child: widget.right,
),
],
),
);
});
}
}
You need to create file for Provider and replce the code below.
import 'package:flutter/cupertino.dart';
class PageData extends ChangeNotifier{
PageData();
int _currentPage = 0;
void setCurrentTab(int index){
this._currentPage = index;
notifyListeners();
}
int get currentPage {
return this._currentPage;
}
}
Happy coding :)
if I now want to place a button inside the right widget: "add new post". This new-post-function should create a new post by copying all data from current page to the new post with a new post-ID. In the same function it then should navigate into the new post to edit copy of comment in a copy of current post. Like:
ElevatedButton(
onPressed: () {
addPost();
}
)
addPost() {
String newId = uuid.v1();
var newPost = Entry(
id: newId,
entry: entryProvider.comment,
);
firestoreService.setEntry(newPost);
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => PostScreen(id: newId)));
}

How to have the drawer push the content instead of going on top of it?

I'm trying to build something similar to the slack app (see screenshot below) where the navigation drawer pushes the screen away instead of going on top.
I've been trying with the Drawer component without success. I've also looked at PageView but it seems that the children need to take 100% of the width.
Does someone have an idea of how to implement it?
EDIT
A similar result can be achieved with a Stack and AnimatedPositioned
class SlidingDrawer extends StatefulWidget {
final Widget drawer;
final Widget child;
final int swipeSensitivity;
final double drawerRatio;
final Color overlayColor;
final double overlayOpacity;
final int animationDuration;
final Curve animationCurve;
SlidingDrawer({
Key key,
#required this.drawer,
#required this.child,
this.swipeSensitivity = 25,
this.drawerRatio = 0.8,
this.overlayColor = Colors.black,
this.overlayOpacity = 0.5,
this.animationDuration = 500,
this.animationCurve = Curves.ease,
}) : super(key: key);
#override
_SlidingDrawerState createState() => _SlidingDrawerState();
}
class _SlidingDrawerState extends State<SlidingDrawer> {
bool _opened = false;
void open() {
setState(() {
_opened = true;
});
}
void close() {
setState(() {
_opened = false;
});
}
#override
Widget build(BuildContext context) {
final width = MediaQuery.of(context).size.width;
final height = MediaQuery.of(context).size.height;
final drawerWidth = width * widget.drawerRatio;
return GestureDetector(
onHorizontalDragUpdate: (details) {
if (details.delta.dx > widget.swipeSensitivity) {
open();
} else if (details.delta.dx < -widget.swipeSensitivity) {
close();
}
},
child: SizedBox(
width: width,
height: height,
child: Stack(
children: [
AnimatedPositioned(
width: drawerWidth,
height: height,
left: _opened ? 0 : -drawerWidth,
duration: Duration(milliseconds: widget.animationDuration),
curve: widget.animationCurve,
child: Container(
color: Colors.amber,
child: widget.drawer,
),
),
AnimatedPositioned(
height: height,
width: width,
left: _opened ? drawerWidth : 0,
duration: Duration(milliseconds: widget.animationDuration),
curve: widget.animationCurve,
child: Stack(
fit: StackFit.expand,
children: [
widget.child,
AnimatedSwitcher(
duration: Duration(milliseconds: widget.animationDuration),
switchInCurve: widget.animationCurve,
switchOutCurve: widget.animationCurve,
child: _opened
? GestureDetector(
onTap: () {
setState(() {
_opened = false;
});
},
child: Container(
color: widget.overlayColor.withOpacity(
widget.overlayOpacity,
),
),
)
: null,
)
],
),
),
],
),
),
);
}
}
ORIGINAL ANSWER
As pointed out by #Yadu in the comment
you could use Single child horizontal scroll view (with disabled scroll physics) with Scrollable.ensureVisible(context) to show the menu
using an horizontal scroll view is working.
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',
home: MyHomePage(),
);
}
}
class MyHomePage extends StatefulWidget {
MyHomePage({Key key}) : super(key: key);
#override
_MyHomePageState createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
bool _drawerOpened = false;
final drawerKey = new GlobalKey();
final mainKey = new GlobalKey();
#override
Widget build(BuildContext context) {
return SingleChildScrollView(
physics: NeverScrollableScrollPhysics(),
scrollDirection: Axis.horizontal,
child: Row(
children: [
Container(
key: drawerKey,
color: Colors.green,
width: MediaQuery.of(context).size.width * 0.8,
),
SizedBox(
key: mainKey,
width: MediaQuery.of(context).size.width,
height: MediaQuery.of(context).size.height,
child: Scaffold(
appBar: AppBar(
title: Text("My Page"),
leading: IconButton(
icon: Icon(Icons.menu),
onPressed: _toggleDrawer,
),
),
body: Container(
color: Colors.yellow,
width: MediaQuery.of(context).size.width,
),
),
),
],
),
);
}
void _toggleDrawer() {
setState(() {
_drawerOpened = !_drawerOpened;
});
if (_drawerOpened) {
Scrollable.ensureVisible(drawerKey.currentContext);
} else {
Scrollable.ensureVisible(mainKey.currentContext);
}
}
}

Flutter adding more options for dialogs

is any solution to make drag and drop dialogs in flutter? for example after showing dialog in center of screen i would like to drag it to top of screen to make fullscreen dialog over current cover, for example this code is simple implementation to show dialog and i'm not sure, how can i do that
import 'package:flutter/material.dart';
void main() => runApp(new MyApp());
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(title: 'Flutter Demo', theme: ThemeData(), home: Page());
}
}
class Page extends StatelessWidget {
#override
Widget build(BuildContext context) {
return Scaffold(
body: Center(
child: RaisedButton.icon(
onPressed: () {
showDialog(
context: context,
builder: (_) => FunkyOverlay(),
);
},
icon: Icon(Icons.message),
label: Text("PopUp!")),
),
);
}
}
class FunkyOverlay extends StatefulWidget {
#override
State<StatefulWidget> createState() => FunkyOverlayState();
}
class FunkyOverlayState extends State<FunkyOverlay>
with SingleTickerProviderStateMixin {
AnimationController controller;
Animation<double> scaleAnimation;
#override
void initState() {
super.initState();
controller =
AnimationController(vsync: this, duration: Duration(milliseconds: 450));
scaleAnimation =
CurvedAnimation(parent: controller, curve: Curves.elasticInOut);
controller.addListener(() {
setState(() {});
});
controller.forward();
}
#override
Widget build(BuildContext context) {
return Center(
child: Material(
color: Colors.transparent,
child: ScaleTransition(
scale: scaleAnimation,
child: Container(
decoration: ShapeDecoration(
color: Colors.white,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(15.0))),
child: Padding(
padding: const EdgeInsets.all(50.0),
child: Text("Well hello there!"),
),
),
),
),
);
}
}
This is one way to do it ,
import 'package:flutter/material.dart';
main() {
runApp(MaterialApp(
theme: ThemeData(
primarySwatch: Colors.indigo,
),
home: App(),
));
}
class App extends StatefulWidget {
#override
State<App> createState() => _AppState();
}
class _AppState extends State<App> {
#override
void initState() {
super.initState();
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(),
body: Container(),
floatingActionButton: FloatingActionButton(
child: Icon(Icons.open_in_new),
onPressed: () {
showGeneralDialog(
context: context,
barrierDismissible: true,
barrierLabel: "hi",
barrierColor: Colors.black.withOpacity(0.2),
transitionDuration: Duration(milliseconds: 500),
pageBuilder: (context, pAnim, sAnim) {
return SafeArea(child: FloatingDialog());
},
transitionBuilder: (context, pAnim, sAnim, child) {
if (pAnim.status == AnimationStatus.reverse) {
return FadeTransition(
opacity: Tween(begin: 0.0, end: 0.0).animate(pAnim),
child: child,
);
} else {
return FadeTransition(
opacity: pAnim,
child: child,
);
}
},
);
},
),
);
}
}
class FloatingDialog extends StatefulWidget {
#override
_FloatingDialogState createState() => _FloatingDialogState();
}
class _FloatingDialogState extends State<FloatingDialog>
with TickerProviderStateMixin {
double _dragStartYPosition;
double _dialogYOffset;
Widget myContents = MyScaffold();
AnimationController _returnBackController;
Animation<double> _dialogAnimation;
#override
void initState() {
super.initState();
_dialogYOffset = 0.0;
_returnBackController =
AnimationController(vsync: this, duration: Duration(milliseconds: 1300))
..addListener(() {
setState(() {
_dialogYOffset = _dialogAnimation.value;
print(_dialogYOffset);
});
});
}
#override
void dispose() {
_returnBackController.dispose();
super.dispose();
}
#override
Widget build(BuildContext context) {
return Padding(
padding: const EdgeInsets.only(
top: 100.0,
bottom: 10.0,
left: 10.0,
right: 10.0,
),
child: Transform.translate(
offset: Offset(0.0, _dialogYOffset),
child: Column(
children: <Widget>[
Icon(
Icons.keyboard_arrow_up,
color: Colors.white,
),
Expanded(
child: GestureDetector(
onVerticalDragStart: (dragStartDetails) {
_dragStartYPosition = dragStartDetails.globalPosition.dy;
print(dragStartDetails.globalPosition);
},
onVerticalDragUpdate: (dragUpdateDetails) {
setState(() {
_dialogYOffset = (dragUpdateDetails.globalPosition.dy) -
_dragStartYPosition;
});
print(_dialogYOffset);
if (_dialogYOffset < -90.0) {
Navigator.of(context).pop();
Navigator.of(context).push(
PageRouteBuilder(
pageBuilder: (context, pAnim, sAnim) => myContents,
transitionDuration: Duration(milliseconds: 500),
transitionsBuilder: (context, pAnim, sAnim, child) {
if (pAnim.status == AnimationStatus.forward) {
return ScaleTransition(
scale: Tween(begin: 0.8, end: 1.0).animate(
CurvedAnimation(
parent: pAnim,
curve: Curves.elasticOut)),
child: child,
);
} else {
return FadeTransition(
opacity: pAnim,
child: child,
);
}
}),
);
}
},
onVerticalDragEnd: (dragEndDetails) {
_dialogAnimation = Tween(begin: _dialogYOffset, end: 0.0)
.animate(CurvedAnimation(
parent: _returnBackController,
curve: Curves.elasticOut));
_returnBackController.forward(from: _dialogYOffset);
_returnBackController.forward(from: 0.0);
},
child: myContents,
),
),
],
),
),
);
}
}
class MyScaffold extends StatelessWidget {
const MyScaffold({
Key key,
}) : super(key: key);
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text("Channels"),
),
body: Center(
child: RaisedButton(
onPressed: () {
Navigator.of(context).pop();
Navigator.of(context).push(
MaterialPageRoute(
builder: (context) => Scaffold(
appBar: AppBar(),
body: Placeholder(),
),
),
);
},
),
),
);
}
}
Output:
You can try this.
void main() => runApp(MaterialApp(home: HomePage()));
class HomePage extends StatefulWidget {
#override
_HomePageState createState() => _HomePageState();
}
class _HomePageState extends State<HomePage> {
bool _shown = false;
double _topOffset = 20, _dialogHeight = 400;
Duration _duration = Duration(milliseconds: 400);
Offset _offset, _initialOffset;
#override
void didChangeDependencies() {
super.didChangeDependencies();
var size = MediaQuery.of(context).size;
_offset = Offset(size.width, (size.height - _dialogHeight) / 2);
_initialOffset = _offset;
}
#override
Widget build(BuildContext context) {
var appBarColor = Colors.blue[800];
return Scaffold(
floatingActionButton: FloatingActionButton(onPressed: () => setState(() => _shown = !_shown)),
body: SizedBox.expand(
child: Stack(
children: <Widget>[
Container(
color: appBarColor,
child: SafeArea(
bottom: false,
child: Align(
child: Column(
children: <Widget>[
MyAppBar(
title: "Image",
color: appBarColor,
icon: Icons.home,
onPressed: () {},
),
Expanded(child: Image.asset("assets/images/landscape.jpeg", fit: BoxFit.cover)),
],
),
),
),
),
AnimatedOpacity(
opacity: _shown ? 1 : 0,
duration: _duration,
child: Material(
elevation: 8,
color: Colors.grey[900].withOpacity(0.5),
child: _shown
? GestureDetector(
onTap: () => setState(() => _shown = !_shown),
child: Container(color: Colors.transparent, child: SizedBox.expand()),
)
: SizedBox.shrink(),
),
),
// this shows our dialog
Positioned(
top: _offset.dy,
left: 10,
right: 10,
height: _shown ? null : 0,
child: AnimatedOpacity(
duration: _duration,
opacity: _shown ? 1 : 0,
child: GestureDetector(
onPanUpdate: (details) => setState(() => _offset += details.delta),
onPanEnd: (details) {
// when tap is lifted and current y position is less than set _offset, navigate to the next page
if (_offset.dy < _topOffset) {
Navigator.push(
context,
PageRouteBuilder(
pageBuilder: (context, anim1, anim2) => Screen2(),
transitionDuration: _duration,
transitionsBuilder: (context, anim1, anim2, child) {
bool isForward = anim1.status == AnimationStatus.forward;
Tween<double> tween = Tween(begin: isForward ? 0.9 : 0.5, end: 1);
return ScaleTransition(
scale: tween.animate(
CurvedAnimation(
parent: anim1,
curve: isForward ? Curves.bounceOut : Curves.easeOut,
),
),
child: child,
);
},
),
).then((_) {
_offset = _initialOffset;
});
}
// make the dialog come back to the original position
else {
Timer.periodic(Duration(milliseconds: 5), (timer) {
if (_offset.dy < _initialOffset.dy - _topOffset) {
_offset = Offset(_offset.dx, _offset.dy + 15);
setState(() {});
} else if (_offset.dy > _initialOffset.dy + _topOffset) {
_offset = Offset(_offset.dx, _offset.dy - 15);
setState(() {});
} else
timer.cancel();
});
}
},
child: Column(
children: <Widget>[
Icon(Icons.keyboard_arrow_up, color: Colors.white, size: 32),
Hero(
tag: "MyTag",
child: SizedBox(
height: _dialogHeight, // makes sure we don't exceed than our specified height
child: SingleChildScrollView(child: CommonWidget(appBar: MyAppBar(title: "FlutterLogo", color: Colors.orange))),
),
),
],
),
),
),
),
],
),
),
);
}
}
// this app bar is used in 1st and 2nd screen
class MyAppBar extends StatelessWidget {
final String title;
final Color color;
final IconData icon;
final VoidCallback onPressed;
const MyAppBar({Key key, #required this.title, #required this.color, this.icon, this.onPressed}) : super(key: key);
#override
Widget build(BuildContext context) {
return Container(
height: kToolbarHeight,
color: color,
width: double.maxFinite,
alignment: Alignment.centerLeft,
child: Row(
children: <Widget>[
icon != null ? IconButton(icon: Icon(icon), onPressed: onPressed, color: Colors.white,) : SizedBox(width: 16),
Text(
title,
style: TextStyle(fontSize: 18, fontWeight: FontWeight.bold, color: Colors.white),
),
],
),
);
}
}
// this is the one which is shown in both Dialog and Screen2
class CommonWidget extends StatelessWidget {
final bool isFullscreen;
final Widget appBar;
const CommonWidget({Key key, this.isFullscreen = false, this.appBar}) : super(key: key);
#override
Widget build(BuildContext context) {
var child = Container(
width: double.maxFinite,
color: Colors.blue,
child: FlutterLogo(size: 300, colors: Colors.orange),
);
return Column(
mainAxisSize: MainAxisSize.min,
children: <Widget>[
appBar,
isFullscreen ? Expanded(child: child) : child,
],
);
}
}
class Screen2 extends StatelessWidget {
#override
Widget build(BuildContext context) {
var appBarColor = Colors.orange;
return Scaffold(
body: Container(
color: appBarColor,
child: SafeArea(
bottom: false,
child: CommonWidget(
isFullscreen: true,
appBar: MyAppBar(
title: "FlutterLogo",
color: appBarColor,
icon: Icons.arrow_back,
onPressed: () => Navigator.pop(context),
),
),
),
),
);
}
}