//Fork from https://github.com/divyanshub024/day_night_switch 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 onChanged; final ValueChanged 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>( 'onChanged', onChanged, ifNull: 'disabled', )); } } class _DayNightSwitchState extends State 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 onChanged; final ValueChanged 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 onDrag; _RenderSwitch({ bool value, Color activeColor, Color inactiveColor, ImageProvider moonImage, ImageProvider sunImage, Color activeTrackColor, Color inactiveTrackColor, ImageConfiguration configuration, BoxConstraints additionalConstraints, @required TextDirection textDirection, ValueChanged 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 = 4 + (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, ); } }