Jaebi
Jaebi

Reputation: 131

Negative to positive slider in Flutter

I'm trying to implement a slider for temperature, which can go from negative to positive values.

I have found many examples that have sliders that go from left to right, but I have not found one which starts from middle, and goes left (negative) and right (positive).

The attached image shows what I am trying to achieve. Is there a widget or library (I am not sure if it is called Slider) that can achieve the desired widget?

Enter image description here

Upvotes: 5

Views: 1608

Answers (3)

deczaloth
deczaloth

Reputation: 7465

The simple answer

Maybe the Slider Widget provided by Flutter could achieve that nice behaviour you are wishing for.

With this widget you can

  1. define minimum value = -50
  2. define maximum value = 50
  3. set starting point at 0
  4. customize the color of the bar.

I wrote a simple yet complete sample for you that might provide you with a good starting point:

import 'package:flutter/material.dart';

void main() {
  runApp(const MyApp());
}

class MyApp extends StatelessWidget {
  const MyApp({super.key});

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Flutter Demo',
      theme: ThemeData(
        primarySwatch: Colors.blue,
      ),
      home: const MyHomePage(title: 'Flutter Demo Home Page'),
    );
  }
}

class MyHomePage extends StatefulWidget {
  const MyHomePage({super.key, required this.title});

  final String title;

  @override
  State<MyHomePage> createState() => _MyHomePageState();
}

class _MyHomePageState extends State<MyHomePage> {
  void _restartSlider() {
    setState(() {
      _sliderValue=0;
    });
  }

  double _sliderValue = 0;

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text(widget.title),
      ),
      body: Center(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.spaceEvenly,
          children: [
            Text("Slider value is: $_sliderValue",),
            Slider(
              value: _sliderValue,
              activeColor: Colors.red,
              inactiveColor: Colors.green,
              thumbColor: _sliderValue == 0 ? Colors.grey : _sliderValue > 0 ? Colors.green : Colors.red,
              min: -50, max: 50,
              divisions: 100,
              label: _sliderValue.toString(),
              onChanged: (double newValue) {
                setState(() {
                  _sliderValue = newValue;
                });
              },
            ),
          ],
        ),
      ),
      floatingActionButton: FloatingActionButton(
        onPressed: _restartSlider,
        tooltip: 'Restart',
        child: const Icon(Icons.restart_alt),
      ),
    );
  }
}

That code will create a slider "which starts from middle, and goes left (negative) and right (positive)". The result is like this (click the floating button to return the slider thumb to 0):

enter image description here

The elaborated answer

Using Stack widget you can draw a bar that looks exactly as what you showed in your pictures.

Basically you just need to set the Slider's bar color to transparent and draw a Container below that have the colors you want. A complete, working example would be as follows:

import 'package:flutter/material.dart';

void main() {
  runApp(const MyApp());
}

class MyApp extends StatelessWidget {
  const MyApp({super.key});

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Flutter Demo',
      theme: ThemeData(
        primarySwatch: Colors.blue,
      ),
      home: const MyHomePage(title: 'Flutter Demo Home Page'),
    );
  }
}

class MyHomePage extends StatefulWidget {
  const MyHomePage({super.key, required this.title});

  final String title;

  @override
  State<MyHomePage> createState() => _MyHomePageState();
}

class _MyHomePageState extends State<MyHomePage> {
  void _restartSlider() {
    setState(() {
      _sliderValue=0;
    });
  }

  double _sliderValue = 0;

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text(widget.title),
      ),
      body: Center(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.spaceEvenly,
          children: [
            Text("Temperature is: $_sliderValue °C",),
            Stack(alignment: AlignmentDirectional.center,

              children: [
                Padding(
                  padding: const EdgeInsets.all(16.0),
                  child: Row(
                    children: [
                      Expanded(
                        child: Container(
                          height: 7,
                          color: _sliderValue < 0 ? Colors.red : Colors.grey,
                        ),
                      ),
                      Expanded(
                        child: Container(
                          height: 7,
                          color: _sliderValue > 0 ? Colors.green : Colors.grey,
                        ),
                      ),
                    ],
                  ),
                ),
                Slider(
                  value: _sliderValue,
                  activeColor: Colors.transparent,
                  inactiveColor: Colors.transparent,
                  thumbColor: _sliderValue == 0 ? Colors.grey : _sliderValue > 0 ? Colors.green : Colors.red,
                  min: -50, max: 50,
                  divisions: 100,
                  label: _sliderValue.toString(),
                  onChanged: (double newValue) {
                    setState(() {
                      _sliderValue = newValue;
                    });
                  },
                ),
              ],
            ),
          ],
        ),
      ),
      floatingActionButton: FloatingActionButton(
        onPressed: _restartSlider,
        tooltip: 'Restart',
        child: const Icon(Icons.restart_alt),
      ),
    );
  }
}

Which would produce a slider as in the following pictures:

  1. Case Temperature is 0:

    Enter image description here

  2. Case Temperature is -50 °C:

    Enter image description here

  3. Case Temperature is 50 °C:

    Enter image description here

If only that part of the slider until the thumb should be coloured you can use LinearProcessIndicator instead to dinamically only colour a part of the bar. This is done as in the code below:

import 'package:flutter/material.dart';

void main() {
  runApp(const MyApp());
}

class MyApp extends StatelessWidget {
  const MyApp({super.key});

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Flutter Demo',
      theme: ThemeData(
        primarySwatch: Colors.blue,
      ),
      home: const MyHomePage(title: 'Flutter Demo Home Page'),
    );
  }
}

class MyHomePage extends StatefulWidget {
  const MyHomePage({super.key, required this.title});

  final String title;

  @override
  State<MyHomePage> createState() => _MyHomePageState();
}

class _MyHomePageState extends State<MyHomePage> {
  void _restartSlider() {
    setState(() {
      _sliderValue=0;
    });
  }

  double _sliderValue = 0;

  double _min = -60;
  double _max = 80;

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text(widget.title),
      ),
      body: Center(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.spaceEvenly,
          children: [
            Text("Temperature is: $_sliderValue °C",),
            Stack(alignment: AlignmentDirectional.center,
              children: [
                Padding(
                  padding: const EdgeInsets.all(16.0),
                  child: Row(
                    children: [
                      Expanded(
                        child: LinearProgressIndicator(
                          value: 1-_sliderValue/_min,
                          color: Colors.grey,
                          backgroundColor: Colors.red,
                        ),
                        flex:_min.abs().round(),
                      ),
                      Expanded(
                        child: LinearProgressIndicator(
                          value: _sliderValue/_max,
                          color: Colors.green,
                          backgroundColor: Colors.grey,
                        ),
                        flex:_max.abs().round(),
                      ),
                    ],
                  ),
                ),
                Slider(
                  value: _sliderValue,
                  activeColor: Colors.transparent,
                  inactiveColor: Colors.transparent,
                  thumbColor: _sliderValue == 0 ? Colors.grey : _sliderValue > 0 ? Colors.green : Colors.red,
                  min: _min, max: _max,
                  divisions: (_min.abs() + _max.abs()).round(),
                  label: _sliderValue.toString(),
                  onChanged: (double newValue) {
                    setState(() {
                      _sliderValue = newValue;
                    });
                  },
                ),
              ],
            ),
          ],
        ),
      ),
      floatingActionButton: FloatingActionButton(
        onPressed: _restartSlider,
        tooltip: 'Restart',
        child: const Icon(Icons.restart_alt),
      ),
    );
  }
}

which gives the following result:

Enter image description here

Upvotes: 8

peyman jani
peyman jani

Reputation: 1

https://i.sstatic.net/0DmhB.png

You can use this:

import 'package:flutter/material.dart';
import 'package:madeup_flutter/base_class/base_widget.dart';
import 'package:sizer/sizer.dart';

import '../../constants/colors.dart';

class CustomSlider extends StatefulWidget {
  double value;
  final Function? onChange;

  double? maxValue;

  CustomSlider({Key? key, required this.value, this.onChange,
   this.maxValue})
     : super(key: key);

  @override
  State<CustomSlider> createState() => _CustomSliderState();
}

class _CustomSliderState extends BaseFullState<CustomSlider> {
       @override
       Widget build(BuildContext context) {
  return SliderTheme(
    data: SliderTheme.of(context).copyWith(
      trackShape: RectangularSliderTrackShape(),
      trackHeight: 15.0,
      valueIndicatorColor:
        widget.value < 0 ? AppColors.red : AppColors.green200,
      showValueIndicator: ShowValueIndicator.always,
      thumbShape: RoundSliderThumbShape(enabledThumbRadius: 10),
      overlayShape: RoundSliderOverlayShape(overlayRadius: 28.0),
      valueIndicatorTextStyle: TextStyle(
        color: Colors.white, letterSpacing: 2.0, fontFamily: "Roboto")),
      child: Stack(
        children: [
          Positioned.fill(
            child: Container(
              margin: EdgeInsets.symmetric(vertical: 3.h, horizontal: 3.w),
              decoration: BoxDecoration(
                  color: AppColors.grey500,
                  borderRadius: BorderRadius.circular(3.w)),
              child: Row(
                children: List.generate(20, (index) {
                  var color = _getColor(index);
                  var isCircle =
                      (color == AppColors.red && index == (widget.value) + 10) ||
                          (color == AppColors.baseAppColor &&
                              index == (widget.value) + 9);
                  return Expanded(
                    child: Container(
                      decoration: BoxDecoration(
                      borderRadius: BorderRadius.horizontal(
                        left: Radius.circular(isCircle
                            ? (color == AppColors.red ? 5 : 0)
                            : (index == 10 && color == AppColors.baseAppColor
                                ? 5
                                : 0)),
                        right: Radius.circular(isCircle
                            ? (color == AppColors.baseAppColor ? 5 : 0)
                            : (index == 10 && color == AppColors.red ? 5 :
                    0))),
                        color: color,
                    )),
                  );
                }),
              ),
          )),
          Slider(
            value: widget.value,
            max: widget.maxValue ?? 10,
            min: -10,
            divisions: 20,
            activeColor: Colors.transparent,
            inactiveColor: Colors.transparent,
            thumbColor: AppColors.grey800,
            label: widget.value.round().toString() ?? "",
            onChangeEnd: (newValue) {
              widget.onChange?.call(newValue);
            },
            onChanged: (newValue) {
              widget.value = newValue;
              setState(() {});
            },
          ),
        ],
      ),
    );
  }

  _getColor(int index) {
    if (widget.value > 0) {
      if (index < 10) {
        return AppColors.grey500;
      } else if (index < (widget.value + 10)) {
        return AppColors.baseAppColor;
      } else {
        return AppColors.grey500;
      }
    } else {
      if (index > 10) {
        return AppColors.grey500;
      } else if (index < (widget.value + 10)) {
        return AppColors.grey500;
      } else {
        return AppColors.red;
      }
    }
  }
}

Upvotes: 0

Abdul Kadhar
Abdul Kadhar

Reputation: 100

You can use the RangeSlider widget.

Below is the example code for the range slider. If suppose you need to make the center point constant, you can use the onChanged callback method to make the center value constant.

class MyStatefulWidget extends StatefulWidget {
  const MyStatefulWidget({Key? key}) : super(key: key);

  @override
  State<MyStatefulWidget> createState() => _MyStatefulWidgetState();
}

class _MyStatefulWidgetState extends State<MyStatefulWidget> {
  RangeValues _currentRangeValues = const RangeValues(0, 0);

  @override
  Widget build(BuildContext context) {
    return RangeSlider(
      values: _currentRangeValues,
      max: 100,
      min: -100,
      divisions: 150,
      labels: RangeLabels(
        _currentRangeValues.start.round().toString(),
        _currentRangeValues.end.round().toString(),
      ),
      onChanged: (RangeValues values) {
        setState(() {
          _currentRangeValues = values;
        });
      },
    );
  }
}

Upvotes: 1

Related Questions