From 8b63e0d2ece2a8d939ff33032a841d7b4a5be9f2 Mon Sep 17 00:00:00 2001 From: broadli Date: Fri, 17 Feb 2023 20:00:37 +0800 Subject: [PATCH 1/5] feat: update tdswitch --- example/ios/Flutter/AppFrameworkInfo.plist | 2 +- example/ios/Podfile | 2 +- example/ios/Podfile.lock | 6 +- example/ios/Runner/Info.plist | 2 + example/lib/page/td_switch_page.dart | 160 +++-- example/pubspec.lock | 31 +- .../switch/td_cupertino_switch.dart | 634 ++++++++++++++++++ .../components/switch/td_loading_paint.dart | 43 ++ lib/src/components/switch/td_switch.dart | 97 ++- lib/td_export.dart | 2 +- pubspec.lock | 31 +- 11 files changed, 890 insertions(+), 120 deletions(-) create mode 100644 lib/src/components/switch/td_cupertino_switch.dart create mode 100644 lib/src/components/switch/td_loading_paint.dart diff --git a/example/ios/Flutter/AppFrameworkInfo.plist b/example/ios/Flutter/AppFrameworkInfo.plist index 8d4492f97..9625e105d 100644 --- a/example/ios/Flutter/AppFrameworkInfo.plist +++ b/example/ios/Flutter/AppFrameworkInfo.plist @@ -21,6 +21,6 @@ CFBundleVersion 1.0 MinimumOSVersion - 9.0 + 11.0 diff --git a/example/ios/Podfile b/example/ios/Podfile index f7d6a5e68..d207307f8 100644 --- a/example/ios/Podfile +++ b/example/ios/Podfile @@ -1,5 +1,5 @@ # Uncomment this line to define a global platform for your project -# platform :ios, '9.0' +# platform :ios, '11.0' # CocoaPods analytics sends network stats synchronously affecting flutter build latency. ENV['COCOAPODS_DISABLE_STATS'] = 'true' diff --git a/example/ios/Podfile.lock b/example/ios/Podfile.lock index 309cd00c4..fe0724ba9 100644 --- a/example/ios/Podfile.lock +++ b/example/ios/Podfile.lock @@ -9,8 +9,8 @@ EXTERNAL SOURCES: :path: Flutter SPEC CHECKSUMS: - Flutter: 50d75fe2f02b26cc09d224853bb45737f8b3214a + Flutter: f04841e97a9d0b0a8025694d0796dd46242b2854 -PODFILE CHECKSUM: 8e679eca47255a8ca8067c4c67aab20e64cb974d +PODFILE CHECKSUM: 663715e941f9adb426e33bf9376914006f9ea95b -COCOAPODS: 1.10.1 +COCOAPODS: 1.11.3 diff --git a/example/ios/Runner/Info.plist b/example/ios/Runner/Info.plist index 40f64a48c..6cc23a150 100644 --- a/example/ios/Runner/Info.plist +++ b/example/ios/Runner/Info.plist @@ -43,5 +43,7 @@ UIViewControllerBasedStatusBarAppearance + CADisableMinimumFrameDurationOnPhone + diff --git a/example/lib/page/td_switch_page.dart b/example/lib/page/td_switch_page.dart index ba43683ff..356c07f72 100644 --- a/example/lib/page/td_switch_page.dart +++ b/example/lib/page/td_switch_page.dart @@ -1,5 +1,6 @@ import 'package:flutter/material.dart'; import 'package:tdesign_flutter/td_export.dart'; + import '../base/example_widget.dart'; /// @@ -17,76 +18,90 @@ class TDSwitchPage extends StatefulWidget { class TDSwitchPageState extends State { @override Widget build(BuildContext context) { - var current = ExamplePage(title: '开关 Switch', children: [ + var current = + ExamplePage(title: 'Switch 开关', desc: '用于控制某个功能的开启和关闭。', children: [ ExampleModule( - title: '默认', + title: '组件类型', children: [ ExampleItem(builder: (_) => _title('基础开关')), ExampleItem( builder: (_) => Container( child: Column( mainAxisSize: MainAxisSize.min, - children: [ - demoRow(context, '开关', on: true), - _divider(), - demoRow(context, '开关', on: false), - _divider(), - demoRow(context, '自定义颜色', - on: true, onColor: Colors.green), - _divider(), - demoRow(context, '自定义颜色', - on: false, - onColor: Colors.green, - offColor: Colors.brown), - _divider(), - demoRow(context, '异步操作', on: true), - ], + children: [demoRow(context, '基础开关', on: true)], ), - padding: const EdgeInsets.only(left: 16, right: 16), - color: Colors.white, )), - ExampleItem(builder: (_) => _title('带描述状态开关')), + ExampleItem(builder: (_) => _title('带描述开关')), ExampleItem( - builder: (_) => Container( - child: Column( - mainAxisSize: MainAxisSize.min, - children: [ - demoRow(context, '开关', desc: '描述信息', on: true), - _divider(), - demoRow(context, '开关', desc: '描述信息', on: false), - _divider(), - demoRow(context, '自定义颜色', - desc: '描述信息', on: true, onColor: Colors.green), - ], - ), - padding: const EdgeInsets.only(left: 16, right: 16), - color: Colors.white, + builder: (_) => Column( + mainAxisSize: MainAxisSize.min, + children: [ + demoRow(context, '带文字开关', + on: true, type: TDSwitchType.text), + demoRow(context, '带图标开关', + on: true, type: TDSwitchType.icon) + ], )), - ExampleItem(builder: (_) => _title('状态')), + ExampleItem(builder: (_) => _title('自定义颜色开关')), ExampleItem( - builder: (_) => Container( - child: Column( - mainAxisSize: MainAxisSize.min, - children: [ - demoRow(context, '开关开启禁用', on: true, enable: false), - _divider(), - demoRow(context, '开关开启禁用', on: false, enable: false), - _divider(), - demoRow(context, '开关开启禁用', - on: true, onColor: Colors.green, enable: false), - _divider(), - demoRow(context, '开关开启禁用', - desc: '描述信息', on: false, enable: false), - _divider(), - demoRow(context, '开关开启禁用', - desc: '描述信息', on: true, enable: false), - ], - ), - padding: const EdgeInsets.only(left: 16, right: 16), - color: Colors.white, - )), + builder: (_) => + demoRow(context, '自定义颜色开关', on: true, onColor: Colors.green), + ), ], - ) + ), + ExampleModule(title: '组件状态', children: [ + ExampleItem(builder: (_) => _title('加载状态')), + ExampleItem( + builder: (_) => Container( + child: Column( + mainAxisSize: MainAxisSize.min, + children: [ + demoRow( + context, + '加载状态', + on: true, + enable: false, + type: TDSwitchType.loading, + ), + demoRow( + context, + '加载状态', + on: false, + enable: false, + type: TDSwitchType.loading, + ), + ], + ), + )), + ExampleItem(builder: (_) => _title('禁用状态')), + ExampleItem( + builder: (_) => Container( + child: Column( + mainAxisSize: MainAxisSize.min, + children: [ + demoRow(context, '禁用状态', on: false, enable: false), + demoRow(context, '禁用状态', on: true, enable: false), + ], + ), + )), + ]), + ExampleModule(title: '组件样式', children: [ + ExampleItem(builder: (_) => _title('开关尺寸')), + ExampleItem( + builder: (_) => Container( + child: Column( + mainAxisSize: MainAxisSize.min, + children: [ + demoRow(context, '大尺寸32', + on: true, size: TDSwitchSize.large), + demoRow(context, '中尺寸28', + on: true, size: TDSwitchSize.medium), + demoRow(context, '小尺寸24', + on: true, size: TDSwitchSize.small), + ], + ), + )), + ]), ]); return current; } @@ -112,6 +127,8 @@ class TDSwitchPageState extends State { bool enable = true, Color? onColor, Color? offColor, + TDSwitchSize? size, + TDSwitchType? type, }) { final theme = TDTheme.of(context); Widget current = Row( @@ -125,17 +142,30 @@ class TDSwitchPageState extends State { desc ?? '', textColor: theme.grayColor6, ), - TDSwitch( - isOn: on, - onColor: onColor, - enable: enable, - offColor: offColor, + SizedBox( + child: TDSwitch( + isOn: on, + onColor: onColor, + enable: enable, + offColor: offColor, + size: size, + type: type, + ), ) ], ); - current = SizedBox( - child: current, - height: 44, + current = Padding( + padding: const EdgeInsets.symmetric(vertical: 8), + child: SizedBox( + child: Container( + child: Padding( + padding: const EdgeInsets.symmetric(horizontal: 16.0), + child: current, + ), + color: Colors.white, + ), + height: 56, + ), ); return current; } diff --git a/example/pubspec.lock b/example/pubspec.lock index d864966bf..07850ac4e 100644 --- a/example/pubspec.lock +++ b/example/pubspec.lock @@ -14,7 +14,7 @@ packages: name: async url: "https://pub.flutter-io.cn" source: hosted - version: "2.8.2" + version: "2.9.0" boolean_selector: dependency: transitive description: @@ -28,21 +28,14 @@ packages: name: characters url: "https://pub.flutter-io.cn" source: hosted - version: "1.2.0" - charcode: - dependency: transitive - description: - name: charcode - url: "https://pub.flutter-io.cn" - source: hosted - version: "1.3.1" + version: "1.2.1" clock: dependency: transitive description: name: clock url: "https://pub.flutter-io.cn" source: hosted - version: "1.1.0" + version: "1.1.1" collection: dependency: transitive description: @@ -63,7 +56,7 @@ packages: name: fake_async url: "https://pub.flutter-io.cn" source: hosted - version: "1.3.0" + version: "1.3.1" flutter: dependency: "direct main" description: flutter @@ -136,28 +129,28 @@ packages: name: matcher url: "https://pub.flutter-io.cn" source: hosted - version: "0.12.11" + version: "0.12.12" material_color_utilities: dependency: transitive description: name: material_color_utilities url: "https://pub.flutter-io.cn" source: hosted - version: "0.1.4" + version: "0.1.5" meta: dependency: transitive description: name: meta url: "https://pub.flutter-io.cn" source: hosted - version: "1.7.0" + version: "1.8.0" path: dependency: transitive description: name: path url: "https://pub.flutter-io.cn" source: hosted - version: "1.8.1" + version: "1.8.2" sky_engine: dependency: transitive description: flutter @@ -169,7 +162,7 @@ packages: name: source_span url: "https://pub.flutter-io.cn" source: hosted - version: "1.8.2" + version: "1.9.0" stack_trace: dependency: transitive description: @@ -190,7 +183,7 @@ packages: name: string_scanner url: "https://pub.flutter-io.cn" source: hosted - version: "1.1.0" + version: "1.1.1" tdesign_flutter: dependency: "direct main" description: @@ -204,14 +197,14 @@ packages: name: term_glyph url: "https://pub.flutter-io.cn" source: hosted - version: "1.2.0" + version: "1.2.1" test_api: dependency: transitive description: name: test_api url: "https://pub.flutter-io.cn" source: hosted - version: "0.4.9" + version: "0.4.12" typed_data: dependency: transitive description: diff --git a/lib/src/components/switch/td_cupertino_switch.dart b/lib/src/components/switch/td_cupertino_switch.dart new file mode 100644 index 000000000..36cff9dbc --- /dev/null +++ b/lib/src/components/switch/td_cupertino_switch.dart @@ -0,0 +1,634 @@ +// Copyright 2014 The Flutter Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +import 'dart:ui' show lerpDouble; + +import 'package:flutter/cupertino.dart'; +import 'package:flutter/foundation.dart'; +import 'package:flutter/gestures.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter/rendering.dart'; +import 'package:flutter/services.dart'; +import 'package:flutter/widgets.dart'; + +// Examples can assume: +// bool _lights = false; +// void setState(VoidCallback fn) { } + +/// An iOS-style switch. +/// +/// Used to toggle the on/off state of a single setting. +/// +/// The switch itself does not maintain any state. Instead, when the state of +/// the switch changes, the widget calls the [onChanged] callback. Most widgets +/// that use a switch will listen for the [onChanged] callback and rebuild the +/// switch with a new [value] to update the visual appearance of the switch. +/// +/// {@tool dartpad} +/// This example shows a toggleable [TDCupertinoSwitch]. When the thumb slides to +/// the other side of the track, the switch is toggled between on/off. +/// +/// ** See code in examples/api/lib/cupertino/switch/cupertino_switch.0.dart ** +/// {@end-tool} +/// +/// {@tool snippet} +/// +/// This sample shows how to use a [TDCupertinoSwitch] in a [ListTile]. The +/// [MergeSemantics] is used to turn the entire [ListTile] into a single item +/// for accessibility tools. +/// +/// ```dart +/// MergeSemantics( +/// child: ListTile( +/// title: const Text('Lights'), +/// trailing: TDCupertinoSwitch( +/// value: _lights, +/// onChanged: (bool value) { setState(() { _lights = value; }); }, +/// ), +/// onTap: () { setState(() { _lights = !_lights; }); }, +/// ), +/// ) +/// ``` +/// {@end-tool} +/// +/// See also: +/// +/// * [Switch], the Material Design equivalent. +/// * +class TDCupertinoSwitch extends StatefulWidget { + /// Creates an iOS-style switch. + /// + /// The [value] parameter must not be null. + /// The [dragStartBehavior] parameter defaults to [DragStartBehavior.start] and must not be null. + const TDCupertinoSwitch({ + Key? key, + required this.value, + required this.onChanged, + this.activeColor, + this.trackColor, + this.thumbColor, + this.thumbView, + this.dragStartBehavior = DragStartBehavior.start, + }) : super(key: key); + + /// Whether this switch is on or off. + /// + /// Must not be null. + final bool value; + + /// Called when the user toggles with switch on or off. + /// + /// The switch passes the new value to the callback but does not actually + /// change state until the parent widget rebuilds the switch with the new + /// value. + /// + /// If null, the switch will be displayed as disabled, which has a reduced opacity. + /// + /// The callback provided to onChanged should update the state of the parent + /// [StatefulWidget] using the [State.setState] method, so that the parent + /// gets rebuilt; for example: + /// + /// ```dart + /// TDCupertinoSwitch( + /// value: _giveVerse, + /// onChanged: (bool newValue) { + /// setState(() { + /// _giveVerse = newValue; + /// }); + /// }, + /// ) + /// ``` + final ValueChanged? onChanged; + + /// The color to use when this switch is on. + /// + /// Defaults to [CupertinoColors.systemGreen] when null and ignores + /// the [CupertinoTheme] in accordance to native iOS behavior. + final Color? activeColor; + + /// The color to use for the background when the switch is off. + /// + /// Defaults to [CupertinoColors.secondarySystemFill] when null. + final Color? trackColor; + + /// The color to use for the thumb of the switch. + /// + /// Defaults to [CupertinoColors.white] when null. + final Color? thumbColor; + + /// The custom widget over the thumb. + final Widget? thumbView; + + /// {@template flutter.cupertino.TDCupertinoSwitch.dragStartBehavior} + /// Determines the way that drag start behavior is handled. + /// + /// If set to [DragStartBehavior.start], the drag behavior used to move the + /// switch from on to off will begin at the position where the drag gesture won + /// the arena. If set to [DragStartBehavior.down] it will begin at the position + /// where a down event was first detected. + /// + /// In general, setting this to [DragStartBehavior.start] will make drag + /// animation smoother and setting it to [DragStartBehavior.down] will make + /// drag behavior feel slightly more reactive. + /// + /// By default, the drag start behavior is [DragStartBehavior.start]. + /// + /// See also: + /// + /// * [DragGestureRecognizer.dragStartBehavior], which gives an example for + /// the different behaviors. + /// + /// {@endtemplate} + final DragStartBehavior dragStartBehavior; + + @override + State createState() => _TDCupertinoSwitchState(); + + @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 _TDCupertinoSwitchState extends State + with TickerProviderStateMixin { + late TapGestureRecognizer _tap; + late HorizontalDragGestureRecognizer _drag; + + late AnimationController _positionController; + late CurvedAnimation position; + + late AnimationController _reactionController; + late Animation _reaction; + + bool get isInteractive => widget.onChanged != null; + + // A non-null boolean value that changes to true at the end of a drag if the + // switch must be animated to the position indicated by the widget's value. + bool needsPositionAnimation = false; + + @override + void initState() { + super.initState(); + + _tap = TapGestureRecognizer() + ..onTapDown = _handleTapDown + ..onTapUp = _handleTapUp + ..onTap = _handleTap + ..onTapCancel = _handleTapCancel; + _drag = HorizontalDragGestureRecognizer() + ..onStart = _handleDragStart + ..onUpdate = _handleDragUpdate + ..onEnd = _handleDragEnd + ..dragStartBehavior = widget.dragStartBehavior; + + _positionController = AnimationController( + duration: _kToggleDuration, + value: widget.value ? 1.0 : 0.0, + vsync: this, + ); + position = CurvedAnimation( + parent: _positionController, + curve: Curves.linear, + ); + _reactionController = AnimationController( + duration: _kReactionDuration, + vsync: this, + ); + _reaction = CurvedAnimation( + parent: _reactionController, + curve: Curves.ease, + ); + } + + @override + void didUpdateWidget(TDCupertinoSwitch oldWidget) { + super.didUpdateWidget(oldWidget); + _drag.dragStartBehavior = widget.dragStartBehavior; + + if (needsPositionAnimation || oldWidget.value != widget.value) { + _resumePositionAnimation(isLinear: needsPositionAnimation); + } + } + + // `isLinear` must be true if the position animation is trying to move the + // thumb to the closest end after the most recent drag animation, so the curve + // does not change when the controller's value is not 0 or 1. + // + // It can be set to false when it's an implicit animation triggered by + // widget.value changes. + void _resumePositionAnimation({bool isLinear = true}) { + needsPositionAnimation = false; + position + ..curve = isLinear ? Curves.linear : Curves.ease + ..reverseCurve = isLinear ? Curves.linear : Curves.ease.flipped; + if (widget.value) { + _positionController.forward(); + } else { + _positionController.reverse(); + } + } + + void _handleTapDown(TapDownDetails details) { + if (isInteractive) { + needsPositionAnimation = false; + } + _reactionController.forward(); + } + + void _handleTap() { + if (isInteractive) { + widget.onChanged!(!widget.value); + _emitVibration(); + } + } + + void _handleTapUp(TapUpDetails details) { + if (isInteractive) { + needsPositionAnimation = false; + _reactionController.reverse(); + } + } + + void _handleTapCancel() { + if (isInteractive) { + _reactionController.reverse(); + } + } + + void _handleDragStart(DragStartDetails details) { + if (isInteractive) { + needsPositionAnimation = false; + _reactionController.forward(); + _emitVibration(); + } + } + + void _handleDragUpdate(DragUpdateDetails details) { + if (isInteractive) { + position + ..curve = Curves.linear + ..reverseCurve = Curves.linear; + final delta = details.primaryDelta! / _kTrackInnerLength; + switch (Directionality.of(context)) { + case TextDirection.rtl: + _positionController.value -= delta; + break; + case TextDirection.ltr: + _positionController.value += delta; + break; + } + } + } + + void _handleDragEnd(DragEndDetails details) { + // Deferring the animation to the next build phase. + setState(() { + needsPositionAnimation = true; + }); + // Call onChanged when the user's intent to change value is clear. + if (position.value >= 0.5 != widget.value) { + widget.onChanged!(!widget.value); + } + _reactionController.reverse(); + } + + void _emitVibration() { + switch (defaultTargetPlatform) { + case TargetPlatform.iOS: + HapticFeedback.lightImpact(); + break; + case TargetPlatform.android: + case TargetPlatform.fuchsia: + case TargetPlatform.linux: + case TargetPlatform.macOS: + case TargetPlatform.windows: + break; + } + } + + @override + Widget build(BuildContext context) { + if (needsPositionAnimation) { + _resumePositionAnimation(); + } + return MouseRegion( + cursor: isInteractive && kIsWeb + ? SystemMouseCursors.click + : MouseCursor.defer, + child: Opacity( + opacity: + widget.onChanged == null ? _kTDCupertinoSwitchDisabledOpacity : 1.0, + child: _TDCupertinoSwitchRenderObjectWidget( + value: widget.value, + activeColor: CupertinoDynamicColor.resolve( + widget.activeColor ?? CupertinoColors.systemGreen, + context, + ), + trackColor: CupertinoDynamicColor.resolve( + widget.trackColor ?? CupertinoColors.secondarySystemFill, + context), + thumbColor: CupertinoDynamicColor.resolve( + widget.thumbColor ?? CupertinoColors.white, context), + onChanged: widget.onChanged, + textDirection: Directionality.of(context), + state: this, + child: widget.thumbView, + ), + ), + ); + } + + @override + void dispose() { + _tap.dispose(); + _drag.dispose(); + + _positionController.dispose(); + _reactionController.dispose(); + super.dispose(); + } +} + +class _TDCupertinoSwitchRenderObjectWidget + extends SingleChildRenderObjectWidget { + const _TDCupertinoSwitchRenderObjectWidget({ + required this.child, + required this.value, + required this.activeColor, + required this.trackColor, + required this.thumbColor, + required this.onChanged, + required this.textDirection, + required this.state, + }); + + final bool value; + final Color activeColor; + final Color trackColor; + final Color thumbColor; + final ValueChanged? onChanged; + final _TDCupertinoSwitchState state; + final TextDirection textDirection; + + /// The widget below this widget in the tree. + /// + /// {@macro flutter.widgets.ProxyWidget.child} + final Widget? child; + + @override + _RenderTDCupertinoSwitch createRenderObject(BuildContext context) { + return _RenderTDCupertinoSwitch( + value: value, + activeColor: activeColor, + trackColor: trackColor, + thumbColor: thumbColor, + onChanged: onChanged, + textDirection: textDirection, + state: state, + ); + } + + @override + void updateRenderObject( + BuildContext context, _RenderTDCupertinoSwitch renderObject) { + renderObject + ..value = value + ..activeColor = activeColor + ..trackColor = trackColor + ..thumbColor = thumbColor + ..onChanged = onChanged + ..textDirection = textDirection; + } +} + +const double _kTrackWidth = 45.0; +const double _kTrackHeight = 28.0; +const double _kTrackRadius = _kTrackHeight / 2.0; +const double _kTrackInnerStart = _kTrackHeight / 2.0; +const double _kTrackInnerEnd = _kTrackWidth - _kTrackInnerStart; +const double _kTrackInnerLength = _kTrackInnerEnd - _kTrackInnerStart; +const double _kSwitchWidth = 45.0; +const double _kSwitchHeight = 28.0; +// Opacity of a disabled switch, as eye-balled from iOS Simulator on Mac. +const double _kTDCupertinoSwitchDisabledOpacity = 0.5; +const double _kThumbRadius = 11; +const double _kThumbMargin = (_kTrackHeight - _kThumbRadius * 2) / 2; + +const Duration _kReactionDuration = Duration(milliseconds: 300); +const Duration _kToggleDuration = Duration(milliseconds: 200); + +class _RenderTDCupertinoSwitch extends RenderConstrainedBox { + _RenderTDCupertinoSwitch({ + required bool value, + required Color activeColor, + required Color trackColor, + required Color thumbColor, + ValueChanged? onChanged, + required TextDirection textDirection, + required _TDCupertinoSwitchState state, + }) : _value = value, + _activeColor = activeColor, + _trackColor = trackColor, + _thumbPainter = CupertinoThumbPainter.switchThumb(color: thumbColor), + _onChanged = onChanged, + _textDirection = textDirection, + _state = state, + super( + additionalConstraints: const BoxConstraints.tightFor( + width: _kSwitchWidth, height: _kSwitchHeight)) { + state.position.addListener(markNeedsPaint); + state._reaction.addListener(markNeedsPaint); + } + + final _TDCupertinoSwitchState _state; + + bool get value => _value; + bool _value; + + set value(bool value) { + if (value == _value) { + return; + } + _value = value; + markNeedsSemanticsUpdate(); + } + + Color get activeColor => _activeColor; + Color _activeColor; + + set activeColor(Color value) { + if (value == _activeColor) { + return; + } + _activeColor = value; + markNeedsPaint(); + } + + Color get trackColor => _trackColor; + Color _trackColor; + + set trackColor(Color value) { + assert(value != null); + if (value == _trackColor) { + return; + } + _trackColor = value; + markNeedsPaint(); + } + + Color get thumbColor => _thumbPainter.color; + CupertinoThumbPainter _thumbPainter; + + set thumbColor(Color value) { + if (value == thumbColor) { + return; + } + _thumbPainter = CupertinoThumbPainter.switchThumb(color: value); + markNeedsPaint(); + } + + ValueChanged? get onChanged => _onChanged; + ValueChanged? _onChanged; + + set onChanged(ValueChanged? value) { + if (value == _onChanged) { + return; + } + final wasInteractive = isInteractive; + _onChanged = value; + if (wasInteractive != isInteractive) { + markNeedsPaint(); + markNeedsSemanticsUpdate(); + } + } + + TextDirection get textDirection => _textDirection; + TextDirection _textDirection; + + set textDirection(TextDirection value) { + assert(value != null); + if (_textDirection == value) { + return; + } + _textDirection = value; + markNeedsPaint(); + } + + bool get isInteractive => onChanged != null; + + @override + bool hitTestSelf(Offset position) => true; + + @override + void handleEvent(PointerEvent event, BoxHitTestEntry entry) { + assert(debugHandleEvent(event, entry)); + if (event is PointerDownEvent && isInteractive) { + _state._drag.addPointer(event); + _state._tap.addPointer(event); + } + } + + @override + void describeSemanticsConfiguration(SemanticsConfiguration config) { + super.describeSemanticsConfiguration(config); + + if (isInteractive) { + config.onTap = _state._handleTap; + } + + config.isEnabled = isInteractive; + config.isToggled = _value; + } + + @override + void paint(PaintingContext context, Offset offset) { + final canvas = context.canvas; + + final currentValue = _state.position.value; + final currentReactionValue = _state._reaction.value; + + final double visualPosition; + switch (textDirection) { + case TextDirection.rtl: + visualPosition = 1.0 - currentValue; + break; + case TextDirection.ltr: + visualPosition = currentValue; + break; + } + + final paint = Paint() + ..color = Color.lerp(trackColor, activeColor, currentValue)!; + + final trackRect = Rect.fromLTWH( + offset.dx + (size.width - _kTrackWidth) / 2.0, + offset.dy + (size.height - _kTrackHeight) / 2.0, + _kTrackWidth, + _kTrackHeight, + ); + final trackRRect = RRect.fromRectAndRadius( + trackRect, const Radius.circular(_kTrackRadius)); + canvas.drawRRect(trackRRect, paint); + + final currentThumbExtension = + CupertinoThumbPainter.extension * currentReactionValue; + final thumbLeft = lerpDouble( + trackRect.left + _kTrackInnerStart - _kThumbRadius, + trackRect.left + _kTrackInnerEnd - _kThumbRadius - currentThumbExtension, + visualPosition, + )!; + final thumbRight = lerpDouble( + trackRect.left + + _kTrackInnerStart + + _kThumbRadius + + currentThumbExtension, + trackRect.left + _kTrackInnerEnd + _kThumbRadius, + visualPosition, + )!; + final thumbCenterY = offset.dy + size.height / 2.0; + final thumbBounds = Rect.fromLTRB( + thumbLeft, + thumbCenterY - _kThumbRadius, + thumbRight, + thumbCenterY + _kThumbRadius, + ); + + _clipRRectLayer.layer = context + .pushClipRRect(needsCompositing, Offset.zero, thumbBounds, trackRRect, + (PaintingContext innerContext, Offset offset) { + _thumbPainter.paint(innerContext.canvas, thumbBounds); + }, oldLayer: _clipRRectLayer.layer); + if (child != null) { + context.paintChild(child!, Offset(thumbBounds.left + _kThumbMargin, 0)); + } + } + + final LayerHandle _clipRRectLayer = + LayerHandle(); + + @override + void dispose() { + _clipRRectLayer.layer = null; + super.dispose(); + } + + @override + void debugFillProperties(DiagnosticPropertiesBuilder description) { + super.debugFillProperties(description); + description.add(FlagProperty('value', + value: value, ifTrue: 'checked', ifFalse: 'unchecked', showName: true)); + description.add(FlagProperty('isInteractive', + value: isInteractive, + ifTrue: 'enabled', + ifFalse: 'disabled', + showName: true, + defaultValue: true)); + } +} diff --git a/lib/src/components/switch/td_loading_paint.dart b/lib/src/components/switch/td_loading_paint.dart new file mode 100644 index 000000000..98c45e107 --- /dev/null +++ b/lib/src/components/switch/td_loading_paint.dart @@ -0,0 +1,43 @@ +import 'dart:math'; +import 'dart:ui' as ui; + +import 'package:flutter/cupertino.dart'; + +class TDLoadingPainter extends CustomPainter { + final Color color; + final double width; + + TDLoadingPainter({required this.color, required this.width}); + + final _paint = Paint()..style = PaintingStyle.stroke; + + @override + void paint(Canvas canvas, Size size) { + var minLength = min(size.width, size.height); + _paint.strokeWidth = width; + _paint.shader = ui.Gradient.sweep(Offset(size.width / 2, size.height / 2), + [const Color(0x01ffffff), color]); + if (minLength == size.width) { + canvas.drawArc( + Rect.fromLTWH( + 0, (size.height - size.width) / 2, size.width, size.width), + 0, + pi * 2, + false, + _paint); + } else { + canvas.drawArc( + Rect.fromLTWH( + (size.width - size.height) / 2, 0, size.height, size.height), + 0, + pi * 2, + false, + _paint); + } + } + + @override + bool shouldRepaint(CustomPainter oldDelegate) { + return true; + } +} diff --git a/lib/src/components/switch/td_switch.dart b/lib/src/components/switch/td_switch.dart index a0fd58554..12c210cc2 100644 --- a/lib/src/components/switch/td_switch.dart +++ b/lib/src/components/switch/td_switch.dart @@ -1,17 +1,23 @@ import 'package:flutter/cupertino.dart'; import '../../../td_export.dart'; +import 'td_cupertino_switch.dart'; -class TDSwitch extends StatefulWidget { +enum TDSwitchSize { large, medium, small } + +enum TDSwitchType { fill, text, loading, icon } +class TDSwitch extends StatefulWidget { const TDSwitch({ Key? key, this.enable = true, this.isOn = false, + this.size = TDSwitchSize.medium, + this.type = TDSwitchType.fill, this.onColor, - this.offColor, + this.offColor = CupertinoColors.secondarySystemFill, this.onChanged, - }): super(key: key); + }) : super(key: key); /// 是否可点击 final bool enable; @@ -25,6 +31,12 @@ class TDSwitch extends StatefulWidget { /// 关闭颜色 final Color? offColor; + /// 尺寸:大、中、小 + final TDSwitchSize? size; + + /// 类型:填充、文本、加载 + final TDSwitchType? type; + ///改变事件 final ValueChanged? onChanged; @@ -35,7 +47,6 @@ class TDSwitch extends StatefulWidget { } class TDSwitchState extends State { - bool isOn = false; @override @@ -53,17 +64,18 @@ class TDSwitchState extends State { @override Widget build(BuildContext context) { final theme = TDTheme.of(context); - Widget current = CupertinoSwitch( + var onColor = widget.onColor ?? theme.brandColor8; + var offColor = widget.offColor ?? theme.grayColor4; + Widget current = TDCupertinoSwitch( value: isOn, - activeColor: widget.onColor ?? theme.brandColor8, - trackColor: widget.offColor ?? theme.grayColor4, + activeColor: onColor, + trackColor: offColor, onChanged: (value) { widget.onChanged?.call(value); isOn = value; - setState(() { - - }); + setState(() {}); }, + thumbView: _getThumbView(onColor, offColor), ); if (!widget.enable) { current = Opacity( @@ -74,6 +86,69 @@ class TDSwitchState extends State { ), ); } - return current; + return SizedBox( + width: _getWidth(), + height: _getHeight(), + child: FittedBox( + child: current, + ), + ); + // return ConstrainedBox( _getWidth(), height: _getHeight(), child: current); + } + + double _getWidth() { + switch (widget.size) { + case TDSwitchSize.large: + return 52; + case TDSwitchSize.medium: + return 45; + case TDSwitchSize.small: + return 39; + } + return 45; + } + + double _getHeight() { + switch (widget.size) { + case TDSwitchSize.large: + return 32; + case TDSwitchSize.medium: + return 28; + case TDSwitchSize.small: + return 24; + } + return 28; + } + + Widget? _getThumbView(Color onColor, Color offColor) { + switch (widget.type) { + case TDSwitchType.text: + return Container( + alignment: Alignment.centerLeft, + child: TDText( + isOn ? '开' : '关', + style: TextStyle(color: isOn ? onColor : offColor), + ), + ); + case TDSwitchType.loading: + return Container( + alignment: Alignment.centerLeft, + child: TDCircleIndicator( + color: onColor, + size: 16, + lineWidth: 3, + duration: 1000, + ), + ); + case TDSwitchType.icon: + return Container( + alignment: Alignment.centerLeft, + child: Icon(isOn ? TDIcons.check : TDIcons.close, + size: 16, color: isOn ? onColor : offColor), + ); + case TDSwitchType.fill: + return null; + } + return null; } } diff --git a/lib/td_export.dart b/lib/td_export.dart index 78224ab13..7c024c789 100644 --- a/lib/td_export.dart +++ b/lib/td_export.dart @@ -44,4 +44,4 @@ export 'src/theme/td_radius.dart'; export 'src/theme/td_shadows.dart'; export 'src/theme/td_spacers.dart'; export 'src/theme/td_theme.dart'; -export 'src/util/platform_util.dart'; \ No newline at end of file +export 'src/util/platform_util.dart'; diff --git a/pubspec.lock b/pubspec.lock index 42b8c67cf..b157022e8 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -7,7 +7,7 @@ packages: name: async url: "https://pub.flutter-io.cn" source: hosted - version: "2.8.2" + version: "2.9.0" boolean_selector: dependency: transitive description: @@ -21,21 +21,14 @@ packages: name: characters url: "https://pub.flutter-io.cn" source: hosted - version: "1.2.0" - charcode: - dependency: transitive - description: - name: charcode - url: "https://pub.flutter-io.cn" - source: hosted - version: "1.3.1" + version: "1.2.1" clock: dependency: transitive description: name: clock url: "https://pub.flutter-io.cn" source: hosted - version: "1.1.0" + version: "1.1.1" collection: dependency: transitive description: @@ -49,7 +42,7 @@ packages: name: fake_async url: "https://pub.flutter-io.cn" source: hosted - version: "1.3.0" + version: "1.3.1" flutter: dependency: "direct main" description: flutter @@ -94,28 +87,28 @@ packages: name: matcher url: "https://pub.flutter-io.cn" source: hosted - version: "0.12.11" + version: "0.12.12" material_color_utilities: dependency: transitive description: name: material_color_utilities url: "https://pub.flutter-io.cn" source: hosted - version: "0.1.4" + version: "0.1.5" meta: dependency: transitive description: name: meta url: "https://pub.flutter-io.cn" source: hosted - version: "1.7.0" + version: "1.8.0" path: dependency: transitive description: name: path url: "https://pub.flutter-io.cn" source: hosted - version: "1.8.1" + version: "1.8.2" sky_engine: dependency: transitive description: flutter @@ -127,7 +120,7 @@ packages: name: source_span url: "https://pub.flutter-io.cn" source: hosted - version: "1.8.2" + version: "1.9.0" stack_trace: dependency: transitive description: @@ -148,21 +141,21 @@ packages: name: string_scanner url: "https://pub.flutter-io.cn" source: hosted - version: "1.1.0" + version: "1.1.1" term_glyph: dependency: transitive description: name: term_glyph url: "https://pub.flutter-io.cn" source: hosted - version: "1.2.0" + version: "1.2.1" test_api: dependency: transitive description: name: test_api url: "https://pub.flutter-io.cn" source: hosted - version: "0.4.9" + version: "0.4.12" vector_math: dependency: transitive description: From 47ff1dc01e068fff305eb72379eaf42cbfff191a Mon Sep 17 00:00:00 2001 From: broadli Date: Fri, 17 Feb 2023 21:37:19 +0800 Subject: [PATCH 2/5] fix: fix code style --- example/lib/page/td_switch_page.dart | 86 ++++++++----------- .../switch/td_cupertino_switch.dart | 4 +- lib/src/components/switch/td_switch.dart | 8 +- 3 files changed, 43 insertions(+), 55 deletions(-) diff --git a/example/lib/page/td_switch_page.dart b/example/lib/page/td_switch_page.dart index 356c07f72..313d1169e 100644 --- a/example/lib/page/td_switch_page.dart +++ b/example/lib/page/td_switch_page.dart @@ -25,11 +25,9 @@ class TDSwitchPageState extends State { children: [ ExampleItem(builder: (_) => _title('基础开关')), ExampleItem( - builder: (_) => Container( - child: Column( - mainAxisSize: MainAxisSize.min, - children: [demoRow(context, '基础开关', on: true)], - ), + builder: (_) => Column( + mainAxisSize: MainAxisSize.min, + children: [demoRow(context, '基础开关', on: true)], )), ExampleItem(builder: (_) => _title('带描述开关')), ExampleItem( @@ -52,64 +50,54 @@ class TDSwitchPageState extends State { ExampleModule(title: '组件状态', children: [ ExampleItem(builder: (_) => _title('加载状态')), ExampleItem( - builder: (_) => Container( - child: Column( - mainAxisSize: MainAxisSize.min, - children: [ - demoRow( - context, - '加载状态', - on: true, - enable: false, - type: TDSwitchType.loading, - ), - demoRow( - context, - '加载状态', - on: false, - enable: false, - type: TDSwitchType.loading, - ), - ], - ), + builder: (_) => Column( + mainAxisSize: MainAxisSize.min, + children: [ + demoRow( + context, + '加载状态', + on: true, + enable: false, + type: TDSwitchType.loading, + ), + demoRow( + context, + '加载状态', + on: false, + enable: false, + type: TDSwitchType.loading, + ), + ], )), ExampleItem(builder: (_) => _title('禁用状态')), ExampleItem( - builder: (_) => Container( - child: Column( - mainAxisSize: MainAxisSize.min, - children: [ - demoRow(context, '禁用状态', on: false, enable: false), - demoRow(context, '禁用状态', on: true, enable: false), - ], - ), + builder: (_) => Column( + mainAxisSize: MainAxisSize.min, + children: [ + demoRow(context, '禁用状态', on: false, enable: false), + demoRow(context, '禁用状态', on: true, enable: false), + ], )), ]), ExampleModule(title: '组件样式', children: [ ExampleItem(builder: (_) => _title('开关尺寸')), ExampleItem( - builder: (_) => Container( - child: Column( - mainAxisSize: MainAxisSize.min, - children: [ - demoRow(context, '大尺寸32', - on: true, size: TDSwitchSize.large), - demoRow(context, '中尺寸28', - on: true, size: TDSwitchSize.medium), - demoRow(context, '小尺寸24', - on: true, size: TDSwitchSize.small), - ], - ), + builder: (_) => Column( + mainAxisSize: MainAxisSize.min, + children: [ + demoRow(context, '大尺寸32', + on: true, size: TDSwitchSize.large), + demoRow(context, '中尺寸28', + on: true, size: TDSwitchSize.medium), + demoRow(context, '小尺寸24', + on: true, size: TDSwitchSize.small), + ], )), ]), ]); return current; } - Widget _divider() { - return Container(height: 0.5, color: const Color(0x33999999)); - } - Widget _title(String title) { return Container( height: 40, diff --git a/lib/src/components/switch/td_cupertino_switch.dart b/lib/src/components/switch/td_cupertino_switch.dart index 36cff9dbc..c11ee706a 100644 --- a/lib/src/components/switch/td_cupertino_switch.dart +++ b/lib/src/components/switch/td_cupertino_switch.dart @@ -10,7 +10,6 @@ import 'package:flutter/gestures.dart'; import 'package:flutter/material.dart'; import 'package:flutter/rendering.dart'; import 'package:flutter/services.dart'; -import 'package:flutter/widgets.dart'; // Examples can assume: // bool _lights = false; @@ -380,6 +379,7 @@ class _TDCupertinoSwitchRenderObjectWidget /// The widget below this widget in the tree. /// /// {@macro flutter.widgets.ProxyWidget.child} + @override final Widget? child; @override @@ -475,7 +475,6 @@ class _RenderTDCupertinoSwitch extends RenderConstrainedBox { Color _trackColor; set trackColor(Color value) { - assert(value != null); if (value == _trackColor) { return; } @@ -513,7 +512,6 @@ class _RenderTDCupertinoSwitch extends RenderConstrainedBox { TextDirection _textDirection; set textDirection(TextDirection value) { - assert(value != null); if (_textDirection == value) { return; } diff --git a/lib/src/components/switch/td_switch.dart b/lib/src/components/switch/td_switch.dart index 12c210cc2..3856cdd06 100644 --- a/lib/src/components/switch/td_switch.dart +++ b/lib/src/components/switch/td_switch.dart @@ -104,8 +104,9 @@ class TDSwitchState extends State { return 45; case TDSwitchSize.small: return 39; + default: + return 45; } - return 45; } double _getHeight() { @@ -116,8 +117,9 @@ class TDSwitchState extends State { return 28; case TDSwitchSize.small: return 24; + default: + return 28; } - return 28; } Widget? _getThumbView(Color onColor, Color offColor) { @@ -147,8 +149,8 @@ class TDSwitchState extends State { size: 16, color: isOn ? onColor : offColor), ); case TDSwitchType.fill: + default: return null; } - return null; } } From 94ae0c0cb6d833e4c0d1d765a5ef0004e76f49bd Mon Sep 17 00:00:00 2001 From: broadli Date: Fri, 17 Feb 2023 21:46:12 +0800 Subject: [PATCH 3/5] =?UTF-8?q?fix:=20=E8=A7=86=E8=A7=89=E8=BF=98=E5=8E=9F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- example/lib/page/td_loading_page.dart | 203 ++++++++++++----------- lib/src/components/switch/td_switch.dart | 4 +- 2 files changed, 110 insertions(+), 97 deletions(-) diff --git a/example/lib/page/td_loading_page.dart b/example/lib/page/td_loading_page.dart index 4fc6aee89..1d1bd1f89 100644 --- a/example/lib/page/td_loading_page.dart +++ b/example/lib/page/td_loading_page.dart @@ -6,6 +6,7 @@ import 'package:flutter/material.dart'; import 'package:tdesign_flutter/td_export.dart'; + import '../../base/example_widget.dart'; import '../annotation/demo.dart'; @@ -23,69 +24,77 @@ class _TDLoadingPageState extends State { @override Widget build(BuildContext context) { return ExamplePage( - title: '加载 Loading', - backgroundColor: TDTheme.of(context).whiteColor1, - exampleCodeGroup: 'loading', - desc: '用于表示页面或操作的加载状态,给予用户反馈的同时减缓等待的焦虑感,由一个或一组反馈动效组成。', - children: [ - ExampleModule(title: '组件类型', children: [ - ExampleItem(desc: '纯图标', builder: _buildPureIconLoading), - ExampleItem( - desc: '图标加文字横向', builder: _buildTextIconHorizontalLoading), - ExampleItem( - desc: '图标加文字竖向', builder: _buildTextIconVerticalLoading), - ExampleItem(desc: '纯文字', builder: _buildPureTextLoading), - ]), - ExampleModule(title: '组件尺寸', children: [ - ExampleItem(desc: '大尺寸', builder: _buildLargeLoading), - ExampleItem(desc: '中尺寸', builder: _buildMediumLoading), - ExampleItem(desc: '小尺寸', builder: _buildSmallLoading), - ]), - ExampleModule(title: '加载速度', children: [ - ExampleItem(desc: '调整加载速度', builder: _buildCustomSpeedLoading), - ]), - ], - test: [ - ExampleItem( - desc: '带图标的失败横向Loading', - ignoreCode: true, - builder: (_){ - return Padding(padding: const EdgeInsets.all(16), - child: TDLoading( - icon: TDLoadingIcon.circle, - size: TDLoadingSize.small, - axis: Axis.horizontal, - text: '加载失败', - refreshWidget: GestureDetector( - child: TDText('刷新', - font: TDTheme.of(context).fontBodySmall, - textColor: TDTheme.of(context).brandNormalColor,), - onTap: (){ - TDToast.showText('刷新', context: context); - }, - ), - ),); - }), - ExampleItem( - desc: '带图标的失败竖向Loading', - ignoreCode: true, - builder: (_){ - return Container(padding: const EdgeInsets.all(16), - child: TDLoading( - icon: TDLoadingIcon.circle, - size: TDLoadingSize.small, - text: '加载失败', - refreshWidget: GestureDetector( - child: TDText('刷新', - font: TDTheme.of(context).fontBodySmall, - textColor: TDTheme.of(context).brandNormalColor,), - onTap: (){ - TDToast.showText('刷新', context: context); - }, - ), - ),); - }), - ],); + title: '加载 Loading', + backgroundColor: TDTheme.of(context).whiteColor1, + exampleCodeGroup: 'loading', + desc: '用于表示页面或操作的加载状态,给予用户反馈的同时减缓等待的焦虑感,由一个或一组反馈动效组成。', + children: [ + ExampleModule(title: '组件类型', children: [ + ExampleItem(desc: '纯图标', builder: _buildPureIconLoading), + ExampleItem( + desc: '图标加文字横向', builder: _buildTextIconHorizontalLoading), + ExampleItem(desc: '图标加文字竖向', builder: _buildTextIconVerticalLoading), + ExampleItem(desc: '纯文字', builder: _buildPureTextLoading), + ]), + ExampleModule(title: '组件尺寸', children: [ + ExampleItem(desc: '大尺寸', builder: _buildLargeLoading), + ExampleItem(desc: '中尺寸', builder: _buildMediumLoading), + ExampleItem(desc: '小尺寸', builder: _buildSmallLoading), + ]), + ExampleModule(title: '加载速度', children: [ + ExampleItem(desc: '调整加载速度', builder: _buildCustomSpeedLoading), + ]), + ], + test: [ + ExampleItem( + desc: '带图标的失败横向Loading', + ignoreCode: true, + builder: (_) { + return Padding( + padding: const EdgeInsets.all(16), + child: TDLoading( + icon: TDLoadingIcon.circle, + size: TDLoadingSize.small, + axis: Axis.horizontal, + text: '加载失败', + refreshWidget: GestureDetector( + child: TDText( + '刷新', + font: TDTheme.of(context).fontBodySmall, + textColor: TDTheme.of(context).brandNormalColor, + ), + onTap: () { + TDToast.showText('刷新', context: context); + }, + ), + ), + ); + }), + ExampleItem( + desc: '带图标的失败竖向Loading', + ignoreCode: true, + builder: (_) { + return Container( + padding: const EdgeInsets.all(16), + child: TDLoading( + icon: TDLoadingIcon.circle, + size: TDLoadingSize.small, + text: '加载失败', + refreshWidget: GestureDetector( + child: TDText( + '刷新', + font: TDTheme.of(context).fontBodySmall, + textColor: TDTheme.of(context).brandNormalColor, + ), + onTap: () { + TDToast.showText('刷新', context: context); + }, + ), + ), + ); + }), + ], + ); } @Demo(group: 'loading') @@ -174,10 +183,12 @@ class _TDLoadingPageState extends State { size: TDLoadingSize.small, text: '加载失败', refreshWidget: GestureDetector( - child: TDText('刷新', - font: TDTheme.of(context).fontBodySmall, - textColor: TDTheme.of(context).brandNormalColor,), - onTap: (){ + child: TDText( + '刷新', + font: TDTheme.of(context).fontBodySmall, + textColor: TDTheme.of(context).brandNormalColor, + ), + onTap: () { TDToast.showText('刷新', context: context); }, ), @@ -224,37 +235,39 @@ class _TDLoadingPageState extends State { ]); } - double _currentSliderValue = 1000; + /// 自定义尺寸 @Demo(group: 'loading') Widget _buildCustomSpeedLoading(BuildContext context) { - return Padding(padding: EdgeInsets.all(16), - child: Column( - mainAxisAlignment: MainAxisAlignment.spaceAround, - mainAxisSize: MainAxisSize.min, - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - TDLoading( - size: TDLoadingSize.small, - icon: TDLoadingIcon.circle, - axis: Axis.horizontal, - text: '加载中…', - duration: _currentSliderValue.round(), - ), - Slider( - value: _currentSliderValue, - max: 2000, - min: -20, - divisions: 100, - label: _currentSliderValue.round().toString(), - onChanged: (double value) { - setState(() { - _currentSliderValue = value; - }); - }, - ), - ], - ),); + return Padding( + padding: const EdgeInsets.all(16), + child: Column( + mainAxisAlignment: MainAxisAlignment.spaceAround, + mainAxisSize: MainAxisSize.min, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + TDLoading( + size: TDLoadingSize.small, + icon: TDLoadingIcon.circle, + axis: Axis.horizontal, + text: '加载中…', + duration: _currentSliderValue.round(), + ), + Slider( + value: _currentSliderValue, + max: 2000, + min: -20, + divisions: 100, + label: _currentSliderValue.round().toString(), + onChanged: (double value) { + setState(() { + _currentSliderValue = value; + }); + }, + ), + ], + ), + ); } } diff --git a/lib/src/components/switch/td_switch.dart b/lib/src/components/switch/td_switch.dart index 3856cdd06..62958dfc2 100644 --- a/lib/src/components/switch/td_switch.dart +++ b/lib/src/components/switch/td_switch.dart @@ -127,9 +127,10 @@ class TDSwitchState extends State { case TDSwitchType.text: return Container( alignment: Alignment.centerLeft, + padding: const EdgeInsets.only(left: 1), child: TDText( isOn ? '开' : '关', - style: TextStyle(color: isOn ? onColor : offColor), + style: TextStyle(color: isOn ? onColor : offColor, fontSize: 14), ), ); case TDSwitchType.loading: @@ -139,7 +140,6 @@ class TDSwitchState extends State { color: onColor, size: 16, lineWidth: 3, - duration: 1000, ), ); case TDSwitchType.icon: From bed03eb4d598ca99776d019676fc7afc30d2ae35 Mon Sep 17 00:00:00 2001 From: broadli Date: Mon, 20 Feb 2023 13:09:44 +0800 Subject: [PATCH 4/5] fix: demo --- example/lib/page/td_switch_page.dart | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/example/lib/page/td_switch_page.dart b/example/lib/page/td_switch_page.dart index 313d1169e..3f3b2959f 100644 --- a/example/lib/page/td_switch_page.dart +++ b/example/lib/page/td_switch_page.dart @@ -56,15 +56,13 @@ class TDSwitchPageState extends State { demoRow( context, '加载状态', - on: true, - enable: false, + on: false, type: TDSwitchType.loading, ), demoRow( context, '加载状态', - on: false, - enable: false, + on: true, type: TDSwitchType.loading, ), ], From 79ed97bd01ee739b5dc2e4ab07086364291c0280 Mon Sep 17 00:00:00 2001 From: broadli Date: Mon, 20 Feb 2023 19:12:15 +0800 Subject: [PATCH 5/5] =?UTF-8?q?feat(switch):=20demo=E9=A1=B5=E9=9D=A2?= =?UTF-8?q?=E6=96=B0=E5=A2=9E=E6=B5=8B=E8=AF=95=E4=BB=A3=E7=A0=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- example/lib/page/td_switch_page.dart | 296 +++++++++++++++++++-------- 1 file changed, 206 insertions(+), 90 deletions(-) diff --git a/example/lib/page/td_switch_page.dart b/example/lib/page/td_switch_page.dart index 3f3b2959f..4e85625c2 100644 --- a/example/lib/page/td_switch_page.dart +++ b/example/lib/page/td_switch_page.dart @@ -1,6 +1,7 @@ import 'package:flutter/material.dart'; import 'package:tdesign_flutter/td_export.dart'; +import '../annotation/demo.dart'; import '../base/example_widget.dart'; /// @@ -18,93 +19,35 @@ class TDSwitchPage extends StatefulWidget { class TDSwitchPageState extends State { @override Widget build(BuildContext context) { - var current = - ExamplePage(title: 'Switch 开关', desc: '用于控制某个功能的开启和关闭。', children: [ - ExampleModule( - title: '组件类型', + var current = ExamplePage( + title: 'Switch 开关', + exampleCodeGroup: 'switch', + desc: '用于控制某个功能的开启和关闭。', children: [ - ExampleItem(builder: (_) => _title('基础开关')), - ExampleItem( - builder: (_) => Column( - mainAxisSize: MainAxisSize.min, - children: [demoRow(context, '基础开关', on: true)], - )), - ExampleItem(builder: (_) => _title('带描述开关')), - ExampleItem( - builder: (_) => Column( - mainAxisSize: MainAxisSize.min, - children: [ - demoRow(context, '带文字开关', - on: true, type: TDSwitchType.text), - demoRow(context, '带图标开关', - on: true, type: TDSwitchType.icon) - ], - )), - ExampleItem(builder: (_) => _title('自定义颜色开关')), - ExampleItem( - builder: (_) => - demoRow(context, '自定义颜色开关', on: true, onColor: Colors.green), + ExampleModule( + title: '组件类型', + children: [ + ExampleItem(desc: '基础开关', builder: _buildSwitchWithBase), + ExampleItem(desc: '带描述开关', builder: _buildSwitchWithText), + ExampleItem(builder: _buildSwitchWithIcon), + ExampleItem(desc: '自定义颜色开关', builder: _buildSwitchWithColor), + ], ), - ], - ), - ExampleModule(title: '组件状态', children: [ - ExampleItem(builder: (_) => _title('加载状态')), - ExampleItem( - builder: (_) => Column( - mainAxisSize: MainAxisSize.min, - children: [ - demoRow( - context, - '加载状态', - on: false, - type: TDSwitchType.loading, - ), - demoRow( - context, - '加载状态', - on: true, - type: TDSwitchType.loading, - ), - ], - )), - ExampleItem(builder: (_) => _title('禁用状态')), - ExampleItem( - builder: (_) => Column( - mainAxisSize: MainAxisSize.min, - children: [ - demoRow(context, '禁用状态', on: false, enable: false), - demoRow(context, '禁用状态', on: true, enable: false), - ], - )), - ]), - ExampleModule(title: '组件样式', children: [ - ExampleItem(builder: (_) => _title('开关尺寸')), - ExampleItem( - builder: (_) => Column( - mainAxisSize: MainAxisSize.min, - children: [ - demoRow(context, '大尺寸32', - on: true, size: TDSwitchSize.large), - demoRow(context, '中尺寸28', - on: true, size: TDSwitchSize.medium), - demoRow(context, '小尺寸24', - on: true, size: TDSwitchSize.small), - ], - )), - ]), - ]); + ExampleModule(title: '组件状态', children: [ + ExampleItem(desc: '加载状态', builder: _buildSwitchWithLoadingOff), + ExampleItem(builder: _buildSwitchWithLoadingOn), + ExampleItem(desc: '禁用状态', builder: _buildSwitchWithDisableOff), + ExampleItem(builder: _buildSwitchWithDisableOn), + ]), + ExampleModule(title: '组件样式', children: [ + ExampleItem(desc: '开关尺寸', builder: _buildSwitchWithSizeLarge), + ExampleItem(builder: _buildSwitchWithSizeMed), + ExampleItem(builder: _buildSwitchWithSizeSmall), + ]), + ]); return current; } - Widget _title(String title) { - return Container( - height: 40, - padding: const EdgeInsets.only(left: 10), - alignment: Alignment.centerLeft, - child: Text(title), - ); - } - Widget demoRow( BuildContext context, String? title, { @@ -129,14 +72,13 @@ class TDSwitchPageState extends State { textColor: theme.grayColor6, ), SizedBox( - child: TDSwitch( - isOn: on, - onColor: onColor, - enable: enable, - offColor: offColor, - size: size, - type: type, - ), + child: _buildSwitch( + on: on, + enable: enable, + onColor: onColor, + offColor: offColor, + size: size, + type: type), ) ], ); @@ -155,4 +97,178 @@ class TDSwitchPageState extends State { ); return current; } + + /// 每一项的封装 + @Demo(group: 'switch') + Widget _buildItem(BuildContext context, Widget switchItem, + {String? title, String? desc}) { + final theme = TDTheme.of(context); + Widget current = Row( + children: [ + Expanded( + child: TDText( + title ?? '', + textColor: theme.fontGyColor1, + )), + TDText( + desc ?? '', + textColor: theme.grayColor6, + ), + SizedBox(child: switchItem) + ], + ); + current = Padding( + padding: const EdgeInsets.symmetric(vertical: 8), + child: SizedBox( + child: Container( + child: Padding( + padding: const EdgeInsets.symmetric(horizontal: 16.0), + child: current, + ), + color: Colors.white, + ), + height: 56, + ), + ); + return Column(mainAxisSize: MainAxisSize.min, children: [current]); + } + + @Demo(group: 'switch') + Widget _buildSwitchWithBase(BuildContext context) { + return _buildItem( + context, + const TDSwitch(), + title: '基础开关', + ); + } + + @Demo(group: 'switch') + Widget _buildSwitchWithText(BuildContext context) { + return _buildItem( + context, + const TDSwitch(isOn: true, type: TDSwitchType.text), + title: '带文字开关', + ); + } + + @Demo(group: 'switch') + Widget _buildSwitchWithIcon(BuildContext context) { + return _buildItem( + context, + const TDSwitch(isOn: true, type: TDSwitchType.icon), + title: '带图标开关', + ); + } + + @Demo(group: 'switch') + Widget _buildSwitchWithColor(BuildContext context) { + return _buildItem( + context, + const TDSwitch(isOn: true, onColor: Colors.green), + title: '自定义颜色开关', + ); + } + + @Demo(group: 'switch') + Widget _buildSwitchWithLoadingOff(BuildContext context) { + return _buildItem( + context, + const TDSwitch( + isOn: false, + type: TDSwitchType.loading, + ), + title: '加载状态', + ); + } + + @Demo(group: 'switch') + Widget _buildSwitchWithLoadingOn(BuildContext context) { + return _buildItem( + context, + const TDSwitch( + isOn: true, + type: TDSwitchType.loading, + ), + title: '加载状态', + ); + } + + @Demo(group: 'switch') + Widget _buildSwitchWithDisableOff(BuildContext context) { + return _buildItem( + context, + const TDSwitch( + enable: false, + isOn: false, + ), + title: '禁用状态', + ); + } + + @Demo(group: 'switch') + Widget _buildSwitchWithDisableOn(BuildContext context) { + return _buildItem( + context, + const TDSwitch( + enable: false, + isOn: true, + ), + title: '禁用状态', + ); + } + + @Demo(group: 'switch') + Widget _buildSwitchWithSizeLarge(BuildContext context) { + return _buildItem( + context, + const TDSwitch( + isOn: true, + size: TDSwitchSize.large, + ), + title: '大尺寸32', + ); + } + + @Demo(group: 'switch') + Widget _buildSwitchWithSizeMed(BuildContext context) { + return _buildItem( + context, + const TDSwitch( + isOn: true, + size: TDSwitchSize.medium, + ), + title: '中尺寸28', + ); + } + + @Demo(group: 'switch') + Widget _buildSwitchWithSizeSmall(BuildContext context) { + return _buildItem( + context, + const TDSwitch( + isOn: true, + size: TDSwitchSize.small, + ), + title: '中尺寸24', + ); + } + + @Demo(group: 'switch') + Widget _buildSwitch({ + bool on = true, + bool enable = true, + Color? onColor, + Color? offColor, + TDSwitchSize? size, + TDSwitchType? type, + }) { + return TDSwitch( + isOn: on, + onColor: onColor, + enable: enable, + offColor: offColor, + size: size, + type: type, + ); + } }