How to stop Multi-Drag in Flutter? - dart

I am developing an application using drag and drop but I want only
one drag at a time on the screen or only one feedback is shown on the
screen. When I am using 2 fingers to drag to boxes both feedback are
shown on the screen. What I have done is if one drag is on the screen
then other boxes are not draggable but If you tap at the same time on
two boxes both box are draggable. How to stop these drags?
Image Example
import 'package:flutter/material.dart';
void main() => runApp(new MyApp());
class MyApp extends StatelessWidget {
// This widget is the root of your application.
#override
Widget build(BuildContext context) {
return new MaterialApp(
title: 'Flutter Demo',
theme: new ThemeData(
primarySwatch: Colors.indigo,
),
home: new MyHomePage(title: 'Flutter Demo Drag Box'),
);
}
}
class MyHomePage extends StatelessWidget {
MyHomePage({Key key, this.title}) : super(key: key);
final String title;
#override
Widget build(BuildContext context) {
return new Scaffold(
appBar: new AppBar(
title: new Text(title),
),
body:
new DragGame(), // This trailing comma makes auto-formatting nicer for build methods.
);
}
}
class DragGame extends StatefulWidget {
#override
_DragGameState createState() => new _DragGameState();
}
class _DragGameState extends State<DragGame> {
int boxNumberIsDragged;
#override
void initState() {
boxNumberIsDragged = null;
super.initState();
}
#override
Widget build(BuildContext context) {
return new Container(
constraints: BoxConstraints.expand(),
color: Colors.grey,
child: new Stack(
children: <Widget>[
buildDraggableBox(1, Colors.red, new Offset(30.0, 100.0)),
buildDraggableBox(2, Colors.yellow, new Offset(30.0, 200.0)),
buildDraggableBox(3, Colors.green, new Offset(30.0, 300.0)),
],
));
}
Widget buildDraggableBox(int boxNumber, Color color, Offset offset) {
return new Draggable(
maxSimultaneousDrags: boxNumberIsDragged == null || boxNumber == boxNumberIsDragged ? 1 : 0,
child: _buildBox(color, offset),
feedback: _buildBox(color, offset),
childWhenDragging: _buildBox(color, offset, onlyBorder: true),
onDragStarted: () {
setState((){
boxNumberIsDragged = boxNumber;
});
},
onDragCompleted: () {
setState((){
boxNumberIsDragged = null;
});
},
onDraggableCanceled: (_,__) {
setState((){
boxNumberIsDragged = null;
});
},
);
}
Widget _buildBox(Color color, Offset offset, {bool onlyBorder: false}) {
return new Container(
height: 50.0,
width: 50.0,
margin: EdgeInsets.only(left: offset.dx, top: offset.dy),
decoration: BoxDecoration(
color: !onlyBorder ? color : Colors.grey,
border: Border.all(color: color)),
);
}
}

Are you looking for maxSimultaneousDrags?
https://docs.flutter.io/flutter/widgets/Draggable-class.html
Edit 1:
onDragStarted is fired only when user started moving the finger. What you need is onTap.
Edit 2:
Please change to onTap => onTapDown. Also use setState to reconstruct the two other widgets when one widget is "tapDowned". Tested on my device. See if this works for you. Good luck.
Widget buildDraggableBox(int boxNumber, Color color, Offset offset) {
return new GestureDetector(
child: Draggable(
maxSimultaneousDrags: boxNumberIsDragged == null || boxNumber == boxNumberIsDragged ? 1 : 0,
child: _buildBox(color, offset),
feedback: _buildBox(color, offset),
childWhenDragging: _buildBox(color, offset, onlyBorder: true),
onDragStarted: () {
setState((){
});
},
onDragCompleted: () {
setState((){
boxNumberIsDragged = null;
});
},
onDraggableCanceled: (_,__) {
setState((){
boxNumberIsDragged = null;
});
},
),
onTapDown: (_) {
setState(() {
boxNumberIsDragged = boxNumber;
});
)
}

Related

How to create a multi-select gridview-layout like android photo app in Flutter?

How do i create a gridview-layout with multi-select feature in Flutter, like android photo app? I was looking for an existing widget but couldn't find one.
What I have at the moment: a gridview-layout with n rows and 2 columns. The cells contain a GridTile-widget with some information and a header text. Now i want to have a functionality like in android photo app, after a long press on one of these tiles, a check-circle appears on the left top corner for all items.
Do i have to build this on my own, or is there an existing Flutter-widget which i didn't find so far?
I also don't know an existing widget, but perhaps this will help you:
import 'package:flutter/material.dart';
import 'package:flutter_staggered_grid_view/flutter_staggered_grid_view.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',
debugShowCheckedModeBanner: false,
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> {
List<String> _imageList = List();
List<int> _selectedIndexList = List();
bool _selectionMode = false;
#override
Widget build(BuildContext context) {
List<Widget> _buttons = List();
if (_selectionMode) {
_buttons.add(IconButton(
icon: Icon(Icons.delete),
onPressed: () {
_selectedIndexList.sort();
print('Delete ${_selectedIndexList.length} items! Index: ${_selectedIndexList.toString()}');
}));
}
return Scaffold(
appBar: AppBar(
title: Text(widget.title),
actions: _buttons,
),
body: _createBody(),
);
}
#override
void initState() {
super.initState();
_imageList.add('https://picsum.photos/800/600/?image=280');
_imageList.add('https://picsum.photos/800/600/?image=281');
_imageList.add('https://picsum.photos/800/600/?image=282');
_imageList.add('https://picsum.photos/800/600/?image=283');
_imageList.add('https://picsum.photos/800/600/?image=284');
}
void _changeSelection({bool enable, int index}) {
_selectionMode = enable;
_selectedIndexList.add(index);
if (index == -1) {
_selectedIndexList.clear();
}
}
Widget _createBody() {
return StaggeredGridView.countBuilder(
crossAxisCount: 2,
mainAxisSpacing: 4.0,
crossAxisSpacing: 4.0,
primary: false,
itemCount: _imageList.length,
itemBuilder: (BuildContext context, int index) {
return getGridTile(index);
},
staggeredTileBuilder: (int index) => StaggeredTile.count(1, 1),
padding: const EdgeInsets.all(4.0),
);
}
GridTile getGridTile(int index) {
if (_selectionMode) {
return GridTile(
header: GridTileBar(
leading: Icon(
_selectedIndexList.contains(index) ? Icons.check_circle_outline : Icons.radio_button_unchecked,
color: _selectedIndexList.contains(index) ? Colors.green : Colors.black,
),
),
child: GestureDetector(
child: Container(
decoration: BoxDecoration(border: Border.all(color: Colors.blue[50], width: 30.0)),
child: Image.network(
_imageList[index],
fit: BoxFit.cover,
),
),
onLongPress: () {
setState(() {
_changeSelection(enable: false, index: -1);
});
},
onTap: () {
setState(() {
if (_selectedIndexList.contains(index)) {
_selectedIndexList.remove(index);
} else {
_selectedIndexList.add(index);
}
});
},
));
} else {
return GridTile(
child: InkResponse(
child: Image.network(
_imageList[index],
fit: BoxFit.cover,
),
onLongPress: () {
setState(() {
_changeSelection(enable: true, index: index);
});
},
),
);
}
}
}
I used staggered grid view to show a grid and grid tiles with a header to have a space for the selection icon. Hope that helps!
This is a plugin from flutter package You can use this
https://pub.dev/packages/drag_select_grid_view

Flutter set floatingactionbutton text on interval basis

I have the following widget which will fit into a different parent widget into its body section. So in the parent widget I call this widget as below
body: MapsDemo(),. The issue now is that at this section I run an interval where every 30 seconds I want to call api to get all the latest markers.
Currently I print the count down as this codes print("${30 - timer.tick * 1}"); My issue is very simple I have 3 three floating action button and I have given them their id. Thus on each count down for the last floatingaction button which is btn3.text I am trying to set its value as ${30 - timer.tick * 1} but it does not work on this basis. How can I update the count down in the button?
class MapsDemo extends StatefulWidget {
#override
State createState() => MapsDemoState();
}
class MapsDemoState extends State<MapsDemo> {
GoogleMapController mapController;
#override
void initState() {
super.initState();
startTimer(30);
}
startTimer(int index) async {
print("Index30");
print(index);
new Timer.periodic(new Duration(seconds: 1), (timer) {
if ((30 / 1) >= timer.tick) {
print("${30 - timer.tick * 1}");
btn3.text = ${30 - timer.tick * 1};
} else {
timer.cancel();
var responseJson = NetworkUtils.getAllMarkers(
authToken
);
mapController.clearMarkers();
//startTimer(index + 1);
}
});
}
//Map<PermissionGroup, PermissionStatus> permissions = await PermissionHandler().requestPermissions([PermissionGroup.contacts]);import 'package:permission_handler/permission_handler.dart';
#override
Widget build(BuildContext context) {
return Scaffold(
body: Stack(
children: <Widget>[
GoogleMap(
onMapCreated: (GoogleMapController controller) {
mapController = controller;
},
initialCameraPosition: new CameraPosition(target: LatLng(3.326411697920109, 102.13127606037108))
),
Padding(
padding: const EdgeInsets.all(16.0),
child: Align(
alignment: Alignment.topRight,
child: Column(
children: <Widget>[
FloatingActionButton(
onPressed: () => print('button pressed'),
materialTapTargetSize: MaterialTapTargetSize.padded,
backgroundColor: Colors.lightBlue,
child: const Icon(Icons.map, size: 30.0),
heroTag: "btn1",
),
SizedBox(height: 5.0),
FloatingActionButton(
onPressed: () => print('second pressed'),
materialTapTargetSize: MaterialTapTargetSize.padded,
backgroundColor: Colors.lightBlue,
child: const Icon(Icons.add, size: 28.0),
heroTag: "btn2",
),
SizedBox(height: 5.0),
FloatingActionButton(
onPressed: () => print('second pressed'),
materialTapTargetSize: MaterialTapTargetSize.padded,
backgroundColor: Colors.lightBlue,
child: const Icon(Icons.directions_bike, size: 28.0),
heroTag: "btn3",
),
]
)
),
),
]
)
);
}
}
The below will display a countdown from 30 seconds inside of a FloatingActionButton.
import 'dart:async';
import 'package:flutter/material.dart';
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
title: 'FAB Countdown Demo',
home: MyHomePage(),
);
}
}
class MyHomePage extends StatefulWidget {
MyHomePage({Key key, this.title}) : super(key: key);
final String title;
#override
_MyHomePageState createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
static const _defaultSeconds = 30;
Timer _timer;
var _countdownSeconds = 0;
#override
void initState() {
_timer = Timer.periodic(Duration(seconds: 1), (Timer t) => _getTime());
super.initState();
}
#override
void dispose() {
_timer.cancel();
super.dispose();
}
void _getTime() {
setState(() {
if (_countdownSeconds == 0) {
_countdownSeconds = _defaultSeconds;
} else {
_countdownSeconds--;
}
});
}
#override
Widget build(BuildContext context) {
return Scaffold(
resizeToAvoidBottomPadding: false,
appBar: AppBar(
title: const Text('FAB Countdown Demo'),
),
floatingActionButton: FloatingActionButton(
child: Text('$_countdownSeconds'), onPressed: () {}),
);
}
}

How to detect the drawer is closed on flutter?

As title. It since that we can detect the drawer is opened, but is this possible to check it is closed or not? Thanks.
I have added this feature in Flutter 2.0.0. Make sure you are using Flutter SDK version >= 2.0.0 to use this.
Simply use a callback in Scaffold
return Scaffold(
appBar: AppBar(
title: Text(widget.title),
),
drawer: NavDrawer(),
onDrawerChanged: (isOpen) {
// write your callback implementation here
print('drawer callback isOpen=$isOpen');
},
endDrawer: NavDrawerEnd(),
onEndDrawerChanged: (isOpen) {
// write your callback implementation here
print('end drawer callback isOpen=$isOpen');
},
body:
...
Pull request merged in 2.0.0: https://github.com/flutter/flutter/pull/67249
Happy coding!
Declare a GlobalKey to reference your drawer:
GlobalKey _drawerKey = GlobalKey();
Put the key in your Drawer:
drawer: Drawer(
key: _drawerKey,
Check if your drawer is visible:
final RenderBox box = _drawerKey.currentContext?.findRenderObject();
if (box != null){
//is visible
} else {
//not visible
}
You can copy paste run full code below
You can wrap Drawer with a StatefulWidget and put callback in initState() and dispose()
initState() will call widget.callback(true); means open
dispose() will call widget.callback(false); means close
Slide also work in this case
code snippet
drawer: CustomDrawer(
callback: (isOpen) {
print("isOpen ${isOpen}");
WidgetsBinding.instance.addPostFrameCallback((_) {
setState(() {
_isDrawerOpen = isOpen;
});
});
},
...
class CustomDrawer extends StatefulWidget {
CustomDrawer({
Key key,
this.elevation = 16.0,
this.child,
this.semanticLabel,
this.callback,
}) : assert(elevation != null && elevation >= 0.0),
super(key: key);
final double elevation;
final Widget child;
final String semanticLabel;
final DrawerCallback callback;
#override
_CustomDrawerState createState() => _CustomDrawerState();
}
class _CustomDrawerState extends State<CustomDrawer> {
#override
void initState() {
if (widget.callback != null) {
widget.callback(true);
}
super.initState();
}
#override
void dispose() {
if (widget.callback != null) {
widget.callback(false);
}
super.dispose();
}
#override
Widget build(BuildContext context) {
return Drawer(
key: widget.key,
elevation: widget.elevation,
semanticLabel: widget.semanticLabel,
child: widget.child);
}
}
working demo
full code
import 'package:flutter/material.dart';
void main() {
runApp(MyApp());
}
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Demo',
theme: ThemeData(
primarySwatch: Colors.blue,
visualDensity: VisualDensity.adaptivePlatformDensity,
),
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> {
bool _isDrawerOpen = false;
int _counter = 0;
void _incrementCounter() {
setState(() {
_counter++;
});
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text(widget.title),
),
drawer: CustomDrawer(
callback: (isOpen) {
print("isOpen ${isOpen}");
WidgetsBinding.instance.addPostFrameCallback((_) {
setState(() {
_isDrawerOpen = isOpen;
});
});
},
child: ListView(
padding: EdgeInsets.zero,
children: <Widget>[
DrawerHeader(
child: Text('Drawer Header'),
decoration: BoxDecoration(
color: Colors.blue,
),
),
ListTile(
title: Text('Item 1'),
onTap: () {
Navigator.push(
context,
MaterialPageRoute(builder: (context) => SecondRoute()),
);
},
),
ListTile(
title: Text('Item 2'),
onTap: () {
// Update the state of the app.
// ...
},
),
],
),
),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
Align(
alignment: Alignment.centerRight,
child: Text(
_isDrawerOpen.toString(),
),
),
Text(
'You have pushed the button this many times:',
),
Text(
'$_counter',
style: Theme.of(context).textTheme.headline4,
),
],
),
),
floatingActionButton: FloatingActionButton(
onPressed: _incrementCounter,
tooltip: 'Increment',
child: Icon(Icons.add),
), // This trailing comma makes auto-formatting nicer for build methods.
);
}
}
class CustomDrawer extends StatefulWidget {
CustomDrawer({
Key key,
this.elevation = 16.0,
this.child,
this.semanticLabel,
this.callback,
}) : assert(elevation != null && elevation >= 0.0),
super(key: key);
final double elevation;
final Widget child;
final String semanticLabel;
final DrawerCallback callback;
#override
_CustomDrawerState createState() => _CustomDrawerState();
}
class _CustomDrawerState extends State<CustomDrawer> {
#override
void initState() {
if (widget.callback != null) {
widget.callback(true);
}
super.initState();
}
#override
void dispose() {
if (widget.callback != null) {
widget.callback(false);
}
super.dispose();
}
#override
Widget build(BuildContext context) {
return Drawer(
key: widget.key,
elevation: widget.elevation,
semanticLabel: widget.semanticLabel,
child: widget.child);
}
}
class SecondRoute extends StatelessWidget {
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text("route test"),
),
body: Text("SecondRoute"));
}
}
You can simply use onDrawerChanged for detecting if the drawer is opened or closed in the Scaffold widget.
Property :
{void Function(bool)? onDrawerChanged}
Type: void Function(bool)?
Optional callback that is called when the Scaffold.drawer is opened or closed.
Example :
#override Widget build(BuildContext context) {
return Scaffold(
onDrawerChanged:(val){
if(val){
setState(() {
//foo bar;
});
}else{
setState(() {
//foo bar;
});
}
},
drawer: Drawer(
child: Container(
)
));
}
When you click a Drawer Item where you will navigate to a new screen, there in the Navigator.push(..) call, you can add a .then(..) clause, and then know when the Drawer item Screen has been popped.
Here is the ListTile for a Drawer item which makes the Navigator.push(..) call when clicked , and the the associated .then(..) callback block:
ListTile(
title: Text('About App'),
onTap: () {
Navigator.push(
_ctxt,
MaterialPageRoute(builder: (context) => AboutScreen()),
).then(
(value) {
print('Drawer callback for About selection');
if (_onReadyCallback != null) {
_onReadyCallback();
}
},
);
}),
_onReadyCallback() represents a Function param you can pass in.
I found this is approach - of leveraging the .then() callback from a .push() call - to be a very useful concept to understand with Flutter in general.
Big thanks to the main 2 answers here:
Force Flutter navigator to reload state when popping
Here's the complete Drawer code:
Drawer drawer = Drawer(
child: ListView(
padding: EdgeInsets.zero,
children: <Widget>[
DrawerHeader(
decoration: BoxDecoration(
color: Color(0xFF7FAD5F),
),
child: Text(App.NAME_MENU),
),
ListTile(
title: Text('About App'),
onTap: () {
Navigator.push(
_ctxt,
MaterialPageRoute(builder: (context) => AboutScreen()),
).then(
(value) {
print('Drawer callback for About selection');
if (_onReadyCallback != null) {
_onReadyCallback();
}
},
);
}),
],
),
);
I would recommend that you use this package : https://pub.dev/packages/visibility_detector.
Afterwards you should assign a GlobalKey, like _drawerKey for instance, to the Drawer widget, after which you would be able to detect when the drawer is closed like this:
VisibilityDetector(
key: _drawerKey,
child: Container(),
onVisibilityChanged: (info) {
if (info.visibleFraction == 0.0) {
// drawer not visible.
}
},
)

How to achieve scrollable canvas in Flutter?

I'm a experienced iOS developer, but completely new to the Flutter. Right now I'm facing a problem with ScrollView in Flutter.
What I want to achieve is building a large scrollable canvas. I did it on iOS before, you can see the screenshot here.
The canvas is a big UIScrollView, and each subview on the canvas is draggable, so I can place them at will. Even if the text is very long, I can scroll the canvas to see the full content. Now I need to do the same thing using Flutter.
Currently, I can only drag the text widgets in Flutter. But the parent widget is not scrollable. I know I need to use a scrollable widget in Flutter to achieve the same result, but I just can't make it work. Here's the code I currently have.
void main() {
//debugPaintLayerBordersEnabled = true;
//debugPaintSizeEnabled = true;
runApp(new MyApp());
}
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return new MaterialApp(
title: 'Flutter Demo',
theme: new ThemeData(
primarySwatch: Colors.indigo,
),
home: new MyHomePage(title: 'Flutter Demo Drag Box'),
);
}
}
class MyHomePage extends StatelessWidget {
MyHomePage({Key key, this.title}) : super(key: key);
final String title;
#override
Widget build(BuildContext context) {
return new Scaffold(
appBar: new AppBar(
title: new Text(title),
),
body: DragBox(Offset(0.0, 0.0)));
}
}
class DragBox extends StatefulWidget {
final Offset position; // widget's position
DragBox(this.position);
#override
_DragBoxState createState() => new _DragBoxState();
}
class _DragBoxState extends State<DragBox> {
Offset _previousOffset;
Offset _offset;
Offset _position;
#override
void initState() {
_offset = Offset.zero;
_previousOffset = Offset.zero;
_position = widget.position;
super.initState();
}
#override
Widget build(BuildContext context) {
return new Container(
constraints: BoxConstraints.expand(),
color: Colors.white24,
child: Stack(
children: <Widget>[
buildDraggableBox(1, Colors.red, _offset)
],
)
);
}
Widget buildDraggableBox(int boxNumber, Color color, Offset offset) {
print('buildDraggableBox $boxNumber !');
return new Stack(
children: <Widget>[
new Positioned(
left: _position.dx,
top: _position.dy,
child: Draggable(
child: _buildBox(color, offset),
feedback: _buildBox(color, offset),
//childWhenDragging: _buildBox(color, offset, onlyBorder: true),
onDragStarted: () {
print('Drag started !');
setState(() {
_previousOffset = _offset;
});
print('Start position: $_position}');
},
onDragCompleted: () {
print('Drag complete !');
},
onDraggableCanceled: (Velocity velocity, Offset offset) {
// update position here
setState(() {
Offset _offset = Offset(offset.dx, offset.dy - 80);
_position = _offset;
print('Drag canceled position: $_position');
});
},
),
)
],
);
}
Widget _buildBox(Color color, Offset offset, {bool onlyBorder: false}) {
return new Container(
child: new Text('Flutter widget',
textAlign: TextAlign.center,
style: new TextStyle(fontWeight: FontWeight.bold, fontSize: 25.0)),
);
}
}
Any suggestions or code samples would be really helpful to me.
PS: Please forget about the rulers on the screenshot, it's not the most important thing to me right now. I just need a big scrollable canvas now.
The below Code may help to resolve your problem it scroll the custom canvas in horizontal direction as you have shown in example image.
import 'package:flutter/material.dart';
class MyScroll extends StatelessWidget {
#override
Widget build(BuildContext context) {
return new MaterialApp(
title: 'Flutter Demo',
theme: new ThemeData(
primarySwatch: Colors.blue,
),
home: new MyHomePage(title: 'Canvas Scroller'),
);
}
}
class MyHomePage extends StatefulWidget {
MyHomePage({Key key, this.title}) : super(key: key);
final String title;
#override
_MyHomePageState createState() => new _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
#override
Widget build(BuildContext context) {
final width = MediaQuery.of(context).size.width;
final height = MediaQuery.of(context).size.height;
return new Scaffold(
appBar: new AppBar(
title: new Text(widget.title),
),
body: new Center(
child: new SingleChildScrollView(
scrollDirection: Axis.horizontal,
child: new CustomPaint(
painter: new MyCanvasView(),
size: new Size(width*2, height/2),
),
),
),
);
}
}
class MyCanvasView extends CustomPainter{
#override
void paint(Canvas canvas, Size size) {
var paint = new Paint();
paint..shader = new LinearGradient(colors: [Colors.yellow[700], Colors.redAccent],
begin: Alignment.centerRight, end: Alignment.centerLeft).createShader(new Offset(0.0, 0.0)&size);
canvas.drawRect(new Offset(0.0, 0.0)&size, paint);
var path = new Path();
path.moveTo(0.0, size.height);
path.lineTo(1*size.width/4, 0*size.height/4);
path.lineTo(2*size.width/4, 2*size.height/4);
path.lineTo(3*size.width/4, 0*size.height/4);
path.lineTo(4*size.width/4, 4*size.height/4);
canvas.drawPath(path, new Paint()..color = Colors.yellow ..strokeWidth = 4.0 .. style = PaintingStyle.stroke);
}
#override
bool shouldRepaint(CustomPainter oldDelegate) {
return false;
}
}

Constraining Draggable area

I'm attempting to create a draggable slider-like widget (like a confirm slider). My question is if there is a way to constrain the draggable area?
import 'package:flutter/material.dart';
import 'confirmation_slider.dart';
void main() => runApp(new MyApp());
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return new MaterialApp(
home: new Scaffold(
body: new ListView(
children: <Widget>[
new Container(
margin: EdgeInsets.only(
top: 50.0
),
),
new Container(
margin: EdgeInsets.only(
left: 50.0,
right: 50.0
),
child: new Draggable(
axis: Axis.horizontal,
child: new FlutterLogo(size: 50.0),
feedback: new FlutterLogo(size: 50.0),
),
height: 50.0,
color: Colors.green
),
],
),
),
);
}
}
I imagined that the container class would constrain the draggable area, but it doesn't appear to do that.
No. That's not the goal of Draggable widget. Instead, use a GestureDetector to detect drag. Then combine it with something like Align to move your content around
Here's a fully working slider based on your current code.
import 'package:flutter/material.dart';
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
home: Scaffold(
body: Center(
child: Slider(),
),
),
);
}
}
class Slider extends StatefulWidget {
final ValueChanged<double> valueChanged;
Slider({this.valueChanged});
#override
SliderState createState() {
return new SliderState();
}
}
class SliderState extends State<Slider> {
ValueNotifier<double> valueListener = ValueNotifier(.0);
#override
void initState() {
valueListener.addListener(notifyParent);
super.initState();
}
void notifyParent() {
if (widget.valueChanged != null) {
widget.valueChanged(valueListener.value);
}
}
#override
Widget build(BuildContext context) {
return Container(
color: Colors.green,
height: 50.0,
padding: EdgeInsets.symmetric(horizontal: 40.0),
child: Builder(
builder: (context) {
final handle = GestureDetector(
onHorizontalDragUpdate: (details) {
valueListener.value = (valueListener.value +
details.delta.dx / context.size.width)
.clamp(.0, 1.0);
},
child: FlutterLogo(size: 50.0),
);
return AnimatedBuilder(
animation: valueListener,
builder: (context, child) {
return Align(
alignment: Alignment(valueListener.value * 2 - 1, .5),
child: child,
);
},
child: handle,
);
},
),
);
}
}
As at 2022 here's a replica of #Remi's answer above, with minor tweaks to handle revisions to flutter/dart since 2018 (e.g. handling null-safety)
import 'package:flutter/material.dart';
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return const MaterialApp(
home: Scaffold(
body: Center(
child: Slider(),
),
),
);
}
}
class Slider extends StatefulWidget {
final ValueChanged<double>? valueChanged;
const Slider({this.valueChanged});
#override
SliderState createState() {
return SliderState();
}
}
class SliderState extends State<Slider> {
ValueNotifier<double> valueListener = ValueNotifier(.0);
#override
void initState() {
valueListener.addListener(notifyParent);
super.initState();
}
void notifyParent() {
if (widget.valueChanged != null) {
widget.valueChanged!(valueListener.value);
}
}
#override
Widget build(BuildContext context) {
return Container(
color: Colors.green,
height: 50.0,
padding: const EdgeInsets.symmetric(horizontal: 40.0),
child: Builder(
builder: (context) {
final handle = GestureDetector(
onHorizontalDragUpdate: (details) {
valueListener.value = (valueListener.value + details.delta.dx / context.size!.width).clamp(.0, 1.0);
},
child: const FlutterLogo(size: 50.0),
);
return AnimatedBuilder(
animation: valueListener,
builder: (context, child) {
return Align(
alignment: Alignment(valueListener.value * 2 - 1, .5),
child: child,
);
},
child: handle,
);
},
),
);
}
}

Resources