Created
September 6, 2021 17:22
-
-
Save roipeker/e58dac5ab90e7d0ec13f92d2f6f0e6b7 to your computer and use it in GitHub Desktop.
radio slider widget sample.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import 'package:flutter/material.dart'; | |
void main() { | |
runApp(const MyApp()); | |
} | |
class MyApp extends StatelessWidget { | |
const MyApp({Key? key}) : super(key: key); | |
@override | |
Widget build(BuildContext context) { | |
return MaterialApp( | |
home: Scaffold( | |
appBar: AppBar(), | |
body: Center( | |
child: SizedBox( | |
height: 130, | |
width: 300, | |
child: RadioSlider( | |
innerGaps: 5, | |
itemW: 12, | |
valueCount: 20, | |
value: 1.4, | |
onChange: (v) { | |
print(v); | |
}, | |
), | |
), | |
), | |
), | |
); | |
} | |
} | |
/// --- radio slider. | |
/// rustic implementation. | |
class RadioSlider extends StatefulWidget { | |
final double value; | |
final int innerGaps; | |
final int valueCount; | |
final double itemW; | |
final ScrollController? controller; | |
final ValueChanged<double>? onChange; | |
final Color valueLineColor; | |
final Color gapLineColor; | |
final Color backgroundLineColor; | |
final Color centerLineColor; | |
final TextStyle? textStyle; | |
const RadioSlider({ | |
Key? key, | |
this.textStyle, | |
this.valueLineColor = Colors.deepPurple, | |
this.gapLineColor = Colors.deepPurple, | |
this.backgroundLineColor = Colors.deepPurple, | |
this.centerLineColor = Colors.black, | |
this.controller, | |
this.onChange, | |
this.value = 0, | |
this.innerGaps = 10, | |
this.valueCount = 10, | |
this.itemW = 12, | |
}) : super(key: key); | |
@override | |
_RadioSliderState createState() => _RadioSliderState(); | |
} | |
class _RadioSliderState extends State<RadioSlider> { | |
int get innerGaps => widget.innerGaps; | |
int get valueCount => widget.valueCount; | |
int get totalItems => innerGaps * valueCount + 1; | |
double get itemW => widget.itemW; | |
late ScrollController _scrollController; | |
@override | |
void initState() { | |
_scrollController = widget.controller ?? ScrollController(); | |
_scrollController.addListener(_onScrollChanged); | |
super.initState(); | |
} | |
void _updateScrollValue() { | |
if (!_scrollController.hasClients) { | |
WidgetsBinding.instance! | |
.addPostFrameCallback((timeStamp) => _updateScrollValue()); | |
} | |
var targetValue = widget.value; | |
var pValue = targetValue / valueCount; | |
var pixelValue = _scrollController.position.maxScrollExtent * pValue; | |
_scrollController.jumpTo(pixelValue); | |
} | |
void _onScrollChanged() { | |
if (widget.onChange != null) { | |
var px = _scrollController.position.pixels; | |
var gapValue = (px ~/ widget.itemW) / innerGaps; | |
// var percentValue = gapValue / valueCount; | |
widget.onChange!.call(gapValue); | |
} | |
} | |
void _updateController() { | |
_scrollController.dispose(); | |
_scrollController = widget.controller ?? ScrollController(); | |
_scrollController.addListener(_onScrollChanged); | |
} | |
@override | |
void dispose() { | |
_scrollController.removeListener(_onScrollChanged); | |
_scrollController.dispose(); | |
super.dispose(); | |
} | |
@override | |
void didUpdateWidget(covariant RadioSlider oldWidget) { | |
bool changed = false; | |
if (oldWidget.valueCount != widget.valueCount) { | |
changed = true; | |
} | |
if (oldWidget.value != widget.value) { | |
_updateScrollValue(); | |
changed = true; | |
} | |
if (oldWidget.controller != widget.controller) { | |
_updateController(); | |
changed = true; | |
} | |
if (changed) { | |
setState(() {}); | |
} | |
super.didUpdateWidget(oldWidget); | |
} | |
@override | |
Widget build(BuildContext context) { | |
return LayoutBuilder( | |
builder: (_, constrains) { | |
final centerX = constrains.maxWidth / 2 - 5; | |
return SizedBox( | |
height: 100, | |
child: Stack( | |
children: [ | |
Center( | |
child: DecoratedBox( | |
decoration: BoxDecoration(color: widget.backgroundLineColor), | |
child: SizedBox(width: double.infinity, height: 2), | |
), | |
), | |
ListView.builder( | |
controller: _scrollController, | |
padding: EdgeInsets.symmetric(horizontal: centerX), | |
itemBuilder: (_, idx) { | |
final isGap = idx % innerGaps == 0; | |
final gapNum = idx ~/ innerGaps; | |
late Widget child; | |
if (!isGap) { | |
child = Container( | |
width: 3, | |
height: 12, | |
decoration: BoxDecoration( | |
color: widget.gapLineColor, | |
borderRadius: BorderRadius.circular(4), | |
), | |
); | |
} else { | |
child = Column( | |
mainAxisSize: MainAxisSize.min, | |
children: [ | |
const SizedBox(height: 32), | |
Container( | |
width: 4, | |
height: 40, | |
decoration: BoxDecoration( | |
color: widget.valueLineColor, | |
borderRadius: BorderRadius.circular(4)), | |
), | |
SizedBox( | |
height: 32, | |
child: OverflowBox( | |
child: Text('${gapNum * 1}', style: widget.textStyle), | |
maxWidth: 80, | |
alignment: Alignment.bottomCenter, | |
), | |
), | |
], | |
); | |
} | |
return SizedBox( | |
width: itemW, | |
child: Center( | |
child: child, | |
), | |
); | |
}, | |
itemCount: totalItems, | |
scrollDirection: Axis.horizontal, | |
), | |
Center( | |
child: Container( | |
width: 6, | |
height: 48, | |
decoration: BoxDecoration( | |
color: widget.centerLineColor, | |
borderRadius: BorderRadius.circular(4), | |
), | |
), | |
), | |
], | |
), | |
); | |
}, | |
); | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment