how to perform drag operation on widget with Gesturedetector flutter - flutter

I want to drag and drop my custom widget with gesture detector. It is showing x- direction and y- direction values but not dragging to anywhere on screen.
Here is my code:
layoutpage:
SizedBox(
width: MediaQuery.of(context).size.width,
height: MediaQuery.of(context).size.height,
child: Stack(
children: _layoutProvider.tables
.map(
(SectionTable sTable) => Positioned(
top: sTable.top,
left: sTable.left,
child: LayoutWidget(
width: sTable.width,
height: sTable.height,
type: sTable.type.index,
name: sTable.name,
left: sTable.left,
top: sTable.top,
rotate: sTable.rotate,
color: sTable.order != null
? Colors.green
: Colors.grey,
seats: sTable.seats,
),
),
)
.toList()),
),
LayoutWidget:
class LayoutWidget extends StatefulWidget {
late double width;
late double height;
late double left;
late double top;
LayoutWidget({
Key? key,
required this.width,
required this.height,
required this.left,
required this.top,
}) : super(key: key);
#override
State<StatefulWidget> createState() => _LayoutWidgetState();
}
class _LayoutWidgetState extends State<LayoutWidget> {
#override
Widget build(BuildContext context) {
return Column(
crossAxisAlignment: CrossAxisAlignment.center,
children: [
RotationTransition(
turns: widget.type == 0
? const AlwaysStoppedAnimation(0)
: AlwaysStoppedAnimation(rotationValue / 360),
child: GestureDetector(
onPanUpdate: (details) {
widget.top = widget.top+ details.delta.dy;
widget.left = widget.left+ details.delta.dx;
setState(() {
});
},
onTap: () {
setState(() {
showMenu = !showMenu;
});
},
child: myWidget()
}
Can anyone help why i am unable to drag on screen. Thanks.

I hope you you can get Idea from this code. In this code you can drag Container anywhere in the Screen and set it. And also check this Gesture Detector Overview for Gesture Detector detail.
import 'package:flutter/cupertino.dart';
import 'package:flutter/gestures.dart';
import 'package:flutter/material.dart';
class GestureDetectorPage extends StatelessWidget {
#override
Widget build(BuildContext context) {
return Scaffold(
body: Container(
color: Colors.grey[100],
width: double.infinity,
height: double.infinity,
child: MainContent(),
),
);
}
}
class MainContent extends StatefulWidget {
#override
_MainContentState createState() => _MainContentState();
}
class _MainContentState extends State<MainContent> {
GlobalKey key = GlobalKey();
String dragDirection = '';
String startDXPoint = '50';
String startDYPoint = '50';
String dXPoint;
String dYPoint;
String velocity;
#override
Widget build(BuildContext context) {
return GestureDetector(
onHorizontalDragStart: _onHorizontalDragStartHandler,
onVerticalDragStart: _onVerticalDragStartHandler,
onHorizontalDragUpdate: _onDragUpdateHandler,
onVerticalDragUpdate: _onDragUpdateHandler,
onHorizontalDragEnd: _onDragEnd,
onVerticalDragEnd: _onDragEnd,
dragStartBehavior: DragStartBehavior.start, // default
behavior: HitTestBehavior.translucent,
child: Stack(
children: [
Positioned(
left: double.parse(this.startDXPoint),
top: double.parse(this.startDYPoint),
child: Container(
decoration: BoxDecoration(
color: Colors.green,
borderRadius: BorderRadius.circular(100)
),
child: Center(
child: Padding(
padding: const EdgeInsets.all(20),
child: Text('Draggable', style: TextStyle(fontSize: 14, color: Colors.white),),
),
),
)
),
],
),
);
}
void _onHorizontalDragStartHandler(DragStartDetails details) {
setState(() {
this.dragDirection = "HORIZONTAL";
this.startDXPoint = '${details.globalPosition.dx.floorToDouble()}';
this.startDYPoint = '${details.globalPosition.dy.floorToDouble()}';
});
}
/// Track starting point of a vertical gesture
void _onVerticalDragStartHandler(DragStartDetails details) {
setState(() {
this.dragDirection = "VERTICAL";
this.startDXPoint = '${details.globalPosition.dx.floorToDouble()}';
this.startDYPoint = '${details.globalPosition.dy.floorToDouble()}';
});
}
void _onDragUpdateHandler(DragUpdateDetails details) {
setState(() {
this.dragDirection = "UPDATING";
this.startDXPoint = '${details.globalPosition.dx.floorToDouble()}';
this.startDYPoint = '${details.globalPosition.dy.floorToDouble()}';
});
}
/// Track current point of a gesture
void _onHorizontalDragUpdateHandler(DragUpdateDetails details) {
setState(() {
this.dragDirection = "HORIZONTAL UPDATING";
this.dXPoint = '${details.globalPosition.dx.floorToDouble()}';
this.dYPoint = '${details.globalPosition.dy.floorToDouble()}';
this.velocity = '';
});
}
/// Track current point of a gesture
void _onVerticalDragUpdateHandler(DragUpdateDetails details) {
setState(() {
this.dragDirection = "VERTICAL UPDATING";
this.dXPoint = '${details.globalPosition.dx.floorToDouble()}';
this.dYPoint = '${details.globalPosition.dy.floorToDouble()}';
this.velocity = '';
});
}
/// What should be done at the end of the gesture ?
void _onDragEnd(DragEndDetails details) {
double result = details.velocity.pixelsPerSecond.dx.abs().floorToDouble();
setState(() {
this.velocity = '$result';
});
}
}

You can use the Draggable widget instead. Please try this
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 MaterialApp(
title: _title,
home: Scaffold(
appBar: AppBar(title: const Text(_title)),
body: const MyStatefulWidget(),
),
);
}
}
class MyStatefulWidget extends StatefulWidget {
const MyStatefulWidget({Key? key}) : super(key: key);
#override
State<MyStatefulWidget> createState() => _MyStatefulWidgetState();
}
class _MyStatefulWidgetState extends State<MyStatefulWidget> {
int acceptedData = 0;
#override
Widget build(BuildContext context) {
return Row(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: <Widget>[
Draggable<int>(
// Data is the value this Draggable stores.
data: 10,
feedback: Container(
color: Colors.deepOrange,
height: 100,
width: 100,
child: const Icon(Icons.directions_run),
),
childWhenDragging: Container(
height: 100.0,
width: 100.0,
color: Colors.pinkAccent,
child: const Center(
child: Text('Child When Dragging'),
),
),
child: Container(
height: 100.0,
width: 100.0,
color: Colors.lightGreenAccent,
child: const Center(
child: Text('Draggable'),
),
),
),
DragTarget<int>(
builder: (
BuildContext context,
List<dynamic> accepted,
List<dynamic> rejected,
) {
return Container(
height: 100.0,
width: 100.0,
color: Colors.cyan,
child: Center(
child: Text('Value is updated to: $acceptedData'),
),
);
},
onAccept: (int data) {
setState(() {
acceptedData += data;
});
},
),
],
);
}
}

Related

Flutter dragAnchorStrategy keep center on virtual point on screen

I want the feedback icon to follow my cursor precisely everywhere on screen, but it's clear from this example that it doesn't:
According to my research this is however the purpose role of pointerDragAnchorStrategy.
This is my code:
import 'package:flutter/material.dart';
class DragTest extends StatelessWidget {
const DragTest({Key? key}) : super(key: key);
#override
Widget build(BuildContext context) {
return Scaffold(
body: SafeArea(
child: Column(children: const <Widget>[
Draggable(
dragAnchorStrategy: pointerDragAnchorStrategy,
feedback: Icon(Icons.circle),
child: Text('DRAG ME'),
),
SizedBox(height: 700),
Draggable(
dragAnchorStrategy: pointerDragAnchorStrategy,
feedback: Icon(Icons.circle),
child: Text('DRAG ME 2'),
),
]),
));
}
}
Even setting an offset manually, I have the exact same behavior:
dragAnchorStrategy:
(Draggable<Object> _, BuildContext __, Offset ___) =>
const Offset(0, 0),
For dragAnchorStrategy use the below offset:
Offset dragAnchorStrategy(
Draggable<Object> d, BuildContext context, Offset point) {
return Offset(d.feedbackOffset.dx + 50, d.feedbackOffset.dy + 50);
}
example widget:
Widget _buildItem({
required String item,
}) {
return Draggable<String>(
data: item,
dragAnchorStrategy: dragAnchorStrategy,
feedback: Icon(Icons.circle),
child: Text(item),
);
}
Full example:
import 'dart:math' as math;
import 'package:flutter/material.dart';
class ExampleDragTarget extends StatefulWidget {
const ExampleDragTarget({super.key});
#override
ExampleDragTargetState createState() => ExampleDragTargetState();
}
class ExampleDragTargetState extends State<ExampleDragTarget> {
Color _color = Colors.grey;
void _handleAccept(Color data) {
setState(() {
_color = data;
});
}
#override
Widget build(BuildContext context) {
return DragTarget<Color>(
onAccept: _handleAccept,
builder: (BuildContext context, List<Color?> data, List<dynamic> rejectedData) {
return Container(
height: 100.0,
margin: const EdgeInsets.all(10.0),
decoration: BoxDecoration(
color: data.isEmpty ? _color : Colors.grey.shade200,
border: Border.all(
width: 3.0,
color: data.isEmpty ? Colors.white : Colors.blue,
),
),
);
},
);
}
}
class Dot extends StatefulWidget {
const Dot({ super.key, this.color, this.size, this.child, this.tappable = false });
final Color? color;
final double? size;
final Widget? child;
final bool tappable;
#override
DotState createState() => DotState();
}
class DotState extends State<Dot> {
int taps = 0;
#override
Widget build(BuildContext context) {
return GestureDetector(
onTap: widget.tappable ? () { setState(() { taps += 1; }); } : null,
child: Container(
width: widget.size,
height: widget.size,
decoration: BoxDecoration(
color: widget.color,
border: Border.all(width: taps.toDouble()),
shape: BoxShape.circle,
),
child: widget.child,
),
);
}
}
class ExampleDragSource extends StatelessWidget {
const ExampleDragSource({
super.key,
this.color,
this.heavy = false,
this.under = true,
this.child,
});
final Color? color;
final bool heavy;
final bool under;
final Widget? child;
static const double kDotSize = 50.0;
static const double kHeavyMultiplier = 1.5;
static const double kFingerSize = 50.0;
#override
Widget build(BuildContext context) {
double size = kDotSize;
if (heavy) {
size *= kHeavyMultiplier;
}
final Widget contents = DefaultTextStyle(
style: Theme.of(context).textTheme.bodyMedium!,
textAlign: TextAlign.center,
child: Dot(
color: color,
size: size,
child: Center(child: child),
),
);
Widget feedback = Opacity(
opacity: 0.75,
child: contents,
);
Offset feedbackOffset;
DragAnchorStrategy dragAnchorStrategy;
if (!under) {
feedback = Transform(
transform: Matrix4.identity()
..translate(-size / 2.0, -(size / 2.0 + kFingerSize)),
child: feedback,
);
feedbackOffset = const Offset(0.0, -kFingerSize);
dragAnchorStrategy = (Draggable<Object> draggable,
BuildContext context, Offset position) {
return Offset.zero;
};
} else {
feedbackOffset = Offset.zero;
dragAnchorStrategy = childDragAnchorStrategy;
}
if (heavy) {
return LongPressDraggable<Color>(
data: color,
feedback: feedback,
feedbackOffset: feedbackOffset,
dragAnchorStrategy: dragAnchorStrategy,
child: contents,
);
} else {
return Draggable<Color>(
data: color,
feedback: feedback,
feedbackOffset: feedbackOffset,
dragAnchorStrategy: dragAnchorStrategy,
child: contents,
);
}
}
}
class DashOutlineCirclePainter extends CustomPainter {
const DashOutlineCirclePainter();
static const int segments = 17;
static const double deltaTheta = math.pi * 2 / segments; // radians
static const double segmentArc = deltaTheta / 2.0; // radians
static const double startOffset = 1.0; // radians
#override
void paint(Canvas canvas, Size size) {
final double radius = size.shortestSide / 2.0;
final Paint paint = Paint()
..color = const Color(0xFF000000)
..style = PaintingStyle.stroke
..strokeWidth = radius / 10.0;
final Path path = Path();
final Rect box = Offset.zero & size;
for (double theta = 0.0; theta < math.pi * 2.0; theta += deltaTheta) {
path.addArc(box, theta + startOffset, segmentArc);
}
canvas.drawPath(path, paint);
}
#override
bool shouldRepaint(DashOutlineCirclePainter oldDelegate) => false;
}
class MovableBall extends StatelessWidget {
const MovableBall(this.position, this.ballPosition, this.callback, {super.key});
final int position;
final int ballPosition;
final ValueChanged<int> callback;
static final GlobalKey kBallKey = GlobalKey();
static const double kBallSize = 50.0;
#override
Widget build(BuildContext context) {
final Widget ball = DefaultTextStyle(
style: Theme.of(context).primaryTextTheme.bodyMedium!,
textAlign: TextAlign.center,
child: Dot(
key: kBallKey,
color: Colors.blue.shade700,
size: kBallSize,
tappable: true,
child: const Center(child: Text('BALL')),
),
);
const Widget dashedBall = SizedBox(
width: kBallSize,
height: kBallSize,
child: CustomPaint(
painter: DashOutlineCirclePainter()
),
);
if (position == ballPosition) {
return Draggable<bool>(
data: true,
childWhenDragging: dashedBall,
feedback: ball,
maxSimultaneousDrags: 1,
child: ball,
);
} else {
return DragTarget<bool>(
onAccept: (bool data) { callback(position); },
builder: (BuildContext context, List<bool?> accepted, List<dynamic> rejected) {
return dashedBall;
},
);
}
}
}
class DragAndDropApp extends StatefulWidget {
const DragAndDropApp({super.key});
#override
DragAndDropAppState createState() => DragAndDropAppState();
}
class DragAndDropAppState extends State<DragAndDropApp> {
int position = 1;
void moveBall(int newPosition) {
setState(() { position = newPosition; });
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text('Drag and Drop Flutter Demo'),
),
body: Column(
children: <Widget>[
Expanded(
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceAround,
children: <Widget>[
ExampleDragSource(
color: Colors.yellow.shade300,
child: const Text('under'),
),
ExampleDragSource(
color: Colors.green.shade300,
under: false,
heavy: true,
child: const Text('long-press above'),
),
ExampleDragSource(
color: Colors.indigo.shade300,
under: false,
child: const Text('above'),
),
],
),
),
Expanded(
child: Row(
children: const <Widget>[
Expanded(child: ExampleDragTarget()),
Expanded(child: ExampleDragTarget()),
Expanded(child: ExampleDragTarget()),
Expanded(child: ExampleDragTarget()),
],
),
),
Expanded(
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceAround,
children: <Widget>[
MovableBall(1, position, moveBall),
MovableBall(2, position, moveBall),
MovableBall(3, position, moveBall),
],
),
),
],
),
);
}
}
use it:
void main() {
runApp(const MaterialApp(
title: 'Drag and Drop Flutter Demo',
home: DragAndDropApp(),
));
}

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, Flow inside Align does not respond to the alignment rules

I am trying to use the Flow widget of Flutter, aligned to the top-right of the screen, and translating towards left. I tried using Stack + Positioned, I tried using Container + Align.
Flow always sticks to the top-left.
Here is the code I am struggling with;
/// Flutter code sample for Flow
// This example uses the [Flow] widget to create a menu that opens and closes
// as it is interacted with, shown above. The color of the button in the menu
// changes to indicate which one has been selected.
import 'package:flutter/material.dart';
void main() => runApp(FlowApp());
class FlowApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
home: Scaffold(
appBar: AppBar(
title: const Text('Flow Example'),
),
body: FlowMenu(),
),
);
}
}
class FlowMenu extends StatefulWidget {
#override
_FlowMenuState createState() => _FlowMenuState();
}
class _FlowMenuState extends State<FlowMenu>
with SingleTickerProviderStateMixin {
final double buttonDiameter = 40;
AnimationController menuAnimation;
IconData lastTapped = Icons.notifications;
final List<IconData> menuItems = <IconData>[
Icons.home,
Icons.new_releases,
Icons.notifications,
Icons.settings,
Icons.menu,
];
void _updateMenu(IconData icon) {
if (icon != Icons.menu) setState(() => lastTapped = icon);
}
#override
void initState() {
super.initState();
menuAnimation = AnimationController(
duration: const Duration(milliseconds: 250),
vsync: this,
);
}
Widget flowMenuItem(IconData icon) {
// final double buttonDiameter = MediaQuery.of(context).size.width / menuItems.length;
return Padding(
padding: const EdgeInsets.symmetric(vertical: 0.0),
child: RawMaterialButton(
fillColor: lastTapped == icon ? Colors.amber[700] : Colors.blue,
splashColor: Colors.amber[100],
shape: CircleBorder(),
constraints: BoxConstraints.tight(Size(buttonDiameter, buttonDiameter)),
onPressed: () {
_updateMenu(icon);
menuAnimation.status == AnimationStatus.completed
? menuAnimation.reverse()
: menuAnimation.forward();
},
child: Icon(
icon,
color: Colors.white,
size: buttonDiameter - 10,
),
),
);
}
#override
Widget build(BuildContext context) {
return Container(
width: MediaQuery.of(context).size.width,
decoration: BoxDecoration(color: Colors.green),
child: Align(
alignment: Alignment.topRight,
child: Container(
constraints: BoxConstraints.tight(
Size((buttonDiameter) * menuItems.length, buttonDiameter+5)),
decoration: BoxDecoration(color: Colors.red),
child: Align( //why this align does not work?
alignment: Alignment.topRight,
child: Flow(
delegate: FlowMenuDelegate(menuAnimation: menuAnimation),
children: menuItems
.map<Widget>((IconData icon) => flowMenuItem(icon))
.toList(),
),
),
),
),
);
}
}
class FlowMenuDelegate extends FlowDelegate {
FlowMenuDelegate({this.menuAnimation}) : super(repaint: menuAnimation);
final Animation<double> menuAnimation;
#override
bool shouldRepaint(FlowMenuDelegate oldDelegate) {
return menuAnimation != oldDelegate.menuAnimation;
}
#override
void paintChildren(FlowPaintingContext context) {
double dx = 0.0;
for (int i = 0; i < context.childCount; ++i) {
dx = context.getChildSize(i).width * i;
context.paintChild(
i,
transform: Matrix4.translationValues(
-dx * menuAnimation.value,
0,
0,
),
);
}
}
}
And the resulting screen is;
Can you help me with aligning flow to the top right? Thank you so much, for reading or responding.
ps. It says I am not allowed to create tags. So here is a list for the future: Flow Widget, Align Widget, Flutter
Edit
What I am trying to achieve:
and 2. gifs are achieved.
the last one shows how I failed to align it to the right.
It seems the problem is the Padding widget wrapping the RawMaterialButton widget.
So I have replaced it with Align, and also tweaked a little your animation
Working code as follows
/// Flutter code sample for Flow
// This example uses the [Flow] widget to create a menu that opens and closes
// as it is interacted with, shown above. The color of the button in the menu
// changes to indicate which one has been selected.
import 'package:flutter/material.dart';
void main() => runApp(FlowApp());
class FlowApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
home: Scaffold(
appBar: AppBar(
title: const Text('Flow Example'),
),
body: FlowMenu(),
),
);
}
}
class FlowMenu extends StatefulWidget {
#override
_FlowMenuState createState() => _FlowMenuState();
}
class _FlowMenuState extends State<FlowMenu>
with SingleTickerProviderStateMixin {
final double buttonDiameter = 40;
AnimationController menuAnimation;
IconData lastTapped = Icons.notifications;
final List<IconData> menuItems = <IconData>[
Icons.home,
Icons.new_releases,
Icons.notifications,
Icons.settings,
Icons.menu,
];
// #override Size getSize(BoxConstraints constraints) => Size(100, 100);
// #override Size getSize(BoxConstraints constraints) => constraints.biggest / 2;
void _updateMenu(IconData icon) {
if (icon != Icons.menu) setState(() => lastTapped = icon);
}
#override
void initState() {
super.initState();
menuAnimation = AnimationController(
duration: const Duration(milliseconds: 250),
vsync: this,
);
}
Widget flowMenuItem(IconData icon) {
// final double buttonDiameter = MediaQuery.of(context).size.width / menuItems.length;
return Container(
decoration: BoxDecoration(
color: Colors.white
),
child: Align(
// padding: const EdgeInsets.symmetric(vertical: 0.0),
alignment: Alignment.topRight,
child: RawMaterialButton(
fillColor: lastTapped == icon ? Colors.amber[700] : Colors.blue,
splashColor: Colors.amber[100],
shape: CircleBorder(),
constraints: BoxConstraints.tight(Size(buttonDiameter, buttonDiameter)),
onPressed: () {
_updateMenu(icon);
menuAnimation.status == AnimationStatus.completed
? menuAnimation.reverse()
: menuAnimation.forward();
},
child: Icon(
icon,
color: Colors.white,
size: buttonDiameter - 10,
),
),
),
);
}
#override
Widget build(BuildContext context) {
return Container(
width: MediaQuery.of(context).size.width,
decoration: BoxDecoration(color: Colors.green),
child: Align(
alignment: Alignment.topRight,
child: Container(
constraints: BoxConstraints.tight(
Size(buttonDiameter * (menuItems.length+1), buttonDiameter+5)),
decoration: BoxDecoration(color: Colors.red),
child: Align(
alignment: Alignment.topRight,
child: Flow(
delegate: FlowMenuDelegate(menuAnimation: menuAnimation),
children: menuItems
.map<Widget>((IconData icon) => flowMenuItem(icon))
.toList(),
),
),
),
),
);
}
}
class FlowMenuDelegate extends FlowDelegate {
FlowMenuDelegate({this.menuAnimation}) : super(repaint: menuAnimation);
final Animation<double> menuAnimation;
#override
bool shouldRepaint(FlowMenuDelegate oldDelegate) {
return menuAnimation != oldDelegate.menuAnimation;
}
// #override Size getSize(BoxConstraints constraints) => constraints.biggest / 2;
#override
void paintChildren(FlowPaintingContext context) {
double dx = 0.0;
for (int i = 0; i < context.childCount; ++i) {
dx = 40.0 * i;
context.paintChild(
i,
transform: Matrix4.translationValues(
-dx * menuAnimation.value,
0,
0,
),
);
}
}
}
solution gif