您最多选择25个主题
主题必须以中文或者字母或数字开头,可以包含连字符 (-),并且长度不得超过35个字符
495 行
18 KiB
495 行
18 KiB
using System;
|
|
using Unity.UIWidgets.animation;
|
|
using Unity.UIWidgets.foundation;
|
|
using Unity.UIWidgets.gestures;
|
|
using Unity.UIWidgets.rendering;
|
|
using Unity.UIWidgets.scheduler;
|
|
using Unity.UIWidgets.ui;
|
|
using Unity.UIWidgets.widgets;
|
|
using UnityEngine;
|
|
using Canvas = Unity.UIWidgets.ui.Canvas;
|
|
using Color = Unity.UIWidgets.ui.Color;
|
|
using Rect = Unity.UIWidgets.ui.Rect;
|
|
|
|
namespace Unity.UIWidgets.cupertino {
|
|
class CupertinoSwitchUtils {
|
|
public const float _kTrackWidth = 51.0f;
|
|
public const float _kTrackHeight = 31.0f;
|
|
public const float _kTrackRadius = _kTrackHeight / 2.0f;
|
|
public const float _kTrackInnerStart = _kTrackHeight / 2.0f;
|
|
public const float _kTrackInnerEnd = _kTrackWidth - _kTrackInnerStart;
|
|
public const float _kTrackInnerLength = _kTrackInnerEnd - _kTrackInnerStart;
|
|
public const float _kSwitchWidth = 59.0f;
|
|
public const float _kSwitchHeight = 39.0f;
|
|
public const float _kCupertinoSwitchDisabledOpacity = 0.5f;
|
|
public static Color _kTrackColor = CupertinoColors.lightBackgroundGray;
|
|
public static TimeSpan _kReactionDuration = new TimeSpan(0, 0, 0, 0, 300);
|
|
public static TimeSpan _kToggleDuration = new TimeSpan(0, 0, 0, 0, 200);
|
|
}
|
|
|
|
public class CupertinoSwitch : StatefulWidget {
|
|
public CupertinoSwitch(
|
|
bool value,
|
|
ValueChanged<bool> onChanged,
|
|
Key key = null,
|
|
Color activeColor = null,
|
|
DragStartBehavior dragStartBehavior = DragStartBehavior.start
|
|
) : base(key: key) {
|
|
this.value = value;
|
|
this.onChanged = onChanged;
|
|
this.activeColor = activeColor;
|
|
this.dragStartBehavior = dragStartBehavior;
|
|
}
|
|
|
|
public readonly bool value;
|
|
|
|
public readonly ValueChanged<bool> onChanged;
|
|
|
|
public readonly Color activeColor;
|
|
|
|
public readonly DragStartBehavior dragStartBehavior;
|
|
|
|
public override State createState() {
|
|
return new _CupertinoSwitchState();
|
|
}
|
|
|
|
public override void debugFillProperties(DiagnosticPropertiesBuilder properties) {
|
|
base.debugFillProperties(properties);
|
|
properties.add(new FlagProperty("value", value: this.value, ifTrue: "on", ifFalse: "off", showName: true));
|
|
properties.add(new ObjectFlagProperty<ValueChanged<bool>>("onChanged", this.onChanged, ifNull: "disabled"));
|
|
}
|
|
}
|
|
|
|
class _CupertinoSwitchState : TickerProviderStateMixin<CupertinoSwitch> {
|
|
public override Widget build(BuildContext context) {
|
|
return new Opacity(
|
|
opacity: this.widget.onChanged == null ? CupertinoSwitchUtils._kCupertinoSwitchDisabledOpacity : 1.0f,
|
|
child: new _CupertinoSwitchRenderObjectWidget(
|
|
value: this.widget.value,
|
|
activeColor: this.widget.activeColor ?? CupertinoColors.activeGreen,
|
|
onChanged: this.widget.onChanged,
|
|
vsync: this,
|
|
dragStartBehavior: this.widget.dragStartBehavior
|
|
)
|
|
);
|
|
}
|
|
}
|
|
|
|
class _CupertinoSwitchRenderObjectWidget : LeafRenderObjectWidget {
|
|
public _CupertinoSwitchRenderObjectWidget(
|
|
Key key = null,
|
|
bool value = false,
|
|
Color activeColor = null,
|
|
ValueChanged<bool> onChanged = null,
|
|
TickerProvider vsync = null,
|
|
DragStartBehavior dragStartBehavior = DragStartBehavior.start
|
|
) : base(key: key) {
|
|
this.value = value;
|
|
this.activeColor = activeColor;
|
|
this.onChanged = onChanged;
|
|
this.vsync = vsync;
|
|
this.dragStartBehavior = dragStartBehavior;
|
|
}
|
|
|
|
public readonly bool value;
|
|
public readonly Color activeColor;
|
|
public readonly ValueChanged<bool> onChanged;
|
|
public readonly TickerProvider vsync;
|
|
public readonly DragStartBehavior dragStartBehavior;
|
|
|
|
public override RenderObject createRenderObject(BuildContext context) {
|
|
return new _RenderCupertinoSwitch(
|
|
value: this.value,
|
|
activeColor: this.activeColor,
|
|
onChanged: this.onChanged,
|
|
textDirection: Directionality.of(context),
|
|
vsync: this.vsync,
|
|
dragStartBehavior: this.dragStartBehavior
|
|
);
|
|
}
|
|
|
|
public override void updateRenderObject(BuildContext context, RenderObject renderObject) {
|
|
var _renderObject = renderObject as _RenderCupertinoSwitch;
|
|
_renderObject.value = this.value;
|
|
_renderObject.activeColor = this.activeColor;
|
|
_renderObject.onChanged = this.onChanged;
|
|
_renderObject.textDirection = Directionality.of(context);
|
|
_renderObject.vsync = this.vsync;
|
|
_renderObject.dragStartBehavior = this.dragStartBehavior;
|
|
}
|
|
}
|
|
|
|
|
|
class _RenderCupertinoSwitch : RenderConstrainedBox {
|
|
public _RenderCupertinoSwitch(
|
|
bool value,
|
|
Color activeColor,
|
|
TextDirection textDirection,
|
|
TickerProvider vsync,
|
|
ValueChanged<bool> onChanged = null,
|
|
DragStartBehavior dragStartBehavior = DragStartBehavior.start
|
|
) : base(additionalConstraints: BoxConstraints.tightFor(
|
|
width: CupertinoSwitchUtils._kSwitchWidth,
|
|
height: CupertinoSwitchUtils._kSwitchHeight)
|
|
) {
|
|
D.assert(activeColor != null);
|
|
D.assert(vsync != null);
|
|
this._value = value;
|
|
this._activeColor = activeColor;
|
|
this._onChanged = onChanged;
|
|
this._textDirection = textDirection;
|
|
this._vsync = vsync;
|
|
|
|
this._tap = new TapGestureRecognizer() {
|
|
onTapDown = this._handleTapDown,
|
|
onTap = this._handleTap,
|
|
onTapUp = this._handleTapUp,
|
|
onTapCancel = this._handleTapCancel,
|
|
};
|
|
|
|
this._drag = new HorizontalDragGestureRecognizer() {
|
|
onStart = this._handleDragStart,
|
|
onUpdate = this._handleDragUpdate,
|
|
onEnd = this._handleDragEnd,
|
|
dragStartBehavior = dragStartBehavior
|
|
};
|
|
|
|
this._positionController = new AnimationController(
|
|
duration: CupertinoSwitchUtils._kToggleDuration,
|
|
value: value ? 1.0f : 0.0f,
|
|
vsync: vsync
|
|
);
|
|
this._position = new CurvedAnimation(
|
|
parent: this._positionController,
|
|
curve: Curves.linear
|
|
);
|
|
this._position.addListener(this.markNeedsPaint);
|
|
this._position.addStatusListener(this._handlePositionStateChanged);
|
|
|
|
this._reactionController = new AnimationController(
|
|
duration: CupertinoSwitchUtils._kReactionDuration,
|
|
vsync: vsync
|
|
);
|
|
this._reaction = new CurvedAnimation(
|
|
parent: this._reactionController,
|
|
curve: Curves.ease
|
|
);
|
|
this._reaction.addListener(this.markNeedsPaint);
|
|
}
|
|
|
|
AnimationController _positionController;
|
|
CurvedAnimation _position;
|
|
AnimationController _reactionController;
|
|
Animation<float> _reaction;
|
|
|
|
public bool value {
|
|
get { return this._value; }
|
|
set {
|
|
if (value == this._value) {
|
|
return;
|
|
}
|
|
|
|
this._value = value;
|
|
// this.markNeedsSemanticsUpdate();
|
|
this._position.curve = Curves.ease;
|
|
this._position.reverseCurve = Curves.ease.flipped;
|
|
if (value) {
|
|
this._positionController.forward();
|
|
}
|
|
else {
|
|
this._positionController.reverse();
|
|
}
|
|
}
|
|
}
|
|
|
|
bool _value;
|
|
|
|
public TickerProvider vsync {
|
|
get { return this._vsync; }
|
|
set {
|
|
D.assert(value != null);
|
|
if (value == this._vsync) {
|
|
return;
|
|
}
|
|
|
|
this._vsync = value;
|
|
this._positionController.resync(this.vsync);
|
|
this._reactionController.resync(this.vsync);
|
|
}
|
|
}
|
|
|
|
TickerProvider _vsync;
|
|
|
|
public Color activeColor {
|
|
get { return this._activeColor; }
|
|
set {
|
|
D.assert(value != null);
|
|
if (value == this._activeColor) {
|
|
return;
|
|
}
|
|
|
|
this._activeColor = value;
|
|
this.markNeedsPaint();
|
|
}
|
|
}
|
|
|
|
Color _activeColor;
|
|
|
|
public ValueChanged<bool> onChanged {
|
|
get { return this._onChanged; }
|
|
set {
|
|
if (value == this._onChanged) {
|
|
return;
|
|
}
|
|
|
|
bool wasInteractive = this.isInteractive;
|
|
this._onChanged = value;
|
|
if (wasInteractive != this.isInteractive) {
|
|
this.markNeedsPaint();
|
|
}
|
|
}
|
|
}
|
|
|
|
ValueChanged<bool> _onChanged;
|
|
|
|
public TextDirection textDirection {
|
|
get { return this._textDirection; }
|
|
set {
|
|
if (this._textDirection == value) {
|
|
return;
|
|
}
|
|
|
|
this._textDirection = value;
|
|
this.markNeedsPaint();
|
|
}
|
|
}
|
|
|
|
TextDirection _textDirection;
|
|
|
|
|
|
public DragStartBehavior dragStartBehavior {
|
|
get { return this._drag.dragStartBehavior; }
|
|
set {
|
|
if (this._drag.dragStartBehavior == value) {
|
|
return;
|
|
}
|
|
|
|
this._drag.dragStartBehavior = value;
|
|
}
|
|
}
|
|
|
|
public bool isInteractive {
|
|
get { return this.onChanged != null; }
|
|
}
|
|
|
|
TapGestureRecognizer _tap;
|
|
HorizontalDragGestureRecognizer _drag;
|
|
|
|
public override void attach(object _owner) {
|
|
base.attach(_owner);
|
|
if (this.value) {
|
|
this._positionController.forward();
|
|
}
|
|
else {
|
|
this._positionController.reverse();
|
|
}
|
|
|
|
if (this.isInteractive) {
|
|
switch (this._reactionController.status) {
|
|
case AnimationStatus.forward:
|
|
this._reactionController.forward();
|
|
break;
|
|
case AnimationStatus.reverse:
|
|
this._reactionController.reverse();
|
|
break;
|
|
case AnimationStatus.dismissed:
|
|
case AnimationStatus.completed:
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
public override void detach() {
|
|
this._positionController.stop();
|
|
this._reactionController.stop();
|
|
base.detach();
|
|
}
|
|
|
|
void _handlePositionStateChanged(AnimationStatus status) {
|
|
if (this.isInteractive) {
|
|
if (status == AnimationStatus.completed && !this._value) {
|
|
this.onChanged(true);
|
|
}
|
|
else if (status == AnimationStatus.dismissed && this._value) {
|
|
this.onChanged(false);
|
|
}
|
|
}
|
|
}
|
|
|
|
void _handleTapDown(TapDownDetails details) {
|
|
if (this.isInteractive) {
|
|
this._reactionController.forward();
|
|
}
|
|
}
|
|
|
|
void _handleTap() {
|
|
if (this.isInteractive) {
|
|
this.onChanged(!this._value);
|
|
this._emitVibration();
|
|
}
|
|
}
|
|
|
|
void _handleTapUp(TapUpDetails details) {
|
|
if (this.isInteractive) {
|
|
this._reactionController.reverse();
|
|
}
|
|
}
|
|
|
|
void _handleTapCancel() {
|
|
if (this.isInteractive) {
|
|
this._reactionController.reverse();
|
|
}
|
|
}
|
|
|
|
void _handleDragStart(DragStartDetails details) {
|
|
if (this.isInteractive) {
|
|
this._reactionController.forward();
|
|
this._emitVibration();
|
|
}
|
|
}
|
|
|
|
void _handleDragUpdate(DragUpdateDetails details) {
|
|
if (this.isInteractive) {
|
|
this._position.curve = null;
|
|
this._position.reverseCurve = null;
|
|
float delta = details.primaryDelta / CupertinoSwitchUtils._kTrackInnerLength ?? 0f;
|
|
|
|
this._positionController.setValue(this._positionController.value + delta);
|
|
|
|
// switch (this.textDirection) {
|
|
// case TextDirection.rtl:
|
|
// this._positionController.setValue(this._positionController.value - delta);
|
|
// break;
|
|
// case TextDirection.ltr:
|
|
// this._positionController.setValue(this._positionController.value + delta);
|
|
// break;
|
|
// }
|
|
}
|
|
}
|
|
|
|
void _handleDragEnd(DragEndDetails details) {
|
|
if (this._position.value >= 0.5) {
|
|
this._positionController.forward();
|
|
}
|
|
else {
|
|
this._positionController.reverse();
|
|
}
|
|
|
|
this._reactionController.reverse();
|
|
}
|
|
|
|
void _emitVibration() {
|
|
// switch (Platform defaultTargetPlatform) {
|
|
// case TargetPlatform.iOS:
|
|
// HapticFeedback.lightImpact();
|
|
// break;
|
|
// case TargetPlatform.fuchsia:
|
|
// case TargetPlatform.android:
|
|
// break;
|
|
// }
|
|
return;
|
|
}
|
|
|
|
protected override bool hitTestSelf(Offset position) {
|
|
return true;
|
|
}
|
|
|
|
public override void handleEvent(PointerEvent evt, HitTestEntry entry) {
|
|
D.assert(this.debugHandleEvent(evt, entry));
|
|
if (evt is PointerDownEvent && this.isInteractive) {
|
|
this._drag.addPointer(evt as PointerDownEvent);
|
|
this._tap.addPointer(evt as PointerDownEvent);
|
|
}
|
|
}
|
|
|
|
// public override void describeSemanticsConfiguration(SemanticsConfiguration config) {
|
|
// base.describeSemanticsConfiguration(config);
|
|
//
|
|
// if (isInteractive)
|
|
// config.onTap = _handleTap;
|
|
//
|
|
// config.isEnabled = isInteractive;
|
|
// config.isToggled = _value;
|
|
// }
|
|
|
|
public readonly CupertinoThumbPainter _thumbPainter = new CupertinoThumbPainter();
|
|
|
|
public override void paint(PaintingContext context, Offset offset) {
|
|
Canvas canvas = context.canvas;
|
|
|
|
float currentValue = this._position.value;
|
|
float currentReactionValue = this._reaction.value;
|
|
|
|
float visualPosition = 0f;
|
|
switch (this.textDirection) {
|
|
case TextDirection.rtl:
|
|
visualPosition = 1.0f - currentValue;
|
|
break;
|
|
case TextDirection.ltr:
|
|
visualPosition = currentValue;
|
|
break;
|
|
}
|
|
|
|
Color trackColor = this._value ? this.activeColor : CupertinoSwitchUtils._kTrackColor;
|
|
float borderThickness =
|
|
1.5f + (CupertinoSwitchUtils._kTrackRadius - 1.5f) * Mathf.Max(currentReactionValue, currentValue);
|
|
|
|
Paint paint = new Paint();
|
|
paint.color = trackColor;
|
|
|
|
Rect trackRect = Rect.fromLTWH(
|
|
offset.dx + (this.size.width - CupertinoSwitchUtils._kTrackWidth) / 2.0f,
|
|
offset.dy + (this.size.height - CupertinoSwitchUtils._kTrackHeight) / 2.0f,
|
|
CupertinoSwitchUtils._kTrackWidth,
|
|
CupertinoSwitchUtils._kTrackHeight
|
|
);
|
|
RRect outerRRect = RRect.fromRectAndRadius(trackRect, Radius.circular(CupertinoSwitchUtils
|
|
._kTrackRadius));
|
|
RRect innerRRect = RRect.fromRectAndRadius(trackRect.deflate(borderThickness), Radius.circular
|
|
(CupertinoSwitchUtils._kTrackRadius));
|
|
canvas.drawDRRect(outerRRect, innerRRect, paint);
|
|
|
|
float currentThumbExtension = CupertinoThumbPainter.extension * currentReactionValue;
|
|
float thumbLeft = MathUtils.lerpFloat(
|
|
trackRect.left + CupertinoSwitchUtils._kTrackInnerStart - CupertinoThumbPainter.radius,
|
|
trackRect.left + CupertinoSwitchUtils._kTrackInnerEnd - CupertinoThumbPainter.radius -
|
|
currentThumbExtension,
|
|
visualPosition
|
|
);
|
|
float thumbRight = MathUtils.lerpFloat(
|
|
trackRect.left + CupertinoSwitchUtils._kTrackInnerStart + CupertinoThumbPainter.radius +
|
|
currentThumbExtension,
|
|
trackRect.left + CupertinoSwitchUtils._kTrackInnerEnd + CupertinoThumbPainter.radius,
|
|
visualPosition
|
|
);
|
|
float thumbCenterY = offset.dy + this.size.height / 2.0f;
|
|
|
|
this._thumbPainter.paint(canvas, Rect.fromLTRB(
|
|
thumbLeft,
|
|
thumbCenterY - CupertinoThumbPainter.radius,
|
|
thumbRight,
|
|
thumbCenterY + CupertinoThumbPainter.radius
|
|
));
|
|
}
|
|
|
|
|
|
public override void debugFillProperties(DiagnosticPropertiesBuilder description) {
|
|
base.debugFillProperties(description);
|
|
description.add(
|
|
new FlagProperty("value", value: this.value, ifTrue: "checked", ifFalse: "unchecked", showName: true));
|
|
description.add(new FlagProperty("isInteractive", value: this.isInteractive, ifTrue: "enabled",
|
|
ifFalse: "disabled",
|
|
showName: true, defaultValue: true));
|
|
}
|
|
}
|
|
}
|