我想从下面的设计中创建具有连续轨迹的自定义滑块,该轨迹将捕捉到一个特定的值,该值在下面显示为黑色矩形。
我可以通过设置定义自定义trackShape
和thumbShape
来重新创建自定义滑块。
我不确定当拇指非常靠近黑色小矩形时如何捕捉拇指,以及如何使黑色矩形可单击。
此行为与具有不连续划分的Slider非常相似,但是它应该是连续的并且黑色小矩形必须是可单击的。
答案 0 :(得分:0)
我最终从https://github.com/tomwyr/step-slider得到了一些启发,独自实现了它:
class SnapSlider extends StatefulWidget {
SnapSlider({
Key key,
this.sliderKey,
this.snapValues = const {},
this.value,
this.onSnapValueChanged,
this.snapDistance = 0.05,
this.animCurve: Curves.fastOutSlowIn,
this.animDuration: const Duration(milliseconds: 350),
this.min: 0.0,
this.max: 1.0,
this.label,
this.divisions,
this.onChanged,
this.onChangeEnd,
this.onChangeStart,
this.activeColor,
this.inactiveColor,
this.semanticFormatterCallback,
}) : assert(snapValues != null),
assert(snapValues.every((it) => it >= min && it <= max),
'Each snap value needs to be within slider values range.'),
super(key: key);
final Key sliderKey;
final Set<double> snapValues;
final double value;
final ValueChanged<double> onSnapValueChanged;
final double snapDistance;
final Curve animCurve;
final Duration animDuration;
final double min;
final double max;
final String label;
final int divisions;
final Color activeColor;
final Color inactiveColor;
final ValueChanged<double> onChanged;
final ValueChanged<double> onChangeEnd;
final ValueChanged<double> onChangeStart;
final SemanticFormatterCallback semanticFormatterCallback;
@override
_StepSliderState createState() => _StepSliderState();
}
class _StepSliderState extends State<SnapSlider>
with SingleTickerProviderStateMixin {
AnimationController _animator;
CurvedAnimation _baseAnim;
Animation<double> _animation;
double _lastSnapValue;
@override
void didUpdateWidget(SnapSlider oldWidget) {
super.didUpdateWidget(oldWidget);
_animator.duration = widget.animDuration;
_baseAnim.curve = widget.animCurve;
}
@override
void initState() {
super.initState();
_animator = AnimationController(
vsync: this, duration: widget.animDuration, value: 1.0);
_baseAnim = CurvedAnimation(parent: _animator, curve: widget.animCurve);
_recreateAnimation(widget.value, widget.value);
_animation.addListener(() {
_onSliderChanged(_animation.value);
widget.onChanged?.call(_animation.value);
});
}
@override
void dispose() {
_animator.dispose();
super.dispose();
}
@override
Widget build(BuildContext context) {
return AnimatedBuilder(
animation: _animator,
builder: (_, __) => Slider(
key: widget.sliderKey,
min: widget.min,
max: widget.max,
label: widget.label,
divisions: widget.divisions,
activeColor: widget.activeColor,
inactiveColor: widget.inactiveColor,
semanticFormatterCallback: widget.semanticFormatterCallback,
value: widget.value,
onChangeStart: (it) {
_animator.stop();
_onSliderChangeStart(it);
widget.onChangeStart?.call(it);
},
onChangeEnd: (it) {
_onSliderChangeEnd(it);
widget.onChangeEnd?.call(it);
},
onChanged: (it) {
_onSliderChanged(it);
widget.onChanged?.call(it);
},
),
);
}
void _onSliderChangeStart(double value) {
}
void _onSliderChangeEnd(double value) {
double snapValue = _closestSnapValue(value);
var distance = (value - snapValue).abs();
if (snapValue != _lastSnapValue) {
if (distance <= widget.snapDistance) {
_animateTo(widget.value, snapValue, true);
widget.onSnapValueChanged?.call(widget.value);
_lastSnapValue = snapValue;
}
} else {
if (distance > widget.snapDistance) {
_lastSnapValue = null;
}
}
}
double _closestSnapValue(double value) {
return widget.snapValues.reduce((a, b) {
var distanceA = (value - a).abs();
var distanceB = (value - b).abs();
return distanceA < distanceB ? a : b;
});
}
void _onSliderChanged(double value) {
}
void _animateTo(double start, double end, bool restart) {
_recreateAnimation(start, end);
_animator.forward(from: 0.0);
}
void _recreateAnimation(double start, double end) {
_animation = Tween(begin: start ?? end, end: end).animate(_baseAnim);
}
}