465 lines
14 KiB
Dart
465 lines
14 KiB
Dart
//Fork from https://github.com/divyanshub024/day_night_switch
|
|
//Copyright https://github.com/divyanshub024
|
|
//Apache License 2.0 https://github.com/divyanshub024/day_night_switch/blob/master/LICENSE
|
|
|
|
|
|
import 'package:flutter/foundation.dart';
|
|
import 'package:flutter/gestures.dart';
|
|
import 'package:flutter/material.dart';
|
|
import 'package:flutter/rendering.dart';
|
|
|
|
const double _kTrackHeight = 80.0;
|
|
const double _kTrackWidth = 160.0;
|
|
const double _kTrackRadius = _kTrackHeight / 2.0;
|
|
const double _kThumbRadius = 36.0;
|
|
const double _kSwitchWidth =
|
|
_kTrackWidth - 2 * _kTrackRadius + 2 * kRadialReactionRadius;
|
|
const double _kSwitchHeight = 2 * kRadialReactionRadius + 8.0;
|
|
|
|
class DayNightSwitch extends StatefulWidget {
|
|
const DayNightSwitch({
|
|
@required this.value,
|
|
@required this.onChanged,
|
|
@required this.onDrag,
|
|
this.dragStartBehavior = DragStartBehavior.start,
|
|
this.height,
|
|
this.moonImage,
|
|
this.sunImage,
|
|
this.sunColor,
|
|
this.moonColor,
|
|
this.dayColor,
|
|
this.nightColor,
|
|
});
|
|
|
|
final bool value;
|
|
final ValueChanged<bool> onChanged;
|
|
final ValueChanged<double> onDrag;
|
|
final DragStartBehavior dragStartBehavior;
|
|
final double height;
|
|
final ImageProvider sunImage;
|
|
final ImageProvider moonImage;
|
|
final Color sunColor;
|
|
final Color moonColor;
|
|
final Color dayColor;
|
|
final Color nightColor;
|
|
|
|
@override
|
|
_DayNightSwitchState createState() => _DayNightSwitchState();
|
|
|
|
@override
|
|
void debugFillProperties(DiagnosticPropertiesBuilder properties) {
|
|
super.debugFillProperties(properties);
|
|
properties.add(FlagProperty('value',
|
|
value: value, ifTrue: 'on', ifFalse: 'off', showName: true));
|
|
properties.add(ObjectFlagProperty<ValueChanged<bool>>(
|
|
'onChanged',
|
|
onChanged,
|
|
ifNull: 'disabled',
|
|
));
|
|
}
|
|
}
|
|
|
|
class _DayNightSwitchState extends State<DayNightSwitch>
|
|
with TickerProviderStateMixin {
|
|
@override
|
|
Widget build(BuildContext context) {
|
|
final Color moonColor = widget.moonColor ?? const Color(0xFFf5f3ce);
|
|
final Color nightColor = widget.nightColor ?? const Color(0xFF003366);
|
|
|
|
Color sunColor = widget.sunColor ?? const Color(0xFFFDB813);
|
|
Color dayColor = widget.dayColor ?? const Color(0xFF87CEEB);
|
|
|
|
return _SwitchRenderObjectWidget(
|
|
dragStartBehavior: widget.dragStartBehavior,
|
|
value: widget.value,
|
|
activeColor: moonColor,
|
|
inactiveColor: sunColor,
|
|
moonImage: widget.moonImage,
|
|
sunImage: widget.sunImage,
|
|
activeTrackColor: nightColor,
|
|
inactiveTrackColor: dayColor,
|
|
configuration: createLocalImageConfiguration(context),
|
|
onChanged: widget.onChanged,
|
|
onDrag: widget.onDrag,
|
|
additionalConstraints:
|
|
BoxConstraints.tight(Size(_kSwitchWidth, _kSwitchHeight)),
|
|
vsync: this,
|
|
);
|
|
}
|
|
}
|
|
|
|
class _SwitchRenderObjectWidget extends LeafRenderObjectWidget {
|
|
const _SwitchRenderObjectWidget({
|
|
Key key,
|
|
this.value,
|
|
this.activeColor,
|
|
this.inactiveColor,
|
|
this.moonImage,
|
|
this.sunImage,
|
|
this.activeTrackColor,
|
|
this.inactiveTrackColor,
|
|
this.configuration,
|
|
this.onChanged,
|
|
this.onDrag,
|
|
this.vsync,
|
|
this.additionalConstraints,
|
|
this.dragStartBehavior,
|
|
}) : super(key: key);
|
|
|
|
final bool value;
|
|
final Color activeColor;
|
|
final Color inactiveColor;
|
|
final ImageProvider moonImage;
|
|
final ImageProvider sunImage;
|
|
final Color activeTrackColor;
|
|
final Color inactiveTrackColor;
|
|
final ImageConfiguration configuration;
|
|
final ValueChanged<bool> onChanged;
|
|
final ValueChanged<double> onDrag;
|
|
final TickerProvider vsync;
|
|
final BoxConstraints additionalConstraints;
|
|
final DragStartBehavior dragStartBehavior;
|
|
|
|
@override
|
|
_RenderSwitch createRenderObject(BuildContext context) {
|
|
return _RenderSwitch(
|
|
dragStartBehavior: dragStartBehavior,
|
|
value: value,
|
|
activeColor: activeColor,
|
|
inactiveColor: inactiveColor,
|
|
moonImage: moonImage,
|
|
sunImage: sunImage,
|
|
activeTrackColor: activeTrackColor,
|
|
inactiveTrackColor: inactiveTrackColor,
|
|
configuration: configuration,
|
|
onChanged: onChanged,
|
|
onDrag: onDrag,
|
|
textDirection: Directionality.of(context),
|
|
additionalConstraints: additionalConstraints,
|
|
vSync: vsync,
|
|
);
|
|
}
|
|
|
|
@override
|
|
void updateRenderObject(BuildContext context, _RenderSwitch renderObject) {
|
|
renderObject
|
|
..value = value
|
|
..activeColor = activeColor
|
|
..inactiveColor = inactiveColor
|
|
..activeThumbImage = moonImage
|
|
..inactiveThumbImage = sunImage
|
|
..activeTrackColor = activeTrackColor
|
|
..inactiveTrackColor = inactiveTrackColor
|
|
..configuration = configuration
|
|
..onChanged = onChanged
|
|
..onDrag = onDrag
|
|
..textDirection = Directionality.of(context)
|
|
..additionalConstraints = additionalConstraints
|
|
..dragStartBehavior = dragStartBehavior
|
|
..vsync = vsync;
|
|
}
|
|
}
|
|
|
|
class _RenderSwitch extends RenderToggleable {
|
|
ValueChanged<double> onDrag;
|
|
_RenderSwitch({
|
|
bool value,
|
|
Color activeColor,
|
|
Color inactiveColor,
|
|
ImageProvider moonImage,
|
|
ImageProvider sunImage,
|
|
Color activeTrackColor,
|
|
Color inactiveTrackColor,
|
|
ImageConfiguration configuration,
|
|
BoxConstraints additionalConstraints,
|
|
@required TextDirection textDirection,
|
|
ValueChanged<bool> onChanged,
|
|
this.onDrag,
|
|
@required TickerProvider vSync,
|
|
DragStartBehavior dragStartBehavior,
|
|
}) : assert(textDirection != null),
|
|
_activeThumbImage = moonImage,
|
|
_inactiveThumbImage = sunImage,
|
|
_activeTrackColor = activeTrackColor,
|
|
_inactiveTrackColor = inactiveTrackColor,
|
|
_configuration = configuration,
|
|
_textDirection = textDirection,
|
|
super(
|
|
value: value,
|
|
tristate: false,
|
|
activeColor: activeColor,
|
|
inactiveColor: inactiveColor,
|
|
onChanged: onChanged,
|
|
additionalConstraints: additionalConstraints,
|
|
vsync: vSync,
|
|
) {
|
|
_drag = HorizontalDragGestureRecognizer()
|
|
..onStart = _handleDragStart
|
|
..onUpdate = _handleDragUpdate
|
|
..onEnd = _handleDragEnd
|
|
..dragStartBehavior = dragStartBehavior;
|
|
}
|
|
|
|
ImageProvider get activeThumbImage => _activeThumbImage;
|
|
ImageProvider _activeThumbImage;
|
|
set activeThumbImage(ImageProvider value) {
|
|
if (value == _activeThumbImage) return;
|
|
_activeThumbImage = value;
|
|
markNeedsPaint();
|
|
}
|
|
|
|
ImageProvider get inactiveThumbImage => _inactiveThumbImage;
|
|
ImageProvider _inactiveThumbImage;
|
|
set inactiveThumbImage(ImageProvider value) {
|
|
if (value == _inactiveThumbImage) return;
|
|
_inactiveThumbImage = value;
|
|
markNeedsPaint();
|
|
}
|
|
|
|
Color get activeTrackColor => _activeTrackColor;
|
|
Color _activeTrackColor;
|
|
set activeTrackColor(Color value) {
|
|
assert(value != null);
|
|
if (value == _activeTrackColor) return;
|
|
_activeTrackColor = value;
|
|
markNeedsPaint();
|
|
}
|
|
|
|
Color get inactiveTrackColor => _inactiveTrackColor;
|
|
Color _inactiveTrackColor;
|
|
set inactiveTrackColor(Color value) {
|
|
assert(value != null);
|
|
if (value == _inactiveTrackColor) return;
|
|
_inactiveTrackColor = value;
|
|
markNeedsPaint();
|
|
}
|
|
|
|
ImageConfiguration get configuration => _configuration;
|
|
ImageConfiguration _configuration;
|
|
set configuration(ImageConfiguration value) {
|
|
assert(value != null);
|
|
if (value == _configuration) return;
|
|
_configuration = value;
|
|
markNeedsPaint();
|
|
}
|
|
|
|
TextDirection get textDirection => _textDirection;
|
|
TextDirection _textDirection;
|
|
set textDirection(TextDirection value) {
|
|
assert(value != null);
|
|
if (_textDirection == value) return;
|
|
_textDirection = value;
|
|
markNeedsPaint();
|
|
}
|
|
|
|
DragStartBehavior get dragStartBehavior => _drag.dragStartBehavior;
|
|
set dragStartBehavior(DragStartBehavior value) {
|
|
assert(value != null);
|
|
if (_drag.dragStartBehavior == value) return;
|
|
_drag.dragStartBehavior = value;
|
|
}
|
|
|
|
@override
|
|
void detach() {
|
|
_cachedThumbPainter?.dispose();
|
|
_cachedThumbPainter = null;
|
|
super.detach();
|
|
}
|
|
|
|
double get _trackInnerLength => size.width - 2.0 * kRadialReactionRadius;
|
|
|
|
HorizontalDragGestureRecognizer _drag;
|
|
|
|
void _handleDragStart(DragStartDetails details) {
|
|
if (isInteractive) reactionController.forward();
|
|
}
|
|
|
|
void _handleDragUpdate(DragUpdateDetails details) {
|
|
if (isInteractive) {
|
|
position
|
|
..curve = null
|
|
..reverseCurve = null;
|
|
final double delta = details.primaryDelta / _trackInnerLength;
|
|
switch (textDirection) {
|
|
case TextDirection.rtl:
|
|
positionController.value -= delta;
|
|
break;
|
|
case TextDirection.ltr:
|
|
positionController.value += delta;
|
|
break;
|
|
}
|
|
positionController.addListener(() {
|
|
onDrag(positionController.value);
|
|
});
|
|
}
|
|
}
|
|
|
|
void _handleDragEnd(DragEndDetails details) {
|
|
if (position.value >= 0.5)
|
|
positionController.forward();
|
|
else
|
|
positionController.reverse();
|
|
reactionController.reverse();
|
|
}
|
|
|
|
@override
|
|
void handleEvent(PointerEvent event, BoxHitTestEntry entry) {
|
|
assert(debugHandleEvent(event, entry));
|
|
if (event is PointerDownEvent && onChanged != null) _drag.addPointer(event);
|
|
super.handleEvent(event, entry);
|
|
}
|
|
|
|
Color _cachedThumbColor;
|
|
ImageProvider _cachedThumbImage;
|
|
BoxPainter _cachedThumbPainter;
|
|
|
|
BoxDecoration _createDefaultThumbDecoration(
|
|
Color color, ImageProvider image) {
|
|
return BoxDecoration(
|
|
color: color,
|
|
image: image == null ? null : DecorationImage(image: image),
|
|
shape: BoxShape.circle,
|
|
boxShadow: kElevationToShadow[1],
|
|
);
|
|
}
|
|
|
|
bool _isPainting = false;
|
|
|
|
void _handleDecorationChanged() {
|
|
// If the image decoration is available synchronously, we'll get called here
|
|
// during paint. There's no reason to mark ourselves as needing paint if we
|
|
// are already in the middle of painting. (In fact, doing so would trigger
|
|
// an assert).
|
|
if (!_isPainting) markNeedsPaint();
|
|
}
|
|
|
|
@override
|
|
void describeSemanticsConfiguration(SemanticsConfiguration config) {
|
|
super.describeSemanticsConfiguration(config);
|
|
config.isToggled = value == true;
|
|
}
|
|
|
|
@override
|
|
void paint(PaintingContext context, Offset offset) {
|
|
final Canvas canvas = context.canvas;
|
|
final bool isEnabled = onChanged != null;
|
|
final double currentValue = position.value;
|
|
|
|
double visualPosition;
|
|
switch (textDirection) {
|
|
case TextDirection.rtl:
|
|
visualPosition = 1.0 - currentValue;
|
|
break;
|
|
case TextDirection.ltr:
|
|
visualPosition = currentValue;
|
|
break;
|
|
}
|
|
|
|
final Color trackColor = isEnabled
|
|
? Color.lerp(inactiveTrackColor, activeTrackColor, currentValue)
|
|
: inactiveTrackColor;
|
|
|
|
final Color thumbColor = isEnabled
|
|
? Color.lerp(inactiveColor, activeColor, currentValue)
|
|
: inactiveColor;
|
|
|
|
final ImageProvider thumbImage = isEnabled
|
|
? (currentValue < 0.5 ? inactiveThumbImage : activeThumbImage)
|
|
: inactiveThumbImage;
|
|
|
|
// Paint the track
|
|
final Paint paint = Paint()..color = trackColor;
|
|
const double trackHorizontalPadding = kRadialReactionRadius - _kTrackRadius;
|
|
final Rect trackRect = Rect.fromLTWH(
|
|
offset.dx + trackHorizontalPadding,
|
|
offset.dy + (size.height - _kTrackHeight) / 2.0,
|
|
size.width - 2.0 * trackHorizontalPadding,
|
|
_kTrackHeight,
|
|
);
|
|
final RRect trackRRect = RRect.fromRectAndRadius(
|
|
trackRect, const Radius.circular(_kTrackRadius));
|
|
canvas.drawRRect(trackRRect, paint);
|
|
|
|
final Offset thumbPosition = Offset(
|
|
kRadialReactionRadius + visualPosition * _trackInnerLength,
|
|
size.height / 2.0,
|
|
);
|
|
|
|
paintRadialReaction(canvas, offset, thumbPosition);
|
|
|
|
var linePaint = Paint()
|
|
..color = Colors.white
|
|
..strokeWidth = 4 + (6 * (1 - currentValue))
|
|
..strokeCap = StrokeCap.round
|
|
..style = PaintingStyle.stroke;
|
|
|
|
canvas.drawLine(
|
|
Offset(offset.dx + _kSwitchWidth * 0.1, offset.dy),
|
|
Offset(
|
|
offset.dx +
|
|
(_kSwitchWidth * 0.1) +
|
|
(_kSwitchWidth / 2 * (1 - currentValue)),
|
|
offset.dy),
|
|
linePaint,
|
|
);
|
|
|
|
canvas.drawLine(
|
|
Offset(offset.dx + _kSwitchWidth * 0.2, offset.dy + _kSwitchHeight),
|
|
Offset(
|
|
offset.dx +
|
|
(_kSwitchWidth * 0.2) +
|
|
(_kSwitchWidth / 2 * (1 - currentValue)),
|
|
offset.dy + _kSwitchHeight),
|
|
linePaint,
|
|
);
|
|
|
|
var starPaint = Paint()
|
|
..strokeWidth = 2 + (6 * (1 - currentValue))
|
|
..strokeCap = StrokeCap.round
|
|
..style = PaintingStyle.stroke
|
|
..color = Color.fromARGB((255 * currentValue).floor(), 255, 255, 255);
|
|
|
|
canvas.drawLine(
|
|
Offset(offset.dx, offset.dy + _kSwitchHeight * 0.7),
|
|
Offset(offset.dx, offset.dy + _kSwitchHeight * 0.7),
|
|
starPaint,
|
|
);
|
|
|
|
try {
|
|
_isPainting = true;
|
|
BoxPainter thumbPainter;
|
|
if (_cachedThumbPainter == null ||
|
|
thumbColor != _cachedThumbColor ||
|
|
thumbImage != _cachedThumbImage) {
|
|
_cachedThumbColor = thumbColor;
|
|
_cachedThumbImage = thumbImage;
|
|
_cachedThumbPainter =
|
|
_createDefaultThumbDecoration(thumbColor, thumbImage)
|
|
.createBoxPainter(_handleDecorationChanged);
|
|
}
|
|
thumbPainter = _cachedThumbPainter;
|
|
|
|
// The thumb contracts slightly during the animation
|
|
final double inset = 1.0 - (currentValue - 0.5).abs() * 2.0;
|
|
final double radius = _kThumbRadius - inset;
|
|
thumbPainter.paint(
|
|
canvas,
|
|
thumbPosition + offset - Offset(radius, radius),
|
|
configuration.copyWith(size: Size.fromRadius(radius)),
|
|
);
|
|
} finally {
|
|
_isPainting = false;
|
|
}
|
|
|
|
canvas.drawLine(
|
|
Offset(offset.dx + _kSwitchWidth * 0.3, offset.dy + _kSwitchHeight * 0.5),
|
|
Offset(
|
|
offset.dx +
|
|
(_kSwitchWidth * 0.3) +
|
|
(_kSwitchWidth / 2 * (1 - currentValue)),
|
|
offset.dy + _kSwitchHeight * 0.5),
|
|
linePaint,
|
|
);
|
|
}
|
|
}
|