Skip to content

Instantly share code, notes, and snippets.

@exavolt
Last active July 13, 2020 05:07
Show Gist options
  • Save exavolt/9a5fdc2b69cc8b84e9a8074da9ccbf50 to your computer and use it in GitHub Desktop.
Save exavolt/9a5fdc2b69cc8b84e9a8074da9ccbf50 to your computer and use it in GitHub Desktop.
A color choice bar similar to Keep's and Instagram's
//
import 'package:flutter/material.dart';
class ColorChoiceController extends ValueNotifier<ColorChoiceValue> {
ColorChoiceController([int index])
: super(index != null ? ColorChoiceValue(index) : null);
Color get color => this.value?.color;
int get index => this.value?.index;
}
class ColorChoiceValue {
final int index;
final Color color;
const ColorChoiceValue(this.index, {this.color});
}
class ColorChoiceBar extends StatefulWidget {
final ColorChoiceController controller;
final List<Color> colors;
final EdgeInsets padding;
ColorChoiceBar({
Key key,
this.controller,
this.colors,
this.padding,
}) : assert(colors == null || colors.isNotEmpty),
super(key: key);
@override
State<StatefulWidget> createState() => _ColorChoiceBarState();
}
class _ColorChoiceBarState extends State<ColorChoiceBar> {
final double _containerSize = 48;
final double _itemSize = 32;
List<Color> _colors;
List<double> _colorLuminances; // Computed luminance for each color
int _currentIndex = 0;
@override
void initState() {
super.initState();
_syncState();
}
@override
void didUpdateWidget(ColorChoiceBar oldWidget) {
if (oldWidget.controller != widget.controller) {
oldWidget.controller.removeListener(_onControllerValueChange);
_syncStateInternal();
widget.controller.addListener(_onControllerValueChange);
}
super.didUpdateWidget(oldWidget);
}
@override
void dispose() {
widget.controller?.removeListener(_onControllerValueChange);
super.dispose();
}
void _onControllerValueChange() {
setState(_syncState);
}
// Synchronize this state's state with controller's value.
// This method won't result in infinite loop.
void _syncState() {
widget.controller?.removeListener(_onControllerValueChange);
_syncStateInternal();
widget.controller?.addListener(_onControllerValueChange);
}
// Actual synchronization. Calling this method could result in infinite
// loop if not properly-guarded.
void _syncStateInternal() {
if (_colors == null || _colors != widget.colors) {
_colors = widget.colors ?? Colors.primaries;
_colorLuminances = _colors
.map<double>(
(e) => e.computeLuminance(),
)
.toList(growable: false);
}
if (widget.controller != null) {
_currentIndex = widget.controller?.index?.clamp(
0,
_colors.length - 1,
) ??
0;
widget.controller.value = ColorChoiceValue(
_currentIndex,
color: _colors[_currentIndex],
);
}
}
void _setValue(int index) {
widget.controller?.removeListener(_onControllerValueChange);
_currentIndex = index;
widget.controller?.value = ColorChoiceValue(
index,
color: _colors[index],
);
widget.controller?.addListener(_onControllerValueChange);
}
@override
Widget build(BuildContext context) {
final listView = Row(
children: _colors.map<Widget>(
(color) {
final index = _colors.indexOf(color);
return Container(
width: _containerSize,
height: _containerSize,
child: Material(
color: Colors.transparent,
child: InkWell(
customBorder: CircleBorder(),
onTap: () {
setState(() {
_setValue(index);
});
},
child: Center(
child: SizedBox(
width: _itemSize,
height: _itemSize,
child: Container(
decoration: BoxDecoration(
shape: BoxShape.circle,
color: color,
border: Border.all(color: Colors.white)),
child: index == _currentIndex
? Icon(
Icons.check,
color: _colorLuminances[index] <= 0.5
? Colors.white
: Colors.black,
)
: null),
),
),
),
),
);
},
).toList(growable: false));
final listViewScroller = SingleChildScrollView(
scrollDirection: Axis.horizontal,
child: widget.padding == null
? listView
: Padding(
padding: widget.padding,
child: listView,
));
return Container(height: _containerSize, child: listViewScroller);
}
}
@exavolt
Copy link
Author

exavolt commented Jul 7, 2020

Light
ColorChoiceBarLight

Dark
ColorChoiceBarDark

@exavolt
Copy link
Author

exavolt commented Jul 13, 2020

Rev. 4: fix state's lifecycle by properly reconnect to the controller on widgets change.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment