Constraining Draggable area
Asked Answered
S

2

12

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.

Sway answered 6/7, 2018 at 19:42 Comment(0)
K
18

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,
          );
        },
      ),
    );
  }
}
Kenaz answered 6/7, 2018 at 20:3 Comment(5)
Is this still the best approach? And why do we need the ValueChanged and notifyParent?Sulphone
This was super helpful - thank you @rémi-rousselet! One small change is to modify the calculation in onHorizontalDragUpdate()... in Rémi's solution, it is calculating based on the container's full width... it should be calculated based on the coordinates of the centre of the child when it is all the way to the left and and when it is all the way to the right. ie. stripping out the margins and half the child's width on both sides. eg. instead of using context.size.width, it should be using: context.size.width - (40+25) - (40+25). If we don't do this, the child position lags behind the touchJiggered
@rémi-rousselet drag not working when long press. Is it possible to drag when long press?Crayfish
``` onLongPressStart: (v) {}, onLongPressEnd: (v) {}, onHorizontalDragUpdate: (details) { ```Crayfish
please check this isse github.com/flutter/flutter/issues/73252#issue-777829850Crayfish
H
3

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,
          );
        },
      ),
    );
  }
}
Holography answered 1/10, 2022 at 8:22 Comment(2)
Was able to run this for a Horizontal widget slider - thanks. However, am not seeing how that green bar is positioned - either as horizontal, or the vertical offset for the fixed position. How to apply this for Vertical slider? This is the update I tried: onVerticalDragUpdate: (details) { valueListener.value = (valueListener.value + details.delta.dy / context.size!.height).clamp(1.0, .0);},Kablesh
@GeneBo, invert Alignment widget parameters. The first one is x, and the second y.Tabby

© 2022 - 2024 — McMap. All rights reserved.