Valdemar
Valdemar

Reputation: 125

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.

Upvotes: 12

Views: 5913

Answers (2)

RumbleFish
RumbleFish

Reputation: 2506

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,
          );
        },
      ),
    );
  }
}

Upvotes: 4

R&#233;mi Rousselet
R&#233;mi Rousselet

Reputation: 277297

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,
          );
        },
      ),
    );
  }
}

Upvotes: 18

Related Questions