Search code examples
flutterdartslider

How to customize a valueIndicatorTextStyle depending of a condition (Flutter Slider)


I'm trying to customize the color of the valueIndicator text of a flutter slider depending of a condition, like this:

SliderThemeData(
                valueIndicatorColor: Colors.transparent,
                valueIndicatorTextStyle: TextStyle(color: _sliderValue > 0 ? Colors.orange : Colors.grey),
               )

Currently, I calculated the tick divisions by 20. However, if for example the user is located in the tick 20 and then goes to the tick 0, the valueIndicatorTextStyle condition will be FALSE and the value indicator will take the color gray, but I don't see this change until the user moves again the thumb to the tick 20 (like a lazy rebuild)

When the user is located in the tick 20

When the user is located in the tick 0 (Here the text have to be gray

Here, the user moves again to the tick 20 and here is showing the gray color that have to be shown in the tick 0

Someone can help me?

Thanks!

UPDATES

The following is the full code of my custom slider widget:

import 'package:flutter/material.dart';

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

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

class _CustomSliderState extends State<CustomSlider> {
  late double _sliderValue;
  late final int _currentDivisor;
  late int _currentDivisions;
  late int _maxPoints;

  // late Color valueIndicatorColor;

  @override
  void initState() {
    _currentDivisor = 20;
    // valueIndicatorColor = Colors.orange;
    _currentDivisions = 5;
    _maxPoints = 100;
    _sliderValue = _currentDivisor.toDouble();
    super.initState();
  }

  void _updateCurrentSliderValue(double points) {
    setState(() {
      _sliderValue = points;
      // valueIndicatorColor = _sliderValue == 0 ? Colors.grey : Colors.orange;
      // print(_sliderValue == 0 ? "Colors.grey" : "Colors.orange");
    });
  }

  @override
  Widget build(BuildContext context) {
    print("rebuild");
    print(_sliderValue);
    return SliderTheme(
        data: SliderThemeData(
            valueIndicatorColor: Colors.transparent,
            disabledThumbColor: Colors.black,
            // valueIndicatorTextStyle: TextStyle(color: valueIndicatorColor, fontWeight: FontWeight.bold),
            valueIndicatorTextStyle: TextStyle(color: _sliderValue == 0.0 ? Colors.grey : Colors.orange, fontWeight: FontWeight.bold)),
        child: Slider(
            activeColor: _sliderValue > 0.0 ? Colors.black : Colors.grey.withOpacity(0.8),
            inactiveColor: _sliderValue > 0.0 ? Colors.black : Colors.grey.withOpacity(0.8),
            min: 0.0,
            max: _maxPoints.toDouble(),
            label: "${_sliderValue.toInt()} points",
            value: _sliderValue,
            divisions: _currentDivisions,
            onChanged: _updateCurrentSliderValue));
  }
}

Solution

  • It looks like Slider caches the indicator label painter somehow and doesn't update it until the following change. So, the only workaround is to create a custom indicator.

    Below is a custom indicator implemented with a combination of LayoutBuilder, Stack, AnimatedOpacity, AnimatedAlign, and a simple Text.

    Check out the live demo on DartPad.

    Screenshot

    And this is the code:

    import 'package:flutter/material.dart';
    import 'package:intl/intl.dart';
    
    void main() {
      runApp(const MyApp());
    }
    
    class MyApp extends StatelessWidget {
      const MyApp({Key? key}) : super(key: key);
    
      @override
      Widget build(BuildContext context) {
        return MaterialApp(
          title: 'Flutter Demo',
          theme: ThemeData(
            primarySwatch: Colors.blue,
          ),
          home: const Scaffold(
            body: Padding(
              padding: EdgeInsets.all(24),
              child: CustomSlider(),
            ),
          ),
          debugShowCheckedModeBanner: false,
        );
      }
    }
    
    class CustomSlider extends StatefulWidget {
      const CustomSlider({Key? key}) : super(key: key);
    
      @override
      State<CustomSlider> createState() => _CustomSliderState();
    }
    
    class _CustomSliderState extends State<CustomSlider> {
      static const _lowerIndicatorTextStyle = TextStyle(color: Colors.grey);
      static final _higherIndicatorTextStyle = TextStyle(color: Colors.orange[900]);
      static final _indicatorFormat = NumberFormat('0.0');
      static const _sliderPadding = 16;
      static const _indicatorMargin = -38;
      static const _maxPoints = 100.0;
      static const _alignCenterFraction = 0.5;
      static const _alignMax = 1.0;
      static const _currentDivisions = 10;
      double _sliderValue = 0;
      bool _isSliding = false;
    
      @override
      Widget build(BuildContext context) {
        return Stack(
          children: [
            LayoutBuilder(builder: (context, constraints) {
              final leftPaddingOffset = _sliderPadding / constraints.maxWidth;
              final maxWidth =
                  (_alignMax - _sliderPadding * 2 / constraints.maxWidth);
              final offsetX = _sliderValue / _maxPoints;
              final offsetY = _indicatorMargin / constraints.maxHeight;
    
              return Stack(
                children: [
                  Slider(
                    value: _sliderValue,
                    min: 0,
                    max: _maxPoints,
                    activeColor: _sliderValue > 0.0
                        ? Colors.black
                        : Colors.grey.withOpacity(0.8),
                    inactiveColor: _sliderValue > 0.0
                        ? Colors.black
                        : Colors.grey.withOpacity(0.8),
                    divisions: _currentDivisions,
                    onChanged: (value) => setState(() => _sliderValue = value),
                    onChangeStart: (_) => setState(() => _isSliding = true),
                    onChangeEnd: (_) => setState(() => _isSliding = false),
                  ),
                  AnimatedOpacity(
                    opacity: _isSliding ? 1 : 0,
                    duration: const Duration(milliseconds: 200),
                    child: AnimatedAlign(
                      duration: const Duration(milliseconds: 100),
                      alignment: FractionalOffset(
                        leftPaddingOffset + offsetX * maxWidth,
                        _alignCenterFraction + offsetY,
                      ),
                      child: Text(
                        '${_indicatorFormat.format(_sliderValue)} points',
                        style: _sliderValue > 0
                            ? _higherIndicatorTextStyle
                            : _lowerIndicatorTextStyle,
                      ),
                    ),
                  ),
                ],
              );
            }),
          ],
        );
      }
    }
    

    Old answer - it doesn't work

    To make it work this way, by changing the MaterialApp.theme, you'd need to use some global state management like provider and change the provider model state, let's say SliderModel.sliderValue instead. And also, MaterialApp would need to be wrapped around a Consumer<SliderModel>.