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