From 1eaadd70377c98356b7bacc453ab0d332da7a487 Mon Sep 17 00:00:00 2001 From: Amos Date: Wed, 15 Apr 2026 03:05:26 +0800 Subject: [PATCH 01/10] feat: refactor Tilt, split configs, deprecate parameters, add TiltAnimatedBuilder --- CHANGELOG.md | 22 + example/lib/main.dart | 4 +- lib/flutter_tilt.dart | 20 +- lib/src/config/tilt_config.dart | 8 - lib/src/config/tilt_light_config.dart | 98 +++- lib/src/config/tilt_shadow_config.dart | 292 ++++++----- lib/src/enums.dart | 38 -- lib/src/internal/tilt_data.dart | 18 +- lib/src/models/tilt_data_model.dart | 10 + lib/src/models/tilt_stream_model.dart | 2 +- lib/src/tilt.dart | 467 ++++-------------- .../containers/tilt_base_container.dart | 200 ++++++++ .../containers/tilt_container_utils.dart | 22 + .../tilt_parallax_container.dart | 42 +- .../containers/tilt_projector_container.dart | 278 +++++++++++ .../widgets/{ => core}/gestures_listener.dart | 6 +- .../widgets/core/tilt_animated_builder.dart | 140 ++++++ .../{ => core}/tilt_stream_builder.dart | 4 +- lib/src/widgets/core/tilt_widget.dart | 422 ++++++++++++++++ lib/src/widgets/{ => effects}/tilt_light.dart | 8 +- .../widgets/{ => effects}/tilt_shadow.dart | 25 +- lib/src/widgets/tilt_container.dart | 353 ------------- pubspec.yaml | 2 +- test/config/tilt_config_test.dart | 2 - test/config/tilt_light_config_test.dart | 6 - test/config/tilt_shadow_config_test.dart | 92 +++- test/models/tilt_data_model_test.dart | 4 + .../tilt_parallax_widget.dart | 26 +- .../tilt_gestures_drag_test.dart | 10 +- .../tilt_gestures_hover_test.dart | 23 +- .../tilt_gestures_touch_test.dart | 29 +- .../tilt_stream_controller_test.dart | 2 +- .../tilt_widget_base.dart | 29 +- test/tilt_widget/tilt_config_tilt_test.dart | 40 +- .../tilt_gestures_drag_test.dart | 18 +- .../tilt_gestures_hover_test.dart | 23 +- .../tilt_gestures_touch_test.dart | 29 +- .../tilt_stream_controller_test.dart | 2 +- .../tilt_widget_projector.dart | 33 +- test/tilt_widget/tilt_widget.dart | 29 +- test/tilt_widget/tilt_widget_test.dart | 12 +- 41 files changed, 1758 insertions(+), 1132 deletions(-) create mode 100644 lib/src/widgets/containers/tilt_base_container.dart create mode 100644 lib/src/widgets/containers/tilt_container_utils.dart rename lib/src/widgets/{ => containers}/tilt_parallax_container.dart (58%) create mode 100644 lib/src/widgets/containers/tilt_projector_container.dart rename lib/src/widgets/{ => core}/gestures_listener.dart (95%) create mode 100644 lib/src/widgets/core/tilt_animated_builder.dart rename lib/src/widgets/{ => core}/tilt_stream_builder.dart (93%) create mode 100644 lib/src/widgets/core/tilt_widget.dart rename lib/src/widgets/{ => effects}/tilt_light.dart (95%) rename lib/src/widgets/{ => effects}/tilt_shadow.dart (93%) delete mode 100644 lib/src/widgets/tilt_container.dart rename test/tilt_widget/{light_shadow_mode_base_widget => tilt_base_container_widget}/tilt_gestures_drag_test.dart (86%) rename test/tilt_widget/{light_shadow_mode_base_widget => tilt_base_container_widget}/tilt_gestures_hover_test.dart (95%) rename test/tilt_widget/{light_shadow_mode_base_widget => tilt_base_container_widget}/tilt_gestures_touch_test.dart (92%) rename test/tilt_widget/{light_shadow_mode_base_widget => tilt_base_container_widget}/tilt_stream_controller_test.dart (99%) rename test/tilt_widget/{light_shadow_mode_base_widget => tilt_base_container_widget}/tilt_widget_base.dart (73%) rename test/tilt_widget/{light_shadow_mode_projector_widget => tilt_projector_container_widget}/tilt_gestures_drag_test.dart (80%) rename test/tilt_widget/{light_shadow_mode_projector_widget => tilt_projector_container_widget}/tilt_gestures_hover_test.dart (95%) rename test/tilt_widget/{light_shadow_mode_projector_widget => tilt_projector_container_widget}/tilt_gestures_touch_test.dart (92%) rename test/tilt_widget/{light_shadow_mode_projector_widget => tilt_projector_container_widget}/tilt_stream_controller_test.dart (99%) rename test/tilt_widget/{light_shadow_mode_projector_widget => tilt_projector_container_widget}/tilt_widget_projector.dart (69%) diff --git a/CHANGELOG.md b/CHANGELOG.md index 99021d1..2ad5e5d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,6 +3,28 @@ > [!IMPORTANT] > See the [Migration Guides](https://amoshuke.github.io/flutter_tilt_book/en/docs/migration-guides/) for the details of breaking changes between versions. +## 4.0.0-rc (Unreleased) + +**Breaking changes** + +Migration Guides: [Migrate to v4.0.0](#) + +- `Tilt` widget now only manages gesture state, sensor state, and animation state... +- The previous `Tilt.lightShadowMode` parameter has been split into the independent widgets `TiltBaseContainer` and `TiltProjectorContainer`. + To reproduce the previous style, you need to compose them inside the `Tilt` widget, + or use `Tilt.base` and `Tilt.projector` directly. +- `ShadowConfig` has been split into `ShadowBaseConfig` and `ShadowProjectorConfig`. + +**Deprecations** + +- The `lightConfig` of `TiltProjectorContainer` is now deprecated. + Because the current simulated light effect is not suitable for Projector, + it is now disabled by default and will be removed in a future release. + +**New features** + +- Add `TiltAnimatedBuilder` to help implement custom tilt logic. + ## 3.3.4 **Improvements** diff --git a/example/lib/main.dart b/example/lib/main.dart index f217597..de22955 100644 --- a/example/lib/main.dart +++ b/example/lib/main.dart @@ -53,7 +53,7 @@ class TiltExample extends StatelessWidget { return Scaffold( backgroundColor: const Color(0xFFFFFFFF), body: Center( - child: Tilt( + child: Tilt.base( borderRadius: BorderRadius.circular(24.0), tiltConfig: const TiltConfig( angle: 20, @@ -61,7 +61,7 @@ class TiltExample extends StatelessWidget { leaveDuration: Duration(milliseconds: 1200), ), lightConfig: const LightConfig(disable: true), - shadowConfig: const ShadowConfig(disable: true), + shadowConfig: const ShadowBaseConfig(disable: true), childLayout: ChildLayout( inner: [ ...innerBox, diff --git a/lib/flutter_tilt.dart b/lib/flutter_tilt.dart index caf44e1..b92c013 100644 --- a/lib/flutter_tilt.dart +++ b/lib/flutter_tilt.dart @@ -4,12 +4,26 @@ // ignore: unnecessary_library_name library flutter_tilt; +// Configurations export 'src/config/tilt_config.dart'; export 'src/config/tilt_light_config.dart'; export 'src/config/tilt_shadow_config.dart'; export 'src/enums.dart'; + +// Data Models export 'src/models/tilt_data_model.dart' show TiltDataModel; export 'src/models/tilt_stream_model.dart' show TiltStreamModel; -export 'src/tilt.dart'; -export 'src/widgets/tilt_light.dart'; -export 'src/widgets/tilt_shadow.dart'; + +// Main Widgets +export 'src/tilt.dart' show Tilt, TiltParallax; +export 'src/widgets/containers/tilt_base_container.dart' show TiltBaseContainer; +export 'src/widgets/containers/tilt_projector_container.dart' + show TiltProjectorContainer; + +// Core Widgets +export 'src/widgets/core/tilt_animated_builder.dart' show TiltAnimatedBuilder; + +// Effects +export 'src/widgets/effects/tilt_light.dart' show TiltLight; +export 'src/widgets/effects/tilt_shadow.dart' + show TiltShadowBase, TiltShadowProjector; diff --git a/lib/src/config/tilt_config.dart b/lib/src/config/tilt_config.dart index d55dd65..f9d6ab9 100644 --- a/lib/src/config/tilt_config.dart +++ b/lib/src/config/tilt_config.dart @@ -23,7 +23,6 @@ class TiltConfig { this.angle = 10.0, this.direction, this.enableReverse = false, - this.filterQuality, this.enableGestureSensors = true, this.sensorFactor = 10.0, this.enableSensorRevert = true, @@ -132,9 +131,6 @@ class TiltConfig { /// final bool enableReverse; - /// FilterQuality - final FilterQuality? filterQuality; - /// Gyroscope sensor triggered tilt. /// /// Only the following gestures: @@ -435,7 +431,6 @@ class TiltConfig { double? angle, List? direction, bool? enableReverse, - FilterQuality? filterQuality, bool? enableGestureSensors, double? sensorFactor, bool? enableSensorRevert, @@ -461,7 +456,6 @@ class TiltConfig { angle: angle ?? this.angle, direction: direction ?? this.direction, enableReverse: enableReverse ?? this.enableReverse, - filterQuality: filterQuality ?? this.filterQuality, enableGestureSensors: enableGestureSensors ?? this.enableGestureSensors, sensorFactor: sensorFactor ?? this.sensorFactor, enableSensorRevert: enableSensorRevert ?? this.enableSensorRevert, @@ -501,7 +495,6 @@ class TiltConfig { Object.hashAll(other.direction ?? []) == Object.hashAll(direction ?? []) && other.enableReverse == enableReverse && - other.filterQuality == filterQuality && other.enableGestureSensors == enableGestureSensors && other.sensorFactor == sensorFactor && other.enableSensorRevert == enableSensorRevert && @@ -530,7 +523,6 @@ class TiltConfig { angle, Object.hashAll(direction ?? []), enableReverse, - filterQuality, enableGestureSensors, sensorFactor, enableSensorRevert, diff --git a/lib/src/config/tilt_light_config.dart b/lib/src/config/tilt_light_config.dart index 7edbe0a..95da278 100644 --- a/lib/src/config/tilt_light_config.dart +++ b/lib/src/config/tilt_light_config.dart @@ -20,7 +20,6 @@ class LightConfig { this.minIntensity = 0.0, this.maxIntensity = 0.5, this.spreadFactor = 4.0, - this.projectorScale = 1.1, this.direction = LightDirection.around, this.enableReverse, }) : assert( @@ -28,8 +27,7 @@ class LightConfig { minIntensity >= 0.0 && maxIntensity <= 1.0, ), - assert(spreadFactor >= 1.0), - assert(projectorScale >= 0.0); + assert(spreadFactor >= 1.0); /// Only disable the light effect. /// @@ -68,20 +66,6 @@ class LightConfig { /// 相对于当前 widget 尺寸。 final double spreadFactor; - /// Light area size scale - /// - /// Only the following mode: - /// [LightShadowMode.projector] - /// - /// ------ - /// - /// 光照区域尺寸比例 - /// - /// 仅以下模式生效: - /// [LightShadowMode.projector] - /// - final double projectorScale; - /// Light direction. /// /// Affects: @@ -124,7 +108,6 @@ class LightConfig { double? minIntensity, double? maxIntensity, double? spreadFactor, - double? projectorScale, LightDirection? direction, bool? enableReverse, }) { @@ -134,7 +117,6 @@ class LightConfig { minIntensity: minIntensity ?? this.minIntensity, maxIntensity: maxIntensity ?? this.maxIntensity, spreadFactor: spreadFactor ?? this.spreadFactor, - projectorScale: projectorScale ?? this.projectorScale, direction: direction ?? this.direction, enableReverse: enableReverse ?? this.enableReverse, ); @@ -154,7 +136,6 @@ class LightConfig { other.minIntensity == minIntensity && other.maxIntensity == maxIntensity && other.spreadFactor == spreadFactor && - other.projectorScale == projectorScale && other.direction == direction && other.enableReverse == enableReverse; } @@ -167,9 +148,84 @@ class LightConfig { minIntensity, maxIntensity, spreadFactor, - projectorScale, direction, enableReverse, ); } } + +/// Light effect config. +/// 光照效果配置。 +@Deprecated( + '\nThe current simulated light effect is not suitable for Projector, ' + 'so it will be removed after version 4.1.0.\n' + '------\n' + '当前的模拟光照效果对 Projector 并不合适,因此将在 4.1.0 版本之后移除。\n', +) +@immutable +class LightProjectorConfig extends LightConfig { + /// Light effect config. + /// 光照效果配置。 + const LightProjectorConfig({ + super.disable = true, + super.color, + super.minIntensity, + super.maxIntensity, + super.spreadFactor, + this.projectorScale = 1.1, + super.direction, + super.enableReverse, + }) : assert(projectorScale >= 0.0); + + /// Light area size scale + /// + /// ------ + /// + /// 光照区域尺寸比例 + /// + final double projectorScale; + + @override + LightProjectorConfig copyWith({ + bool? disable, + Color? color, + double? minIntensity, + double? maxIntensity, + double? spreadFactor, + double? projectorScale, + LightDirection? direction, + bool? enableReverse, + }) { + return LightProjectorConfig( + disable: disable ?? this.disable, + color: color ?? this.color, + minIntensity: minIntensity ?? this.minIntensity, + maxIntensity: maxIntensity ?? this.maxIntensity, + spreadFactor: spreadFactor ?? this.spreadFactor, + projectorScale: projectorScale ?? this.projectorScale, + direction: direction ?? this.direction, + enableReverse: enableReverse ?? this.enableReverse, + ); + } + + @override + bool operator ==(Object other) { + if (identical(this, other)) { + return true; + } + if (other.runtimeType != runtimeType) { + return false; + } + return other is LightProjectorConfig && + super == other && + other.projectorScale == projectorScale; + } + + @override + int get hashCode { + return Object.hash( + super.hashCode, + projectorScale, + ); + } +} diff --git a/lib/src/config/tilt_shadow_config.dart b/lib/src/config/tilt_shadow_config.dart index cd69adf..1443b3b 100644 --- a/lib/src/config/tilt_shadow_config.dart +++ b/lib/src/config/tilt_shadow_config.dart @@ -6,7 +6,7 @@ import '../enums.dart'; /// Shadow effect config. /// 阴影效果配置。 @immutable -class ShadowConfig { +abstract class ShadowConfig { /// Shadow effect config. /// 阴影效果配置。 /// @@ -26,14 +26,6 @@ class ShadowConfig { this.maxIntensity = 0.5, this.offsetInitial = Offset.zero, this.offsetFactor = 0.1, - this.spreadInitial = 0.0, - this.spreadFactor = 0.0, - this.minBlurRadius = 10.0, - this.maxBlurRadius = 20.0, - this.projectorScaleFrom = 1.0, - this.projectorScaleTo = 1.0, - this.projectorBlurSigmaFrom = 2.0, - this.projectorBlurSigmaTo = 10.0, this.direction, this.enableReverse, }) : assert( @@ -41,11 +33,7 @@ class ShadowConfig { minIntensity >= 0.0 && maxIntensity <= 1.0, ), - assert(offsetFactor >= 0.0), - assert(spreadFactor >= 0.0), - assert(minBlurRadius <= maxBlurRadius && minBlurRadius >= 0.0), - assert(projectorScaleFrom >= 0 && projectorScaleTo >= 0), - assert(projectorBlurSigmaFrom >= 0.0 && projectorBlurSigmaTo >= 0.0); + assert(offsetFactor >= 0.0); /// Only disable the shadow effect. /// @@ -102,169 +90,253 @@ class ShadowConfig { /// 相对于当前 widget 尺寸。 final double offsetFactor; - /// Initial value of shadow spread radius. + /// Shadow direction. + /// 阴影方向。 /// - /// Only the following mode: - /// [LightShadowMode.base] + /// {@template tilt.ShadowConfig.direction} + /// 指定后将不受以下影响 + /// - 失效:光源方向 [LightConfig.direction] + /// - 失效:光源反向 [LightConfig.enableReverse] + /// {@endtemplate} + final ShadowDirection? direction; + + /// Reverse shadow direction. + /// 开启反转阴影方向。 + /// + /// {@template tilt.ShadowConfig.enableReverse} + /// 指定后将不受以下影响 + /// - 失效:光源反向 [LightConfig.enableReverse] + /// {@endtemplate} + final bool? enableReverse; + + @override + bool operator ==(Object other) { + if (identical(this, other)) { + return true; + } + if (other.runtimeType != runtimeType) { + return false; + } + return other is ShadowConfig && + other.disable == disable && + other.color == color && + other.minIntensity == minIntensity && + other.maxIntensity == maxIntensity && + other.offsetInitial == offsetInitial && + other.offsetFactor == offsetFactor && + other.direction == direction && + other.enableReverse == enableReverse; + } + + @override + int get hashCode { + return Object.hash( + disable, + color, + minIntensity, + maxIntensity, + offsetInitial, + offsetFactor, + direction, + enableReverse, + ); + } +} + +/// Shadow effect config (base). +/// 阴影效果配置 (base)。 +@immutable +class ShadowBaseConfig extends ShadowConfig { + /// Shadow effect config. + /// 阴影效果配置。 + /// + /// {@macro tilt.ShadowConfig} + const ShadowBaseConfig({ + super.disable, + super.color, + super.minIntensity, + super.maxIntensity, + super.offsetInitial, + super.offsetFactor, + super.direction, + super.enableReverse, + this.spreadInitial = 0.0, + this.spreadFactor = 0.0, + this.minBlurRadius = 10.0, + this.maxBlurRadius = 20.0, + }) : assert(spreadFactor >= 0.0), + assert(minBlurRadius <= maxBlurRadius && minBlurRadius >= 0.0); + + /// Initial value of shadow spread radius. /// /// ------ /// /// 阴影扩散半径初始值。 - /// - /// 仅以下模式生效: - /// [LightShadowMode.base] final double spreadInitial; /// Shadow spread radius factor, /// relative to current widget size. /// - /// Only the following mode: - /// [LightShadowMode.base] - /// /// ------ /// /// 阴影扩散半径系数, /// 相对于当前 widget 尺寸。 /// /// 移动时相对当前的尺寸进行扩散 - /// - /// 仅以下模式生效: - /// [LightShadowMode.base] final double spreadFactor; /// Minimum blur radius, also initial blur radius. /// - /// Only the following mode: - /// [LightShadowMode.base] - /// /// ------ /// /// 最小阴影模糊半径,也是初始模糊半径。 - /// - /// 仅以下模式生效: - /// [LightShadowMode.base] final double minBlurRadius; /// Maximum blur radius for tilt progresses. /// - /// Only the following mode: - /// [LightShadowMode.base] - /// /// ------ /// /// 最大阴影模糊半径,跟随倾斜最大进度。 - /// - /// 仅以下模式生效: - /// [LightShadowMode.base] final double maxBlurRadius; - /// Size scale for minimum progress, also initial size scale. + ShadowBaseConfig copyWith({ + bool? disable, + Color? color, + double? minIntensity, + double? maxIntensity, + Offset? offsetInitial, + double? offsetFactor, + ShadowDirection? direction, + bool? enableReverse, + double? spreadInitial, + double? spreadFactor, + double? minBlurRadius, + double? maxBlurRadius, + }) { + return ShadowBaseConfig( + disable: disable ?? this.disable, + color: color ?? this.color, + minIntensity: minIntensity ?? this.minIntensity, + maxIntensity: maxIntensity ?? this.maxIntensity, + offsetInitial: offsetInitial ?? this.offsetInitial, + offsetFactor: offsetFactor ?? this.offsetFactor, + direction: direction ?? this.direction, + enableReverse: enableReverse ?? this.enableReverse, + spreadInitial: spreadInitial ?? this.spreadInitial, + spreadFactor: spreadFactor ?? this.spreadFactor, + minBlurRadius: minBlurRadius ?? this.minBlurRadius, + maxBlurRadius: maxBlurRadius ?? this.maxBlurRadius, + ); + } + + @override + bool operator ==(Object other) { + if (identical(this, other)) { + return true; + } + if (other.runtimeType != runtimeType) { + return false; + } + return other is ShadowBaseConfig && + super == other && + other.spreadInitial == spreadInitial && + other.spreadFactor == spreadFactor && + other.minBlurRadius == minBlurRadius && + other.maxBlurRadius == maxBlurRadius; + } + + @override + int get hashCode { + return Object.hash( + super.hashCode, + spreadInitial, + spreadFactor, + minBlurRadius, + maxBlurRadius, + ); + } +} + +/// Shadow effect config. +/// 阴影效果配置。 +@immutable +class ShadowProjectorConfig extends ShadowConfig { + /// Shadow effect config. + /// 阴影效果配置。 /// - /// Only the following mode: - /// [LightShadowMode.projector] + /// {@macro tilt.ShadowConfig} + const ShadowProjectorConfig({ + super.disable, + super.color, + super.minIntensity, + super.maxIntensity, + super.offsetInitial, + super.offsetFactor, + super.direction, + super.enableReverse, + this.projectorScaleFrom = 1.0, + this.projectorScaleTo = 1.0, + this.projectorBlurSigmaFrom = 2.0, + this.projectorBlurSigmaTo = 10.0, + }) : assert(projectorScaleFrom >= 0 && projectorScaleTo >= 0), + assert(projectorBlurSigmaFrom >= 0.0 && projectorBlurSigmaTo >= 0.0); + + /// Size scale for minimum progress, also initial size scale. /// /// ------ /// /// 最小倾斜进度的阴影尺寸比例,也是初始尺寸比例。 - /// - /// 仅以下模式生效: - /// [LightShadowMode.projector] final double projectorScaleFrom; /// Size scale for maximum progress. /// - /// Only the following mode: - /// [LightShadowMode.projector] - /// /// ------ /// /// 最大倾斜进度的阴影尺寸比例。 - /// - /// 仅以下模式生效: - /// [LightShadowMode.projector] final double projectorScaleTo; /// Blur sigma for minimum progress, also initial blur sigma. /// - /// Only the following mode: - /// [LightShadowMode.projector] - /// /// ------ /// /// 最小倾斜进度的阴影模糊 Sigma,也是初始模糊 Sigma。 - /// - /// 仅以下模式生效: - /// [LightShadowMode.projector] final double projectorBlurSigmaFrom; /// Blur sigma for maximum progress. /// - /// Only the following mode: - /// [LightShadowMode.projector] - /// /// ------ /// /// 最大倾斜进度的阴影模糊 Sigma。 - /// - /// 仅以下模式生效: - /// [LightShadowMode.projector] final double projectorBlurSigmaTo; - /// Shadow direction. - /// 阴影方向。 - /// - /// {@template tilt.ShadowConfig.direction} - /// 指定后将不受以下影响 - /// - 失效:光源方向 [LightConfig.direction] - /// - 失效:光源反向 [LightConfig.enableReverse] - /// {@endtemplate} - final ShadowDirection? direction; - - /// Reverse shadow direction. - /// 开启反转阴影方向。 - /// - /// {@template tilt.ShadowConfig.enableReverse} - /// 指定后将不受以下影响 - /// - 失效:光源反向 [LightConfig.enableReverse] - /// {@endtemplate} - final bool? enableReverse; - - ShadowConfig copyWith({ + ShadowProjectorConfig copyWith({ bool? disable, Color? color, double? minIntensity, double? maxIntensity, Offset? offsetInitial, double? offsetFactor, - double? spreadInitial, - double? spreadFactor, - double? minBlurRadius, - double? maxBlurRadius, + ShadowDirection? direction, + bool? enableReverse, double? projectorScaleFrom, double? projectorScaleTo, double? projectorBlurSigmaFrom, double? projectorBlurSigmaTo, - ShadowDirection? direction, - bool? enableReverse, }) { - return ShadowConfig( + return ShadowProjectorConfig( disable: disable ?? this.disable, color: color ?? this.color, minIntensity: minIntensity ?? this.minIntensity, maxIntensity: maxIntensity ?? this.maxIntensity, offsetInitial: offsetInitial ?? this.offsetInitial, offsetFactor: offsetFactor ?? this.offsetFactor, - spreadInitial: spreadInitial ?? this.spreadInitial, - spreadFactor: spreadFactor ?? this.spreadFactor, - minBlurRadius: minBlurRadius ?? this.minBlurRadius, - maxBlurRadius: maxBlurRadius ?? this.maxBlurRadius, + direction: direction ?? this.direction, + enableReverse: enableReverse ?? this.enableReverse, projectorScaleFrom: projectorScaleFrom ?? this.projectorScaleFrom, projectorScaleTo: projectorScaleTo ?? this.projectorScaleTo, projectorBlurSigmaFrom: projectorBlurSigmaFrom ?? this.projectorBlurSigmaFrom, projectorBlurSigmaTo: projectorBlurSigmaTo ?? this.projectorBlurSigmaTo, - direction: direction ?? this.direction, - enableReverse: enableReverse ?? this.enableReverse, ); } @@ -276,44 +348,22 @@ class ShadowConfig { if (other.runtimeType != runtimeType) { return false; } - return other is ShadowConfig && - other.disable == disable && - other.color == color && - other.minIntensity == minIntensity && - other.maxIntensity == maxIntensity && - other.offsetInitial == offsetInitial && - other.offsetFactor == offsetFactor && - other.spreadInitial == spreadInitial && - other.spreadFactor == spreadFactor && - other.minBlurRadius == minBlurRadius && - other.maxBlurRadius == maxBlurRadius && + return other is ShadowProjectorConfig && + super == other && other.projectorScaleFrom == projectorScaleFrom && other.projectorScaleTo == projectorScaleTo && other.projectorBlurSigmaFrom == projectorBlurSigmaFrom && - other.projectorBlurSigmaTo == projectorBlurSigmaTo && - other.direction == direction && - other.enableReverse == enableReverse; + other.projectorBlurSigmaTo == projectorBlurSigmaTo; } @override int get hashCode { return Object.hash( - disable, - color, - minIntensity, - maxIntensity, - offsetInitial, - offsetFactor, - spreadInitial, - spreadFactor, - minBlurRadius, - maxBlurRadius, + super.hashCode, projectorScaleFrom, projectorScaleTo, projectorBlurSigmaFrom, projectorBlurSigmaTo, - direction, - enableReverse, ); } } diff --git a/lib/src/enums.dart b/lib/src/enums.dart index 3f58efb..d156057 100644 --- a/lib/src/enums.dart +++ b/lib/src/enums.dart @@ -107,41 +107,3 @@ enum ShadowDirection implements Direction { /// [GesturesType.touch] > [GesturesType.hover] > [GesturesType.controller] > [GesturesType.sensors] /// {@endtemplate} enum GesturesType { none, touch, hover, controller, sensors } - -/// Light & Shadow Mode -/// 光影 Mode -enum LightShadowMode { - /// "No performance risk" - /// - /// Use normal shadow effects only for `Tilt.child` without performance degradation. - /// - /// ------ - /// - /// “无性能风险” - /// - /// 仅对 `Tilt.child` 使用普通阴影效果,无性能损耗。 - base, - - /// "Performance risk exists" - /// - /// Apply a shadow to the entire `Tilt` widget, similar to a projector effect. - /// will display a shadow that exactly matches the non-transparent parts of the widget. - /// - /// Recommended for the following scenarios: - /// - Images only - /// - No data states - /// - No Hero tags - /// - /// ------ - /// - /// “有性能风险” - /// - /// 对整个 `Tilt` widget 使用类似投影仪效果的阴影, - /// 会显示与 widget 非透明部分完全一致的阴影。 - /// - /// 建议在以下场景使用: - /// - 仅图片 - /// - 无数据状态 - /// - 无 Hero 标签 - projector, -} diff --git a/lib/src/internal/tilt_data.dart b/lib/src/internal/tilt_data.dart index 02bdcd4..2d0981c 100644 --- a/lib/src/internal/tilt_data.dart +++ b/lib/src/internal/tilt_data.dart @@ -32,14 +32,6 @@ class TiltData { /// 倾斜配置 final TiltConfig tiltConfig; - /// 倾斜数据 - TiltDataModel get data => TiltDataModel( - position: position, - transform: transform, - areaProgress: areaProgress, - angle: angle, - ); - /// 当前坐标 Offset get position => Utils.progressPosition(width, height, areaProgress); @@ -75,4 +67,14 @@ class TiltData { ..rotateX(rotateX) ..rotateY(rotateY); } + + /// 转换为 TiltDataModel + TiltDataModel toModel() => TiltDataModel( + width: width, + height: height, + position: position, + transform: transform, + areaProgress: areaProgress, + angle: angle, + ); } diff --git a/lib/src/models/tilt_data_model.dart b/lib/src/models/tilt_data_model.dart index 2d776c4..59a1383 100644 --- a/lib/src/models/tilt_data_model.dart +++ b/lib/src/models/tilt_data_model.dart @@ -7,12 +7,18 @@ class TiltDataModel { /// TiltDataModel /// 倾斜数据 Model const TiltDataModel({ + required this.width, + required this.height, required this.position, required this.areaProgress, required this.transform, required this.angle, }); + final double width; + + final double height; + /// 当前坐标 final Offset position; @@ -56,6 +62,8 @@ class TiltDataModel { return false; } return other is TiltDataModel && + other.width == width && + other.height == height && other.position == position && other.areaProgress == areaProgress && other.transform == transform && @@ -64,6 +72,8 @@ class TiltDataModel { @override int get hashCode => Object.hash( + width.hashCode, + height.hashCode, position.hashCode, areaProgress.hashCode, transform.hashCode, diff --git a/lib/src/models/tilt_stream_model.dart b/lib/src/models/tilt_stream_model.dart index d518e77..f72ad51 100644 --- a/lib/src/models/tilt_stream_model.dart +++ b/lib/src/models/tilt_stream_model.dart @@ -27,7 +27,7 @@ class TiltStreamModel { /// 会触发对应位置的倾斜效果。 /// /// 例如: - /// 有一个组件尺寸为 width: 10, height: 10, + /// 有一个 widget 尺寸为 width: 10, height: 10, /// - (0, 0):会触发最左上的倾斜。 /// - (10, 10):会触发最右下的倾斜。 /// diff --git a/lib/src/tilt.dart b/lib/src/tilt.dart index e61f96f..411846f 100644 --- a/lib/src/tilt.dart +++ b/lib/src/tilt.dart @@ -1,408 +1,132 @@ -import 'dart:async' as async; import 'package:flutter/widgets.dart'; import 'config/tilt_config.dart'; import 'config/tilt_light_config.dart'; import 'config/tilt_shadow_config.dart'; -import 'enums.dart'; -import 'internal/controllers/fps_timer_controller.dart'; -import 'internal/controllers/tilt_gestures_controller.dart'; -import 'internal/provider/tilt_provider.dart'; -import 'internal/tilt_data.dart'; -import 'models/tilt_stream_model.dart'; -import 'utils.dart'; -import 'widgets/gestures_listener.dart'; -import 'widgets/tilt_container.dart'; -import 'widgets/tilt_parallax_container.dart'; -import 'widgets/tilt_stream_builder.dart'; +import 'widgets/containers/tilt_base_container.dart'; +import 'widgets/containers/tilt_parallax_container.dart'; +import 'widgets/containers/tilt_projector_container.dart'; +import 'widgets/core/tilt_widget.dart'; /// Tilt /// 倾斜 -class Tilt extends TiltContainer { +class Tilt extends TiltWidget { /// Tilt /// 倾斜 /// - /// - [childLayout] : Other child layouts. e.g. [TiltParallax] parallax inner, outer, behind. - /// - [tiltStreamController] : `StreamController.broadcast()` to control the tilt. - /// - [disable] : Disable all effects. - /// - [fps] : Gesture triggered frames. - /// - [border] : BoxDecoration border. - /// - [borderRadius] : BoxDecoration borderRadius. - /// - [clipBehavior] : Flutter clipBehavior. - /// - [tiltConfig] : Tilt effect config. - /// - [lightShadowMode] : Light & Shadow Mode - /// - [lightConfig] : Light effect config. - /// - [shadowConfig] : Shadow effect config. - /// - [onGestureMove] : Gesture move callback. - /// - [onGestureLeave] : Gesture leave callback. + /// {@macro tilt.TiltWidget.desc.en} + /// {@macro tilt.TiltWidget.param.en} /// /// ------ /// - /// - [childLayout] : 其他 child 布局。例如:位于 child 内部、外部、后面的视差布局 [TiltParallax]。 - /// - [tiltStreamController] : 通过 `StreamController.broadcast()` 来自定义控制倾斜。 - /// - [disable] : 禁用所有效果。 - /// - [fps] : 手势触发的帧数。 - /// - [border] : BoxDecoration 边框样式。 - /// - [borderRadius] : BoxDecoration 边框圆角半径。 - /// - [clipBehavior] : Flutter clipBehavior。 - /// - [tiltConfig] : 倾斜效果配置。 - /// - [lightShadowMode] : 光影 Mode - /// - [lightConfig] : 光照效果配置。 - /// - [shadowConfig] : 阴影效果配置。 - /// - [onGestureMove] : 手势移动的回调触发。 - /// - [onGestureLeave] : 手势离开的回调触发。 - /// + /// {@macro tilt.TiltWidget.desc.zh} + /// {@macro tilt.TiltWidget.param.zh} const Tilt({ super.key, required super.child, - super.childLayout = const ChildLayout(), - this.tiltStreamController, - this.disable = false, - this.fps = 120, - super.border, - super.borderRadius, - super.clipBehavior = Clip.antiAlias, - super.tiltConfig = const TiltConfig(), - super.lightShadowMode, - super.lightConfig = const LightConfig(), - super.shadowConfig = const ShadowConfig(), - this.onGestureMove, - this.onGestureLeave, + super.tiltStreamController, + super.disable, + super.fps, + super.tiltConfig, + super.onGestureMove, + super.onGestureLeave, }); - /// Tilt Stream Controller - /// - /// `StreamController.broadcast()` to control the tilt. + /// Tilt with built-in [TiltBaseContainer]. /// - /// ------ + /// {@macro tilt.TiltBaseContainer.desc.en} /// - /// 通过 `StreamController.broadcast()` 来自定义控制倾斜。 + /// {@macro tilt.TiltWidget.param.en} /// - final async.StreamController? tiltStreamController; - - /// Disable all effects. + /// {@macro tilt.TiltBaseContainer.param.en} /// /// ------ /// - /// 禁用所有效果。 - final bool disable; - - /// Gesture triggered frames. + /// 内置 [TiltBaseContainer] 的 Tilt。 /// - /// ------ + /// {@macro tilt.TiltBaseContainer.desc.zh} /// - /// 手势触发的帧数。 - final int fps; - - /// Gesture move callback. + /// {@macro tilt.TiltWidget.param.zh} /// - /// ------ - /// - /// 手势移动的回调触发。 - final TiltCallback? onGestureMove; + /// {@macro tilt.TiltBaseContainer.param.zh} + Tilt.base({ + super.key, + required Widget child, + super.tiltStreamController, + super.disable, + super.fps, + super.tiltConfig, + super.onGestureMove, + super.onGestureLeave, + ChildLayout childLayout = const ChildLayout(), + LightConfig lightConfig = const LightConfig(), + ShadowBaseConfig shadowConfig = const ShadowBaseConfig(), + BoxBorder? border, + BorderRadiusGeometry? borderRadius, + Clip clipBehavior = Clip.antiAlias, + FilterQuality? filterQuality, + }) : super( + child: TiltBaseContainer( + childLayout: childLayout, + lightConfig: lightConfig, + shadowConfig: shadowConfig, + border: border, + borderRadius: borderRadius, + clipBehavior: clipBehavior, + filterQuality: filterQuality, + child: child, + ), + ); - /// Gesture leave callback. + /// Tilt with built-in [TiltProjectorContainer]. + /// + /// {@macro tilt.TiltProjectorContainer.desc.en} /// /// ------ /// - /// 手势离开的回调触发。 - final TiltCallback? onGestureLeave; - - @override - State createState() => _TiltState(); -} - -class _TiltState extends State { - /// 初始坐标区域进度 - Offset get _initAreaProgress => widget.tiltConfig.initial ?? Offset.zero; - - /// 是否初始化 - bool _isInit = false; - - /// 尺寸 - double _width = 0.0, _height = 0.0; - - /// 当前坐标的区域进度 - late Offset _areaProgress = _initAreaProgress; - - /// 是否正在移动 - bool _isMove = false; - - /// 当前手势类型 - GesturesType _currentGesturesType = GesturesType.none; - - /// 倾斜手势控制器 - late TiltGesturesController _tiltGesturesController; - - /// 默认 TiltStreamController + /// {@macro tilt.TiltWidget.param.en} /// - /// [widget.tiltStreamController] 为 null 时使用 - final async.StreamController _kDefaultTiltStreamController = - async.StreamController.broadcast(); - - late FpsTimerController _fpsTimerController; - - /// 当前坐标 - late Offset _currentPosition = Utils.progressPosition( - _width, - _height, - _initAreaProgress, - ); - - @override - void initState() { - super.initState(); - _initControllers(); - } - - @override - void dispose() { - _kDefaultTiltStreamController.close(); - _fpsTimerController.dispose(); - _tiltGesturesController.dispose(); - super.dispose(); - } - - @override - void didUpdateWidget(Tilt oldWidget) { - super.didUpdateWidget(oldWidget); - if (_shouldReinitControllers(oldWidget)) { - _initControllers(oldWidget: oldWidget); - } - } - - @override - Widget build(BuildContext context) { - return GesturesListener( - tiltGesturesController: _tiltGesturesController, - child: TiltStreamBuilder( - tiltGesturesController: _tiltGesturesController, - builder: (context, snapshot) { - _handleGesturesStream(snapshot.data); - return TiltProvider( - isInit: _isInit, - width: _width, - height: _height, - areaProgress: _areaProgress, - isMove: _isMove, - currentGesturesType: _currentGesturesType, - tiltConfig: widget.tiltConfig, - onResize: _onResize, - child: TiltContainer( - border: widget.border, - borderRadius: widget.borderRadius, - clipBehavior: widget.clipBehavior, - tiltConfig: widget.tiltConfig, - lightShadowMode: widget.lightShadowMode, - lightConfig: widget.lightConfig, - shadowConfig: widget.shadowConfig, - childLayout: widget.childLayout, - child: widget.child, - ), - ); - }, - ), - ); - } - - /// 初始化控制器 + /// {@macro tilt.TiltProjectorContainer.param.en} /// - /// - [oldWidget] 旧 Wdiget 一般用于 [didUpdateWidget] - void _initControllers({Tilt? oldWidget}) { - /// 是否需要取消 [TiltGesturesController] 中的手势协调器,避免泄露 - if (oldWidget != null && oldWidget.tiltConfig != widget.tiltConfig) { - _tiltGesturesController.cancelGesturesHarmonizerTimer(); - } - - /// 初始化 TiltGesturesController - final tiltStreamController = - widget.tiltStreamController ?? _kDefaultTiltStreamController; - _tiltGesturesController = TiltGesturesController( - tiltStreamController: tiltStreamController, - disable: widget.disable, - fps: widget.fps, - tiltConfig: widget.tiltConfig, - initialPosition: _currentPosition, - ); - - /// 初始化 FpsTimerController - _fpsTimerController = FpsTimerController(widget.fps); - } - - /// 判断是否需要重新初始化控制器 - bool _shouldReinitControllers(Tilt oldWidget) { - return oldWidget.tiltStreamController != widget.tiltStreamController || - oldWidget.disable != widget.disable || - oldWidget.fps != widget.fps || - oldWidget.tiltConfig != widget.tiltConfig; - } - - /// 调整尺寸及初始参数 - void _onResize(Size size) { - final oldSize = Size(_width, _height); - if (oldSize != size) { - _isInit = true; - _width = size.width; - _height = size.height; - setState(() { - _currentPosition = Utils.progressPosition( - _width, - _height, - _initAreaProgress, - ); - }); - } - } - - /// 处理手势 Stream 触发 - void _handleGesturesStream(TiltStreamModel? tiltStreamModel) { - if (tiltStreamModel == null) return; - if (tiltStreamModel.gesturesType == GesturesType.none) return; - if (!_isInit || widget.disable) return; - switch (tiltStreamModel.gesturesType) { - case GesturesType.none: - break; - case GesturesType.touch || GesturesType.hover || GesturesType.controller: - if (tiltStreamModel.gestureUse) { - _onGesturesMove( - tiltStreamModel.position, - tiltStreamModel.gesturesType, - ); - } else { - _onGesturesRevert( - tiltStreamModel.position, - tiltStreamModel.gesturesType, - ); - } - case GesturesType.sensors: - // Sensors 只会触发 onGestureMove,不会触发 onGestureLeave - _currentPosition += - tiltStreamModel.position * widget.tiltConfig.sensorFactor; - _onGesturesSensorsRevert(); - _currentPosition = Utils.constraintsPosition( - _width, - _height, - _currentPosition, - ); - _onGesturesMove(_currentPosition, tiltStreamModel.gesturesType); - } - } - - /// 手势移动触发 + /// ------ /// - /// [offset] 当前坐标 - void _onGesturesMove(Offset offset, GesturesType gesturesType) { - if (!_isInit || widget.disable) return; - if (!_fpsTimerController.shouldTrigger()) return; - if (widget.tiltConfig.enableOutsideAreaMove || - Utils.isInRange(_width, _height, offset)) { - _currentPosition = offset; - _areaProgress = Utils.p2cAreaProgress( - _width, - _height, - offset, - widget.tiltConfig.direction, - ); - _isMove = true; - _currentGesturesType = gesturesType; - _onGestureMove(_areaProgress, gesturesType); - } else { - _onGesturesRevert(offset, gesturesType); - } - } - - /// 手势复原触发 + /// 内置 [TiltProjectorContainer] 的 Tilt。 /// - /// [offset] 当前坐标 - void _onGesturesRevert(Offset offset, GesturesType gesturesType) { - if (!_isInit || widget.disable || !_isMove) return; - - /// 是否还原的取值 - final position = widget.tiltConfig.enableRevert - ? Utils.progressPosition(_width, _height, _initAreaProgress) - : _currentPosition; - _currentPosition = position; - _areaProgress = Utils.p2cAreaProgress( - _width, - _height, - position, - widget.tiltConfig.direction, - ); - _isMove = false; - _currentGesturesType = gesturesType; - _onGestureLeave(_areaProgress, gesturesType); - } - - /// 手势传感器复原触发 + /// {@macro tilt.TiltProjectorContainer.desc.zh} /// - /// Sensors 只会触发 onGestureMove,不会触发 onGestureLeave - void _onGesturesSensorsRevert() { - if (!widget.tiltConfig.enableSensorRevert) return; - - /// 默认坐标 - final initPosition = Utils.progressPosition( - _width, - _height, - _initAreaProgress, - ); - - /// 还原 - _currentPosition -= Offset( - _currentPosition.dx - initPosition.dx, - _currentPosition.dy - initPosition.dy, - ) * - widget.tiltConfig.sensorRevertFactor; - } - - /// onGestureMove + /// ------ /// - /// - [areaProgress] 当前坐标的区域进度 - void _onGestureMove(Offset areaProgress, GesturesType gesturesType) { - if (widget.onGestureMove != null) { - _triggerGestureCallback( - widget.onGestureMove, - areaProgress, - gesturesType, - ); - } - } - - /// onGestureLeave + /// {@macro tilt.TiltWidget.param.zh} /// - /// - [areaProgress] 当前坐标的区域进度 - void _onGestureLeave(Offset areaProgress, GesturesType gesturesType) { - if (widget.onGestureLeave != null) { - _triggerGestureCallback( - widget.onGestureLeave, - areaProgress, - gesturesType, - ); - } - } - - /// 触发手势回调 - void _triggerGestureCallback( - TiltCallback? callback, - Offset areaProgress, - GesturesType gesturesType, - ) { - if (callback != null) { - WidgetsBinding.instance.addPostFrameCallback((_) { - if (mounted) { - callback( - TiltData( - isInit: _isInit, - width: _width, - height: _height, - areaProgress: areaProgress, - tiltConfig: widget.tiltConfig, - ).data, - gesturesType, - ); - } - }); - } - } + /// {@macro tilt.TiltProjectorContainer.param.zh} + Tilt.projector({ + super.key, + required Widget child, + super.tiltStreamController, + super.disable, + super.fps, + super.tiltConfig, + super.onGestureMove, + super.onGestureLeave, + ChildLayout childLayout = const ChildLayout(), + LightProjectorConfig lightConfig = const LightProjectorConfig(), + ShadowProjectorConfig shadowConfig = const ShadowProjectorConfig(), + BoxBorder? border, + BorderRadiusGeometry? borderRadius, + Clip clipBehavior = Clip.antiAlias, + FilterQuality? filterQuality, + }) : super( + child: TiltProjectorContainer( + childLayout: childLayout, + lightConfig: lightConfig, + shadowConfig: shadowConfig, + border: border, + borderRadius: borderRadius, + clipBehavior: clipBehavior, + filterQuality: filterQuality, + child: child, + ), + ); } /// TiltParallax @@ -411,18 +135,15 @@ class TiltParallax extends TiltParallaxContainer { /// TiltParallax /// 倾斜视差 /// - /// Parallax that can only be used in [Tilt.childLayout]. + /// {@macro tilt.TiltParallaxContainer.desc.en} /// - /// - [size] : Parallax size. - /// - [filterQuality] : Flutter FilterQuality. + /// {@macro tilt.TiltParallaxContainer.param.en} /// /// ------ /// - /// 只能在 [Tilt.childLayout] 中使用的视差效果。 - /// - /// - [size] : 视差大小。 - /// - [filterQuality] : Flutter FilterQuality。 + /// {@macro tilt.TiltParallaxContainer.desc.zh} /// + /// {@macro tilt.TiltParallaxContainer.param.zh} const TiltParallax({ super.key, required super.child, diff --git a/lib/src/widgets/containers/tilt_base_container.dart b/lib/src/widgets/containers/tilt_base_container.dart new file mode 100644 index 0000000..90deda8 --- /dev/null +++ b/lib/src/widgets/containers/tilt_base_container.dart @@ -0,0 +1,200 @@ +import 'package:flutter/widgets.dart'; + +import '../../config/tilt_config.dart'; +import '../../config/tilt_light_config.dart'; +import '../../config/tilt_shadow_config.dart'; +import '../core/tilt_animated_builder.dart'; +import '../effects/tilt_light.dart'; +import '../effects/tilt_shadow.dart'; +import 'tilt_container_utils.dart'; + +class TiltBaseContainer extends StatelessWidget { + /// {@template tilt.TiltBaseContainer.desc.en} + /// A base container widget that provides tilt and simulated light and shadow effects. + /// + /// Use normal shadow effects only for [child]. + /// {@endtemplate} + /// + /// {@template tilt.TiltBaseContainer.param.en} + /// - [child]: The main child widget to which tilt and effects are applied. + /// - [childLayout]: Other child layouts. e.g. [TiltParallax] parallax inner, outer, behind. + /// - [lightConfig]: Light effect config. + /// - [shadowConfig]: Shadow effect config. + /// - [border]: BoxDecoration border. + /// - [borderRadius]: BoxDecoration borderRadius. + /// - [clipBehavior]: Clip behavior for the container. + /// - [filterQuality]: Filter quality for the transform. + /// {@endtemplate} + /// + /// ------ + /// + /// {@template tilt.TiltBaseContainer.desc.zh} + /// 基础容器 widget 包含倾斜、模拟光影效果。 + /// + /// 仅对 [child] 使用普通阴影效果。 + /// {@endtemplate} + /// + /// {@template tilt.TiltBaseContainer.param.zh} + /// - [child]:主要 child,倾斜和效果将应用于此 widget。 + /// - [childLayout]:其他 child 布局。例如:位于 child 内部、外部、后面的视差布局 [TiltParallax]。 + /// - [lightConfig]:光照效果配置。 + /// - [shadowConfig]:阴影效果配置。 + /// - [border]:BoxDecoration 边框样式。 + /// - [borderRadius]:BoxDecoration 边框圆角半径。 + /// - [clipBehavior]:容器的裁剪行为。 + /// - [filterQuality]:Transform 的滤镜质量。 + /// {@endtemplate} + const TiltBaseContainer({ + super.key, + required this.child, + this.childLayout = const ChildLayout(), + this.lightConfig = const LightConfig(), + this.shadowConfig = const ShadowBaseConfig(), + this.border, + this.borderRadius, + this.clipBehavior = Clip.antiAlias, + this.filterQuality, + }); + + final Widget child; + + /// Other child layouts. e.g. [TiltParallax] parallax inner, outer, behind. + /// + /// ------ + /// + /// 其他 child 布局。例如:位于 child 内部、外部、后面的视差布局 [TiltParallax]。 + final ChildLayout childLayout; + + /// Light effect config. + /// + /// ------ + /// + /// 光照效果配置。 + final LightConfig lightConfig; + + /// Shadow effect config. + /// + /// ------ + /// + /// 阴影效果配置。 + final ShadowBaseConfig shadowConfig; + + /// BoxDecoration border. + /// + /// ------ + /// + /// BoxDecoration 边框样式。 + final BoxBorder? border; + + /// BoxDecoration borderRadius. + /// + /// ------ + /// + /// BoxDecoration 边框圆角半径。 + final BorderRadiusGeometry? borderRadius; + + /// Clip + final Clip clipBehavior; + + /// Transform filter quality. + final FilterQuality? filterQuality; + + @override + Widget build(BuildContext context) { + return TiltAnimatedBuilder( + builder: (context, tiltData, tiltConfig, onResize, child) { + return Transform( + alignment: AlignmentDirectional.center, + filterQuality: filterQuality, + transform: tiltData.transform, + child: Stack( + alignment: AlignmentDirectional.center, + clipBehavior: Clip.none, + children: _buildChildren( + child: child, + width: tiltData.width, + height: tiltData.height, + areaProgress: tiltData.areaProgress, + onResize: onResize, + ), + ), + ); + }, + child: child, + ); + } + + List _buildChildren({ + Widget? child, + required double width, + required double height, + required Offset areaProgress, + required void Function(Size) onResize, + }) { + return [ + /// behind child + ...childLayout.behind, + + /// main child + TiltShadowBase( + width: width, + height: height, + areaProgress: areaProgress, + border: border, + borderRadius: borderRadius, + clipBehavior: clipBehavior, + lightConfig: lightConfig, + shadowConfig: shadowConfig, + child: _buildStackInner( + clipBehavior: clipBehavior, + children: [ + /// body + Container( + decoration: BoxDecoration( + borderRadius: borderRadius, + ), + clipBehavior: clipBehavior, + child: child, + ), + + /// inner child + ...childLayout.inner, + + /// light + TiltLight( + width: width, + height: height, + areaProgress: areaProgress, + lightConfig: lightConfig, + ), + + /// resize + Positioned.fill( + child: TiltContainerUtils.buildWidgetResize(onResize), + ), + ], + ), + ), + + /// outer child + ...childLayout.outer, + ]; + } + + /// 构建内部 Stack + /// + /// - [clipBehavior] 裁剪行为 + /// - [children] 子组件列表 + Widget _buildStackInner({ + required Clip clipBehavior, + required List children, + }) { + return Stack( + alignment: AlignmentDirectional.center, + + /// 避免暴露其他组件,[Clip.none] 时,默认赋值 [Clip.hardEdge] + clipBehavior: clipBehavior == Clip.none ? Clip.hardEdge : clipBehavior, + children: children, + ); + } +} diff --git a/lib/src/widgets/containers/tilt_container_utils.dart b/lib/src/widgets/containers/tilt_container_utils.dart new file mode 100644 index 0000000..3125f44 --- /dev/null +++ b/lib/src/widgets/containers/tilt_container_utils.dart @@ -0,0 +1,22 @@ +import 'package:flutter/widgets.dart'; + +abstract final class TiltContainerUtils { + /// 尺寸监听 Widget + /// + /// - [onResize] 调整尺寸回调 + static Widget buildWidgetResize(void Function(Size) onResize) { + return LayoutBuilder( + builder: ( + BuildContext context, + BoxConstraints constraints, + ) { + WidgetsBinding.instance.addPostFrameCallback((_) { + if (context.mounted) { + onResize(constraints.biggest); + } + }); + return const SizedBox(); + }, + ); + } +} diff --git a/lib/src/widgets/tilt_parallax_container.dart b/lib/src/widgets/containers/tilt_parallax_container.dart similarity index 58% rename from lib/src/widgets/tilt_parallax_container.dart rename to lib/src/widgets/containers/tilt_parallax_container.dart index e9d3292..55fc562 100644 --- a/lib/src/widgets/tilt_parallax_container.dart +++ b/lib/src/widgets/containers/tilt_parallax_container.dart @@ -1,15 +1,31 @@ import 'package:flutter/widgets.dart'; -import '../internal/provider/tilt_animation_provider.dart'; -import '../internal/provider/tilt_provider.dart'; +import '../core/tilt_animated_builder.dart'; -/// 倾斜视差 class TiltParallaxContainer extends StatelessWidget { - /// 倾斜视差 + /// {@template tilt.TiltParallaxContainer.desc.en} + /// A parallax effect widget that can only be used inside [Tilt]. /// - /// 用作视差的 Widget + /// Create a parallax layer within the Tilt widget tree. + /// {@endtemplate} /// - /// 只能在 [Tilt.childLayout] 中使用 + /// {@template tilt.TiltParallaxContainer.param.en} + /// - [size]: Parallax size. + /// - [filterQuality]: Flutter FilterQuality. + /// {@endtemplate} + /// + /// ------ + /// + /// {@template tilt.TiltParallaxContainer.desc.zh} + /// 倾斜视差,只能在 [Tilt] 中使用的视差效果 widget。 + /// + /// 在 Tilt widget 树内创建视差效果层。 + /// {@endtemplate} + /// + /// {@template tilt.TiltParallaxContainer.param.zh} + /// - [size]:视差大小。 + /// - [filterQuality]:Flutter FilterQuality。 + /// {@endtemplate} const TiltParallaxContainer({ super.key, required this.child, @@ -26,18 +42,12 @@ class TiltParallaxContainer extends StatelessWidget { @override Widget build(BuildContext context) { - final tiltAnimationProvider = TiltAnimationProvider.of(context); - final tiltTweenAnimationProvider = tiltAnimationProvider.tiltTweenAnimation; - final tiltProvider = TiltProvider.of(context); - - return AnimatedBuilder( - animation: tiltTweenAnimationProvider, - builder: (BuildContext context, Widget? child) { - final areaProgress = tiltTweenAnimationProvider.value; + return TiltAnimatedBuilder( + builder: (context, tiltData, tiltConfig, onResize, child) { final tiltParallaxTransform = this.tiltParallaxTransform( - areaProgress, + tiltData.areaProgress, size, - tiltProvider.tiltConfig.enableReverse, + tiltConfig.enableReverse, ); return Transform( diff --git a/lib/src/widgets/containers/tilt_projector_container.dart b/lib/src/widgets/containers/tilt_projector_container.dart new file mode 100644 index 0000000..229a29e --- /dev/null +++ b/lib/src/widgets/containers/tilt_projector_container.dart @@ -0,0 +1,278 @@ +import 'package:flutter/widgets.dart'; + +import '../../config/tilt_config.dart'; +import '../../config/tilt_light_config.dart'; +import '../../config/tilt_shadow_config.dart'; +import '../core/tilt_animated_builder.dart'; +import '../effects/tilt_light.dart'; +import '../effects/tilt_shadow.dart'; +import 'tilt_container_utils.dart'; + +class TiltProjectorContainer extends StatelessWidget { + /// {@template tilt.TiltProjectorContainer.desc.en} + /// Apply a shadow to the entire [child] and [childLayout], similar to a projector effect. + /// will display a shadow that exactly matches the non-transparent parts of the widget. + /// + /// "Performance risk exists" + /// + /// Recommended for the following scenarios: + /// - Images only + /// - No data states + /// - No Hero tags + /// {@endtemplate} + /// + /// ------ + /// + /// {@template tilt.TiltProjectorContainer.param.en} + /// - [child]: The main child widget to which tilt and effects are applied. + /// - [childLayout]: Other child layouts. e.g. [TiltParallax] parallax inner, outer, behind. + /// - [lightConfig]: Light effect config. + /// - [shadowConfig]: Shadow effect config. + /// - [border]: BoxDecoration border. + /// - [borderRadius]: BoxDecoration borderRadius. + /// - [clipBehavior]: Clip behavior for the container. + /// - [filterQuality]: Filter quality for the transform. + /// {@endtemplate} + /// + /// ------ + /// + /// {@template tilt.TiltProjectorContainer.desc.zh} + /// 对整个 [child]、[childLayout] 使用类似投影仪效果的阴影, + /// 会显示与 widget 非透明部分完全一致的阴影。 + /// + /// “有性能风险” + /// + /// 建议在以下场景使用: + /// - 仅图片 + /// - 无数据状态 + /// - 无 Hero 标签 + /// {@endtemplate} + /// + /// ------ + /// + /// {@template tilt.TiltProjectorContainer.param.zh} + /// - [child]:主要 child,倾斜和效果将应用于此 widget。 + /// - [childLayout]:其他 child 布局。例如:位于 child 内部、外部、后面的视差布局 [TiltParallax]。 + /// - [lightConfig]:光照效果配置。 + /// - [shadowConfig]:阴影效果配置。 + /// - [border]:BoxDecoration 边框样式。 + /// - [borderRadius]:BoxDecoration 边框圆角半径。 + /// - [clipBehavior]:容器的裁剪行为。 + /// - [filterQuality]:Transform 的滤镜质量。 + /// {@endtemplate} + const TiltProjectorContainer({ + super.key, + required this.child, + this.childLayout = const ChildLayout(), + @Deprecated( + '\nThe current simulated light effect is not suitable for Projector, ' + 'so it will be removed after version 4.1.0.\n' + '------\n' + '当前的模拟光照效果对 Projector 并不合适,因此将在 4.1.0 版本之后移除。\n', + ) + this.lightConfig = const LightProjectorConfig(), + this.shadowConfig = const ShadowProjectorConfig(), + this.border, + this.borderRadius, + this.clipBehavior = Clip.antiAlias, + this.filterQuality, + }); + + final Widget child; + + /// Other child layouts. e.g. [TiltParallax] parallax inner, outer, behind. + /// + /// ------ + /// + /// 其他 child 布局。例如:位于 child 内部、外部、后面的视差布局 [TiltParallax]。 + /// + final ChildLayout childLayout; + + /// Light effect config. + /// + /// ------ + /// + /// 光照效果配置。 + @Deprecated( + '\nThe current simulated light effect is not suitable for Projector, ' + 'so it will be removed after version 4.1.0.\n' + '------\n' + '当前的模拟光照效果对 Projector 并不合适,因此将在 4.1.0 版本之后移除。\n', + ) + final LightProjectorConfig lightConfig; + + /// Shadow effect config. + /// + /// ------ + /// + /// 阴影效果配置。 + final ShadowProjectorConfig shadowConfig; + + /// BoxDecoration border. + /// + /// ------ + /// + /// BoxDecoration 边框样式。 + final BoxBorder? border; + + /// BoxDecoration borderRadius. + /// + /// ------ + /// + /// BoxDecoration 边框圆角半径。 + final BorderRadiusGeometry? borderRadius; + + /// Clip + final Clip clipBehavior; + + /// Transform filter quality. + final FilterQuality? filterQuality; + + @override + Widget build(BuildContext context) { + return TiltAnimatedBuilder( + builder: (context, tiltData, tiltConfig, onResize, child) { + return Transform( + alignment: AlignmentDirectional.center, + filterQuality: filterQuality, + transform: tiltData.transform, + child: Stack( + alignment: AlignmentDirectional.center, + clipBehavior: Clip.none, + children: _buildChildren( + child: child, + width: tiltData.width, + height: tiltData.height, + areaProgress: tiltData.areaProgress, + onResize: onResize, + ), + ), + ); + }, + child: child, + ); + } + + List _buildChildren({ + Widget? child, + required double width, + required double height, + required Offset areaProgress, + required void Function(Size) onResize, + }) { + return [ + /// shadow + TiltShadowProjector( + width: width, + height: height, + areaProgress: areaProgress, + lightConfig: lightConfig, + shadowConfig: shadowConfig, + child: Stack( + alignment: AlignmentDirectional.center, + clipBehavior: Clip.none, + children: [ + /// behind child + ...childLayout.behind, + + /// main child + Container( + decoration: BoxDecoration( + border: border, + borderRadius: borderRadius, + ), + clipBehavior: clipBehavior, + child: _buildStackInner( + clipBehavior: clipBehavior, + children: [ + /// body + child ?? const SizedBox(), + + /// inner child + ...childLayout.inner, + ], + ), + ), + + /// outer child + ...childLayout.outer, + ], + ), + ), + + /// behind child + ...childLayout.behind, + + /// main child + Container( + decoration: BoxDecoration( + border: border, + borderRadius: borderRadius, + ), + clipBehavior: clipBehavior, + child: _buildStackInner( + clipBehavior: clipBehavior, + children: [ + /// body + child ?? const SizedBox(), + + /// inner child + ...childLayout.inner, + + /// resize + Positioned.fill( + child: TiltContainerUtils.buildWidgetResize(onResize), + ), + ], + ), + ), + + /// outer child + ...childLayout.outer, + + /// TODO: 即将弃用,不适合 Projector 效果,4.1.0 版本将移除 + if (!lightConfig.disable) + IgnorePointer( + child: Transform.scale( + scale: lightConfig.projectorScale, + child: Container( + width: width, + height: height, + decoration: BoxDecoration( + borderRadius: borderRadius, + ), + clipBehavior: clipBehavior, + child: _buildStackInner( + clipBehavior: clipBehavior, + children: [ + TiltLight( + width: width, + height: height, + areaProgress: areaProgress, + lightConfig: lightConfig, + ), + ], + ), + ), + ), + ), + ]; + } + + /// 构建内部 Stack + /// + /// - [clipBehavior] 裁剪行为 + /// - [children] 子组件列表 + Widget _buildStackInner({ + required Clip clipBehavior, + required List children, + }) { + return Stack( + alignment: AlignmentDirectional.center, + + /// 避免暴露其他组件,[Clip.none] 时,默认赋值 [Clip.hardEdge] + clipBehavior: clipBehavior == Clip.none ? Clip.hardEdge : clipBehavior, + children: children, + ); + } +} diff --git a/lib/src/widgets/gestures_listener.dart b/lib/src/widgets/core/gestures_listener.dart similarity index 95% rename from lib/src/widgets/gestures_listener.dart rename to lib/src/widgets/core/gestures_listener.dart index 9a456cb..d2eba50 100644 --- a/lib/src/widgets/gestures_listener.dart +++ b/lib/src/widgets/core/gestures_listener.dart @@ -1,9 +1,9 @@ import 'package:flutter/gestures.dart'; import 'package:flutter/widgets.dart'; -import '../enums.dart'; -import '../internal/controllers/tilt_gestures_controller.dart'; -import '../models/tilt_stream_model.dart'; +import '../../enums.dart'; +import '../../internal/controllers/tilt_gestures_controller.dart'; +import '../../models/tilt_stream_model.dart'; /// 手势监听 class GesturesListener extends StatefulWidget { diff --git a/lib/src/widgets/core/tilt_animated_builder.dart b/lib/src/widgets/core/tilt_animated_builder.dart new file mode 100644 index 0000000..84283dc --- /dev/null +++ b/lib/src/widgets/core/tilt_animated_builder.dart @@ -0,0 +1,140 @@ +import 'package:flutter/widgets.dart'; + +import '../../config/tilt_config.dart'; +import '../../internal/provider/tilt_animation_provider.dart'; +import '../../internal/provider/tilt_provider.dart'; +import '../../internal/tilt_data.dart'; +import '../../models/tilt_data_model.dart'; + +/// {@template tilt.TiltAnimatedWidgetBuilder.en} +/// A builder callback for [TiltAnimatedBuilder] that is called on every animation frame. +/// +/// - [context]: The build context. +/// - [tiltData]: The current tilt data snapshot, updated on every animation tick. +/// - [tiltConfig]: The current tilt configuration. +/// - [onResize]: Call this with the widget's [Size] whenever the widget is resized. +/// You can also use a [LayoutBuilder] inside the layout to listen for size changes and call this. +/// - [child]: The pre-built subtree passed to [TiltAnimatedBuilder.child], or `null`. +/// {@endtemplate} +/// +/// ------ +/// +/// {@template tilt.TiltAnimatedWidgetBuilder.zh} +/// [TiltAnimatedBuilder] 的 builder 回调,每帧动画更新时触发。 +/// +/// - [context]:当前 build context。 +/// - [tiltData]:当前帧的倾斜数据快照,每帧动画更新时变化。 +/// - [tiltConfig]:当前的倾斜配置。 +/// - [onResize]:当 widget 尺寸发生变化时,调用此方法并传入新的 [Size]。 +/// 也可以在布局内部使用 [LayoutBuilder] 来监听尺寸变化,并调用此方法。 +/// - [child]:传入 [TiltAnimatedBuilder.child] 的预构建子树,可能为 `null`。 +/// {@endtemplate} +typedef TiltAnimatedWidgetBuilder = Widget Function( + BuildContext context, + TiltDataModel tiltData, + TiltConfig tiltConfig, + void Function(Size) onResize, + Widget? child, +); + +/// TiltAnimatedBuilder +class TiltAnimatedBuilder extends StatelessWidget { + /// It can only be used inside the Tilt widget tree. + /// + /// It provides a customisable animation [builder] callback for rendering logic. + /// (for example, [Transform]). + /// + /// ------ + /// + /// 只能在 [Tilt] widget 树内使用。 + /// + /// 提供可自定义动画的 [builder] 回调, + /// 用于实现自定义渲染逻辑(例如 [Transform])。 + /// + /// ------ + /// + /// Example: + /// + /// ```dart + /// Tilt( + /// child: TiltAnimatedBuilder( + /// builder: (context, tiltData, onResize, child) { + /// return Transform( + /// alignment: AlignmentDirectional.center, + /// transform: tiltData.transform, + /// child: MyCustomWidget(progress: tiltData.areaProgress, child: child), + /// ); + /// }, + /// child: SomeWidget(), + /// ), + /// ) + /// ``` + /// + const TiltAnimatedBuilder({ + super.key, + required this.builder, + required this.child, + }); + + /// [TiltAnimatedWidgetBuilder] + /// + /// Called every time the Tilt animation changes. + /// + /// The child passed to this builder should typically be included in the returned widget tree. + /// + /// {@macro tilt.TiltAnimatedWidgetBuilder.en} + /// + /// ------ + /// + /// 当 Tilt 动画变化时会触发此回调。 + /// + /// 传入该 builder 的 child 通常应包含在最终返回的 widget 树中。 + /// + /// {@macro tilt.TiltAnimatedWidgetBuilder.zh} + /// + final TiltAnimatedWidgetBuilder builder; + + /// The child widget passed to the [builder]. + /// + /// If part of the builder subtree does not depend on Tilt animation changes, + /// prebuild it as child to avoid rebuilding it on every animation update. + /// + /// Using child is optional, + /// but it can significantly improve performance in suitable cases and is a recommended practice. + /// + /// ------ + /// + /// 用于传给 [builder] 的 child widget。 + /// + /// 当 builder 中有一部分子树不依赖 Tilt 动画变化时, + /// 建议通过预构建 child 传入,避免每次动画更新都重复构建该部分。 + /// + /// child 参数是可选的, + /// 但在合适场景下能显著提升性能,属于推荐的做法。 + final Widget? child; + + @override + Widget build(BuildContext context) { + final tiltProvider = TiltProvider.of(context); + final tiltAnimationProvider = TiltAnimationProvider.of(context); + final tiltTweenAnimation = tiltAnimationProvider.tiltTweenAnimation; + final onResize = tiltProvider.onResize; + final tiltConfig = tiltProvider.tiltConfig; + + return AnimatedBuilder( + animation: tiltTweenAnimation, + builder: (BuildContext context, Widget? child) { + final areaProgress = tiltTweenAnimation.value; + final tiltData = TiltData( + isInit: tiltProvider.isInit, + width: tiltProvider.width, + height: tiltProvider.height, + areaProgress: areaProgress, + tiltConfig: tiltConfig, + ).toModel(); + return builder(context, tiltData, tiltConfig, onResize, child); + }, + child: child, + ); + } +} diff --git a/lib/src/widgets/tilt_stream_builder.dart b/lib/src/widgets/core/tilt_stream_builder.dart similarity index 93% rename from lib/src/widgets/tilt_stream_builder.dart rename to lib/src/widgets/core/tilt_stream_builder.dart index 33ca73a..a15ad6b 100644 --- a/lib/src/widgets/tilt_stream_builder.dart +++ b/lib/src/widgets/core/tilt_stream_builder.dart @@ -1,7 +1,7 @@ import 'package:flutter/widgets.dart'; -import '../internal/controllers/tilt_gestures_controller.dart'; -import '../models/tilt_stream_model.dart'; +import '../../internal/controllers/tilt_gestures_controller.dart'; +import '../../models/tilt_stream_model.dart'; /// Tilt StreamBuilder class TiltStreamBuilder extends StatefulWidget { diff --git a/lib/src/widgets/core/tilt_widget.dart b/lib/src/widgets/core/tilt_widget.dart new file mode 100644 index 0000000..5032d12 --- /dev/null +++ b/lib/src/widgets/core/tilt_widget.dart @@ -0,0 +1,422 @@ +import 'dart:async' as async; + +import 'package:flutter/widgets.dart'; + +import '../../config/tilt_config.dart'; +import '../../enums.dart'; +import '../../internal/controllers/fps_timer_controller.dart'; +import '../../internal/controllers/tilt_gestures_controller.dart'; +import '../../internal/mixin/tilt_tween_animation_mixin.dart'; +import '../../internal/provider/tilt_animation_provider.dart'; +import '../../internal/provider/tilt_provider.dart'; +import '../../internal/tilt_data.dart'; +import '../../models/tilt_stream_model.dart'; +import '../../utils.dart'; +import 'gestures_listener.dart'; +import 'tilt_stream_builder.dart'; + +/// TiltWidget +/// 倾斜 +class TiltWidget extends StatefulWidget { + /// TiltWidget + /// 倾斜 + /// + /// {@template tilt.TiltWidget.desc.en} + /// Core tilt widget with gesture handling, stream management, and animation. + /// {@endtemplate} + /// + /// {@template tilt.TiltWidget.param.en} + /// - [tiltStreamController] : `StreamController.broadcast()` to control the tilt. + /// - [disable] : Disable all effects. + /// - [fps] : Gesture triggered frames. + /// - [tiltConfig] : Tilt effect config. + /// - [onGestureMove] : Gesture move callback. + /// - [onGestureLeave] : Gesture leave callback. + /// {@endtemplate} + /// + /// ------ + /// + /// {@template tilt.TiltWidget.desc.zh} + /// 核心倾斜 widget,包含手势处理、流管理和动画。 + /// {@endtemplate} + /// + /// {@template tilt.TiltWidget.param.zh} + /// - [tiltStreamController]:通过 `StreamController.broadcast()` 来自定义控制倾斜。 + /// - [disable]:禁用所有效果。 + /// - [fps]:手势触发的帧数。 + /// - [tiltConfig]:倾斜效果配置。 + /// - [onGestureMove]:手势移动的回调触发。 + /// - [onGestureLeave]:手势离开的回调触发。 + /// {@endtemplate} + /// + const TiltWidget({ + super.key, + required this.child, + this.tiltStreamController, + this.disable = false, + this.fps = 120, + this.tiltConfig = const TiltConfig(), + this.onGestureMove, + this.onGestureLeave, + }); + + final Widget child; + + /// Tilt Stream Controller + /// + /// `StreamController.broadcast()` to control the tilt. + /// + /// ------ + /// + /// 通过 `StreamController.broadcast()` 来自定义控制倾斜。 + /// + final async.StreamController? tiltStreamController; + + /// Disable all effects. + /// + /// ------ + /// + /// 禁用所有效果。 + final bool disable; + + /// Gesture triggered frames. + /// + /// ------ + /// + /// 手势触发的帧数。 + final int fps; + + /// Tilt effect config. + /// + /// ------ + /// + /// 倾斜效果配置。 + final TiltConfig tiltConfig; + + /// Gesture move callback. + /// + /// ------ + /// + /// 手势移动的回调触发。 + final TiltCallback? onGestureMove; + + /// Gesture leave callback. + /// + /// ------ + /// + /// 手势离开的回调触发。 + final TiltCallback? onGestureLeave; + + @override + State createState() => _TiltWidgetState(); +} + +class _TiltWidgetState extends State { + /// 初始坐标区域进度 + Offset get _initAreaProgress => widget.tiltConfig.initial ?? Offset.zero; + + /// 是否初始化 + bool _isInit = false; + + /// 尺寸 + double _width = 0.0, _height = 0.0; + + /// 当前坐标的区域进度 + late Offset _areaProgress = _initAreaProgress; + + /// 是否正在移动 + bool _isMove = false; + + /// 当前手势类型 + GesturesType _currentGesturesType = GesturesType.none; + + /// 倾斜手势控制器 + late TiltGesturesController _tiltGesturesController; + + /// 默认 TiltStreamController + /// + /// [widget.tiltStreamController] 为 null 时使用 + final async.StreamController _kDefaultTiltStreamController = + async.StreamController.broadcast(); + + late FpsTimerController _fpsTimerController; + + /// 当前坐标 + late Offset _currentPosition = Utils.progressPosition( + _width, + _height, + _initAreaProgress, + ); + + @override + void initState() { + super.initState(); + _initControllers(); + } + + @override + void dispose() { + _kDefaultTiltStreamController.close(); + _fpsTimerController.dispose(); + _tiltGesturesController.dispose(); + super.dispose(); + } + + @override + void didUpdateWidget(TiltWidget oldWidget) { + super.didUpdateWidget(oldWidget); + if (_shouldReinitControllers(oldWidget)) { + _initControllers(oldWidget: oldWidget); + } + } + + @override + Widget build(BuildContext context) { + return GesturesListener( + tiltGesturesController: _tiltGesturesController, + child: TiltStreamBuilder( + tiltGesturesController: _tiltGesturesController, + builder: (context, snapshot) { + _handleGesturesStream(snapshot.data); + return TiltProvider( + isInit: _isInit, + width: _width, + height: _height, + areaProgress: _areaProgress, + isMove: _isMove, + currentGesturesType: _currentGesturesType, + tiltConfig: widget.tiltConfig, + onResize: _onResize, + child: _TiltAnimationProviderWrapper(child: widget.child), + ); + }, + ), + ); + } + + /// 初始化控制器 + /// + /// - [oldWidget] 旧 Wdiget 一般用于 [didUpdateWidget] + void _initControllers({TiltWidget? oldWidget}) { + /// 是否需要取消 [TiltGesturesController] 中的手势协调器,避免泄露 + if (oldWidget != null && oldWidget.tiltConfig != widget.tiltConfig) { + _tiltGesturesController.cancelGesturesHarmonizerTimer(); + } + + /// 初始化 TiltGesturesController + final tiltStreamController = + widget.tiltStreamController ?? _kDefaultTiltStreamController; + _tiltGesturesController = TiltGesturesController( + tiltStreamController: tiltStreamController, + disable: widget.disable, + fps: widget.fps, + tiltConfig: widget.tiltConfig, + initialPosition: _currentPosition, + ); + + /// 初始化 FpsTimerController + _fpsTimerController = FpsTimerController(widget.fps); + } + + /// 判断是否需要重新初始化控制器 + bool _shouldReinitControllers(TiltWidget oldWidget) { + return oldWidget.tiltStreamController != widget.tiltStreamController || + oldWidget.disable != widget.disable || + oldWidget.fps != widget.fps || + oldWidget.tiltConfig != widget.tiltConfig; + } + + /// 调整尺寸及初始参数 + void _onResize(Size size) { + final oldSize = Size(_width, _height); + if (oldSize != size) { + _isInit = true; + _width = size.width; + _height = size.height; + setState(() { + _currentPosition = Utils.progressPosition( + _width, + _height, + _initAreaProgress, + ); + }); + } + } + + /// 处理手势 Stream 触发 + void _handleGesturesStream(TiltStreamModel? tiltStreamModel) { + if (tiltStreamModel == null) return; + if (tiltStreamModel.gesturesType == GesturesType.none) return; + if (!_isInit || widget.disable) return; + switch (tiltStreamModel.gesturesType) { + case GesturesType.none: + break; + case GesturesType.touch || GesturesType.hover || GesturesType.controller: + if (tiltStreamModel.gestureUse) { + _onGesturesMove( + tiltStreamModel.position, + tiltStreamModel.gesturesType, + ); + } else { + _onGesturesRevert( + tiltStreamModel.position, + tiltStreamModel.gesturesType, + ); + } + case GesturesType.sensors: + // Sensors 只会触发 onGestureMove,不会触发 onGestureLeave + _currentPosition += + tiltStreamModel.position * widget.tiltConfig.sensorFactor; + _onGesturesSensorsRevert(); + _currentPosition = Utils.constraintsPosition( + _width, + _height, + _currentPosition, + ); + _onGesturesMove(_currentPosition, tiltStreamModel.gesturesType); + } + } + + /// 手势移动触发 + /// + /// [offset] 当前坐标 + void _onGesturesMove(Offset offset, GesturesType gesturesType) { + if (!_isInit || widget.disable) return; + if (!_fpsTimerController.shouldTrigger()) return; + if (widget.tiltConfig.enableOutsideAreaMove || + Utils.isInRange(_width, _height, offset)) { + _currentPosition = offset; + _areaProgress = Utils.p2cAreaProgress( + _width, + _height, + offset, + widget.tiltConfig.direction, + ); + _isMove = true; + _currentGesturesType = gesturesType; + _onGestureMove(_areaProgress, gesturesType); + } else { + _onGesturesRevert(offset, gesturesType); + } + } + + /// 手势复原触发 + /// + /// [offset] 当前坐标 + void _onGesturesRevert(Offset offset, GesturesType gesturesType) { + if (!_isInit || widget.disable || !_isMove) return; + + /// 是否还原的取值 + final position = widget.tiltConfig.enableRevert + ? Utils.progressPosition(_width, _height, _initAreaProgress) + : _currentPosition; + _currentPosition = position; + _areaProgress = Utils.p2cAreaProgress( + _width, + _height, + position, + widget.tiltConfig.direction, + ); + _isMove = false; + _currentGesturesType = gesturesType; + _onGestureLeave(_areaProgress, gesturesType); + } + + /// 手势传感器复原触发 + /// + /// Sensors 只会触发 onGestureMove,不会触发 onGestureLeave + void _onGesturesSensorsRevert() { + if (!widget.tiltConfig.enableSensorRevert) return; + + /// 默认坐标 + final initPosition = Utils.progressPosition( + _width, + _height, + _initAreaProgress, + ); + + /// 还原 + _currentPosition -= Offset( + _currentPosition.dx - initPosition.dx, + _currentPosition.dy - initPosition.dy, + ) * + widget.tiltConfig.sensorRevertFactor; + } + + /// onGestureMove + /// + /// - [areaProgress] 当前坐标的区域进度 + void _onGestureMove(Offset areaProgress, GesturesType gesturesType) { + if (widget.onGestureMove != null) { + _triggerGestureCallback( + widget.onGestureMove, + areaProgress, + gesturesType, + ); + } + } + + /// onGestureLeave + /// + /// - [areaProgress] 当前坐标的区域进度 + void _onGestureLeave(Offset areaProgress, GesturesType gesturesType) { + if (widget.onGestureLeave != null) { + _triggerGestureCallback( + widget.onGestureLeave, + areaProgress, + gesturesType, + ); + } + } + + /// 触发手势回调 + void _triggerGestureCallback( + TiltCallback? callback, + Offset areaProgress, + GesturesType gesturesType, + ) { + if (callback != null) { + WidgetsBinding.instance.addPostFrameCallback((_) { + if (mounted) { + callback( + TiltData( + isInit: _isInit, + width: _width, + height: _height, + areaProgress: areaProgress, + tiltConfig: widget.tiltConfig, + ).toModel(), + gesturesType, + ); + } + }); + } + } +} + +class _TiltAnimationProviderWrapper extends StatefulWidget { + /// Provides a shared tween animation in a single Tilt tree to avoid duplicate Providers + /// + /// ------ + /// + /// 在单个 Tilt 树内统一提供 tween 动画,避免多个子 widget 重复创建 Provider + const _TiltAnimationProviderWrapper({required this.child}); + + final Widget child; + + @override + State<_TiltAnimationProviderWrapper> createState() => + _TiltAnimationProviderWrapperState(); +} + +class _TiltAnimationProviderWrapperState + extends State<_TiltAnimationProviderWrapper> + with TickerProviderStateMixin, TiltTweenAnimationMixin { + @override + Widget build(BuildContext context) { + return TiltAnimationProvider( + tiltTweenAnimation: tiltTweenAnimation, + child: widget.child, + ); + } +} diff --git a/lib/src/widgets/tilt_light.dart b/lib/src/widgets/effects/tilt_light.dart similarity index 95% rename from lib/src/widgets/tilt_light.dart rename to lib/src/widgets/effects/tilt_light.dart index 802b65a..346a35b 100644 --- a/lib/src/widgets/tilt_light.dart +++ b/lib/src/widgets/effects/tilt_light.dart @@ -1,9 +1,9 @@ import 'package:flutter/widgets.dart'; -import '../config/tilt_light_config.dart'; -import '../enums.dart'; -import '../internal/mixin/tilt_decoration_mixin.dart'; -import '../utils.dart'; +import '../../config/tilt_light_config.dart'; +import '../../enums.dart'; +import '../../internal/mixin/tilt_decoration_mixin.dart'; +import '../../utils.dart'; /// 光源 class TiltLight extends StatelessWidget with TiltDecorationMixin { diff --git a/lib/src/widgets/tilt_shadow.dart b/lib/src/widgets/effects/tilt_shadow.dart similarity index 93% rename from lib/src/widgets/tilt_shadow.dart rename to lib/src/widgets/effects/tilt_shadow.dart index 538668a..0d656fe 100644 --- a/lib/src/widgets/tilt_shadow.dart +++ b/lib/src/widgets/effects/tilt_shadow.dart @@ -3,13 +3,14 @@ import 'dart:ui'; import 'package:flutter/foundation.dart' show kIsWeb; import 'package:flutter/widgets.dart'; -import '../config/tilt_light_config.dart'; -import '../config/tilt_shadow_config.dart'; -import '../enums.dart'; -import '../internal/mixin/tilt_decoration_mixin.dart'; -import '../utils.dart'; - -abstract class TiltShadow extends StatelessWidget with TiltDecorationMixin { +import '../../config/tilt_light_config.dart'; +import '../../config/tilt_shadow_config.dart'; +import '../../enums.dart'; +import '../../internal/mixin/tilt_decoration_mixin.dart'; +import '../../utils.dart'; + +abstract class TiltShadow + extends StatelessWidget with TiltDecorationMixin { const TiltShadow({ super.key, required this.child, @@ -30,7 +31,7 @@ abstract class TiltShadow extends StatelessWidget with TiltDecorationMixin { final LightConfig lightConfig; /// 阴影配置 - final ShadowConfig shadowConfig; + final TShadowConfig shadowConfig; /// 阴影显示(受光源影响) /// @@ -87,8 +88,8 @@ abstract class TiltShadow extends StatelessWidget with TiltDecorationMixin { } /// 阴影 Base -/// [LightShadowMode.base] -class TiltShadowBase extends TiltShadow { +/// [TiltBaseContainer] +class TiltShadowBase extends TiltShadow { /// 阴影 Base /// /// 作用于其他组件上的阴影效果 @@ -189,8 +190,8 @@ class TiltShadowBase extends TiltShadow { } /// 阴影 Projector -/// [LightShadowMode.projector] -class TiltShadowProjector extends TiltShadow { +/// [TiltProjectorContainer] +class TiltShadowProjector extends TiltShadow { /// 阴影 Projector /// /// 作用于其他组件上的阴影效果 diff --git a/lib/src/widgets/tilt_container.dart b/lib/src/widgets/tilt_container.dart deleted file mode 100644 index 67aafa1..0000000 --- a/lib/src/widgets/tilt_container.dart +++ /dev/null @@ -1,353 +0,0 @@ -import 'package:flutter/widgets.dart'; - -import '../config/tilt_config.dart'; -import '../config/tilt_light_config.dart'; -import '../config/tilt_shadow_config.dart'; -import '../enums.dart'; -import '../internal/mixin/tilt_tween_animation_mixin.dart'; -import '../internal/provider/tilt_animation_provider.dart'; -import '../internal/provider/tilt_provider.dart'; -import '../internal/tilt_data.dart'; -import 'tilt_light.dart'; -import 'tilt_shadow.dart'; - -class TiltContainer extends StatefulWidget { - const TiltContainer({ - super.key, - required this.child, - required this.childLayout, - this.border, - this.borderRadius, - required this.clipBehavior, - required this.tiltConfig, - this.lightShadowMode = LightShadowMode.base, - required this.lightConfig, - required this.shadowConfig, - }); - - final Widget child; - - /// Other child layouts. e.g. [TiltParallax] parallax inner, outer, behind. - /// - /// ------ - /// - /// 其他 child 布局。例如:位于 child 内部、外部、后面的视差布局 [TiltParallax]。 - /// - final ChildLayout childLayout; - - /// BoxDecoration border. - /// - /// ------ - /// - /// BoxDecoration 边框样式。 - final BoxBorder? border; - - /// BoxDecoration borderRadius. - /// - /// ------ - /// - /// BoxDecoration 边框圆角半径。 - final BorderRadiusGeometry? borderRadius; - - /// Clip - final Clip clipBehavior; - - /// Tilt effect config. - /// - /// ------ - /// - /// 倾斜效果配置。 - final TiltConfig tiltConfig; - - /// Light & Shadow Mode. - /// - /// ------ - /// - /// 光影模式。 - final LightShadowMode lightShadowMode; - - /// Light effect config. - /// - /// ------ - /// - /// 光照效果配置。 - final LightConfig lightConfig; - - /// Shadow effect config. - /// - /// ------ - /// - /// 阴影效果配置。 - final ShadowConfig shadowConfig; - - @override - State createState() => _TiltContainerState(); -} - -class _TiltContainerState extends State - with TickerProviderStateMixin, TiltTweenAnimationMixin { - @override - Widget build(BuildContext context) { - final tiltProvider = TiltProvider.of(context); - - return TiltAnimationProvider( - tiltTweenAnimation: tiltTweenAnimation, - child: AnimatedBuilder( - animation: tiltTweenAnimation, - builder: (BuildContext context, Widget? child) { - final areaProgress = tiltTweenAnimation.value; - - final tiltData = TiltData( - isInit: tiltProvider.isInit, - width: tiltProvider.width, - height: tiltProvider.height, - areaProgress: areaProgress, - tiltConfig: widget.tiltConfig, - ); - - return Transform( - alignment: AlignmentDirectional.center, - filterQuality: widget.tiltConfig.filterQuality, - transform: tiltData.transform, - child: Stack( - alignment: AlignmentDirectional.center, - clipBehavior: Clip.none, - children: _buildLightShadowMode( - width: tiltProvider.width, - height: tiltProvider.height, - areaProgress: areaProgress, - onResize: tiltProvider.onResize, - child: child, - ), - ), - ); - }, - child: widget.child, - ), - ); - } - - /// Build LightShadowMode widgets - /// - /// - [child] - /// - [width] 宽度 - /// - [height] 高度 - /// - [areaProgress] 区域进度 - /// - [onResize] 调整尺寸回调 - List _buildLightShadowMode({ - Widget? child, - required double width, - required double height, - required Offset areaProgress, - required void Function(Size) onResize, - }) { - return switch (widget.lightShadowMode) { - LightShadowMode.base => _buildLightShadowModeBase( - width: width, - height: height, - areaProgress: areaProgress, - onResize: onResize, - child: child, - ), - LightShadowMode.projector => _buildLightShadowModeProjector( - width: width, - height: height, - areaProgress: areaProgress, - onResize: onResize, - child: child, - ) - }; - } - - /// LightShadowMode - Base - /// - /// - [child] - /// - [width] 宽度 - /// - [height] 高度 - /// - [areaProgress] 区域进度 - /// - [onResize] 调整尺寸回调 - List _buildLightShadowModeBase({ - Widget? child, - required double width, - required double height, - required Offset areaProgress, - required void Function(Size) onResize, - }) { - return [ - /// behind child - ...widget.childLayout.behind, - - /// main child - TiltShadowBase( - width: width, - height: height, - areaProgress: areaProgress, - border: widget.border, - borderRadius: widget.borderRadius, - clipBehavior: widget.clipBehavior, - lightConfig: widget.lightConfig, - shadowConfig: widget.shadowConfig, - child: _buildStackInner([ - /// body - Container( - decoration: BoxDecoration( - borderRadius: widget.borderRadius, - ), - clipBehavior: widget.clipBehavior, - child: child, - ), - - /// inner child - ...widget.childLayout.inner, - - /// light - TiltLight( - width: width, - height: height, - areaProgress: areaProgress, - lightConfig: widget.lightConfig, - ), - - /// resize - _widgetResize(onResize), - ]), - ), - - /// outer child - ...widget.childLayout.outer, - ]; - } - - /// LightShadowMode - Projector - /// - /// - [child] - /// - [width] 宽度 - /// - [height] 高度 - /// - [areaProgress] 区域进度 - /// - [onResize] 调整尺寸回调 - List _buildLightShadowModeProjector({ - Widget? child, - required double width, - required double height, - required Offset areaProgress, - required void Function(Size) onResize, - }) { - return [ - /// shadow - TiltShadowProjector( - width: width, - height: height, - areaProgress: areaProgress, - lightConfig: widget.lightConfig, - shadowConfig: widget.shadowConfig, - child: Stack( - alignment: AlignmentDirectional.center, - clipBehavior: Clip.none, - children: [ - /// behind child - ...widget.childLayout.behind, - - /// main child - Container( - decoration: BoxDecoration( - border: widget.border, - borderRadius: widget.borderRadius, - ), - clipBehavior: widget.clipBehavior, - child: _buildStackInner([ - /// body - child ?? const SizedBox(), - - /// inner child - ...widget.childLayout.inner, - ]), - ), - - /// outer child - ...widget.childLayout.outer, - ], - ), - ), - - /// behind child - ...widget.childLayout.behind, - - /// main child - Container( - decoration: BoxDecoration( - border: widget.border, - borderRadius: widget.borderRadius, - ), - clipBehavior: widget.clipBehavior, - child: _buildStackInner([ - /// body - child ?? const SizedBox(), - - /// inner child - ...widget.childLayout.inner, - - /// resize - _widgetResize(onResize), - ]), - ), - - /// outer child - ...widget.childLayout.outer, - - /// light - IgnorePointer( - child: Transform.scale( - scale: widget.lightConfig.projectorScale, - child: Container( - width: width, - height: height, - decoration: BoxDecoration( - borderRadius: widget.borderRadius, - ), - clipBehavior: widget.clipBehavior, - child: _buildStackInner([ - TiltLight( - width: width, - height: height, - areaProgress: areaProgress, - lightConfig: widget.lightConfig, - ), - ]), - ), - ), - ), - ]; - } - - /// Stack Inner - Widget _buildStackInner(List children) { - return Stack( - alignment: AlignmentDirectional.center, - - /// 避免暴露其他组件,[Clip.none] 时,默认赋值 [Clip.hardEdge] - clipBehavior: widget.clipBehavior == Clip.none - ? Clip.hardEdge - : widget.clipBehavior, - children: children, - ); - } - - /// Widget Resize - Widget _widgetResize(void Function(Size) onResize) { - return Positioned.fill( - child: LayoutBuilder( - builder: ( - BuildContext context, - BoxConstraints constraints, - ) { - WidgetsBinding.instance.addPostFrameCallback((_) { - if (mounted) { - onResize(constraints.biggest); - } - }); - return const SizedBox(); - }, - ), - ); - } -} diff --git a/pubspec.yaml b/pubspec.yaml index 2d3043e..76b19cd 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -8,7 +8,7 @@ description: Easily apply tilt parallax hover effects for Flutter, which support # https://semver.org/spec/v2.0.0-rc.1.html # https://dart.dev/tools/pub/versioning#semantic-versions # https://dart.dev/tools/pub/dependencies#version-constraints -version: 3.3.4 +version: 4.0.0-rc.1 homepage: https://amoshuke.github.io/flutter_tilt_book repository: https://github.com/fluttercandies/flutter_tilt issue_tracker: https://github.com/fluttercandies/flutter_tilt/issues diff --git a/test/config/tilt_config_test.dart b/test/config/tilt_config_test.dart index 4809f5d..c6b3c57 100644 --- a/test/config/tilt_config_test.dart +++ b/test/config/tilt_config_test.dart @@ -12,7 +12,6 @@ void main() { angle: 1.0, direction: [TiltDirection.top, TiltDirection.bottom], enableReverse: true, - filterQuality: FilterQuality.high, enableGestureSensors: false, sensorFactor: 2.0, enableSensorRevert: false, @@ -38,7 +37,6 @@ void main() { angle: 1.0, direction: [TiltDirection.top, TiltDirection.bottom], enableReverse: true, - filterQuality: FilterQuality.high, enableGestureSensors: false, sensorFactor: 2.0, enableSensorRevert: false, diff --git a/test/config/tilt_light_config_test.dart b/test/config/tilt_light_config_test.dart index 58171de..6817bb1 100644 --- a/test/config/tilt_light_config_test.dart +++ b/test/config/tilt_light_config_test.dart @@ -31,10 +31,6 @@ void main() { () => LightConfig(spreadFactor: 0.1), throwsAssertionError, ); - expect( - () => LightConfig(projectorScale: -1), - throwsAssertionError, - ); }); test('copyWith', () { const lightConfig = LightConfig(); @@ -44,7 +40,6 @@ void main() { minIntensity: 0.0, maxIntensity: 0.5, spreadFactor: 1.0, - projectorScale: 2.0, direction: LightDirection.around, enableReverse: true, ); @@ -54,7 +49,6 @@ void main() { minIntensity: 0.0, maxIntensity: 0.5, spreadFactor: 1.0, - projectorScale: 2.0, direction: LightDirection.around, enableReverse: true, ); diff --git a/test/config/tilt_shadow_config_test.dart b/test/config/tilt_shadow_config_test.dart index c05183e..dd2daf0 100644 --- a/test/config/tilt_shadow_config_test.dart +++ b/test/config/tilt_shadow_config_test.dart @@ -7,61 +7,111 @@ void main() { group('ShadowConfig ::', () { test('assert', () { expect( - () => ShadowConfig(minIntensity: 1.0, maxIntensity: 0.0), + () => ShadowBaseConfig(minIntensity: 1.0, maxIntensity: 0.0), throwsAssertionError, ); expect( - () => ShadowConfig(minIntensity: -0.1, maxIntensity: 1.0), + () => ShadowBaseConfig(minIntensity: -0.1, maxIntensity: 1.0), throwsAssertionError, ); expect( - () => ShadowConfig(minIntensity: 0.1, maxIntensity: 1.1), + () => ShadowBaseConfig(minIntensity: 0.1, maxIntensity: 1.1), throwsAssertionError, ); expect( - () => ShadowConfig(offsetFactor: -0.1), + () => ShadowBaseConfig(offsetFactor: -0.1), throwsAssertionError, ); + }); + }); + + group('ShadowBaseConfig ::', () { + test('assert', () { expect( - () => ShadowConfig(spreadFactor: -0.1), + () => ShadowBaseConfig(spreadFactor: -0.1), throwsAssertionError, ); expect( - () => ShadowConfig(minBlurRadius: 0.2, maxBlurRadius: 0.1), + () => ShadowBaseConfig(minBlurRadius: 0.2, maxBlurRadius: 0.1), throwsAssertionError, ); expect( - () => ShadowConfig(minBlurRadius: -0.1, maxBlurRadius: 1.0), + () => ShadowBaseConfig(minBlurRadius: -0.1, maxBlurRadius: 1.0), throwsAssertionError, ); + }); + + test('copyWith', () { + const shadowConfig = ShadowBaseConfig(); + const shadowConfigExpect = ShadowBaseConfig( + disable: true, + color: Color(0xFF9E9E90), + minIntensity: 1.0, + maxIntensity: 1.0, + offsetInitial: Offset(3.0, 3.0), + offsetFactor: 4.0, + spreadInitial: 5.0, + spreadFactor: 6.0, + minBlurRadius: 7.0, + maxBlurRadius: 8.0, + direction: ShadowDirection.all, + enableReverse: true, + ); + final shadowConfigCopyWith = shadowConfig.copyWith( + disable: true, + color: const Color(0xFF9E9E90), + minIntensity: 1.0, + maxIntensity: 1.0, + offsetInitial: const Offset(3.0, 3.0), + offsetFactor: 4.0, + spreadInitial: 5.0, + spreadFactor: 6.0, + minBlurRadius: 7.0, + maxBlurRadius: 8.0, + direction: ShadowDirection.all, + enableReverse: true, + ); + expect(shadowConfig, shadowConfig.copyWith()); + expect(shadowConfigCopyWith, shadowConfigExpect); + expect(shadowConfigCopyWith.hashCode, shadowConfigExpect.hashCode); + }); + + test('hashCode', () { + final shadowConfig = const ShadowBaseConfig(); + final shadowConfig2 = const ShadowBaseConfig(); + expect(shadowConfig == shadowConfig2, true); + expect(shadowConfig, shadowConfig2); + expect(shadowConfig.hashCode, shadowConfig2.hashCode); + }); + }); + + group('ShadowProjectorConfig ::', () { + test('assert', () { expect( - () => ShadowConfig( + () => ShadowProjectorConfig( projectorScaleFrom: -0.1, projectorScaleTo: -0.1, ), throwsAssertionError, ); expect( - () => ShadowConfig( + () => ShadowProjectorConfig( projectorBlurSigmaFrom: -0.1, projectorBlurSigmaTo: -0.1, ), throwsAssertionError, ); }); + test('copyWith', () { - const shadowConfig = ShadowConfig(); - const shadowConfigExpect = ShadowConfig( + const shadowConfig = ShadowProjectorConfig(); + const shadowConfigExpect = ShadowProjectorConfig( disable: true, color: Color(0xFF9E9E90), minIntensity: 1.0, maxIntensity: 1.0, offsetInitial: Offset(3.0, 3.0), offsetFactor: 4.0, - spreadInitial: 5.0, - spreadFactor: 6.0, - minBlurRadius: 7.0, - maxBlurRadius: 8.0, projectorScaleFrom: 2.0, projectorScaleTo: 2.0, projectorBlurSigmaFrom: 2.0, @@ -76,10 +126,6 @@ void main() { maxIntensity: 1.0, offsetInitial: const Offset(3.0, 3.0), offsetFactor: 4.0, - spreadInitial: 5.0, - spreadFactor: 6.0, - minBlurRadius: 7.0, - maxBlurRadius: 8.0, projectorScaleFrom: 2.0, projectorScaleTo: 2.0, projectorBlurSigmaFrom: 2.0, @@ -91,5 +137,13 @@ void main() { expect(shadowConfigCopyWith, shadowConfigExpect); expect(shadowConfigCopyWith.hashCode, shadowConfigExpect.hashCode); }); + + test('hashCode', () { + final shadowConfig = const ShadowProjectorConfig(); + final shadowConfig2 = const ShadowProjectorConfig(); + expect(shadowConfig == shadowConfig2, true); + expect(shadowConfig, shadowConfig2); + expect(shadowConfig.hashCode, shadowConfig2.hashCode); + }); }); } diff --git a/test/models/tilt_data_model_test.dart b/test/models/tilt_data_model_test.dart index cc6e8e0..de69958 100644 --- a/test/models/tilt_data_model_test.dart +++ b/test/models/tilt_data_model_test.dart @@ -6,12 +6,16 @@ void main() { group('TiltDataModel ::', () { test('hashCode', () { final tiltDataModel = TiltDataModel( + width: 1, + height: 1, position: const Offset(5, 5), areaProgress: Offset.zero, transform: Matrix4.identity(), angle: Offset.zero, ); final tiltDataModel2 = TiltDataModel( + width: 1, + height: 1, position: const Offset(5, 5), areaProgress: Offset.zero, transform: Matrix4.identity(), diff --git a/test/tilt_parallax_widget/tilt_parallax_widget.dart b/test/tilt_parallax_widget/tilt_parallax_widget.dart index e82d94d..44cc9f0 100644 --- a/test/tilt_parallax_widget/tilt_parallax_widget.dart +++ b/test/tilt_parallax_widget/tilt_parallax_widget.dart @@ -12,7 +12,7 @@ class TiltParallaxWidget extends StatelessWidget { this.clipBehavior = Clip.antiAlias, this.tiltConfig = const TiltConfig(), this.lightConfig = const LightConfig(), - this.shadowConfig = const ShadowConfig(), + this.shadowConfig = const ShadowBaseConfig(), this.onGestureMove, this.onGestureLeave, }); @@ -25,7 +25,7 @@ class TiltParallaxWidget extends StatelessWidget { final Clip clipBehavior; final TiltConfig tiltConfig; final LightConfig lightConfig; - final ShadowConfig shadowConfig; + final ShadowBaseConfig shadowConfig; final void Function(TiltDataModel, GesturesType)? onGestureMove; final void Function(TiltDataModel, GesturesType)? onGestureLeave; @@ -40,21 +40,23 @@ class TiltParallaxWidget extends StatelessWidget { child: Center( child: Tilt( key: const Key('tilt_widget'), - childLayout: childLayout, disable: disable, fps: fps, - border: border, - borderRadius: borderRadius, - clipBehavior: clipBehavior, tiltConfig: tiltConfig.copyWith(enableGestureSensors: false), - lightConfig: lightConfig, - shadowConfig: shadowConfig, onGestureMove: onGestureMove, onGestureLeave: onGestureLeave, - child: const SizedBox( - width: 10, - height: 10, - child: Text('Tilt'), + child: TiltBaseContainer( + childLayout: childLayout, + border: border, + borderRadius: borderRadius, + clipBehavior: clipBehavior, + lightConfig: lightConfig, + shadowConfig: shadowConfig, + child: const SizedBox( + width: 10, + height: 10, + child: Text('Tilt'), + ), ), ), ), diff --git a/test/tilt_widget/light_shadow_mode_base_widget/tilt_gestures_drag_test.dart b/test/tilt_widget/tilt_base_container_widget/tilt_gestures_drag_test.dart similarity index 86% rename from test/tilt_widget/light_shadow_mode_base_widget/tilt_gestures_drag_test.dart rename to test/tilt_widget/tilt_base_container_widget/tilt_gestures_drag_test.dart index 8664afa..fa9b73b 100644 --- a/test/tilt_widget/light_shadow_mode_base_widget/tilt_gestures_drag_test.dart +++ b/test/tilt_widget/tilt_base_container_widget/tilt_gestures_drag_test.dart @@ -4,7 +4,7 @@ import 'package:flutter_test/flutter_test.dart'; import 'package:flutter_tilt/flutter_tilt.dart'; void main() { - group('LightShadowMode.base :: tilt gestures drag ::', () { + group('TiltBaseContainer :: tilt gestures drag ::', () { testWidgets('scroll', (WidgetTester tester) async { final childFinder = find.text('Tilt'); final scrollFinder = find.byKey(const Key('scroll')); @@ -21,7 +21,13 @@ void main() { children: const [ Tilt( key: Key('tilt_widget'), - child: SizedBox(width: 100, height: 100, child: Text('Tilt')), + child: TiltBaseContainer( + child: SizedBox( + width: 100, + height: 100, + child: Text('Tilt'), + ), + ), ), SizedBox(key: Key('scroll'), height: 100, width: 100), SizedBox(height: 1000), diff --git a/test/tilt_widget/light_shadow_mode_base_widget/tilt_gestures_hover_test.dart b/test/tilt_widget/tilt_base_container_widget/tilt_gestures_hover_test.dart similarity index 95% rename from test/tilt_widget/light_shadow_mode_base_widget/tilt_gestures_hover_test.dart rename to test/tilt_widget/tilt_base_container_widget/tilt_gestures_hover_test.dart index 560c163..488201f 100644 --- a/test/tilt_widget/light_shadow_mode_base_widget/tilt_gestures_hover_test.dart +++ b/test/tilt_widget/tilt_base_container_widget/tilt_gestures_hover_test.dart @@ -6,7 +6,7 @@ import 'package:flutter_tilt/src/internal/tilt_data.dart'; import 'tilt_widget_base.dart'; void main() { - group('LightShadowMode.base :: tilt gestures hover ::', () { + group('TiltBaseContainer :: tilt gestures hover ::', () { const tiltConfig = TiltConfig(); TiltData tiltDataTestCalculate(Offset areaProgress) => TiltData( isInit: true, @@ -19,7 +19,7 @@ void main() { final tiltWidgetFinder = find.byKey(const Key('tilt_widget')); final childFinder = find.text('Tilt'); final testPointer = TestPointer(1, PointerDeviceKind.mouse); - final leaveTiltDataExpect = tiltDataTestCalculate(Offset.zero).data; + final leaveTiltDataExpect = tiltDataTestCalculate(Offset.zero).toModel(); testWidgets('gestures move leave', (WidgetTester tester) async { TiltDataModel? moveTiltDataTest; @@ -63,7 +63,8 @@ void main() { testWidgets('move top', (WidgetTester tester) async { TiltDataModel? tiltDataTest; GesturesType? gesturesTypeTest; - final tiltDataExpect = tiltDataTestCalculate(const Offset(0.0, 1.0)).data; + final tiltDataExpect = + tiltDataTestCalculate(const Offset(0.0, 1.0)).toModel(); /// 回调赋值 await tester.pumpWidget( @@ -106,7 +107,7 @@ void main() { TiltDataModel? tiltDataTest; GesturesType? gesturesTypeTest; final tiltDataExpect = - tiltDataTestCalculate(const Offset(0.0, -0.8)).data; + tiltDataTestCalculate(const Offset(0.0, -0.8)).toModel(); /// 回调赋值 await tester.pumpWidget( @@ -148,7 +149,8 @@ void main() { testWidgets('move left', (WidgetTester tester) async { TiltDataModel? tiltDataTest; GesturesType? gesturesTypeTest; - final tiltDataExpect = tiltDataTestCalculate(const Offset(1.0, 0.0)).data; + final tiltDataExpect = + tiltDataTestCalculate(const Offset(1.0, 0.0)).toModel(); /// 回调赋值 await tester.pumpWidget( @@ -191,7 +193,7 @@ void main() { TiltDataModel? tiltDataTest; GesturesType? gesturesTypeTest; final tiltDataExpect = - tiltDataTestCalculate(const Offset(-0.8, 0.0)).data; + tiltDataTestCalculate(const Offset(-0.8, 0.0)).toModel(); /// 回调赋值 await tester.pumpWidget( @@ -233,7 +235,8 @@ void main() { testWidgets('move top left', (WidgetTester tester) async { TiltDataModel? tiltDataTest; GesturesType? gesturesTypeTest; - final tiltDataExpect = tiltDataTestCalculate(const Offset(1.0, 1.0)).data; + final tiltDataExpect = + tiltDataTestCalculate(const Offset(1.0, 1.0)).toModel(); /// 回调赋值 await tester.pumpWidget( @@ -276,7 +279,7 @@ void main() { TiltDataModel? tiltDataTest; GesturesType? gesturesTypeTest; final tiltDataExpect = - tiltDataTestCalculate(const Offset(-0.8, 1.0)).data; + tiltDataTestCalculate(const Offset(-0.8, 1.0)).toModel(); /// 回调赋值 await tester.pumpWidget( @@ -319,7 +322,7 @@ void main() { TiltDataModel? tiltDataTest; GesturesType? gesturesTypeTest; final tiltDataExpect = - tiltDataTestCalculate(const Offset(1.0, -0.8)).data; + tiltDataTestCalculate(const Offset(1.0, -0.8)).toModel(); /// 回调赋值 await tester.pumpWidget( @@ -362,7 +365,7 @@ void main() { TiltDataModel? tiltDataTest; GesturesType? gesturesTypeTest; final tiltDataExpect = - tiltDataTestCalculate(const Offset(-0.8, -0.8)).data; + tiltDataTestCalculate(const Offset(-0.8, -0.8)).toModel(); /// 回调赋值 await tester.pumpWidget( diff --git a/test/tilt_widget/light_shadow_mode_base_widget/tilt_gestures_touch_test.dart b/test/tilt_widget/tilt_base_container_widget/tilt_gestures_touch_test.dart similarity index 92% rename from test/tilt_widget/light_shadow_mode_base_widget/tilt_gestures_touch_test.dart rename to test/tilt_widget/tilt_base_container_widget/tilt_gestures_touch_test.dart index 7f2b1cf..5b8a502 100644 --- a/test/tilt_widget/light_shadow_mode_base_widget/tilt_gestures_touch_test.dart +++ b/test/tilt_widget/tilt_base_container_widget/tilt_gestures_touch_test.dart @@ -5,7 +5,7 @@ import 'package:flutter_tilt/src/internal/tilt_data.dart'; import 'tilt_widget_base.dart'; void main() { - group('LightShadowMode.base :: tilt gestures touch ::', () { + group('TiltBaseContainer :: tilt gestures touch ::', () { const tiltConfig = TiltConfig(); TiltData tiltDataTestCalculate(Offset areaProgress) => TiltData( isInit: true, @@ -25,7 +25,8 @@ void main() { TiltDataModel? leaveTiltDataTest; GesturesType? moveGesturesTypeTest; GesturesType? leaveGesturesTypeTest; - final leaveTiltDataTestExpect = tiltDataTestCalculate(Offset.zero).data; + final leaveTiltDataTestExpect = + tiltDataTestCalculate(Offset.zero).toModel(); /// 回调赋值 await tester.pumpWidget( @@ -67,7 +68,8 @@ void main() { testWidgets('move top', (WidgetTester tester) async { TiltDataModel? tiltDataTest; GesturesType? gesturesTypeTest; - final tiltDataExpect = tiltDataTestCalculate(const Offset(0.0, 1.0)).data; + final tiltDataExpect = + tiltDataTestCalculate(const Offset(0.0, 1.0)).toModel(); /// 回调赋值 await tester.pumpWidget( @@ -95,7 +97,7 @@ void main() { TiltDataModel? tiltDataTest; GesturesType? gesturesTypeTest; final tiltDataExpect = - tiltDataTestCalculate(const Offset(0.0, -1.0)).data; + tiltDataTestCalculate(const Offset(0.0, -1.0)).toModel(); /// 回调赋值 await tester.pumpWidget( @@ -122,7 +124,8 @@ void main() { testWidgets('move left', (WidgetTester tester) async { TiltDataModel? tiltDataTest; GesturesType? gesturesTypeTest; - final tiltDataExpect = tiltDataTestCalculate(const Offset(1.0, 0.0)).data; + final tiltDataExpect = + tiltDataTestCalculate(const Offset(1.0, 0.0)).toModel(); /// 回调赋值 await tester.pumpWidget( @@ -150,7 +153,7 @@ void main() { TiltDataModel? tiltDataTest; GesturesType? gesturesTypeTest; final tiltDataExpect = - tiltDataTestCalculate(const Offset(-1.0, 0.0)).data; + tiltDataTestCalculate(const Offset(-1.0, 0.0)).toModel(); /// 回调赋值 await tester.pumpWidget( @@ -177,7 +180,8 @@ void main() { testWidgets('move top left', (WidgetTester tester) async { TiltDataModel? tiltDataTest; GesturesType? gesturesTypeTest; - final tiltDataExpect = tiltDataTestCalculate(const Offset(1.0, 1.0)).data; + final tiltDataExpect = + tiltDataTestCalculate(const Offset(1.0, 1.0)).toModel(); /// 回调赋值 await tester.pumpWidget( @@ -205,7 +209,7 @@ void main() { TiltDataModel? tiltDataTest; GesturesType? gesturesTypeTest; final tiltDataExpect = - tiltDataTestCalculate(const Offset(-1.0, 1.0)).data; + tiltDataTestCalculate(const Offset(-1.0, 1.0)).toModel(); /// 回调赋值 await tester.pumpWidget( @@ -233,7 +237,7 @@ void main() { TiltDataModel? tiltDataTest; GesturesType? gesturesTypeTest; final tiltDataExpect = - tiltDataTestCalculate(const Offset(1.0, -1.0)).data; + tiltDataTestCalculate(const Offset(1.0, -1.0)).toModel(); /// 回调赋值 await tester.pumpWidget( @@ -261,7 +265,7 @@ void main() { TiltDataModel? tiltDataTest; GesturesType? gesturesTypeTest; final tiltDataExpect = - tiltDataTestCalculate(const Offset(-1.0, -1.0)).data; + tiltDataTestCalculate(const Offset(-1.0, -1.0)).toModel(); /// 回调赋值 await tester.pumpWidget( @@ -291,12 +295,13 @@ void main() { TiltDataModel? leaveTiltDataTest; GesturesType? moveGesturesTypeTest; GesturesType? leaveGesturesTypeTest; - final leaveTiltDataTestExpect = tiltDataTestCalculate(Offset.zero).data; + final leaveTiltDataTestExpect = + tiltDataTestCalculate(Offset.zero).toModel(); /// 回调赋值 await tester.pumpWidget( TiltWidgetBase( - shadowConfig: const ShadowConfig(disable: true), + shadowConfig: const ShadowBaseConfig(disable: true), onGestureMove: (TiltDataModel tiltData, GesturesType gesturesType) { moveTiltDataTest = tiltData; moveGesturesTypeTest = gesturesType; diff --git a/test/tilt_widget/light_shadow_mode_base_widget/tilt_stream_controller_test.dart b/test/tilt_widget/tilt_base_container_widget/tilt_stream_controller_test.dart similarity index 99% rename from test/tilt_widget/light_shadow_mode_base_widget/tilt_stream_controller_test.dart rename to test/tilt_widget/tilt_base_container_widget/tilt_stream_controller_test.dart index f3d56c3..1772d48 100644 --- a/test/tilt_widget/light_shadow_mode_base_widget/tilt_stream_controller_test.dart +++ b/test/tilt_widget/tilt_base_container_widget/tilt_stream_controller_test.dart @@ -6,7 +6,7 @@ import 'tilt_widget_base.dart'; void main() { final childFinder = find.text('Tilt'); - group('LightShadowMode.base :: tilt TiltStreamController ::', () { + group('TiltBaseContainer :: tilt TiltStreamController ::', () { testWidgets('stream listen', (WidgetTester tester) async { var tiltStreamModelTest = const TiltStreamModel( position: Offset(1, 1), diff --git a/test/tilt_widget/light_shadow_mode_base_widget/tilt_widget_base.dart b/test/tilt_widget/tilt_base_container_widget/tilt_widget_base.dart similarity index 73% rename from test/tilt_widget/light_shadow_mode_base_widget/tilt_widget_base.dart rename to test/tilt_widget/tilt_base_container_widget/tilt_widget_base.dart index 593fcd0..a723be3 100644 --- a/test/tilt_widget/light_shadow_mode_base_widget/tilt_widget_base.dart +++ b/test/tilt_widget/tilt_base_container_widget/tilt_widget_base.dart @@ -14,9 +14,8 @@ class TiltWidgetBase extends StatelessWidget { this.borderRadius, this.clipBehavior = Clip.antiAlias, this.tiltConfig = const TiltConfig(), - this.lightShadowMode = LightShadowMode.base, this.lightConfig = const LightConfig(), - this.shadowConfig = const ShadowConfig(), + this.shadowConfig = const ShadowBaseConfig(), this.onGestureMove, this.onGestureLeave, }); @@ -29,9 +28,8 @@ class TiltWidgetBase extends StatelessWidget { final BorderRadiusGeometry? borderRadius; final Clip clipBehavior; final TiltConfig tiltConfig; - final LightShadowMode lightShadowMode; final LightConfig lightConfig; - final ShadowConfig shadowConfig; + final ShadowBaseConfig shadowConfig; final void Function(TiltDataModel, GesturesType)? onGestureMove; final void Function(TiltDataModel, GesturesType)? onGestureLeave; @@ -42,23 +40,24 @@ class TiltWidgetBase extends StatelessWidget { key: const Key('tilt_scaffold'), body: Tilt( key: const Key('tilt_widget'), - childLayout: childLayout, tiltStreamController: tiltStreamController, disable: disable, fps: fps, - border: border, - borderRadius: borderRadius, - clipBehavior: clipBehavior, - lightShadowMode: lightShadowMode, tiltConfig: tiltConfig.copyWith(enableGestureSensors: false), - lightConfig: lightConfig, - shadowConfig: shadowConfig, onGestureMove: onGestureMove, onGestureLeave: onGestureLeave, - child: const SizedBox( - width: 10, - height: 10, - child: Text('Tilt'), + child: TiltBaseContainer( + childLayout: childLayout, + border: border, + borderRadius: borderRadius, + clipBehavior: clipBehavior, + lightConfig: lightConfig, + shadowConfig: shadowConfig, + child: const SizedBox( + width: 10, + height: 10, + child: Text('Tilt'), + ), ), ), ), diff --git a/test/tilt_widget/tilt_config_tilt_test.dart b/test/tilt_widget/tilt_config_tilt_test.dart index 209887b..2295ee6 100644 --- a/test/tilt_widget/tilt_config_tilt_test.dart +++ b/test/tilt_widget/tilt_config_tilt_test.dart @@ -32,7 +32,7 @@ void main() { final tiltDataExpect = tiltDataTestCalculate( areaProgress: const Offset(0.0, 1.0), tiltConfig: tiltConfigTest, - ).data; + ).toModel(); await tester.pumpWidget( TiltWidget( @@ -56,11 +56,11 @@ void main() { final tiltDataInitialExpect = tiltDataTestCalculate( areaProgress: const Offset(1.0, 1.0), tiltConfig: tiltConfigTest, - ).data; + ).toModel(); final tiltDataExpect = tiltDataTestCalculate( areaProgress: const Offset(-0.8, -0.8), tiltConfig: tiltConfigTest, - ).data; + ).toModel(); await tester.pumpWidget( TiltWidget( @@ -107,7 +107,7 @@ void main() { final tiltDataExpect = tiltDataTestCalculate( areaProgress: const Offset(1.0, 1.0), tiltConfig: tiltConfigTest, - ).data; + ).toModel(); await tester.pumpWidget( TiltWidget( @@ -144,11 +144,11 @@ void main() { final tiltDataExpect = tiltDataTestCalculate( areaProgress: const Offset(1.0, 1.0), tiltConfig: tiltConfigTest, - ).data; + ).toModel(); final tiltDataExpect2 = tiltDataTestCalculate( areaProgress: const Offset(-1.0, -1.0), tiltConfig: tiltConfigTest, - ).data; + ).toModel(); await tester.pumpWidget( TiltWidget( @@ -182,11 +182,11 @@ void main() { final tiltDataExpect = tiltDataTestCalculate( areaProgress: const Offset(1.0, 1.0), tiltConfig: tiltConfigTest, - ).data; + ).toModel(); final tiltDataExpect2 = tiltDataTestCalculate( areaProgress: Offset.zero, tiltConfig: tiltConfigTest, - ).data; + ).toModel(); await tester.pumpWidget( TiltWidget( @@ -218,7 +218,7 @@ void main() { final tiltDataExpect = tiltDataTestCalculate( areaProgress: const Offset(1.0, 1.0), tiltConfig: tiltConfigTest, - ).data; + ).toModel(); await tester.pumpWidget( TiltWidget( @@ -265,7 +265,7 @@ void main() { final tiltDataExpect = tiltDataTestCalculate( areaProgress: const Offset(1.0, 1.0), tiltConfig: tiltConfigTest, - ).data; + ).toModel(); await tester.pumpWidget( TiltWidget( @@ -293,11 +293,11 @@ void main() { final tiltDataExpect = tiltDataTestCalculate( areaProgress: const Offset(1.0, 1.0), tiltConfig: tiltConfigTest, - ).data; + ).toModel(); final tiltDataExpect2 = tiltDataTestCalculate( areaProgress: Offset.zero, tiltConfig: tiltConfigTest, - ).data; + ).toModel(); await tester.pumpWidget( TiltWidget( @@ -335,7 +335,7 @@ void main() { final tiltDataExpect = tiltDataTestCalculate( areaProgress: const Offset(-0.8, -0.8), tiltConfig: tiltConfigTest, - ).data; + ).toModel(); await tester.pumpWidget( TiltWidget( @@ -383,11 +383,6 @@ void main() { Tilt( key: const Key('tilt_widget'), tiltConfig: const TiltConfig(enableGestureTouch: false), - child: const SizedBox( - width: 100, - height: 100, - child: Text('Tilt'), - ), onGestureMove: ( TiltDataModel tiltData, GesturesType gesturesType, @@ -395,6 +390,13 @@ void main() { tiltDataTest = tiltData; gesturesTypeTest = gesturesType; }, + child: const TiltBaseContainer( + child: SizedBox( + width: 100, + height: 100, + child: Text('Tilt'), + ), + ), ), const SizedBox(key: Key('scroll'), height: 100, width: 100), const SizedBox(height: 1000), @@ -425,7 +427,7 @@ void main() { final tiltDataExpect = tiltDataTestCalculate( areaProgress: const Offset(-1.0, -1.0), tiltConfig: tiltConfigTest, - ).data; + ).toModel(); await tester.pumpWidget( TiltWidget( diff --git a/test/tilt_widget/light_shadow_mode_projector_widget/tilt_gestures_drag_test.dart b/test/tilt_widget/tilt_projector_container_widget/tilt_gestures_drag_test.dart similarity index 80% rename from test/tilt_widget/light_shadow_mode_projector_widget/tilt_gestures_drag_test.dart rename to test/tilt_widget/tilt_projector_container_widget/tilt_gestures_drag_test.dart index 3ca8706..4a8d0a1 100644 --- a/test/tilt_widget/light_shadow_mode_projector_widget/tilt_gestures_drag_test.dart +++ b/test/tilt_widget/tilt_projector_container_widget/tilt_gestures_drag_test.dart @@ -4,7 +4,7 @@ import 'package:flutter_test/flutter_test.dart'; import 'package:flutter_tilt/flutter_tilt.dart'; void main() { - group('LightShadowMode.projector :: tilt gestures drag ::', () { + group('TiltProjectorContainer :: tilt gestures drag ::', () { testWidgets('scroll', (WidgetTester tester) async { final childFinder = find.text('Tilt'); final scrollFinder = find.byKey(const Key('scroll')); @@ -19,14 +19,16 @@ void main() { children: const [ Tilt( key: Key('tilt_widget'), - lightShadowMode: LightShadowMode.projector, - shadowConfig: ShadowConfig( - projectorScaleFrom: 2, - projectorScaleTo: 1, - projectorBlurSigmaFrom: 2, - projectorBlurSigmaTo: 1, + child: TiltProjectorContainer( + shadowConfig: ShadowProjectorConfig( + projectorScaleFrom: 2, + projectorScaleTo: 1, + projectorBlurSigmaFrom: 2, + projectorBlurSigmaTo: 1, + ), + child: + SizedBox(width: 100, height: 100, child: Text('Tilt')), ), - child: SizedBox(width: 100, height: 100, child: Text('Tilt')), ), SizedBox(key: Key('scroll'), height: 100, width: 100), SizedBox(height: 1000), diff --git a/test/tilt_widget/light_shadow_mode_projector_widget/tilt_gestures_hover_test.dart b/test/tilt_widget/tilt_projector_container_widget/tilt_gestures_hover_test.dart similarity index 95% rename from test/tilt_widget/light_shadow_mode_projector_widget/tilt_gestures_hover_test.dart rename to test/tilt_widget/tilt_projector_container_widget/tilt_gestures_hover_test.dart index cb1c836..41cf1da 100644 --- a/test/tilt_widget/light_shadow_mode_projector_widget/tilt_gestures_hover_test.dart +++ b/test/tilt_widget/tilt_projector_container_widget/tilt_gestures_hover_test.dart @@ -6,7 +6,7 @@ import 'package:flutter_tilt/src/internal/tilt_data.dart'; import 'tilt_widget_projector.dart'; void main() { - group('LightShadowMode.projector :: tilt gestures hover ::', () { + group('TiltProjectorContainer :: tilt gestures hover ::', () { const tiltConfig = TiltConfig(); TiltData tiltDataTestCalculate(Offset areaProgress) => TiltData( isInit: true, @@ -19,7 +19,7 @@ void main() { final tiltWidgetFinder = find.byKey(const Key('tilt_widget')); final childFinder = find.text('Tilt'); final testPointer = TestPointer(1, PointerDeviceKind.mouse); - final leaveTiltDataExpect = tiltDataTestCalculate(Offset.zero).data; + final leaveTiltDataExpect = tiltDataTestCalculate(Offset.zero).toModel(); testWidgets('gestures move leave', (WidgetTester tester) async { TiltDataModel? moveTiltDataTest; @@ -63,7 +63,8 @@ void main() { testWidgets('move top', (WidgetTester tester) async { TiltDataModel? tiltDataTest; GesturesType? gesturesTypeTest; - final tiltDataExpect = tiltDataTestCalculate(const Offset(0.0, 1.0)).data; + final tiltDataExpect = + tiltDataTestCalculate(const Offset(0.0, 1.0)).toModel(); /// 回调赋值 await tester.pumpWidget( @@ -106,7 +107,7 @@ void main() { TiltDataModel? tiltDataTest; GesturesType? gesturesTypeTest; final tiltDataExpect = - tiltDataTestCalculate(const Offset(0.0, -0.8)).data; + tiltDataTestCalculate(const Offset(0.0, -0.8)).toModel(); /// 回调赋值 await tester.pumpWidget( @@ -148,7 +149,8 @@ void main() { testWidgets('move left', (WidgetTester tester) async { TiltDataModel? tiltDataTest; GesturesType? gesturesTypeTest; - final tiltDataExpect = tiltDataTestCalculate(const Offset(1.0, 0.0)).data; + final tiltDataExpect = + tiltDataTestCalculate(const Offset(1.0, 0.0)).toModel(); /// 回调赋值 await tester.pumpWidget( @@ -191,7 +193,7 @@ void main() { TiltDataModel? tiltDataTest; GesturesType? gesturesTypeTest; final tiltDataExpect = - tiltDataTestCalculate(const Offset(-0.8, 0.0)).data; + tiltDataTestCalculate(const Offset(-0.8, 0.0)).toModel(); /// 回调赋值 await tester.pumpWidget( @@ -233,7 +235,8 @@ void main() { testWidgets('move top left', (WidgetTester tester) async { TiltDataModel? tiltDataTest; GesturesType? gesturesTypeTest; - final tiltDataExpect = tiltDataTestCalculate(const Offset(1.0, 1.0)).data; + final tiltDataExpect = + tiltDataTestCalculate(const Offset(1.0, 1.0)).toModel(); /// 回调赋值 await tester.pumpWidget( @@ -276,7 +279,7 @@ void main() { TiltDataModel? tiltDataTest; GesturesType? gesturesTypeTest; final tiltDataExpect = - tiltDataTestCalculate(const Offset(-0.8, 1.0)).data; + tiltDataTestCalculate(const Offset(-0.8, 1.0)).toModel(); /// 回调赋值 await tester.pumpWidget( @@ -319,7 +322,7 @@ void main() { TiltDataModel? tiltDataTest; GesturesType? gesturesTypeTest; final tiltDataExpect = - tiltDataTestCalculate(const Offset(1.0, -0.8)).data; + tiltDataTestCalculate(const Offset(1.0, -0.8)).toModel(); /// 回调赋值 await tester.pumpWidget( @@ -362,7 +365,7 @@ void main() { TiltDataModel? tiltDataTest; GesturesType? gesturesTypeTest; final tiltDataExpect = - tiltDataTestCalculate(const Offset(-0.8, -0.8)).data; + tiltDataTestCalculate(const Offset(-0.8, -0.8)).toModel(); /// 回调赋值 await tester.pumpWidget( diff --git a/test/tilt_widget/light_shadow_mode_projector_widget/tilt_gestures_touch_test.dart b/test/tilt_widget/tilt_projector_container_widget/tilt_gestures_touch_test.dart similarity index 92% rename from test/tilt_widget/light_shadow_mode_projector_widget/tilt_gestures_touch_test.dart rename to test/tilt_widget/tilt_projector_container_widget/tilt_gestures_touch_test.dart index e4c7ba0..67816aa 100644 --- a/test/tilt_widget/light_shadow_mode_projector_widget/tilt_gestures_touch_test.dart +++ b/test/tilt_widget/tilt_projector_container_widget/tilt_gestures_touch_test.dart @@ -5,7 +5,7 @@ import 'package:flutter_tilt/src/internal/tilt_data.dart'; import 'tilt_widget_projector.dart'; void main() { - group('LightShadowMode.projector :: tilt gestures touch ::', () { + group('TiltProjectorContainer :: tilt gestures touch ::', () { const tiltConfig = TiltConfig(); TiltData tiltDataTestCalculate(Offset areaProgress) => TiltData( isInit: true, @@ -25,7 +25,8 @@ void main() { TiltDataModel? leaveTiltDataTest; GesturesType? moveGesturesTypeTest; GesturesType? leaveGesturesTypeTest; - final leaveTiltDataTestExpect = tiltDataTestCalculate(Offset.zero).data; + final leaveTiltDataTestExpect = + tiltDataTestCalculate(Offset.zero).toModel(); /// 回调赋值 await tester.pumpWidget( @@ -67,7 +68,8 @@ void main() { testWidgets('move top', (WidgetTester tester) async { TiltDataModel? tiltDataTest; GesturesType? gesturesTypeTest; - final tiltDataExpect = tiltDataTestCalculate(const Offset(0.0, 1.0)).data; + final tiltDataExpect = + tiltDataTestCalculate(const Offset(0.0, 1.0)).toModel(); /// 回调赋值 await tester.pumpWidget( @@ -95,7 +97,7 @@ void main() { TiltDataModel? tiltDataTest; GesturesType? gesturesTypeTest; final tiltDataExpect = - tiltDataTestCalculate(const Offset(0.0, -1.0)).data; + tiltDataTestCalculate(const Offset(0.0, -1.0)).toModel(); /// 回调赋值 await tester.pumpWidget( @@ -122,7 +124,8 @@ void main() { testWidgets('move left', (WidgetTester tester) async { TiltDataModel? tiltDataTest; GesturesType? gesturesTypeTest; - final tiltDataExpect = tiltDataTestCalculate(const Offset(1.0, 0.0)).data; + final tiltDataExpect = + tiltDataTestCalculate(const Offset(1.0, 0.0)).toModel(); /// 回调赋值 await tester.pumpWidget( @@ -150,7 +153,7 @@ void main() { TiltDataModel? tiltDataTest; GesturesType? gesturesTypeTest; final tiltDataExpect = - tiltDataTestCalculate(const Offset(-1.0, 0.0)).data; + tiltDataTestCalculate(const Offset(-1.0, 0.0)).toModel(); /// 回调赋值 await tester.pumpWidget( @@ -177,7 +180,8 @@ void main() { testWidgets('move top left', (WidgetTester tester) async { TiltDataModel? tiltDataTest; GesturesType? gesturesTypeTest; - final tiltDataExpect = tiltDataTestCalculate(const Offset(1.0, 1.0)).data; + final tiltDataExpect = + tiltDataTestCalculate(const Offset(1.0, 1.0)).toModel(); /// 回调赋值 await tester.pumpWidget( @@ -205,7 +209,7 @@ void main() { TiltDataModel? tiltDataTest; GesturesType? gesturesTypeTest; final tiltDataExpect = - tiltDataTestCalculate(const Offset(-1.0, 1.0)).data; + tiltDataTestCalculate(const Offset(-1.0, 1.0)).toModel(); /// 回调赋值 await tester.pumpWidget( @@ -233,7 +237,7 @@ void main() { TiltDataModel? tiltDataTest; GesturesType? gesturesTypeTest; final tiltDataExpect = - tiltDataTestCalculate(const Offset(1.0, -1.0)).data; + tiltDataTestCalculate(const Offset(1.0, -1.0)).toModel(); /// 回调赋值 await tester.pumpWidget( @@ -261,7 +265,7 @@ void main() { TiltDataModel? tiltDataTest; GesturesType? gesturesTypeTest; final tiltDataExpect = - tiltDataTestCalculate(const Offset(-1.0, -1.0)).data; + tiltDataTestCalculate(const Offset(-1.0, -1.0)).toModel(); /// 回调赋值 await tester.pumpWidget( @@ -291,12 +295,13 @@ void main() { TiltDataModel? leaveTiltDataTest; GesturesType? moveGesturesTypeTest; GesturesType? leaveGesturesTypeTest; - final leaveTiltDataTestExpect = tiltDataTestCalculate(Offset.zero).data; + final leaveTiltDataTestExpect = + tiltDataTestCalculate(Offset.zero).toModel(); /// 回调赋值 await tester.pumpWidget( TiltWidgetProjector( - shadowConfig: const ShadowConfig(disable: true), + shadowConfig: const ShadowProjectorConfig(disable: true), onGestureMove: (TiltDataModel tiltData, GesturesType gesturesType) { moveTiltDataTest = tiltData; moveGesturesTypeTest = gesturesType; diff --git a/test/tilt_widget/light_shadow_mode_projector_widget/tilt_stream_controller_test.dart b/test/tilt_widget/tilt_projector_container_widget/tilt_stream_controller_test.dart similarity index 99% rename from test/tilt_widget/light_shadow_mode_projector_widget/tilt_stream_controller_test.dart rename to test/tilt_widget/tilt_projector_container_widget/tilt_stream_controller_test.dart index f84e32b..c5db498 100644 --- a/test/tilt_widget/light_shadow_mode_projector_widget/tilt_stream_controller_test.dart +++ b/test/tilt_widget/tilt_projector_container_widget/tilt_stream_controller_test.dart @@ -6,7 +6,7 @@ import 'tilt_widget_projector.dart'; void main() { final childFinder = find.text('Tilt'); - group('LightShadowMode.projector :: tilt TiltStreamController ::', () { + group('TiltProjectorContainer :: tilt TiltStreamController ::', () { testWidgets('stream listen', (WidgetTester tester) async { var tiltStreamModelTest = const TiltStreamModel( position: Offset(1, 1), diff --git a/test/tilt_widget/light_shadow_mode_projector_widget/tilt_widget_projector.dart b/test/tilt_widget/tilt_projector_container_widget/tilt_widget_projector.dart similarity index 69% rename from test/tilt_widget/light_shadow_mode_projector_widget/tilt_widget_projector.dart rename to test/tilt_widget/tilt_projector_container_widget/tilt_widget_projector.dart index fba5e60..9e1bb69 100644 --- a/test/tilt_widget/light_shadow_mode_projector_widget/tilt_widget_projector.dart +++ b/test/tilt_widget/tilt_projector_container_widget/tilt_widget_projector.dart @@ -14,9 +14,8 @@ class TiltWidgetProjector extends StatelessWidget { this.borderRadius, this.clipBehavior = Clip.antiAlias, this.tiltConfig = const TiltConfig(), - this.lightShadowMode = LightShadowMode.projector, - this.lightConfig = const LightConfig(), - this.shadowConfig = const ShadowConfig(), + this.lightConfig = const LightProjectorConfig(), + this.shadowConfig = const ShadowProjectorConfig(), this.onGestureMove, this.onGestureLeave, }); @@ -29,9 +28,8 @@ class TiltWidgetProjector extends StatelessWidget { final BorderRadiusGeometry? borderRadius; final Clip clipBehavior; final TiltConfig tiltConfig; - final LightShadowMode lightShadowMode; - final LightConfig lightConfig; - final ShadowConfig shadowConfig; + final LightProjectorConfig lightConfig; + final ShadowProjectorConfig shadowConfig; final void Function(TiltDataModel, GesturesType)? onGestureMove; final void Function(TiltDataModel, GesturesType)? onGestureLeave; @@ -42,23 +40,24 @@ class TiltWidgetProjector extends StatelessWidget { key: const Key('tilt_scaffold'), body: Tilt( key: const Key('tilt_widget'), - childLayout: childLayout, tiltStreamController: tiltStreamController, disable: disable, fps: fps, - border: border, - borderRadius: borderRadius, - clipBehavior: clipBehavior, - lightShadowMode: lightShadowMode, tiltConfig: tiltConfig.copyWith(enableGestureSensors: false), - lightConfig: lightConfig, - shadowConfig: shadowConfig, onGestureMove: onGestureMove, onGestureLeave: onGestureLeave, - child: const SizedBox( - width: 10, - height: 10, - child: Text('Tilt'), + child: TiltProjectorContainer( + childLayout: childLayout, + border: border, + borderRadius: borderRadius, + clipBehavior: clipBehavior, + lightConfig: lightConfig, + shadowConfig: shadowConfig, + child: const SizedBox( + width: 10, + height: 10, + child: Text('Tilt'), + ), ), ), ), diff --git a/test/tilt_widget/tilt_widget.dart b/test/tilt_widget/tilt_widget.dart index 4a60d36..266cbfc 100644 --- a/test/tilt_widget/tilt_widget.dart +++ b/test/tilt_widget/tilt_widget.dart @@ -14,9 +14,8 @@ class TiltWidget extends StatelessWidget { this.borderRadius, this.clipBehavior = Clip.antiAlias, this.tiltConfig = const TiltConfig(), - this.lightShadowMode = LightShadowMode.base, this.lightConfig = const LightConfig(), - this.shadowConfig = const ShadowConfig(), + this.shadowConfig = const ShadowBaseConfig(), this.onGestureMove, this.onGestureLeave, }); @@ -29,9 +28,8 @@ class TiltWidget extends StatelessWidget { final BorderRadiusGeometry? borderRadius; final Clip clipBehavior; final TiltConfig tiltConfig; - final LightShadowMode lightShadowMode; final LightConfig lightConfig; - final ShadowConfig shadowConfig; + final ShadowBaseConfig shadowConfig; final void Function(TiltDataModel, GesturesType)? onGestureMove; final void Function(TiltDataModel, GesturesType)? onGestureLeave; @@ -42,23 +40,24 @@ class TiltWidget extends StatelessWidget { key: const Key('tilt_scaffold'), body: Tilt( key: const Key('tilt_widget'), - childLayout: childLayout, tiltStreamController: tiltStreamController, disable: disable, fps: fps, - border: border, - borderRadius: borderRadius, - clipBehavior: clipBehavior, - lightShadowMode: lightShadowMode, tiltConfig: tiltConfig.copyWith(enableGestureSensors: false), - lightConfig: lightConfig, - shadowConfig: shadowConfig, onGestureMove: onGestureMove, onGestureLeave: onGestureLeave, - child: const SizedBox( - width: 10, - height: 10, - child: Text('Tilt', key: Key('child')), + child: TiltBaseContainer( + childLayout: childLayout, + border: border, + borderRadius: borderRadius, + clipBehavior: clipBehavior, + lightConfig: lightConfig, + shadowConfig: shadowConfig, + child: const SizedBox( + width: 10, + height: 10, + child: Text('Tilt', key: Key('child')), + ), ), ), ), diff --git a/test/tilt_widget/tilt_widget_test.dart b/test/tilt_widget/tilt_widget_test.dart index 4cf0c44..8f53b9d 100644 --- a/test/tilt_widget/tilt_widget_test.dart +++ b/test/tilt_widget/tilt_widget_test.dart @@ -30,11 +30,6 @@ void main() { Tilt( key: const Key('tilt_widget'), disable: true, - child: const SizedBox( - width: 100, - height: 100, - child: Text('Tilt'), - ), onGestureMove: ( TiltDataModel tiltData, GesturesType gesturesType, @@ -42,6 +37,13 @@ void main() { tiltDataTest = tiltData; gesturesTypeTest = gesturesType; }, + child: const TiltBaseContainer( + child: SizedBox( + width: 100, + height: 100, + child: Text('Tilt'), + ), + ), ), const SizedBox(key: Key('scroll'), height: 100, width: 100), const SizedBox(height: 1000), From ef0861b3cb8c930b4eb4329050bb3921af6a17b4 Mon Sep 17 00:00:00 2001 From: Amos Date: Wed, 15 Apr 2026 17:36:32 +0800 Subject: [PATCH 02/10] test: refactor and split Tilt widget tests --- lib/src/tilt.dart | 6 + .../widgets/core/tilt_animated_builder.dart | 2 +- test/config/tilt_light_config_test.dart | 60 ++++++ .../shared_widgets}/tilt_parallax_widget.dart | 0 test/tilt/shared_widgets/tilt_widget.dart | 186 ++++++++++++++++++ test/tilt/tilt_base_widget_test.dart | 42 ++++ .../tilt_config_tilt_test.dart | 4 +- .../tilt_parallax_test.dart | 67 +------ test/tilt/tilt_projector_widget_test.dart | 48 +++++ .../tilt_widget_test.dart | 34 +--- test/tilt_widget/tilt_widget.dart | 66 ------- .../tilt_gestures_drag_test.dart | 0 .../tilt_gestures_hover_test.dart | 20 +- .../tilt_gestures_touch_test.dart | 24 +-- .../tilt_stream_controller_test.dart | 8 +- .../tilt_parallax_container_test.dart | 36 ++++ .../tilt_gestures_drag_test.dart | 0 .../tilt_gestures_hover_test.dart | 20 +- .../tilt_gestures_touch_test.dart | 24 +-- .../tilt_stream_controller_test.dart | 10 +- .../core/tilt_animated_builder_test.dart | 108 ++++++++++ .../tilt_base_container_widget.dart} | 4 +- .../tilt_parallax_container_widget.dart | 107 ++++++++++ .../tilt_projector_container_widget.dart} | 4 +- 24 files changed, 659 insertions(+), 221 deletions(-) rename test/{tilt_parallax_widget => tilt/shared_widgets}/tilt_parallax_widget.dart (100%) create mode 100644 test/tilt/shared_widgets/tilt_widget.dart create mode 100644 test/tilt/tilt_base_widget_test.dart rename test/{tilt_widget => tilt}/tilt_config_tilt_test.dart (99%) rename test/{tilt_parallax_widget => tilt}/tilt_parallax_test.dart (50%) create mode 100644 test/tilt/tilt_projector_widget_test.dart rename test/{tilt_widget => tilt}/tilt_widget_test.dart (88%) delete mode 100644 test/tilt_widget/tilt_widget.dart rename test/{tilt_widget/tilt_base_container_widget => widgets/containers/tilt_base_container}/tilt_gestures_drag_test.dart (100%) rename test/{tilt_widget/tilt_base_container_widget => widgets/containers/tilt_base_container}/tilt_gestures_hover_test.dart (97%) rename test/{tilt_widget/tilt_base_container_widget => widgets/containers/tilt_base_container}/tilt_gestures_touch_test.dart (96%) rename test/{tilt_widget/tilt_base_container_widget => widgets/containers/tilt_base_container}/tilt_stream_controller_test.dart (98%) create mode 100644 test/widgets/containers/tilt_parallax_container/tilt_parallax_container_test.dart rename test/{tilt_widget/tilt_projector_container_widget => widgets/containers/tilt_projector_container}/tilt_gestures_drag_test.dart (100%) rename test/{tilt_widget/tilt_projector_container_widget => widgets/containers/tilt_projector_container}/tilt_gestures_hover_test.dart (97%) rename test/{tilt_widget/tilt_projector_container_widget => widgets/containers/tilt_projector_container}/tilt_gestures_touch_test.dart (95%) rename test/{tilt_widget/tilt_projector_container_widget => widgets/containers/tilt_projector_container}/tilt_stream_controller_test.dart (98%) create mode 100644 test/widgets/core/tilt_animated_builder_test.dart rename test/{tilt_widget/tilt_base_container_widget/tilt_widget_base.dart => widgets/shared_widgets/tilt_base_container_widget.dart} (95%) create mode 100644 test/widgets/shared_widgets/tilt_parallax_container_widget.dart rename test/{tilt_widget/tilt_projector_container_widget/tilt_widget_projector.dart => widgets/shared_widgets/tilt_projector_container_widget.dart} (95%) diff --git a/lib/src/tilt.dart b/lib/src/tilt.dart index 411846f..2e37621 100644 --- a/lib/src/tilt.dart +++ b/lib/src/tilt.dart @@ -109,6 +109,12 @@ class Tilt extends TiltWidget { super.onGestureMove, super.onGestureLeave, ChildLayout childLayout = const ChildLayout(), + @Deprecated( + '\nThe current simulated light effect is not suitable for Projector, ' + 'so it will be removed after version 4.1.0.\n' + '------\n' + '当前的模拟光照效果对 Projector 并不合适,因此将在 4.1.0 版本之后移除。\n', + ) LightProjectorConfig lightConfig = const LightProjectorConfig(), ShadowProjectorConfig shadowConfig = const ShadowProjectorConfig(), BoxBorder? border, diff --git a/lib/src/widgets/core/tilt_animated_builder.dart b/lib/src/widgets/core/tilt_animated_builder.dart index 84283dc..a7449d6 100644 --- a/lib/src/widgets/core/tilt_animated_builder.dart +++ b/lib/src/widgets/core/tilt_animated_builder.dart @@ -73,7 +73,7 @@ class TiltAnimatedBuilder extends StatelessWidget { const TiltAnimatedBuilder({ super.key, required this.builder, - required this.child, + this.child, }); /// [TiltAnimatedWidgetBuilder] diff --git a/test/config/tilt_light_config_test.dart b/test/config/tilt_light_config_test.dart index 6817bb1..f844f83 100644 --- a/test/config/tilt_light_config_test.dart +++ b/test/config/tilt_light_config_test.dart @@ -57,4 +57,64 @@ void main() { expect(lightConfigCopyWith.hashCode, lightConfigExpect.hashCode); }); }); + + group('LightProjectorConfig ::', () { + test('assert', () { + expect( + () => LightProjectorConfig( + minIntensity: 1.0, + maxIntensity: 0.0, + ), + throwsAssertionError, + ); + expect( + () => LightProjectorConfig( + minIntensity: -0.1, + maxIntensity: 1.0, + ), + throwsAssertionError, + ); + expect( + () => LightProjectorConfig( + minIntensity: 0.1, + maxIntensity: 1.1, + ), + throwsAssertionError, + ); + expect( + () => LightProjectorConfig(spreadFactor: 0.1), + throwsAssertionError, + ); + expect( + () => LightProjectorConfig(projectorScale: -1.0), + throwsAssertionError, + ); + }); + test('copyWith', () { + const lightConfig = LightProjectorConfig(); + const lightConfigExpect = LightProjectorConfig( + disable: true, + color: Color(0xFFFFFFF0), + minIntensity: 0.0, + maxIntensity: 0.5, + spreadFactor: 1.0, + projectorScale: 1.0, + direction: LightDirection.around, + enableReverse: true, + ); + final lightConfigCopyWith = lightConfig.copyWith( + disable: true, + color: const Color(0xFFFFFFF0), + minIntensity: 0.0, + maxIntensity: 0.5, + spreadFactor: 1.0, + projectorScale: 1.0, + direction: LightDirection.around, + enableReverse: true, + ); + expect(lightConfig, lightConfig.copyWith()); + expect(lightConfigCopyWith, lightConfigExpect); + expect(lightConfigCopyWith.hashCode, lightConfigExpect.hashCode); + }); + }); } diff --git a/test/tilt_parallax_widget/tilt_parallax_widget.dart b/test/tilt/shared_widgets/tilt_parallax_widget.dart similarity index 100% rename from test/tilt_parallax_widget/tilt_parallax_widget.dart rename to test/tilt/shared_widgets/tilt_parallax_widget.dart diff --git a/test/tilt/shared_widgets/tilt_widget.dart b/test/tilt/shared_widgets/tilt_widget.dart new file mode 100644 index 0000000..f331f58 --- /dev/null +++ b/test/tilt/shared_widgets/tilt_widget.dart @@ -0,0 +1,186 @@ +import 'dart:async'; + +import 'package:flutter/material.dart'; +import 'package:flutter_tilt/flutter_tilt.dart'; + +class TiltWidget extends StatelessWidget { + const TiltWidget({ + super.key, + this.childLayout = const ChildLayout(), + this.tiltStreamController, + this.disable = false, + this.fps = 120, + this.border, + this.borderRadius, + this.clipBehavior = Clip.antiAlias, + this.tiltConfig = const TiltConfig(), + this.lightConfig = const LightConfig(), + this.shadowConfig = const ShadowBaseConfig(), + this.onGestureMove, + this.onGestureLeave, + }); + + final ChildLayout childLayout; + final StreamController? tiltStreamController; + final bool disable; + final int fps; + final BoxBorder? border; + final BorderRadiusGeometry? borderRadius; + final Clip clipBehavior; + final TiltConfig tiltConfig; + final LightConfig lightConfig; + final ShadowBaseConfig shadowConfig; + final void Function(TiltDataModel, GesturesType)? onGestureMove; + final void Function(TiltDataModel, GesturesType)? onGestureLeave; + + @override + Widget build(BuildContext context) { + return MaterialApp( + home: Scaffold( + key: const Key('tilt_scaffold'), + body: Tilt( + key: const Key('tilt_widget'), + tiltStreamController: tiltStreamController, + disable: disable, + fps: fps, + tiltConfig: tiltConfig.copyWith(enableGestureSensors: false), + onGestureMove: onGestureMove, + onGestureLeave: onGestureLeave, + child: TiltBaseContainer( + childLayout: childLayout, + border: border, + borderRadius: borderRadius, + clipBehavior: clipBehavior, + lightConfig: lightConfig, + shadowConfig: shadowConfig, + child: const SizedBox( + width: 10, + height: 10, + child: Text('Tilt', key: Key('child')), + ), + ), + ), + ), + ); + } +} + +class TiltBaseWidget extends StatelessWidget { + const TiltBaseWidget({ + super.key, + this.childLayout = const ChildLayout(), + this.tiltStreamController, + this.disable = false, + this.fps = 120, + this.border, + this.borderRadius, + this.clipBehavior = Clip.antiAlias, + this.tiltConfig = const TiltConfig(), + this.lightConfig = const LightConfig(), + this.shadowConfig = const ShadowBaseConfig(), + this.onGestureMove, + this.onGestureLeave, + }); + + final ChildLayout childLayout; + final StreamController? tiltStreamController; + final bool disable; + final int fps; + final BoxBorder? border; + final BorderRadiusGeometry? borderRadius; + final Clip clipBehavior; + final TiltConfig tiltConfig; + final LightConfig lightConfig; + final ShadowBaseConfig shadowConfig; + final void Function(TiltDataModel, GesturesType)? onGestureMove; + final void Function(TiltDataModel, GesturesType)? onGestureLeave; + + @override + Widget build(BuildContext context) { + return MaterialApp( + home: Scaffold( + key: const Key('tilt_scaffold'), + body: Tilt.base( + key: const Key('tilt_widget'), + tiltStreamController: tiltStreamController, + disable: disable, + fps: fps, + tiltConfig: tiltConfig.copyWith(enableGestureSensors: false), + onGestureMove: onGestureMove, + onGestureLeave: onGestureLeave, + border: border, + borderRadius: borderRadius, + clipBehavior: clipBehavior, + lightConfig: lightConfig, + shadowConfig: shadowConfig, + childLayout: childLayout, + child: const SizedBox( + width: 10, + height: 10, + child: Text('Tilt', key: Key('child')), + ), + ), + ), + ); + } +} + +class TiltProjectorWidget extends StatelessWidget { + const TiltProjectorWidget({ + super.key, + this.childLayout = const ChildLayout(), + this.tiltStreamController, + this.disable = false, + this.fps = 120, + this.border, + this.borderRadius, + this.clipBehavior = Clip.antiAlias, + this.tiltConfig = const TiltConfig(), + this.lightConfig = const LightProjectorConfig(disable: false), + this.shadowConfig = const ShadowProjectorConfig(), + this.onGestureMove, + this.onGestureLeave, + }); + + final ChildLayout childLayout; + final StreamController? tiltStreamController; + final bool disable; + final int fps; + final BoxBorder? border; + final BorderRadiusGeometry? borderRadius; + final Clip clipBehavior; + final TiltConfig tiltConfig; + final LightProjectorConfig lightConfig; + final ShadowProjectorConfig shadowConfig; + final void Function(TiltDataModel, GesturesType)? onGestureMove; + final void Function(TiltDataModel, GesturesType)? onGestureLeave; + + @override + Widget build(BuildContext context) { + return MaterialApp( + home: Scaffold( + key: const Key('tilt_scaffold'), + body: Tilt.projector( + key: const Key('tilt_widget'), + tiltStreamController: tiltStreamController, + disable: disable, + fps: fps, + tiltConfig: tiltConfig.copyWith(enableGestureSensors: false), + onGestureMove: onGestureMove, + onGestureLeave: onGestureLeave, + border: border, + borderRadius: borderRadius, + clipBehavior: clipBehavior, + lightConfig: lightConfig, + shadowConfig: shadowConfig, + childLayout: childLayout, + child: const SizedBox( + width: 10, + height: 10, + child: Text('Tilt', key: Key('child')), + ), + ), + ), + ); + } +} diff --git a/test/tilt/tilt_base_widget_test.dart b/test/tilt/tilt_base_widget_test.dart new file mode 100644 index 0000000..16694ac --- /dev/null +++ b/test/tilt/tilt_base_widget_test.dart @@ -0,0 +1,42 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_test/flutter_test.dart'; +import 'package:flutter_tilt/flutter_tilt.dart'; +import 'shared_widgets/tilt_widget.dart'; + +void main() { + final childFinder = find.text('Tilt'); + + group('Tilt.base ::', () { + testWidgets('default', (WidgetTester tester) async { + await tester.pumpWidget(const TiltBaseWidget()); + await tester.pumpAndSettle(); + expect(childFinder, findsOneWidget); + }); + + testWidgets('ChildLayout UI layer order', (WidgetTester tester) async { + const outerKey = Key('outer'); + const innerKey = Key('inner'); + const childKey = Key('child'); + const behindKey = Key('behind'); + + Widget buildTestWidget() { + return const TiltBaseWidget( + childLayout: ChildLayout( + outer: [Text('outer', key: outerKey)], + inner: [Text('inner', key: innerKey)], + behind: [Text('behind', key: behindKey)], + ), + ); + } + + await tester.pumpWidget(buildTestWidget()); + await tester.pumpAndSettle(); + final allTextKeys = tester + .widgetList(find.byType(Text)) + .map((value) => value.key) + .toList() + .reversed; + expect(allTextKeys, [outerKey, innerKey, childKey, behindKey]); + }); + }); +} diff --git a/test/tilt_widget/tilt_config_tilt_test.dart b/test/tilt/tilt_config_tilt_test.dart similarity index 99% rename from test/tilt_widget/tilt_config_tilt_test.dart rename to test/tilt/tilt_config_tilt_test.dart index 2295ee6..57332f6 100644 --- a/test/tilt_widget/tilt_config_tilt_test.dart +++ b/test/tilt/tilt_config_tilt_test.dart @@ -4,7 +4,7 @@ import 'package:flutter/material.dart'; import 'package:flutter_test/flutter_test.dart'; import 'package:flutter_tilt/flutter_tilt.dart'; import 'package:flutter_tilt/src/internal/tilt_data.dart'; -import 'tilt_widget.dart'; +import 'shared_widgets/tilt_widget.dart'; void main() { const tiltConfig = TiltConfig(); @@ -24,7 +24,7 @@ void main() { final childFinder = find.text('Tilt'); final testPointer = TestPointer(1, PointerDeviceKind.mouse); - group('tilt TiltConfig ::', () { + group('Tilt.tiltConfig ::', () { testWidgets('disable true', (WidgetTester tester) async { TiltDataModel? tiltDataTest; GesturesType? gesturesTypeTest; diff --git a/test/tilt_parallax_widget/tilt_parallax_test.dart b/test/tilt/tilt_parallax_test.dart similarity index 50% rename from test/tilt_parallax_widget/tilt_parallax_test.dart rename to test/tilt/tilt_parallax_test.dart index 9aab630..3c29fc5 100644 --- a/test/tilt_parallax_widget/tilt_parallax_test.dart +++ b/test/tilt/tilt_parallax_test.dart @@ -1,10 +1,10 @@ import 'package:flutter/widgets.dart'; import 'package:flutter_test/flutter_test.dart'; import 'package:flutter_tilt/flutter_tilt.dart'; -import 'tilt_parallax_widget.dart'; +import 'shared_widgets/tilt_parallax_widget.dart'; void main() { - group('tilt parallax ::', () { + group('TiltParallax ::', () { testWidgets('default', (WidgetTester tester) async { final outerFinder = find.text('outer'); final innerFinder = find.text('inner'); @@ -67,68 +67,5 @@ void main() { const Offset(24.061335345163766, 14.999999999999998), ); }); - testWidgets('TiltConfig enableReverse true', (WidgetTester tester) async { - final outerFinder = find.text('outer'); - final innerFinder = find.text('inner'); - final behindFinder = find.text('behind'); - - await tester.pumpWidget( - const TiltParallaxWidget( - tiltConfig: TiltConfig(initial: Offset(1, 0), enableReverse: true), - childLayout: ChildLayout( - outer: [ - Positioned( - child: TiltParallax( - child: SizedBox( - width: 10, - height: 10, - child: Text('outer'), - ), - ), - ), - ], - inner: [ - Positioned( - child: TiltParallax( - child: SizedBox( - width: 10, - height: 10, - child: Text('inner'), - ), - ), - ), - ], - behind: [ - Positioned( - child: TiltParallax( - child: SizedBox( - width: 10, - height: 10, - child: Text('behind'), - ), - ), - ), - ], - ), - ), - ); - - await tester.pumpAndSettle(); - final outerLocation = tester.getCenter(outerFinder); - final innerLocation = tester.getCenter(innerFinder); - final behindLocation = tester.getCenter(behindFinder); - expect( - outerLocation, - const Offset(5.938664654836236, 14.999999999999995), - ); - expect( - innerLocation, - const Offset(5.938664654836236, 14.999999999999995), - ); - expect( - behindLocation, - const Offset(5.938664654836236, 14.999999999999995), - ); - }); }); } diff --git a/test/tilt/tilt_projector_widget_test.dart b/test/tilt/tilt_projector_widget_test.dart new file mode 100644 index 0000000..09bf140 --- /dev/null +++ b/test/tilt/tilt_projector_widget_test.dart @@ -0,0 +1,48 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_test/flutter_test.dart'; +import 'package:flutter_tilt/flutter_tilt.dart'; +import 'shared_widgets/tilt_widget.dart'; + +void main() { + final childFinder = find.text('Tilt'); + + group('Tilt.projector ::', () { + testWidgets('default', (WidgetTester tester) async { + await tester.pumpWidget(const TiltProjectorWidget()); + await tester.pumpAndSettle(); + expect(childFinder, findsNWidgets(2)); + }); + + testWidgets('ChildLayout UI layer order', (WidgetTester tester) async { + const outerKey = Key('outer'); + const innerKey = Key('inner'); + const childKey = Key('child'); + const behindKey = Key('behind'); + const layerOrder = [outerKey, innerKey, childKey, behindKey]; + + Widget buildTestWidget() { + return const TiltProjectorWidget( + childLayout: ChildLayout( + outer: [Text('outer', key: outerKey)], + inner: [Text('inner', key: innerKey)], + behind: [Text('behind', key: behindKey)], + ), + ); + } + + await tester.pumpWidget(buildTestWidget()); + await tester.pumpAndSettle(); + final allTextKeys = tester + .widgetList(find.byType(Text)) + .map((value) => value.key) + .toList() + .reversed; + expect(allTextKeys, [ + ...layerOrder, + + /// 投影层 + ...layerOrder, + ]); + }); + }); +} diff --git a/test/tilt_widget/tilt_widget_test.dart b/test/tilt/tilt_widget_test.dart similarity index 88% rename from test/tilt_widget/tilt_widget_test.dart rename to test/tilt/tilt_widget_test.dart index 8f53b9d..67d551c 100644 --- a/test/tilt_widget/tilt_widget_test.dart +++ b/test/tilt/tilt_widget_test.dart @@ -3,13 +3,13 @@ import 'dart:async' show StreamController; import 'package:flutter/material.dart'; import 'package:flutter_test/flutter_test.dart'; import 'package:flutter_tilt/flutter_tilt.dart'; -import 'tilt_widget.dart'; +import 'shared_widgets/tilt_widget.dart'; void main() { final tiltWidgetFinder = find.byKey(const Key('tilt_widget')); final childFinder = find.text('Tilt'); - group('tilt config ::', () { + group('Tilt config ::', () { testWidgets('default', (WidgetTester tester) async { await tester.pumpWidget(const TiltWidget()); await tester.pumpAndSettle(); @@ -93,7 +93,7 @@ void main() { }); }); - group('didUpdateWidget ::', () { + group('Tilt didUpdateWidget ::', () { testWidgets('tiltStreamController', (WidgetTester tester) async { final tiltStreamController1 = StreamController.broadcast(); @@ -240,32 +240,4 @@ void main() { } }); }); - - group('ChildLayout ::', () { - testWidgets('UI layer order', (WidgetTester tester) async { - const outerKey = Key('outer'); - const innerKey = Key('inner'); - const childKey = Key('child'); - const behindKey = Key('behind'); - - Widget buildTestWidget() { - return const TiltWidget( - childLayout: ChildLayout( - outer: [Text('outer', key: outerKey)], - inner: [Text('inner', key: innerKey)], - behind: [Text('behind', key: behindKey)], - ), - ); - } - - await tester.pumpWidget(buildTestWidget()); - await tester.pumpAndSettle(); - final allTextKeys = tester - .widgetList(find.byType(Text)) - .map((value) => value.key) - .toList() - .reversed; - expect(allTextKeys, [outerKey, innerKey, childKey, behindKey]); - }); - }); } diff --git a/test/tilt_widget/tilt_widget.dart b/test/tilt_widget/tilt_widget.dart deleted file mode 100644 index 266cbfc..0000000 --- a/test/tilt_widget/tilt_widget.dart +++ /dev/null @@ -1,66 +0,0 @@ -import 'dart:async'; - -import 'package:flutter/material.dart'; -import 'package:flutter_tilt/flutter_tilt.dart'; - -class TiltWidget extends StatelessWidget { - const TiltWidget({ - super.key, - this.childLayout = const ChildLayout(), - this.tiltStreamController, - this.disable = false, - this.fps = 120, - this.border, - this.borderRadius, - this.clipBehavior = Clip.antiAlias, - this.tiltConfig = const TiltConfig(), - this.lightConfig = const LightConfig(), - this.shadowConfig = const ShadowBaseConfig(), - this.onGestureMove, - this.onGestureLeave, - }); - - final ChildLayout childLayout; - final StreamController? tiltStreamController; - final bool disable; - final int fps; - final BoxBorder? border; - final BorderRadiusGeometry? borderRadius; - final Clip clipBehavior; - final TiltConfig tiltConfig; - final LightConfig lightConfig; - final ShadowBaseConfig shadowConfig; - final void Function(TiltDataModel, GesturesType)? onGestureMove; - final void Function(TiltDataModel, GesturesType)? onGestureLeave; - - @override - Widget build(BuildContext context) { - return MaterialApp( - home: Scaffold( - key: const Key('tilt_scaffold'), - body: Tilt( - key: const Key('tilt_widget'), - tiltStreamController: tiltStreamController, - disable: disable, - fps: fps, - tiltConfig: tiltConfig.copyWith(enableGestureSensors: false), - onGestureMove: onGestureMove, - onGestureLeave: onGestureLeave, - child: TiltBaseContainer( - childLayout: childLayout, - border: border, - borderRadius: borderRadius, - clipBehavior: clipBehavior, - lightConfig: lightConfig, - shadowConfig: shadowConfig, - child: const SizedBox( - width: 10, - height: 10, - child: Text('Tilt', key: Key('child')), - ), - ), - ), - ), - ); - } -} diff --git a/test/tilt_widget/tilt_base_container_widget/tilt_gestures_drag_test.dart b/test/widgets/containers/tilt_base_container/tilt_gestures_drag_test.dart similarity index 100% rename from test/tilt_widget/tilt_base_container_widget/tilt_gestures_drag_test.dart rename to test/widgets/containers/tilt_base_container/tilt_gestures_drag_test.dart diff --git a/test/tilt_widget/tilt_base_container_widget/tilt_gestures_hover_test.dart b/test/widgets/containers/tilt_base_container/tilt_gestures_hover_test.dart similarity index 97% rename from test/tilt_widget/tilt_base_container_widget/tilt_gestures_hover_test.dart rename to test/widgets/containers/tilt_base_container/tilt_gestures_hover_test.dart index 488201f..5b3f59e 100644 --- a/test/tilt_widget/tilt_base_container_widget/tilt_gestures_hover_test.dart +++ b/test/widgets/containers/tilt_base_container/tilt_gestures_hover_test.dart @@ -3,7 +3,7 @@ import 'package:flutter/widgets.dart'; import 'package:flutter_test/flutter_test.dart'; import 'package:flutter_tilt/flutter_tilt.dart'; import 'package:flutter_tilt/src/internal/tilt_data.dart'; -import 'tilt_widget_base.dart'; +import '../../shared_widgets/tilt_base_container_widget.dart'; void main() { group('TiltBaseContainer :: tilt gestures hover ::', () { @@ -29,7 +29,7 @@ void main() { /// 回调赋值 await tester.pumpWidget( - TiltWidgetBase( + TiltBaseContainerWidget( onGestureMove: (TiltDataModel tiltData, GesturesType gesturesType) { moveTiltDataTest = tiltData; moveGesturesTypeTest = gesturesType; @@ -68,7 +68,7 @@ void main() { /// 回调赋值 await tester.pumpWidget( - TiltWidgetBase( + TiltBaseContainerWidget( onGestureMove: (TiltDataModel tiltData, GesturesType gesturesType) { tiltDataTest = tiltData; gesturesTypeTest = gesturesType; @@ -111,7 +111,7 @@ void main() { /// 回调赋值 await tester.pumpWidget( - TiltWidgetBase( + TiltBaseContainerWidget( onGestureMove: (TiltDataModel tiltData, GesturesType gesturesType) { tiltDataTest = tiltData; gesturesTypeTest = gesturesType; @@ -154,7 +154,7 @@ void main() { /// 回调赋值 await tester.pumpWidget( - TiltWidgetBase( + TiltBaseContainerWidget( onGestureMove: (TiltDataModel tiltData, GesturesType gesturesType) { tiltDataTest = tiltData; gesturesTypeTest = gesturesType; @@ -197,7 +197,7 @@ void main() { /// 回调赋值 await tester.pumpWidget( - TiltWidgetBase( + TiltBaseContainerWidget( onGestureMove: (TiltDataModel tiltData, GesturesType gesturesType) { tiltDataTest = tiltData; gesturesTypeTest = gesturesType; @@ -240,7 +240,7 @@ void main() { /// 回调赋值 await tester.pumpWidget( - TiltWidgetBase( + TiltBaseContainerWidget( onGestureMove: (TiltDataModel tiltData, GesturesType gesturesType) { tiltDataTest = tiltData; gesturesTypeTest = gesturesType; @@ -283,7 +283,7 @@ void main() { /// 回调赋值 await tester.pumpWidget( - TiltWidgetBase( + TiltBaseContainerWidget( onGestureMove: (TiltDataModel tiltData, GesturesType gesturesType) { tiltDataTest = tiltData; gesturesTypeTest = gesturesType; @@ -326,7 +326,7 @@ void main() { /// 回调赋值 await tester.pumpWidget( - TiltWidgetBase( + TiltBaseContainerWidget( onGestureMove: (TiltDataModel tiltData, GesturesType gesturesType) { tiltDataTest = tiltData; gesturesTypeTest = gesturesType; @@ -369,7 +369,7 @@ void main() { /// 回调赋值 await tester.pumpWidget( - TiltWidgetBase( + TiltBaseContainerWidget( onGestureMove: (TiltDataModel tiltData, GesturesType gesturesType) { tiltDataTest = tiltData; gesturesTypeTest = gesturesType; diff --git a/test/tilt_widget/tilt_base_container_widget/tilt_gestures_touch_test.dart b/test/widgets/containers/tilt_base_container/tilt_gestures_touch_test.dart similarity index 96% rename from test/tilt_widget/tilt_base_container_widget/tilt_gestures_touch_test.dart rename to test/widgets/containers/tilt_base_container/tilt_gestures_touch_test.dart index 5b8a502..80c9f0c 100644 --- a/test/tilt_widget/tilt_base_container_widget/tilt_gestures_touch_test.dart +++ b/test/widgets/containers/tilt_base_container/tilt_gestures_touch_test.dart @@ -2,7 +2,7 @@ import 'package:flutter/widgets.dart'; import 'package:flutter_test/flutter_test.dart'; import 'package:flutter_tilt/flutter_tilt.dart'; import 'package:flutter_tilt/src/internal/tilt_data.dart'; -import 'tilt_widget_base.dart'; +import '../../shared_widgets/tilt_base_container_widget.dart'; void main() { group('TiltBaseContainer :: tilt gestures touch ::', () { @@ -30,7 +30,7 @@ void main() { /// 回调赋值 await tester.pumpWidget( - TiltWidgetBase( + TiltBaseContainerWidget( onGestureMove: (TiltDataModel tiltData, GesturesType gesturesType) { moveTiltDataTest = tiltData; moveGesturesTypeTest = gesturesType; @@ -55,7 +55,7 @@ void main() { expect(leaveTiltDataTest, leaveTiltDataTestExpect); }); testWidgets('onPointerCancel', (WidgetTester tester) async { - await tester.pumpWidget(const TiltWidgetBase()); + await tester.pumpWidget(const TiltBaseContainerWidget()); final location = tester.getCenter(tiltWidgetFinder); await tester.sendEventToBinding(testPointer.down(location)); @@ -73,7 +73,7 @@ void main() { /// 回调赋值 await tester.pumpWidget( - TiltWidgetBase( + TiltBaseContainerWidget( onGestureMove: (TiltDataModel tiltData, GesturesType gesturesType) { tiltDataTest = tiltData; gesturesTypeTest = gesturesType; @@ -101,7 +101,7 @@ void main() { /// 回调赋值 await tester.pumpWidget( - TiltWidgetBase( + TiltBaseContainerWidget( onGestureMove: (TiltDataModel tiltData, GesturesType gesturesType) { tiltDataTest = tiltData; gesturesTypeTest = gesturesType; @@ -129,7 +129,7 @@ void main() { /// 回调赋值 await tester.pumpWidget( - TiltWidgetBase( + TiltBaseContainerWidget( onGestureMove: (TiltDataModel tiltData, GesturesType gesturesType) { tiltDataTest = tiltData; gesturesTypeTest = gesturesType; @@ -157,7 +157,7 @@ void main() { /// 回调赋值 await tester.pumpWidget( - TiltWidgetBase( + TiltBaseContainerWidget( onGestureMove: (TiltDataModel tiltData, GesturesType gesturesType) { tiltDataTest = tiltData; gesturesTypeTest = gesturesType; @@ -185,7 +185,7 @@ void main() { /// 回调赋值 await tester.pumpWidget( - TiltWidgetBase( + TiltBaseContainerWidget( onGestureMove: (TiltDataModel tiltData, GesturesType gesturesType) { tiltDataTest = tiltData; gesturesTypeTest = gesturesType; @@ -213,7 +213,7 @@ void main() { /// 回调赋值 await tester.pumpWidget( - TiltWidgetBase( + TiltBaseContainerWidget( onGestureMove: (TiltDataModel tiltData, GesturesType gesturesType) { tiltDataTest = tiltData; gesturesTypeTest = gesturesType; @@ -241,7 +241,7 @@ void main() { /// 回调赋值 await tester.pumpWidget( - TiltWidgetBase( + TiltBaseContainerWidget( onGestureMove: (TiltDataModel tiltData, GesturesType gesturesType) { tiltDataTest = tiltData; gesturesTypeTest = gesturesType; @@ -269,7 +269,7 @@ void main() { /// 回调赋值 await tester.pumpWidget( - TiltWidgetBase( + TiltBaseContainerWidget( onGestureMove: (TiltDataModel tiltData, GesturesType gesturesType) { tiltDataTest = tiltData; gesturesTypeTest = gesturesType; @@ -300,7 +300,7 @@ void main() { /// 回调赋值 await tester.pumpWidget( - TiltWidgetBase( + TiltBaseContainerWidget( shadowConfig: const ShadowBaseConfig(disable: true), onGestureMove: (TiltDataModel tiltData, GesturesType gesturesType) { moveTiltDataTest = tiltData; diff --git a/test/tilt_widget/tilt_base_container_widget/tilt_stream_controller_test.dart b/test/widgets/containers/tilt_base_container/tilt_stream_controller_test.dart similarity index 98% rename from test/tilt_widget/tilt_base_container_widget/tilt_stream_controller_test.dart rename to test/widgets/containers/tilt_base_container/tilt_stream_controller_test.dart index 1772d48..eb2d062 100644 --- a/test/tilt_widget/tilt_base_container_widget/tilt_stream_controller_test.dart +++ b/test/widgets/containers/tilt_base_container/tilt_stream_controller_test.dart @@ -2,7 +2,7 @@ import 'dart:async'; import 'package:flutter_test/flutter_test.dart'; import 'package:flutter_tilt/flutter_tilt.dart'; -import 'tilt_widget_base.dart'; +import '../../shared_widgets/tilt_base_container_widget.dart'; void main() { final childFinder = find.text('Tilt'); @@ -23,7 +23,7 @@ void main() { final tiltStreamController = StreamController.broadcast(); await tester.pumpWidget( - TiltWidgetBase(tiltStreamController: tiltStreamController), + TiltBaseContainerWidget(tiltStreamController: tiltStreamController), ); await tester.pumpAndSettle(); expect(childFinder, findsOneWidget); @@ -64,7 +64,7 @@ void main() { final tiltStreamController = StreamController.broadcast(); await tester.pumpWidget( - TiltWidgetBase( + TiltBaseContainerWidget( tiltStreamController: tiltStreamController, tiltConfig: const TiltConfig( enableGestureTouch: false, @@ -256,7 +256,7 @@ void main() { final tiltStreamController = StreamController.broadcast(); await tester.pumpWidget( - TiltWidgetBase( + TiltBaseContainerWidget( tiltStreamController: tiltStreamController, onGestureMove: ( TiltDataModel tiltDataModel, diff --git a/test/widgets/containers/tilt_parallax_container/tilt_parallax_container_test.dart b/test/widgets/containers/tilt_parallax_container/tilt_parallax_container_test.dart new file mode 100644 index 0000000..bf460e1 --- /dev/null +++ b/test/widgets/containers/tilt_parallax_container/tilt_parallax_container_test.dart @@ -0,0 +1,36 @@ +import 'package:flutter_test/flutter_test.dart'; +import 'package:flutter_tilt/flutter_tilt.dart'; +import '../../shared_widgets/tilt_parallax_container_widget.dart'; + +void main() { + group('TiltParallaxContainer ::', () { + testWidgets('TiltConfig enableReverse true', (WidgetTester tester) async { + final outerFinder = find.text('outer'); + final innerFinder = find.text('inner'); + final behindFinder = find.text('behind'); + + await tester.pumpWidget( + const TiltParallaxContainerWidget( + tiltConfig: TiltConfig(initial: Offset(1, 0), enableReverse: true), + ), + ); + + await tester.pumpAndSettle(); + final outerLocation = tester.getCenter(outerFinder); + final innerLocation = tester.getCenter(innerFinder); + final behindLocation = tester.getCenter(behindFinder); + expect( + outerLocation, + const Offset(5.938664654836236, 14.999999999999995), + ); + expect( + innerLocation, + const Offset(5.938664654836236, 14.999999999999995), + ); + expect( + behindLocation, + const Offset(5.938664654836236, 14.999999999999995), + ); + }); + }); +} diff --git a/test/tilt_widget/tilt_projector_container_widget/tilt_gestures_drag_test.dart b/test/widgets/containers/tilt_projector_container/tilt_gestures_drag_test.dart similarity index 100% rename from test/tilt_widget/tilt_projector_container_widget/tilt_gestures_drag_test.dart rename to test/widgets/containers/tilt_projector_container/tilt_gestures_drag_test.dart diff --git a/test/tilt_widget/tilt_projector_container_widget/tilt_gestures_hover_test.dart b/test/widgets/containers/tilt_projector_container/tilt_gestures_hover_test.dart similarity index 97% rename from test/tilt_widget/tilt_projector_container_widget/tilt_gestures_hover_test.dart rename to test/widgets/containers/tilt_projector_container/tilt_gestures_hover_test.dart index 41cf1da..61cdde4 100644 --- a/test/tilt_widget/tilt_projector_container_widget/tilt_gestures_hover_test.dart +++ b/test/widgets/containers/tilt_projector_container/tilt_gestures_hover_test.dart @@ -3,7 +3,7 @@ import 'package:flutter/widgets.dart'; import 'package:flutter_test/flutter_test.dart'; import 'package:flutter_tilt/flutter_tilt.dart'; import 'package:flutter_tilt/src/internal/tilt_data.dart'; -import 'tilt_widget_projector.dart'; +import '../../shared_widgets/tilt_projector_container_widget.dart'; void main() { group('TiltProjectorContainer :: tilt gestures hover ::', () { @@ -29,7 +29,7 @@ void main() { /// 回调赋值 await tester.pumpWidget( - TiltWidgetProjector( + TiltProjectorContainerWidget( onGestureMove: (TiltDataModel tiltData, GesturesType gesturesType) { moveTiltDataTest = tiltData; moveGesturesTypeTest = gesturesType; @@ -68,7 +68,7 @@ void main() { /// 回调赋值 await tester.pumpWidget( - TiltWidgetProjector( + TiltProjectorContainerWidget( onGestureMove: (TiltDataModel tiltData, GesturesType gesturesType) { tiltDataTest = tiltData; gesturesTypeTest = gesturesType; @@ -111,7 +111,7 @@ void main() { /// 回调赋值 await tester.pumpWidget( - TiltWidgetProjector( + TiltProjectorContainerWidget( onGestureMove: (TiltDataModel tiltData, GesturesType gesturesType) { tiltDataTest = tiltData; gesturesTypeTest = gesturesType; @@ -154,7 +154,7 @@ void main() { /// 回调赋值 await tester.pumpWidget( - TiltWidgetProjector( + TiltProjectorContainerWidget( onGestureMove: (TiltDataModel tiltData, GesturesType gesturesType) { tiltDataTest = tiltData; gesturesTypeTest = gesturesType; @@ -197,7 +197,7 @@ void main() { /// 回调赋值 await tester.pumpWidget( - TiltWidgetProjector( + TiltProjectorContainerWidget( onGestureMove: (TiltDataModel tiltData, GesturesType gesturesType) { tiltDataTest = tiltData; gesturesTypeTest = gesturesType; @@ -240,7 +240,7 @@ void main() { /// 回调赋值 await tester.pumpWidget( - TiltWidgetProjector( + TiltProjectorContainerWidget( onGestureMove: (TiltDataModel tiltData, GesturesType gesturesType) { tiltDataTest = tiltData; gesturesTypeTest = gesturesType; @@ -283,7 +283,7 @@ void main() { /// 回调赋值 await tester.pumpWidget( - TiltWidgetProjector( + TiltProjectorContainerWidget( onGestureMove: (TiltDataModel tiltData, GesturesType gesturesType) { tiltDataTest = tiltData; gesturesTypeTest = gesturesType; @@ -326,7 +326,7 @@ void main() { /// 回调赋值 await tester.pumpWidget( - TiltWidgetProjector( + TiltProjectorContainerWidget( onGestureMove: (TiltDataModel tiltData, GesturesType gesturesType) { tiltDataTest = tiltData; gesturesTypeTest = gesturesType; @@ -369,7 +369,7 @@ void main() { /// 回调赋值 await tester.pumpWidget( - TiltWidgetProjector( + TiltProjectorContainerWidget( onGestureMove: (TiltDataModel tiltData, GesturesType gesturesType) { tiltDataTest = tiltData; gesturesTypeTest = gesturesType; diff --git a/test/tilt_widget/tilt_projector_container_widget/tilt_gestures_touch_test.dart b/test/widgets/containers/tilt_projector_container/tilt_gestures_touch_test.dart similarity index 95% rename from test/tilt_widget/tilt_projector_container_widget/tilt_gestures_touch_test.dart rename to test/widgets/containers/tilt_projector_container/tilt_gestures_touch_test.dart index 67816aa..c77522f 100644 --- a/test/tilt_widget/tilt_projector_container_widget/tilt_gestures_touch_test.dart +++ b/test/widgets/containers/tilt_projector_container/tilt_gestures_touch_test.dart @@ -2,7 +2,7 @@ import 'package:flutter/widgets.dart'; import 'package:flutter_test/flutter_test.dart'; import 'package:flutter_tilt/flutter_tilt.dart'; import 'package:flutter_tilt/src/internal/tilt_data.dart'; -import 'tilt_widget_projector.dart'; +import '../../shared_widgets/tilt_projector_container_widget.dart'; void main() { group('TiltProjectorContainer :: tilt gestures touch ::', () { @@ -30,7 +30,7 @@ void main() { /// 回调赋值 await tester.pumpWidget( - TiltWidgetProjector( + TiltProjectorContainerWidget( onGestureMove: (TiltDataModel tiltData, GesturesType gesturesType) { moveTiltDataTest = tiltData; moveGesturesTypeTest = gesturesType; @@ -55,7 +55,7 @@ void main() { expect(leaveTiltDataTest, leaveTiltDataTestExpect); }); testWidgets('onPointerCancel', (WidgetTester tester) async { - await tester.pumpWidget(const TiltWidgetProjector()); + await tester.pumpWidget(const TiltProjectorContainerWidget()); final location = tester.getCenter(tiltWidgetFinder); await tester.sendEventToBinding(testPointer.down(location)); @@ -73,7 +73,7 @@ void main() { /// 回调赋值 await tester.pumpWidget( - TiltWidgetProjector( + TiltProjectorContainerWidget( onGestureMove: (TiltDataModel tiltData, GesturesType gesturesType) { tiltDataTest = tiltData; gesturesTypeTest = gesturesType; @@ -101,7 +101,7 @@ void main() { /// 回调赋值 await tester.pumpWidget( - TiltWidgetProjector( + TiltProjectorContainerWidget( onGestureMove: (TiltDataModel tiltData, GesturesType gesturesType) { tiltDataTest = tiltData; gesturesTypeTest = gesturesType; @@ -129,7 +129,7 @@ void main() { /// 回调赋值 await tester.pumpWidget( - TiltWidgetProjector( + TiltProjectorContainerWidget( onGestureMove: (TiltDataModel tiltData, GesturesType gesturesType) { tiltDataTest = tiltData; gesturesTypeTest = gesturesType; @@ -157,7 +157,7 @@ void main() { /// 回调赋值 await tester.pumpWidget( - TiltWidgetProjector( + TiltProjectorContainerWidget( onGestureMove: (TiltDataModel tiltData, GesturesType gesturesType) { tiltDataTest = tiltData; gesturesTypeTest = gesturesType; @@ -185,7 +185,7 @@ void main() { /// 回调赋值 await tester.pumpWidget( - TiltWidgetProjector( + TiltProjectorContainerWidget( onGestureMove: (TiltDataModel tiltData, GesturesType gesturesType) { tiltDataTest = tiltData; gesturesTypeTest = gesturesType; @@ -213,7 +213,7 @@ void main() { /// 回调赋值 await tester.pumpWidget( - TiltWidgetProjector( + TiltProjectorContainerWidget( onGestureMove: (TiltDataModel tiltData, GesturesType gesturesType) { tiltDataTest = tiltData; gesturesTypeTest = gesturesType; @@ -241,7 +241,7 @@ void main() { /// 回调赋值 await tester.pumpWidget( - TiltWidgetProjector( + TiltProjectorContainerWidget( onGestureMove: (TiltDataModel tiltData, GesturesType gesturesType) { tiltDataTest = tiltData; gesturesTypeTest = gesturesType; @@ -269,7 +269,7 @@ void main() { /// 回调赋值 await tester.pumpWidget( - TiltWidgetProjector( + TiltProjectorContainerWidget( onGestureMove: (TiltDataModel tiltData, GesturesType gesturesType) { tiltDataTest = tiltData; gesturesTypeTest = gesturesType; @@ -300,7 +300,7 @@ void main() { /// 回调赋值 await tester.pumpWidget( - TiltWidgetProjector( + TiltProjectorContainerWidget( shadowConfig: const ShadowProjectorConfig(disable: true), onGestureMove: (TiltDataModel tiltData, GesturesType gesturesType) { moveTiltDataTest = tiltData; diff --git a/test/tilt_widget/tilt_projector_container_widget/tilt_stream_controller_test.dart b/test/widgets/containers/tilt_projector_container/tilt_stream_controller_test.dart similarity index 98% rename from test/tilt_widget/tilt_projector_container_widget/tilt_stream_controller_test.dart rename to test/widgets/containers/tilt_projector_container/tilt_stream_controller_test.dart index c5db498..664bddf 100644 --- a/test/tilt_widget/tilt_projector_container_widget/tilt_stream_controller_test.dart +++ b/test/widgets/containers/tilt_projector_container/tilt_stream_controller_test.dart @@ -2,7 +2,7 @@ import 'dart:async'; import 'package:flutter_test/flutter_test.dart'; import 'package:flutter_tilt/flutter_tilt.dart'; -import 'tilt_widget_projector.dart'; +import '../../shared_widgets/tilt_projector_container_widget.dart'; void main() { final childFinder = find.text('Tilt'); @@ -23,7 +23,9 @@ void main() { final tiltStreamController = StreamController.broadcast(); await tester.pumpWidget( - TiltWidgetProjector(tiltStreamController: tiltStreamController), + TiltProjectorContainerWidget( + tiltStreamController: tiltStreamController, + ), ); await tester.pumpAndSettle(); expect(childFinder, findsNWidgets(2)); @@ -64,7 +66,7 @@ void main() { final tiltStreamController = StreamController.broadcast(); await tester.pumpWidget( - TiltWidgetProjector( + TiltProjectorContainerWidget( tiltStreamController: tiltStreamController, tiltConfig: const TiltConfig( enableGestureTouch: false, @@ -256,7 +258,7 @@ void main() { final tiltStreamController = StreamController.broadcast(); await tester.pumpWidget( - TiltWidgetProjector( + TiltProjectorContainerWidget( tiltStreamController: tiltStreamController, onGestureMove: ( TiltDataModel tiltDataModel, diff --git a/test/widgets/core/tilt_animated_builder_test.dart b/test/widgets/core/tilt_animated_builder_test.dart new file mode 100644 index 0000000..26aef91 --- /dev/null +++ b/test/widgets/core/tilt_animated_builder_test.dart @@ -0,0 +1,108 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_test/flutter_test.dart'; +import 'package:flutter_tilt/flutter_tilt.dart'; + +void main() { + group('TiltAnimatedBuilder ::', () { + testWidgets('basic build and child usage', (WidgetTester tester) async { + const initialProgress = Offset(1.0, 1.0); + + await tester.pumpWidget( + MaterialApp( + home: Scaffold( + body: Tilt( + tiltConfig: const TiltConfig(initial: initialProgress), + child: TiltAnimatedBuilder( + builder: (context, tiltData, tiltConfig, onResize, child) { + /// 检查 builder 数据是否可用 + expect(tiltData, isNotNull); + expect(tiltData.areaProgress, initialProgress); + expect(tiltConfig, isNotNull); + expect(onResize, isA()); + + /// 渲染 child + return Column( + mainAxisSize: MainAxisSize.min, + children: [ + Expanded( + child: Text('progress: ${tiltData.areaProgress}'), + ), + if (child != null) child, + ], + ); + }, + child: const Text('static child'), + ), + ), + ), + ), + ); + + await tester.pumpAndSettle(); + + /// 检查静态 child 是否渲染 + expect(find.text('static child'), findsOneWidget); + + /// 检查 builder 渲染内容 + expect(find.byType(Column), findsOneWidget); + expect(find.textContaining('progress: $initialProgress'), findsOneWidget); + }); + + testWidgets('onResize receives new size', (WidgetTester tester) async { + Size? receivedSize; + late void Function(Size) onResizeRef; + + await tester.pumpWidget( + MaterialApp( + home: Scaffold( + body: Tilt( + child: TiltAnimatedBuilder( + builder: (context, tiltData, tiltConfig, onResize, child) { + onResizeRef = onResize; + receivedSize = Size(tiltData.width, tiltData.height); + + return Stack( + children: [ + child!, + + /// 使用 LayoutBuilder 监听尺寸变化,并调用 onResize + Positioned.fill( + child: LayoutBuilder( + builder: (context, constraints) { + WidgetsBinding.instance.addPostFrameCallback((_) { + if (context.mounted) { + onResize(constraints.biggest); + } + }); + return const SizedBox(); + }, + ), + ), + ], + ); + }, + child: const SizedBox(width: 10, height: 10), + ), + ), + ), + ), + ); + + /// 初始尺寸应该是 child 的尺寸 + await tester.pumpAndSettle(); + expect(receivedSize, const Size(10, 10)); + + /// 调用 onResize 更新尺寸 + const testSize = Size(123, 456); + onResizeRef(testSize); + + /// 等待 1 帧以确保 onResize 被处理 + await tester.pump(); + expect(receivedSize, testSize); + + /// 再等待 1 帧将尺寸还原为 LayoutBuilder 监听的尺寸 + await tester.pump(); + expect(receivedSize, const Size(10, 10)); + }); + }); +} diff --git a/test/tilt_widget/tilt_base_container_widget/tilt_widget_base.dart b/test/widgets/shared_widgets/tilt_base_container_widget.dart similarity index 95% rename from test/tilt_widget/tilt_base_container_widget/tilt_widget_base.dart rename to test/widgets/shared_widgets/tilt_base_container_widget.dart index a723be3..764670d 100644 --- a/test/tilt_widget/tilt_base_container_widget/tilt_widget_base.dart +++ b/test/widgets/shared_widgets/tilt_base_container_widget.dart @@ -3,8 +3,8 @@ import 'dart:async'; import 'package:flutter/material.dart'; import 'package:flutter_tilt/flutter_tilt.dart'; -class TiltWidgetBase extends StatelessWidget { - const TiltWidgetBase({ +class TiltBaseContainerWidget extends StatelessWidget { + const TiltBaseContainerWidget({ super.key, this.childLayout = const ChildLayout(), this.tiltStreamController, diff --git a/test/widgets/shared_widgets/tilt_parallax_container_widget.dart b/test/widgets/shared_widgets/tilt_parallax_container_widget.dart new file mode 100644 index 0000000..aac98d7 --- /dev/null +++ b/test/widgets/shared_widgets/tilt_parallax_container_widget.dart @@ -0,0 +1,107 @@ +import 'dart:async'; + +import 'package:flutter/material.dart'; +import 'package:flutter_tilt/flutter_tilt.dart'; + +class TiltParallaxContainerWidget extends StatelessWidget { + const TiltParallaxContainerWidget({ + super.key, + this.childLayout, + this.tiltStreamController, + this.disable = false, + this.fps = 60, + this.border, + this.borderRadius, + this.clipBehavior = Clip.antiAlias, + this.tiltConfig = const TiltConfig(), + this.lightConfig = const LightConfig(), + this.shadowConfig = const ShadowBaseConfig(), + this.onGestureMove, + this.onGestureLeave, + }); + + final ChildLayout? childLayout; + final StreamController? tiltStreamController; + final bool disable; + final int fps; + final BoxBorder? border; + final BorderRadiusGeometry? borderRadius; + final Clip clipBehavior; + final TiltConfig tiltConfig; + final LightConfig lightConfig; + final ShadowBaseConfig shadowConfig; + final void Function(TiltDataModel, GesturesType)? onGestureMove; + final void Function(TiltDataModel, GesturesType)? onGestureLeave; + + @override + Widget build(BuildContext context) { + return MaterialApp( + home: Scaffold( + key: const Key('tilt_scaffold'), + body: SizedBox( + width: 30, + height: 30, + child: Center( + child: Tilt( + key: const Key('tilt_widget'), + tiltStreamController: tiltStreamController, + disable: disable, + fps: fps, + tiltConfig: tiltConfig.copyWith(enableGestureSensors: false), + onGestureMove: onGestureMove, + onGestureLeave: onGestureLeave, + child: TiltBaseContainer( + border: border, + borderRadius: borderRadius, + clipBehavior: clipBehavior, + lightConfig: lightConfig, + shadowConfig: shadowConfig, + childLayout: childLayout ?? + const ChildLayout( + outer: [ + Positioned( + child: TiltParallax( + child: SizedBox( + width: 10, + height: 10, + child: Text('outer'), + ), + ), + ), + ], + inner: [ + Positioned( + child: TiltParallax( + child: SizedBox( + width: 10, + height: 10, + child: Text('inner'), + ), + ), + ), + ], + behind: [ + Positioned( + child: TiltParallax( + child: SizedBox( + width: 10, + height: 10, + child: Text('behind'), + ), + ), + ), + ], + ), + child: const SizedBox( + width: 10, + height: 10, + child: Text('Tilt'), + ), + ), + ), + ), + ), + ), + ); + } +} diff --git a/test/tilt_widget/tilt_projector_container_widget/tilt_widget_projector.dart b/test/widgets/shared_widgets/tilt_projector_container_widget.dart similarity index 95% rename from test/tilt_widget/tilt_projector_container_widget/tilt_widget_projector.dart rename to test/widgets/shared_widgets/tilt_projector_container_widget.dart index 9e1bb69..a3612f5 100644 --- a/test/tilt_widget/tilt_projector_container_widget/tilt_widget_projector.dart +++ b/test/widgets/shared_widgets/tilt_projector_container_widget.dart @@ -3,8 +3,8 @@ import 'dart:async'; import 'package:flutter/material.dart'; import 'package:flutter_tilt/flutter_tilt.dart'; -class TiltWidgetProjector extends StatelessWidget { - const TiltWidgetProjector({ +class TiltProjectorContainerWidget extends StatelessWidget { + const TiltProjectorContainerWidget({ super.key, this.childLayout = const ChildLayout(), this.tiltStreamController, From 9c0710f989bdc066ba226b92909423e3eeb61c8c Mon Sep 17 00:00:00 2001 From: Amos Date: Wed, 15 Apr 2026 21:20:46 +0800 Subject: [PATCH 03/10] refactor: replace LayoutBuilder with WidgetSizeGetter for auto size --- lib/src/internal/provider/tilt_provider.dart | 4 - .../containers/tilt_base_container.dart | 12 +- .../containers/tilt_container_utils.dart | 3 + .../containers/tilt_parallax_container.dart | 2 +- .../containers/tilt_projector_container.dart | 12 +- .../widgets/core/tilt_animated_builder.dart | 10 +- lib/src/widgets/core/tilt_widget.dart | 6 +- lib/src/widgets/core/widget_size_getter.dart | 79 ++++++++++++++ .../tilt_tween_animation_mixin_test.dart | 7 +- .../core/tilt_animated_builder_test.dart | 103 +++++------------- .../widgets/core/widget_size_getter_test.dart | 36 ++++++ .../tilt_parallax_container_widget.dart | 10 +- 12 files changed, 172 insertions(+), 112 deletions(-) create mode 100644 lib/src/widgets/core/widget_size_getter.dart create mode 100644 test/widgets/core/widget_size_getter_test.dart diff --git a/lib/src/internal/provider/tilt_provider.dart b/lib/src/internal/provider/tilt_provider.dart index e253af7..cae7044 100644 --- a/lib/src/internal/provider/tilt_provider.dart +++ b/lib/src/internal/provider/tilt_provider.dart @@ -16,7 +16,6 @@ class TiltProvider extends InheritedWidget { required this.isMove, required this.currentGesturesType, required this.tiltConfig, - required this.onResize, }); /// 是否初始化 @@ -40,9 +39,6 @@ class TiltProvider extends InheritedWidget { /// 倾斜配置 final TiltConfig tiltConfig; - /// 调整尺寸 - final void Function(Size) onResize; - static TiltProvider of(BuildContext context) { final tiltProvider = context.dependOnInheritedWidgetOfExactType(); diff --git a/lib/src/widgets/containers/tilt_base_container.dart b/lib/src/widgets/containers/tilt_base_container.dart index 90deda8..8681a81 100644 --- a/lib/src/widgets/containers/tilt_base_container.dart +++ b/lib/src/widgets/containers/tilt_base_container.dart @@ -6,7 +6,6 @@ import '../../config/tilt_shadow_config.dart'; import '../core/tilt_animated_builder.dart'; import '../effects/tilt_light.dart'; import '../effects/tilt_shadow.dart'; -import 'tilt_container_utils.dart'; class TiltBaseContainer extends StatelessWidget { /// {@template tilt.TiltBaseContainer.desc.en} @@ -102,7 +101,7 @@ class TiltBaseContainer extends StatelessWidget { @override Widget build(BuildContext context) { return TiltAnimatedBuilder( - builder: (context, tiltData, tiltConfig, onResize, child) { + builder: (context, tiltData, tiltConfig, child) { return Transform( alignment: AlignmentDirectional.center, filterQuality: filterQuality, @@ -115,7 +114,6 @@ class TiltBaseContainer extends StatelessWidget { width: tiltData.width, height: tiltData.height, areaProgress: tiltData.areaProgress, - onResize: onResize, ), ), ); @@ -129,7 +127,6 @@ class TiltBaseContainer extends StatelessWidget { required double width, required double height, required Offset areaProgress, - required void Function(Size) onResize, }) { return [ /// behind child @@ -168,10 +165,11 @@ class TiltBaseContainer extends StatelessWidget { lightConfig: lightConfig, ), + /// TODO: 已被 [WidgetSizeGetter] 替代,后续删除 /// resize - Positioned.fill( - child: TiltContainerUtils.buildWidgetResize(onResize), - ), + // Positioned.fill( + // child: TiltContainerUtils.buildWidgetResize(onResize), + // ), ], ), ), diff --git a/lib/src/widgets/containers/tilt_container_utils.dart b/lib/src/widgets/containers/tilt_container_utils.dart index 3125f44..14d6898 100644 --- a/lib/src/widgets/containers/tilt_container_utils.dart +++ b/lib/src/widgets/containers/tilt_container_utils.dart @@ -1,6 +1,9 @@ import 'package:flutter/widgets.dart'; +import '../core/widget_size_getter.dart'; abstract final class TiltContainerUtils { + /// TODO: 已被 [WidgetSizeGetter] 替代,后续删除 + /// /// 尺寸监听 Widget /// /// - [onResize] 调整尺寸回调 diff --git a/lib/src/widgets/containers/tilt_parallax_container.dart b/lib/src/widgets/containers/tilt_parallax_container.dart index 55fc562..fee4778 100644 --- a/lib/src/widgets/containers/tilt_parallax_container.dart +++ b/lib/src/widgets/containers/tilt_parallax_container.dart @@ -43,7 +43,7 @@ class TiltParallaxContainer extends StatelessWidget { @override Widget build(BuildContext context) { return TiltAnimatedBuilder( - builder: (context, tiltData, tiltConfig, onResize, child) { + builder: (context, tiltData, tiltConfig, child) { final tiltParallaxTransform = this.tiltParallaxTransform( tiltData.areaProgress, size, diff --git a/lib/src/widgets/containers/tilt_projector_container.dart b/lib/src/widgets/containers/tilt_projector_container.dart index 229a29e..6bb6efb 100644 --- a/lib/src/widgets/containers/tilt_projector_container.dart +++ b/lib/src/widgets/containers/tilt_projector_container.dart @@ -6,7 +6,6 @@ import '../../config/tilt_shadow_config.dart'; import '../core/tilt_animated_builder.dart'; import '../effects/tilt_light.dart'; import '../effects/tilt_shadow.dart'; -import 'tilt_container_utils.dart'; class TiltProjectorContainer extends StatelessWidget { /// {@template tilt.TiltProjectorContainer.desc.en} @@ -131,7 +130,7 @@ class TiltProjectorContainer extends StatelessWidget { @override Widget build(BuildContext context) { return TiltAnimatedBuilder( - builder: (context, tiltData, tiltConfig, onResize, child) { + builder: (context, tiltData, tiltConfig, child) { return Transform( alignment: AlignmentDirectional.center, filterQuality: filterQuality, @@ -144,7 +143,6 @@ class TiltProjectorContainer extends StatelessWidget { width: tiltData.width, height: tiltData.height, areaProgress: tiltData.areaProgress, - onResize: onResize, ), ), ); @@ -158,7 +156,6 @@ class TiltProjectorContainer extends StatelessWidget { required double width, required double height, required Offset areaProgress, - required void Function(Size) onResize, }) { return [ /// shadow @@ -219,10 +216,11 @@ class TiltProjectorContainer extends StatelessWidget { /// inner child ...childLayout.inner, + /// TODO: 已被 [WidgetSizeGetter] 替代,后续删除 /// resize - Positioned.fill( - child: TiltContainerUtils.buildWidgetResize(onResize), - ), + // Positioned.fill( + // child: TiltContainerUtils.buildWidgetResize(onResize), + // ), ], ), ), diff --git a/lib/src/widgets/core/tilt_animated_builder.dart b/lib/src/widgets/core/tilt_animated_builder.dart index a7449d6..c9cd2cc 100644 --- a/lib/src/widgets/core/tilt_animated_builder.dart +++ b/lib/src/widgets/core/tilt_animated_builder.dart @@ -12,8 +12,6 @@ import '../../models/tilt_data_model.dart'; /// - [context]: The build context. /// - [tiltData]: The current tilt data snapshot, updated on every animation tick. /// - [tiltConfig]: The current tilt configuration. -/// - [onResize]: Call this with the widget's [Size] whenever the widget is resized. -/// You can also use a [LayoutBuilder] inside the layout to listen for size changes and call this. /// - [child]: The pre-built subtree passed to [TiltAnimatedBuilder.child], or `null`. /// {@endtemplate} /// @@ -25,15 +23,12 @@ import '../../models/tilt_data_model.dart'; /// - [context]:当前 build context。 /// - [tiltData]:当前帧的倾斜数据快照,每帧动画更新时变化。 /// - [tiltConfig]:当前的倾斜配置。 -/// - [onResize]:当 widget 尺寸发生变化时,调用此方法并传入新的 [Size]。 -/// 也可以在布局内部使用 [LayoutBuilder] 来监听尺寸变化,并调用此方法。 /// - [child]:传入 [TiltAnimatedBuilder.child] 的预构建子树,可能为 `null`。 /// {@endtemplate} typedef TiltAnimatedWidgetBuilder = Widget Function( BuildContext context, TiltDataModel tiltData, TiltConfig tiltConfig, - void Function(Size) onResize, Widget? child, ); @@ -58,7 +53,7 @@ class TiltAnimatedBuilder extends StatelessWidget { /// ```dart /// Tilt( /// child: TiltAnimatedBuilder( - /// builder: (context, tiltData, onResize, child) { + /// builder: (context, tiltData, tiltConfig, child) { /// return Transform( /// alignment: AlignmentDirectional.center, /// transform: tiltData.transform, @@ -118,7 +113,6 @@ class TiltAnimatedBuilder extends StatelessWidget { final tiltProvider = TiltProvider.of(context); final tiltAnimationProvider = TiltAnimationProvider.of(context); final tiltTweenAnimation = tiltAnimationProvider.tiltTweenAnimation; - final onResize = tiltProvider.onResize; final tiltConfig = tiltProvider.tiltConfig; return AnimatedBuilder( @@ -132,7 +126,7 @@ class TiltAnimatedBuilder extends StatelessWidget { areaProgress: areaProgress, tiltConfig: tiltConfig, ).toModel(); - return builder(context, tiltData, tiltConfig, onResize, child); + return builder(context, tiltData, tiltConfig, child); }, child: child, ); diff --git a/lib/src/widgets/core/tilt_widget.dart b/lib/src/widgets/core/tilt_widget.dart index 5032d12..da9f930 100644 --- a/lib/src/widgets/core/tilt_widget.dart +++ b/lib/src/widgets/core/tilt_widget.dart @@ -14,6 +14,7 @@ import '../../models/tilt_stream_model.dart'; import '../../utils.dart'; import 'gestures_listener.dart'; import 'tilt_stream_builder.dart'; +import 'widget_size_getter.dart'; /// TiltWidget /// 倾斜 @@ -186,8 +187,9 @@ class _TiltWidgetState extends State { isMove: _isMove, currentGesturesType: _currentGesturesType, tiltConfig: widget.tiltConfig, - onResize: _onResize, - child: _TiltAnimationProviderWrapper(child: widget.child), + child: _TiltAnimationProviderWrapper( + child: WidgetSizeGetter(onSize: _onResize, child: widget.child), + ), ); }, ), diff --git a/lib/src/widgets/core/widget_size_getter.dart b/lib/src/widgets/core/widget_size_getter.dart new file mode 100644 index 0000000..6679cde --- /dev/null +++ b/lib/src/widgets/core/widget_size_getter.dart @@ -0,0 +1,79 @@ +import 'package:flutter/widgets.dart'; + +/// WidgetSizeGetter +class WidgetSizeGetter extends StatefulWidget { + /// Measures the actual layout size of [child] and reports updates via [onSize]. + /// + /// It reports the initial size after the first frame, + /// and keeps listening for subsequent size changes. + /// + /// ------ + /// + /// 获取 [child] 的实际布局尺寸,并通过 [onSize] 回调上报。 + /// + /// 会在首帧布局完成后上报初始尺寸, + /// 并在后续尺寸变化时持续监听和上报。 + const WidgetSizeGetter({ + super.key, + required this.child, + required this.onSize, + }); + + /// The widget whose layout size will be measured. + /// + /// ------ + /// + /// 需要测量实际布局尺寸的 child widget。 + final Widget child; + + /// Called when the actual layout size changes. + /// + /// ------ + /// + /// 当实际布局尺寸发生变化时触发的回调。 + final void Function(Size size) onSize; + + @override + State createState() => _WidgetSizeGetterState(); +} + +class _WidgetSizeGetterState extends State { + final _globalKey = GlobalKey(); + Size? _lastSize; + + @override + void initState() { + super.initState(); + _scheduleNotifySize(); + } + + void _scheduleNotifySize() { + WidgetsBinding.instance.addPostFrameCallback((_) { + if (mounted) { + _notifySize(); + } + }); + } + + void _notifySize() { + final context = _globalKey.currentContext; + if (context != null && context.mounted) { + final size = context.size; + if (size != null && size != _lastSize) { + _lastSize = size; + widget.onSize(size); + } + } + } + + @override + Widget build(BuildContext context) { + return NotificationListener( + onNotification: (_) { + _scheduleNotifySize(); + return false; + }, + child: SizeChangedLayoutNotifier(key: _globalKey, child: widget.child), + ); + } +} diff --git a/test/internal/mixin/tilt_tween_animation_mixin_test.dart b/test/internal/mixin/tilt_tween_animation_mixin_test.dart index 2933aa1..5d62487 100644 --- a/test/internal/mixin/tilt_tween_animation_mixin_test.dart +++ b/test/internal/mixin/tilt_tween_animation_mixin_test.dart @@ -217,16 +217,15 @@ class TiltTweenAnimationMixinTestWidget extends StatelessWidget { @override Widget build(BuildContext context) { - return TiltProvider( - tiltConfig: const TiltConfig(), + return const TiltProvider( + tiltConfig: TiltConfig(), isInit: true, width: 10.0, height: 10.0, areaProgress: Offset.zero, isMove: true, currentGesturesType: GesturesType.touch, - onResize: (_) {}, - child: const TiltTweenAnimationMixinTest(), + child: TiltTweenAnimationMixinTest(), ); } } diff --git a/test/widgets/core/tilt_animated_builder_test.dart b/test/widgets/core/tilt_animated_builder_test.dart index 26aef91..1dbf75d 100644 --- a/test/widgets/core/tilt_animated_builder_test.dart +++ b/test/widgets/core/tilt_animated_builder_test.dart @@ -6,32 +6,37 @@ void main() { group('TiltAnimatedBuilder ::', () { testWidgets('basic build and child usage', (WidgetTester tester) async { const initialProgress = Offset(1.0, 1.0); + Size? reportedSize; await tester.pumpWidget( MaterialApp( home: Scaffold( body: Tilt( tiltConfig: const TiltConfig(initial: initialProgress), - child: TiltAnimatedBuilder( - builder: (context, tiltData, tiltConfig, onResize, child) { - /// 检查 builder 数据是否可用 - expect(tiltData, isNotNull); - expect(tiltData.areaProgress, initialProgress); - expect(tiltConfig, isNotNull); - expect(onResize, isA()); - - /// 渲染 child - return Column( - mainAxisSize: MainAxisSize.min, - children: [ - Expanded( - child: Text('progress: ${tiltData.areaProgress}'), - ), - if (child != null) child, - ], - ); - }, - child: const Text('static child'), + child: SizedBox( + width: 100, + height: 100, + child: TiltAnimatedBuilder( + builder: (context, tiltData, tiltConfig, child) { + reportedSize = Size(tiltData.width, tiltData.height); + + /// 检查 builder 数据是否可用 + expect(tiltData, isNotNull); + expect(tiltData.areaProgress, initialProgress); + expect(tiltConfig, isNotNull); + + /// 渲染 child + return Column( + children: [ + Expanded( + child: Text('progress: ${tiltData.areaProgress}'), + ), + if (child != null) child, + ], + ); + }, + child: const Text('static child'), + ), ), ), ), @@ -46,63 +51,9 @@ void main() { /// 检查 builder 渲染内容 expect(find.byType(Column), findsOneWidget); expect(find.textContaining('progress: $initialProgress'), findsOneWidget); - }); - - testWidgets('onResize receives new size', (WidgetTester tester) async { - Size? receivedSize; - late void Function(Size) onResizeRef; - - await tester.pumpWidget( - MaterialApp( - home: Scaffold( - body: Tilt( - child: TiltAnimatedBuilder( - builder: (context, tiltData, tiltConfig, onResize, child) { - onResizeRef = onResize; - receivedSize = Size(tiltData.width, tiltData.height); - - return Stack( - children: [ - child!, - - /// 使用 LayoutBuilder 监听尺寸变化,并调用 onResize - Positioned.fill( - child: LayoutBuilder( - builder: (context, constraints) { - WidgetsBinding.instance.addPostFrameCallback((_) { - if (context.mounted) { - onResize(constraints.biggest); - } - }); - return const SizedBox(); - }, - ), - ), - ], - ); - }, - child: const SizedBox(width: 10, height: 10), - ), - ), - ), - ), - ); - - /// 初始尺寸应该是 child 的尺寸 - await tester.pumpAndSettle(); - expect(receivedSize, const Size(10, 10)); - - /// 调用 onResize 更新尺寸 - const testSize = Size(123, 456); - onResizeRef(testSize); - - /// 等待 1 帧以确保 onResize 被处理 - await tester.pump(); - expect(receivedSize, testSize); - /// 再等待 1 帧将尺寸还原为 LayoutBuilder 监听的尺寸 - await tester.pump(); - expect(receivedSize, const Size(10, 10)); + /// 检查尺寸回调是否正确 + expect(reportedSize, const Size(100, 100)); }); }); } diff --git a/test/widgets/core/widget_size_getter_test.dart b/test/widgets/core/widget_size_getter_test.dart new file mode 100644 index 0000000..76fc952 --- /dev/null +++ b/test/widgets/core/widget_size_getter_test.dart @@ -0,0 +1,36 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_test/flutter_test.dart'; +import 'package:flutter_tilt/src/widgets/core/widget_size_getter.dart'; + +void main() { + group('WidgetSizeGetter ::', () { + testWidgets('reports initial size and descendant layout changes', ( + WidgetTester tester, + ) async { + final reportedSizes = []; + final widthNotifier = ValueNotifier(10.0); + + await tester.pumpWidget( + Center( + child: WidgetSizeGetter( + onSize: reportedSizes.add, + child: ValueListenableBuilder( + valueListenable: widthNotifier, + builder: (context, width, _) { + return SizedBox(width: width, height: 20.0); + }, + ), + ), + ), + ); + + await tester.pump(); + expect(reportedSizes.last, const Size(10.0, 20.0)); + + widthNotifier.value = 40.0; + await tester.pump(); + + expect(reportedSizes.last, const Size(40.0, 20.0)); + }); + }); +} diff --git a/test/widgets/shared_widgets/tilt_parallax_container_widget.dart b/test/widgets/shared_widgets/tilt_parallax_container_widget.dart index aac98d7..7d5690f 100644 --- a/test/widgets/shared_widgets/tilt_parallax_container_widget.dart +++ b/test/widgets/shared_widgets/tilt_parallax_container_widget.dart @@ -2,6 +2,7 @@ import 'dart:async'; import 'package:flutter/material.dart'; import 'package:flutter_tilt/flutter_tilt.dart'; +import 'package:flutter_tilt/src/widgets/containers/tilt_parallax_container.dart'; class TiltParallaxContainerWidget extends StatelessWidget { const TiltParallaxContainerWidget({ @@ -60,7 +61,8 @@ class TiltParallaxContainerWidget extends StatelessWidget { const ChildLayout( outer: [ Positioned( - child: TiltParallax( + child: TiltParallaxContainer( + size: Offset(10, 10), child: SizedBox( width: 10, height: 10, @@ -71,7 +73,8 @@ class TiltParallaxContainerWidget extends StatelessWidget { ], inner: [ Positioned( - child: TiltParallax( + child: TiltParallaxContainer( + size: Offset(10, 10), child: SizedBox( width: 10, height: 10, @@ -82,7 +85,8 @@ class TiltParallaxContainerWidget extends StatelessWidget { ], behind: [ Positioned( - child: TiltParallax( + child: TiltParallaxContainer( + size: Offset(10, 10), child: SizedBox( width: 10, height: 10, From 9c22683bc70019a769ab0a6278d84d2c23570648 Mon Sep 17 00:00:00 2001 From: Amos Date: Thu, 23 Apr 2026 03:34:50 +0800 Subject: [PATCH 04/10] refactor: migrate to TiltController and restructure input sources --- CHANGELOG.md | 1 + lib/flutter_tilt.dart | 14 +- lib/src/controllers/tilt_controller.dart | 110 ++++++ .../sources/tilt_action_input_source.dart | 6 + .../sources/tilt_pointer_input_source.dart | 64 ++++ .../sources/tilt_sensor_input_source.dart | 189 ++++++++++ .../tilt_action_input_coordinator.dart | 102 ++++++ .../tilt_action_input_resolver.dart | 160 +++++++++ .../controllers/tilt_gestures_controller.dart | 332 ------------------ .../internal/mixin/tilt_decoration_mixin.dart | 2 +- .../mixin/tilt_tween_animation_mixin.dart | 38 +- lib/src/internal/provider/tilt_provider.dart | 45 +-- lib/src/internal/tilt_data.dart | 80 ----- lib/src/internal/tilt_state.dart | 191 ++++++++++ lib/src/models/tilt_data_model.dart | 1 - lib/src/models/tilt_stream_model.dart | 50 +-- lib/src/tilt.dart | 6 +- .../fps_throttle.dart} | 4 +- lib/src/{ => utils}/utils.dart | 2 +- .../containers/tilt_base_container.dart | 12 +- .../containers/tilt_parallax_container.dart | 6 +- .../containers/tilt_projector_container.dart | 12 +- lib/src/widgets/core/gestures_listener.dart | 107 ++---- .../widgets/core/tilt_animated_builder.dart | 110 ++++-- lib/src/widgets/core/tilt_stream_builder.dart | 63 ---- lib/src/widgets/core/tilt_widget.dart | 273 +++++++------- lib/src/widgets/effects/tilt_light.dart | 2 +- lib/src/widgets/effects/tilt_shadow.dart | 2 +- .../tilt_pointer_input_source_test.dart | 101 ++++++ .../tilt_sensor_input_source_test.dart} | 188 +++------- .../tilt_action_input_resolver_test.dart | 124 +++++++ .../tilt_tween_animation_mixin_test.dart | 17 +- test/internal/tilt_data_test.dart | 73 ---- test/internal/tilt_state_test.dart | 166 +++++++++ test/models/tilt_stream_model_test.dart | 2 - test/tilt/shared_widgets/tilt_widget.dart | 20 +- test/tilt/tilt_config_tilt_test.dart | 14 +- test/tilt/tilt_widget_test.dart | 70 ++-- test/{ => utils}/utils_test.dart | 2 +- ...er_test.dart => tilt_controller_test.dart} | 158 ++++----- .../tilt_gestures_hover_test.dart | 6 +- .../tilt_gestures_touch_test.dart | 6 +- ...er_test.dart => tilt_controller_test.dart} | 160 ++++----- .../tilt_gestures_hover_test.dart | 6 +- .../tilt_gestures_touch_test.dart | 6 +- .../core/tilt_animated_builder_test.dart | 103 +++++- .../tilt_base_container_widget.dart | 8 +- .../tilt_parallax_container_widget.dart | 8 +- .../tilt_projector_container_widget.dart | 8 +- 49 files changed, 1933 insertions(+), 1297 deletions(-) create mode 100644 lib/src/controllers/tilt_controller.dart create mode 100644 lib/src/internal/action_input/sources/tilt_action_input_source.dart create mode 100644 lib/src/internal/action_input/sources/tilt_pointer_input_source.dart create mode 100644 lib/src/internal/action_input/sources/tilt_sensor_input_source.dart create mode 100644 lib/src/internal/action_input/tilt_action_input_coordinator.dart create mode 100644 lib/src/internal/action_input/tilt_action_input_resolver.dart delete mode 100644 lib/src/internal/controllers/tilt_gestures_controller.dart delete mode 100644 lib/src/internal/tilt_data.dart create mode 100644 lib/src/internal/tilt_state.dart rename lib/src/{internal/controllers/fps_timer_controller.dart => utils/fps_throttle.dart} (89%) rename lib/src/{ => utils}/utils.dart (99%) delete mode 100644 lib/src/widgets/core/tilt_stream_builder.dart create mode 100644 test/internal/action_input/sources/tilt_pointer_input_source_test.dart rename test/internal/{controllers/tilt_gestures_controller_test.dart => action_input/sources/tilt_sensor_input_source_test.dart} (52%) create mode 100644 test/internal/action_input/tilt_action_input_resolver_test.dart delete mode 100644 test/internal/tilt_data_test.dart create mode 100644 test/internal/tilt_state_test.dart rename test/{ => utils}/utils_test.dart (98%) rename test/widgets/containers/tilt_base_container/{tilt_stream_controller_test.dart => tilt_controller_test.dart} (74%) rename test/widgets/containers/tilt_projector_container/{tilt_stream_controller_test.dart => tilt_controller_test.dart} (74%) diff --git a/CHANGELOG.md b/CHANGELOG.md index 2ad5e5d..9defc8b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,6 +10,7 @@ Migration Guides: [Migrate to v4.0.0](#) - `Tilt` widget now only manages gesture state, sensor state, and animation state... +- Replace `Tilt.TiltStreamController` with `Tilt.TiltController` to unify input stream management. - The previous `Tilt.lightShadowMode` parameter has been split into the independent widgets `TiltBaseContainer` and `TiltProjectorContainer`. To reproduce the previous style, you need to compose them inside the `Tilt` widget, or use `Tilt.base` and `Tilt.projector` directly. diff --git a/lib/flutter_tilt.dart b/lib/flutter_tilt.dart index b92c013..e539cff 100644 --- a/lib/flutter_tilt.dart +++ b/lib/flutter_tilt.dart @@ -1,29 +1,33 @@ /// Easily apply tilt parallax hover effects for Flutter, /// which supports tilt, light, shadow effects, gyroscope sensors and many custom parameters. +// ignore_for_file: directives_ordering // ignore: unnecessary_library_name library flutter_tilt; -// Configurations +/// Configurations export 'src/config/tilt_config.dart'; export 'src/config/tilt_light_config.dart'; export 'src/config/tilt_shadow_config.dart'; export 'src/enums.dart'; -// Data Models +/// Controllers +export 'src/controllers/tilt_controller.dart' show TiltController; + +/// Data Models export 'src/models/tilt_data_model.dart' show TiltDataModel; export 'src/models/tilt_stream_model.dart' show TiltStreamModel; -// Main Widgets +/// Main Widgets export 'src/tilt.dart' show Tilt, TiltParallax; export 'src/widgets/containers/tilt_base_container.dart' show TiltBaseContainer; export 'src/widgets/containers/tilt_projector_container.dart' show TiltProjectorContainer; -// Core Widgets +/// Core Widgets export 'src/widgets/core/tilt_animated_builder.dart' show TiltAnimatedBuilder; -// Effects +/// Effects export 'src/widgets/effects/tilt_light.dart' show TiltLight; export 'src/widgets/effects/tilt_shadow.dart' show TiltShadowBase, TiltShadowProjector; diff --git a/lib/src/controllers/tilt_controller.dart b/lib/src/controllers/tilt_controller.dart new file mode 100644 index 0000000..1a3288e --- /dev/null +++ b/lib/src/controllers/tilt_controller.dart @@ -0,0 +1,110 @@ +import 'dart:async' as async show StreamController; + +import 'package:flutter/widgets.dart'; + +import '../enums.dart'; +import '../models/tilt_stream_model.dart'; + +/// TiltController +class TiltController { + /// Controls Tilt-related events (such as move/leave). + /// + /// Call [dispose] when no longer used. + /// + /// ------ + /// + /// 用于控制 Tilt 相关事件(如 move/leave)。 + /// + /// 不再使用时调用 [dispose] 释放资源。 + TiltController() + : _streamController = async.StreamController.broadcast(); + + final async.StreamController _streamController; + + Stream get stream => _streamController.stream; + + /// Whether there is a subscriber on the [Stream]. + /// + /// ------ + /// + /// [Stream] 上是否有订阅者。 + bool get hasListener => _streamController.hasListener; + + Future dispose() async { + await _streamController.close(); + } + + /// Emit a [TiltStreamModel] to the stream. + /// + /// ------ + /// + /// 向 stream 发出一个 [TiltStreamModel]。 + void emit(TiltStreamModel tiltStreamModel) { + _streamController.sink.add(tiltStreamModel); + } + + /// Handle the "move" input event. + /// + /// Should be used together with [leave] to indicate the end of the gesture. + /// + /// + /// [position] + /// {@macro tilt.TiltStreamModel.position.en} + /// + /// [gesturesType] + /// {@macro tilt.TiltStreamModel.gesturesType.en} + /// + /// ------ + /// + /// 处理 "move" 输入事件。 + /// + /// 应与 [leave] 配合使用以表示手势结束。 + /// + /// [position] + /// {@macro tilt.TiltStreamModel.position.zh} + /// + /// [gesturesType] + /// {@macro tilt.TiltStreamModel.gesturesType.zh} + void move({ + required Offset position, + GesturesType gesturesType = GesturesType.controller, + }) { + emit( + TiltStreamModel( + position: position, + gesturesType: gesturesType, + isActive: true, + ), + ); + } + + /// Handle the "leave" input event. + /// + /// [position] + /// {@macro tilt.TiltStreamModel.position.en} + /// + /// [gesturesType] + /// {@macro tilt.TiltStreamModel.gesturesType.en} + /// + /// ------ + /// + /// 处理 "leave" 输入事件。 + /// + /// [position] + /// {@macro tilt.TiltStreamModel.position.zh} + /// + /// [gesturesType] + /// {@macro tilt.TiltStreamModel.gesturesType.zh} + void leave({ + required Offset position, + GesturesType gesturesType = GesturesType.controller, + }) { + emit( + TiltStreamModel( + position: position, + gesturesType: gesturesType, + isActive: false, + ), + ); + } +} diff --git a/lib/src/internal/action_input/sources/tilt_action_input_source.dart b/lib/src/internal/action_input/sources/tilt_action_input_source.dart new file mode 100644 index 0000000..6819a50 --- /dev/null +++ b/lib/src/internal/action_input/sources/tilt_action_input_source.dart @@ -0,0 +1,6 @@ +import 'package:flutter/widgets.dart'; + +abstract class TiltActionInputSource { + void init(BuildContext context); + void dispose(); +} diff --git a/lib/src/internal/action_input/sources/tilt_pointer_input_source.dart b/lib/src/internal/action_input/sources/tilt_pointer_input_source.dart new file mode 100644 index 0000000..77cb8af --- /dev/null +++ b/lib/src/internal/action_input/sources/tilt_pointer_input_source.dart @@ -0,0 +1,64 @@ +import 'package:flutter/gestures.dart'; +import 'package:flutter/widgets.dart'; + +import '../../../controllers/tilt_controller.dart'; +import '../../../enums.dart'; +import 'tilt_action_input_source.dart'; + +/// 处理 touch 和 hover 的原始输入 +class TiltPointerInputSource implements TiltActionInputSource { + TiltPointerInputSource({required this.tiltController}); + + final TiltController tiltController; + + /// 当前是否处于 touch 状态 + bool _isTouch = false; + + /// 当前是否处于 hover 状态 + bool _isHover = false; + + @override + void init(BuildContext context) {} + + @override + void dispose() {} + + void handlePointerMove(PointerMoveEvent event) { + _isTouch = true; + _move(event.localPosition, GesturesType.touch); + } + + void handlePointerUp(PointerUpEvent event) { + _isTouch = false; + _leave(event.localPosition, GesturesType.touch); + } + + void handlePointerCancel(PointerCancelEvent event) { + _isTouch = false; + _leave(event.localPosition, GesturesType.touch); + } + + void handleMouseEnter(PointerEnterEvent event) { + if (_isTouch) return; + _isHover = true; + } + + void handleMouseHover(PointerHoverEvent event) { + if (!_isHover || _isTouch) return; + _move(event.localPosition, GesturesType.hover); + } + + void handleMouseExit(PointerExitEvent event) { + if (_isTouch) return; + _isHover = false; + _leave(event.localPosition, GesturesType.hover); + } + + void _move(Offset position, GesturesType gesturesType) { + tiltController.move(position: position, gesturesType: gesturesType); + } + + void _leave(Offset position, GesturesType gesturesType) { + tiltController.leave(position: position, gesturesType: gesturesType); + } +} diff --git a/lib/src/internal/action_input/sources/tilt_sensor_input_source.dart b/lib/src/internal/action_input/sources/tilt_sensor_input_source.dart new file mode 100644 index 0000000..95a331e --- /dev/null +++ b/lib/src/internal/action_input/sources/tilt_sensor_input_source.dart @@ -0,0 +1,189 @@ +import 'dart:async' as async show StreamSubscription, Timer; + +import 'package:flutter/services.dart' show DeviceOrientation; +import 'package:flutter/widgets.dart'; + +import 'package:sensors_plus/sensors_plus.dart'; + +import '../../../config/tilt_config.dart'; +import '../../../controllers/tilt_controller.dart'; +import '../../../enums.dart'; +import '../../../utils/utils.dart'; +import 'tilt_action_input_source.dart'; + +/// 处理传感器的原始输入,订阅、补帧与设备方向识别 +class TiltSensorInputSource implements TiltActionInputSource { + TiltSensorInputSource({ + required this.tiltController, + required this.disable, + required this.fps, + required this.tiltConfig, + }); + + final TiltController tiltController; + + /// 是否禁用输入流 + final bool disable; + + /// 传感器采样帧率 + final int fps; + + final TiltConfig tiltConfig; + + /// 当前平台是否支持传感器 + bool _canSensorsPlatformSupport = Utils.sensorsPlatformSupport(); + + /// Gyroscope 订阅 + async.StreamSubscription? _gyroscopeSubscription; + async.StreamSubscription? get gyroscopeSubscription => + _gyroscopeSubscription; + + /// Accelerometer 订阅 + async.StreamSubscription? _accelerometerSubscription; + async.StreamSubscription? get accelerometerSubscription => + _accelerometerSubscription; + + /// 当前设备方向 + DeviceOrientation deviceOrientation = DeviceOrientation.portraitUp; + + /// Gyroscope 采样定时器(用于补帧或限制帧率) + async.Timer? _gyroscopeSamplingTimer; + + /// 传感器是否已初始化 + bool sensorsInitialized = false; + + /// 待分发的最新 Gyroscope 输入位置 + Offset? _pendingGyroscopeTiltPosition; + + @override + void init(BuildContext context) { + _initSensors(context, enableTiltStream: !disable); + } + + @override + void dispose() { + cancelAccelerometerSubscription(); + cancelGyroscopeSubscription(); + cancelGyroscopeSamplingTimer(); + sensorsInitialized = false; + } + + /// 初始化传感器 + void _initSensors( + BuildContext context, { + required bool enableTiltStream, + }) { + if (!_canSensorsPlatformSupport || + sensorsInitialized || + !enableTiltStream || + !tiltConfig.enableGestureSensors) { + return; + } + + sensorsInitialized = true; + subscribeToDeviceOrientation(context); + subscribeToGyroscopeTilt(); + } + + /// 取消 Gyroscope 订阅 + void cancelGyroscopeSubscription() { + _gyroscopeSubscription?.cancel(); + _gyroscopeSubscription = null; + } + + /// 取消 Accelerometer 订阅 + void cancelAccelerometerSubscription() { + _accelerometerSubscription?.cancel(); + _accelerometerSubscription = null; + } + + /// 取消 Gyroscope 采样定时器 + void cancelGyroscopeSamplingTimer() { + _gyroscopeSamplingTimer?.cancel(); + _gyroscopeSamplingTimer = null; + _pendingGyroscopeTiltPosition = null; + } + + /// 订阅 Gyroscope 输入,并按帧率补帧 + void subscribeToGyroscopeTilt() { + final frameDuration = Duration(milliseconds: (1000 / fps) ~/ 1); + + _gyroscopeSubscription = gyroscopeEventStream().listen( + (GyroscopeEvent gyroscopeEvent) { + if (_gyroscopeSubscription == null) return; + + _pendingGyroscopeTiltPosition = Offset( + gyroscopeEvent.y, + gyroscopeEvent.x, + ); + + _gyroscopeSamplingTimer ??= async.Timer.periodic( + frameDuration, + (_) => _pushLatestGyroscopeTiltPosition(), + ); + }, + onError: (_) { + _canSensorsPlatformSupport = false; + dispose(); + }, + cancelOnError: true, + ); + } + + /// 订阅 Accelerometer 输入,用于识别设备方向 + void subscribeToDeviceOrientation(BuildContext context) { + _accelerometerSubscription = accelerometerEventStream().listen( + (AccelerometerEvent event) { + if (!context.mounted) return; + if (_accelerometerSubscription == null) return; + + handleDeviceOrientationEvent(context, event); + }, + onError: (_) { + _canSensorsPlatformSupport = false; + dispose(); + }, + cancelOnError: true, + ); + } + + /// 处理 Accelerometer 事件,识别并更新设备方向 + void handleDeviceOrientationEvent( + BuildContext context, + AccelerometerEvent event, + ) { + if (!context.mounted) return; + + final x = event.x, y = event.y, z = event.z; + final mediaOrientation = MediaQuery.of(context).orientation; + + switch (mediaOrientation) { + case Orientation.landscape: + if (x.abs() > y.abs() && x.abs() > z.abs()) { + if (x > 0) { + deviceOrientation = DeviceOrientation.landscapeLeft; + } else { + deviceOrientation = DeviceOrientation.landscapeRight; + } + } + case Orientation.portrait: + if (y.abs() > x.abs() && y.abs() > z.abs()) { + if (y > 0) { + deviceOrientation = DeviceOrientation.portraitUp; + } else { + deviceOrientation = DeviceOrientation.portraitDown; + } + } + } + } + + /// 推送最新 Gyroscope 输入到 TiltController + void _pushLatestGyroscopeTiltPosition() { + if (_pendingGyroscopeTiltPosition != null) { + tiltController.move( + position: _pendingGyroscopeTiltPosition!, + gesturesType: GesturesType.sensors, + ); + } + } +} diff --git a/lib/src/internal/action_input/tilt_action_input_coordinator.dart b/lib/src/internal/action_input/tilt_action_input_coordinator.dart new file mode 100644 index 0000000..14132ae --- /dev/null +++ b/lib/src/internal/action_input/tilt_action_input_coordinator.dart @@ -0,0 +1,102 @@ +import 'package:flutter/widgets.dart'; + +import '../../config/tilt_config.dart'; +import '../../controllers/tilt_controller.dart'; +import '../../enums.dart'; +import '../../models/tilt_stream_model.dart'; +import 'sources/tilt_action_input_source.dart'; +import 'sources/tilt_pointer_input_source.dart'; +import 'sources/tilt_sensor_input_source.dart'; +import 'tilt_action_input_resolver.dart'; + +/// 协调 Tilt 内部多种输入源(如手势、传感器),统一管理输入规则与生命周期 +/// +/// 职责: +/// - 统一管理手势、传感器等输入源的初始化与释放 +/// - 负责输入流的优先级、冲突协调(委托给 [TiltActionInputResolver]) +/// - 提供输入 stream 的统一入口 +class TiltActionInputCoordinator { + TiltActionInputCoordinator({ + required this.tiltController, + required this.disable, + required this.fps, + required this.tiltConfig, + required this.initialPosition, + }) : initialTiltStreamModel = TiltStreamModel( + position: initialPosition, + gesturesType: GesturesType.none, + ); + + /// Tilt 控制器,负责输入流的分发与管理 + final TiltController tiltController; + + /// 是否禁用输入流 + final bool disable; + + final int fps; + final TiltConfig tiltConfig; + + /// 输入流初始位置 + final Offset initialPosition; + + /// 初始输入 [TiltStreamModel] + final TiltStreamModel initialTiltStreamModel; + + /// 输入流优先级处理与冲突协调器 + late final TiltActionInputResolver _tiltActionInputResolver = + TiltActionInputResolver( + initialTiltStreamModel: initialTiltStreamModel, + tiltConfig: tiltConfig, + ); + + /// 手势输入源(如指针、触摸等) + late final TiltPointerInputSource pointerInputSource = + TiltPointerInputSource(tiltController: tiltController); + + /// 传感器输入源(如陀螺仪等) + late final TiltSensorInputSource _sensorInputSource = TiltSensorInputSource( + tiltController: tiltController, + disable: disable, + fps: fps, + tiltConfig: tiltConfig, + ); + + /// 所有输入源列表 + late final List tiltActionInputSources = [ + pointerInputSource, + _sensorInputSource, + ]; + + /// 输入流统一入口,自动处理优先级与冲突 + late final Stream? stream = + enableTiltStream ? tiltController.stream.map(_filterTiltStream) : null; + + /// 是否启用输入流 + bool get enableTiltStream => !disable; + + void init(BuildContext context) { + for (final inputSource in tiltActionInputSources) { + inputSource.init(context); + } + } + + void dispose() { + for (final inputSource in tiltActionInputSources) { + inputSource.dispose(); + } + disposeResolver(); + } + + /// 仅释放 [TiltActionInputResolver] + void disposeResolver() { + _tiltActionInputResolver.dispose(); + } + + /// 输入流优先级与冲突消解主入口 + TiltStreamModel _filterTiltStream(TiltStreamModel tiltStreamModel) { + return _tiltActionInputResolver.resolve( + tiltStreamModel, + deviceOrientation: _sensorInputSource.deviceOrientation, + ); + } +} diff --git a/lib/src/internal/action_input/tilt_action_input_resolver.dart b/lib/src/internal/action_input/tilt_action_input_resolver.dart new file mode 100644 index 0000000..ce32f52 --- /dev/null +++ b/lib/src/internal/action_input/tilt_action_input_resolver.dart @@ -0,0 +1,160 @@ +import 'dart:async' as async show Timer; + +import 'package:flutter/services.dart' show DeviceOrientation; +import 'package:flutter/widgets.dart'; + +import '../../config/tilt_config.dart'; +import '../../enums.dart'; +import '../../models/tilt_stream_model.dart'; + +/// 统一处理 Tilt 各类输入(如手势、传感器等)的优先级和冲突 +/// +/// 职责: +/// - 维护当前最新的输入 [TiltStreamModel] +/// - 根据输入类型(如手势、传感器等)动态切换优先级 +/// - 协调不同行为输入(如手势与传感器)之间冲突和切换时机 +class TiltActionInputResolver { + TiltActionInputResolver({ + required this.tiltConfig, + required this.initialTiltStreamModel, + }) : latestTiltStreamModel = initialTiltStreamModel; + + final TiltConfig tiltConfig; + + /// 初始 [TiltStreamModel] + final TiltStreamModel initialTiltStreamModel; + + /// 当前最新的 [TiltStreamModel] + late TiltStreamModel latestTiltStreamModel; + + /// 是否允许传感器输入生效 + bool _enableSensors = true; + + /// 手势冲突协调定时器 + async.Timer? _gesturesHarmonizerTimer; + + void dispose() { + cancelGesturesHarmonizerTimer(); + } + + /// 取消手势冲突协调定时器 + void cancelGesturesHarmonizerTimer() { + _gesturesHarmonizerTimer?.cancel(); + _gesturesHarmonizerTimer = null; + } + + /// 输入流主入口 + /// + /// 根据不同输入类型(手势/传感器)动态切换优先级, + /// 并处理输入冲突、状态切换等。 + /// + /// - [tiltStreamModel]:当前输入 [TiltStreamModel] + /// - [deviceOrientation]:当前设备方向 + /// + /// @return [TiltStreamModel] + TiltStreamModel resolve( + TiltStreamModel tiltStreamModel, { + required DeviceOrientation deviceOrientation, + }) { + switch (tiltStreamModel.gesturesType) { + case GesturesType.none: + break; + case GesturesType.touch || GesturesType.hover || GesturesType.controller: + { + if (latestTiltStreamModel == tiltStreamModel) { + return tiltStreamModel; + } + + final isHighPriority = compareGesturesTypePriority( + tiltStreamModel.gesturesType, + latestTiltStreamModel.gesturesType, + ) == + tiltStreamModel.gesturesType; + + /// 若为高优先级或当前无激活输入,则切换为当前输入 + if (isHighPriority || !latestTiltStreamModel.isActive) { + latestTiltStreamModel = tiltStreamModel; + } + + /// 手势结束后,允许传感器输入重新生效 + if (!tiltStreamModel.isActive) { + _handleGesturesConflict(tiltStreamModel.gesturesType); + _enableSensors = true; + } else { + _enableSensors = false; + } + } + case GesturesType.sensors: + { + /// 仅在允许传感器输入且无手势冲突时,更新传感器输入 + if (_enableSensors && _gesturesHarmonizerTimer == null) { + _updateSensorsTiltPosition(tiltStreamModel, deviceOrientation); + } + } + } + + return latestTiltStreamModel; + } + + /// 判断两个输入类型的优先级,返回优先级更高的类型 + GesturesType compareGesturesTypePriority( + GesturesType gesturesType1, + GesturesType gesturesType2, + ) { + if (gesturesType1 == gesturesType2) return gesturesType1; + + final gesturePriority = [ + GesturesType.touch, // 最高优先级 + GesturesType.hover, + GesturesType.controller, + GesturesType.sensors, // 最低优先级 + GesturesType.none, + ]; + return gesturePriority.indexOf(gesturesType1) < + gesturePriority.indexOf(gesturesType2) + ? gesturesType1 + : gesturesType2; + } + + /// 根据设备方向修正传感器输入的坐标,并更新最新输入流 + void _updateSensorsTiltPosition( + TiltStreamModel tiltStreamModel, + DeviceOrientation deviceOrientation, + ) { + final sensorsX = tiltStreamModel.position.dx; + final sensorsY = tiltStreamModel.position.dy; + final tiltPosition = switch (deviceOrientation) { + DeviceOrientation.portraitUp => Offset(sensorsX, sensorsY), + DeviceOrientation.portraitDown => -Offset(sensorsX, sensorsY), + DeviceOrientation.landscapeLeft => Offset(sensorsY, -sensorsX), + DeviceOrientation.landscapeRight => Offset(-sensorsY, sensorsX), + }; + latestTiltStreamModel = TiltStreamModel( + position: tiltPosition, + gesturesType: tiltStreamModel.gesturesType, + isActive: true, + ); + } + + /// 处理手势输入结束后的冲突 + void _handleGesturesConflict(GesturesType gesturesType) { + final duration = switch (gesturesType) { + GesturesType.touch || GesturesType.hover => tiltConfig.leaveDuration, + GesturesType.controller => tiltConfig.controllerLeaveDuration, + _ => null, + }; + if (duration != null) { + _startGesturesHarmonizer(duration); + } + } + + /// 启动手势冲突协调定时器,定时结束后允许传感器输入 + void _startGesturesHarmonizer(Duration duration) { + if (_gesturesHarmonizerTimer != null) return; + _gesturesHarmonizerTimer?.cancel(); + _gesturesHarmonizerTimer = async.Timer( + duration, + () => _gesturesHarmonizerTimer = null, + ); + } +} diff --git a/lib/src/internal/controllers/tilt_gestures_controller.dart b/lib/src/internal/controllers/tilt_gestures_controller.dart deleted file mode 100644 index bc548a2..0000000 --- a/lib/src/internal/controllers/tilt_gestures_controller.dart +++ /dev/null @@ -1,332 +0,0 @@ -import 'dart:async' as async show StreamController, StreamSubscription, Timer; -import 'package:flutter/services.dart' show DeviceOrientation; -import 'package:flutter/widgets.dart'; - -import 'package:sensors_plus/sensors_plus.dart'; - -import '../../config/tilt_config.dart'; -import '../../enums.dart'; -import '../../models/tilt_stream_model.dart'; -import '../../utils.dart'; - -/// 倾斜手势控制器 -class TiltGesturesController { - TiltGesturesController({ - required this.tiltStreamController, - required this.disable, - required this.fps, - required this.tiltConfig, - required this.initialPosition, - }); - - /// TiltStreamController - final async.StreamController tiltStreamController; - - /// 是否禁用所有效果 - final bool disable; - - final int fps; - - final TiltConfig tiltConfig; - - /// 初始坐标 - final Offset initialPosition; - - /// 传感器平台支持 - bool _canSensorsPlatformSupport = Utils.sensorsPlatformSupport(); - - async.StreamSubscription? _gyroscopeSubscription; - - async.StreamSubscription? get gyroscopeSubscription => - _gyroscopeSubscription; - - async.StreamSubscription? _accelerometerSubscription; - - async.StreamSubscription? get accelerometerSubscription => - _accelerometerSubscription; - - /// 设备方向 - DeviceOrientation deviceOrientation = DeviceOrientation.portraitUp; - - /// 是否开启传感器 - bool _enableSensors = true; - - /// 初始 TiltStreamModel - late TiltStreamModel initialTiltStreamModel = TiltStreamModel( - position: initialPosition, - gesturesType: GesturesType.none, - ); - - /// 最新 TiltStreamModel(缓存) - late TiltStreamModel latestTiltStreamModel = initialTiltStreamModel; - - /// 手势协调器 - async.Timer? _gesturesHarmonizerTimer; - - /// 陀螺仪采样定时器 - async.Timer? _gyroscopeSamplingTimer; - - /// 是否已完成传感器订阅初始化 - bool _sensorsInitialized = false; - - /// 最近一帧可用于补帧/限流的陀螺仪数据 - TiltStreamModel? _pendingGyroscopeTiltStreamModel; - - /// 是否开启 TiltStream - bool get enableTiltStream => !disable; - - Stream? get tiltStream => enableTiltStream - ? tiltStreamController.stream.map(filterTiltStream) - : null; - - void dispose() { - disposeSensors(); - cancelGesturesHarmonizerTimer(); - } - - void disposeSensors() { - cancelAccelerometerSubscription(); - cancelGyroscopeSubscription(); - cancelGyroscopeSamplingTimer(); - _sensorsInitialized = false; - } - - /// 取消陀螺仪订阅 - void cancelGyroscopeSubscription() { - _gyroscopeSubscription?.cancel(); - _gyroscopeSubscription = null; - } - - /// 取消加速度计订阅 - void cancelAccelerometerSubscription() { - _accelerometerSubscription?.cancel(); - _accelerometerSubscription = null; - } - - /// 取消手势协调器 - void cancelGesturesHarmonizerTimer() { - _gesturesHarmonizerTimer?.cancel(); - _gesturesHarmonizerTimer = null; - } - - /// 取消陀螺仪采样定时器(补帧/限流) - void cancelGyroscopeSamplingTimer() { - _gyroscopeSamplingTimer?.cancel(); - _gyroscopeSamplingTimer = null; - _pendingGyroscopeTiltStreamModel = null; - } - - /// 过滤 TiltStream - TiltStreamModel filterTiltStream(TiltStreamModel tiltStreamModel) { - switch (tiltStreamModel.gesturesType) { - case GesturesType.none: - break; - case GesturesType.touch || GesturesType.hover || GesturesType.controller: - { - if (latestTiltStreamModel == tiltStreamModel) { - return tiltStreamModel; - } - - /// 当前手势是否高优先级 - final isHighPriority = gesturesTypePriority( - tiltStreamModel.gesturesType, - latestTiltStreamModel.gesturesType, - ) == - tiltStreamModel.gesturesType; - - if (isHighPriority || !latestTiltStreamModel.gestureUse) { - latestTiltStreamModel = tiltStreamModel; - } - - /// 避免 sensors 与其他手势触发冲突 - if (!tiltStreamModel.gestureUse) { - _handleGestureConflict(tiltStreamModel.gesturesType); - _enableSensors = true; - } else { - _enableSensors = false; - } - } - case GesturesType.sensors: - { - /// 避免 sensors 与其他手势触发冲突 - if (_enableSensors && _gesturesHarmonizerTimer == null) { - _updateSensorTiltPosition(tiltStreamModel); - } - } - } - return latestTiltStreamModel; - } - - /// 更新 sensors 对应的倾斜位置 - void _updateSensorTiltPosition(TiltStreamModel tiltStreamModel) { - final sensorsX = tiltStreamModel.position.dx; - final sensorsY = tiltStreamModel.position.dy; - final tiltPosition = switch (deviceOrientation) { - DeviceOrientation.portraitUp => Offset(sensorsX, sensorsY), - DeviceOrientation.portraitDown => -Offset(sensorsX, sensorsY), - DeviceOrientation.landscapeLeft => Offset(sensorsY, -sensorsX), - DeviceOrientation.landscapeRight => Offset(-sensorsY, sensorsX), - }; - latestTiltStreamModel = TiltStreamModel( - position: tiltPosition, - gesturesType: tiltStreamModel.gesturesType, - gestureUse: true, - ); - } - - /// 处理对应手势的冲突 - void _handleGestureConflict(GesturesType gesturesType) { - final duration = switch (gesturesType) { - GesturesType.touch || GesturesType.hover => tiltConfig.leaveDuration, - GesturesType.controller => tiltConfig.controllerLeaveDuration, - _ => null - }; - if (duration != null) { - _gesturesHarmonizer(duration); - } - } - - /// 手势协调器 - /// - /// 开启避免 sensors 与其他手势冲突的计时器 - /// - /// 避免其他手势离开后的动画与 sensors 冲突(出现闪现) - void _gesturesHarmonizer(Duration duration) { - if (_gesturesHarmonizerTimer != null) return; - _gesturesHarmonizerTimer?.cancel(); - _gesturesHarmonizerTimer = async.Timer( - duration, - () => _gesturesHarmonizerTimer = null, - ); - } - - /// 手势优先级比较 - /// - /// {@macro tilt.GesturesType.gesturePriority} - /// - /// - [gesturesType1] 手势类型1 - /// - [gesturesType2] 手势类型2 - /// - /// @return [GesturesType] 优先级最高的手势类型 - GesturesType gesturesTypePriority( - GesturesType gesturesType1, - GesturesType gesturesType2, - ) { - if (gesturesType1 == gesturesType2) return gesturesType1; - - /// 手势优先级(下标越小,优先级越高) - final gesturePriority = [ - GesturesType.touch, - GesturesType.hover, - GesturesType.controller, - GesturesType.sensors, - GesturesType.none, - ]; - return gesturePriority.indexOf(gesturesType1) < - gesturePriority.indexOf(gesturesType2) - ? gesturesType1 - : gesturesType2; - } - - /// 初始化传感器 - void initSensors(BuildContext context) { - if (!_canSensorsPlatformSupport || - _sensorsInitialized || - !enableTiltStream || - !tiltConfig.enableGestureSensors) { - return; - } - - _sensorsInitialized = true; - - /// 订阅设备方向事件 - subscribeToDeviceOrientation(context); - - /// 订阅陀螺仪倾斜事件 - subscribeToGyroscopeTilt(); - } - - /// 订阅陀螺仪倾斜事件 - void subscribeToGyroscopeTilt() { - final frameDuration = Duration(milliseconds: (1000 / fps) ~/ 1); - - _gyroscopeSubscription = gyroscopeEventStream().listen( - (GyroscopeEvent gyroscopeEvent) { - if (_gyroscopeSubscription == null) return; - - _pendingGyroscopeTiltStreamModel = TiltStreamModel( - position: Offset(gyroscopeEvent.y, gyroscopeEvent.x), - gesturesType: GesturesType.sensors, - gestureUse: true, - ); - - /// 如果陀螺仪存在数据,则开启陀螺仪采样定时器(补帧/限流) - _gyroscopeSamplingTimer ??= async.Timer.periodic( - frameDuration, - (_) => _pushLatestGyroscopeTiltStreamModel(), - ); - }, - onError: (_) { - _canSensorsPlatformSupport = false; - disposeSensors(); - }, - cancelOnError: true, - ); - } - - /// 按帧推送当前缓存的最新陀螺仪值,用于补帧和限流 - void _pushLatestGyroscopeTiltStreamModel() { - final tiltStreamModel = _pendingGyroscopeTiltStreamModel; - - if (tiltStreamModel != null) { - tiltStreamController.sink.add(tiltStreamModel); - } - } - - /// 订阅设备方向事件 - void subscribeToDeviceOrientation(BuildContext context) { - _accelerometerSubscription = accelerometerEventStream().listen( - (AccelerometerEvent event) { - if (!context.mounted) return; - if (_accelerometerSubscription == null) return; - - handleDeviceOrientationEvent(context, event); - }, - onError: (_) { - _canSensorsPlatformSupport = false; - disposeSensors(); - }, - cancelOnError: true, - ); - } - - /// 处理设备方向事件 - void handleDeviceOrientationEvent( - BuildContext context, - AccelerometerEvent event, - ) { - if (!context.mounted) return; - - final x = event.x, y = event.y, z = event.z; - final mediaOrientation = MediaQuery.of(context).orientation; - - switch (mediaOrientation) { - case Orientation.landscape: - if (x.abs() > y.abs() && x.abs() > z.abs()) { - if (x > 0) { - deviceOrientation = DeviceOrientation.landscapeLeft; - } else { - deviceOrientation = DeviceOrientation.landscapeRight; - } - } - case Orientation.portrait: - if (y.abs() > x.abs() && y.abs() > z.abs()) { - if (y > 0) { - deviceOrientation = DeviceOrientation.portraitUp; - } else { - deviceOrientation = DeviceOrientation.portraitDown; - } - } - } - } -} diff --git a/lib/src/internal/mixin/tilt_decoration_mixin.dart b/lib/src/internal/mixin/tilt_decoration_mixin.dart index 5c853b9..993db86 100644 --- a/lib/src/internal/mixin/tilt_decoration_mixin.dart +++ b/lib/src/internal/mixin/tilt_decoration_mixin.dart @@ -1,7 +1,7 @@ import 'package:flutter/widgets.dart'; import '../../enums.dart'; -import '../../utils.dart'; +import '../../utils/utils.dart'; mixin TiltDecorationMixin { /// 计算提供的方向进度 diff --git a/lib/src/internal/mixin/tilt_tween_animation_mixin.dart b/lib/src/internal/mixin/tilt_tween_animation_mixin.dart index 141058e..3d5a631 100644 --- a/lib/src/internal/mixin/tilt_tween_animation_mixin.dart +++ b/lib/src/internal/mixin/tilt_tween_animation_mixin.dart @@ -3,6 +3,7 @@ import 'package:flutter/widgets.dart'; import '../../config/tilt_config.dart'; import '../../enums.dart'; import '../provider/tilt_provider.dart'; +import '../tilt_state.dart'; mixin TiltTweenAnimationMixin on State, TickerProviderStateMixin { @@ -37,8 +38,9 @@ mixin TiltTweenAnimationMixin @override void didChangeDependencies() { super.didChangeDependencies(); - final tiltProvider = TiltProvider.of(context); - _updateTiltTweenAnimation(tiltProvider); + final tiltData = TiltProvider.of(context); + _updateTiltTweenAnimation(tiltData); + // 标记本帧由 didChangeDependencies 触发 _didChangeDependencies = true; } @@ -106,7 +108,7 @@ mixin TiltTweenAnimationMixin GesturesType.none || GesturesType.controller => Curves.linear, GesturesType.touch || GesturesType.hover => - _dynamicAnimationCurve(isMove, currentGesturesType, tiltConfig), + _dynamicAnimationCurve(isMove, tiltConfig), GesturesType.sensors => Curves.linear, }; } @@ -135,28 +137,28 @@ mixin TiltTweenAnimationMixin } /// 更新 TiltTween 动画 - void _updateTiltTweenAnimation(TiltProvider tiltProvider) { + void _updateTiltTweenAnimation(TiltState tiltState) { final animationEnd = tiltTweenAnimationEnd( - tiltProvider.isMove, - tiltProvider.tiltConfig, - tiltProvider.areaProgress, + tiltState.isActive, + tiltState.tiltConfig, + tiltState.areaProgress, ); /// 更新 enterToMoveAnimationController 的持续时间 enterToMoveAnimationController.duration = - tiltProvider.tiltConfig.enterToMoveDuration; + tiltState.tiltConfig.enterToMoveDuration; if (tiltTweenAnimation.value == animationEnd) return; final animationDuration = tiltTweenAnimationDuration( - tiltProvider.isMove, - tiltProvider.currentGesturesType, - tiltProvider.tiltConfig, + tiltState.isActive, + tiltState.currentGesturesType, + tiltState.tiltConfig, ); final animationCurve = tiltTweenAnimationCurve( - tiltProvider.isMove, - tiltProvider.currentGesturesType, - tiltProvider.tiltConfig, + tiltState.isActive, + tiltState.currentGesturesType, + tiltState.tiltConfig, ); /// 更新 tiltTweenAnimationController 的持续时间 @@ -179,7 +181,7 @@ mixin TiltTweenAnimationMixin void _updateEnterToMoveAnimation() { if (!context.mounted) return; - /// 避免 didChangeDependencies 和 build 的 updateTiltTweenAnimation 更新同步调用 + /// 只在动画进行时且本帧不是 didChangeDependencies 触发时 setState if (!_didChangeDependencies && enterToMoveAnimationController.isAnimating) { final tiltProvider = TiltProvider.of(context); _updateTiltTweenAnimation(tiltProvider); @@ -295,11 +297,7 @@ mixin TiltTweenAnimationMixin } /// 动态计算 Animation Curve - Curve _dynamicAnimationCurve( - bool isMove, - GesturesType currentGesturesType, - TiltConfig tiltConfig, - ) { + Curve _dynamicAnimationCurve(bool isMove, TiltConfig tiltConfig) { /// 离开 if (!isMove) { return tiltConfig.leaveCurve; diff --git a/lib/src/internal/provider/tilt_provider.dart b/lib/src/internal/provider/tilt_provider.dart index cae7044..9da2649 100644 --- a/lib/src/internal/provider/tilt_provider.dart +++ b/lib/src/internal/provider/tilt_provider.dart @@ -1,7 +1,6 @@ import 'package:flutter/widgets.dart'; -import '../../config/tilt_config.dart'; -import '../../enums.dart'; +import '../tilt_state.dart'; /// Tilt Base Provider class TiltProvider extends InheritedWidget { @@ -9,50 +8,18 @@ class TiltProvider extends InheritedWidget { const TiltProvider({ super.key, required super.child, - required this.isInit, - required this.width, - required this.height, - required this.areaProgress, - required this.isMove, - required this.currentGesturesType, - required this.tiltConfig, + required this.data, }); - /// 是否初始化 - final bool isInit; + final TiltState data; - /// 尺寸 width - final double width; - - /// 尺寸 height - final double height; - - /// 当前坐标的区域进度 - final Offset areaProgress; - - /// 是否正在移动 - final bool isMove; - - /// 当前手势类型 - final GesturesType currentGesturesType; - - /// 倾斜配置 - final TiltConfig tiltConfig; - - static TiltProvider of(BuildContext context) { + static TiltState of(BuildContext context) { final tiltProvider = context.dependOnInheritedWidgetOfExactType(); assert(tiltProvider != null, 'No TiltProvider found in context'); - return tiltProvider!; + return tiltProvider!.data; } @override - bool updateShouldNotify(TiltProvider oldWidget) => - isInit != oldWidget.isInit || - width != oldWidget.width || - height != oldWidget.height || - areaProgress != oldWidget.areaProgress || - isMove != oldWidget.isMove || - currentGesturesType != oldWidget.currentGesturesType || - tiltConfig != oldWidget.tiltConfig; + bool updateShouldNotify(TiltProvider oldWidget) => data != oldWidget.data; } diff --git a/lib/src/internal/tilt_data.dart b/lib/src/internal/tilt_data.dart deleted file mode 100644 index 2d0981c..0000000 --- a/lib/src/internal/tilt_data.dart +++ /dev/null @@ -1,80 +0,0 @@ -import 'dart:math' as math; -import 'package:flutter/widgets.dart'; - -import '../config/tilt_config.dart'; -import '../models/tilt_data_model.dart'; -import '../utils.dart'; - -/// 倾斜数据 -@immutable -class TiltData { - /// 倾斜数据 - const TiltData({ - required this.isInit, - required this.width, - required this.height, - required this.areaProgress, - required this.tiltConfig, - }); - - /// 是否初始化 - final bool isInit; - - /// 尺寸 width - final double width; - - /// 尺寸 height - final double height; - - /// 区域进度 - final Offset areaProgress; - - /// 倾斜配置 - final TiltConfig tiltConfig; - - /// 当前坐标 - Offset get position => Utils.progressPosition(width, height, areaProgress); - - /// Transform - Matrix4 get transform => - isInit && !disable ? tiltTransform() : Matrix4.identity(); - - /// 实际旋转角度 - /// - /// {@macro tilt.TiltDataModel.angle} - Offset get angle => Utils.rotateAxis( - areaProgress * tiltConfig.angle, - tiltConfig.enableReverse, - ); - - /// 禁用 - bool get disable => tiltConfig.disable; - - /// 计算当前坐标进度的倾斜 - Matrix4 tiltTransform() { - final rotate = Utils.rotateAxis( - /// 旋转大小:区域进度 * 弧度 - areaProgress * Utils.radian(tiltConfig.angle), - tiltConfig.enableReverse, - ); - final rotateX = rotate.dx, rotateY = rotate.dy; - final maxSize = math.max(width, height); - - return Matrix4.identity() - // 近大远小效果(适配不同尺寸的组件) - ..setEntry(3, 2, 0.5 / maxSize) - // 旋转轴 - ..rotateX(rotateX) - ..rotateY(rotateY); - } - - /// 转换为 TiltDataModel - TiltDataModel toModel() => TiltDataModel( - width: width, - height: height, - position: position, - transform: transform, - areaProgress: areaProgress, - angle: angle, - ); -} diff --git a/lib/src/internal/tilt_state.dart b/lib/src/internal/tilt_state.dart new file mode 100644 index 0000000..8adceb1 --- /dev/null +++ b/lib/src/internal/tilt_state.dart @@ -0,0 +1,191 @@ +import 'dart:math' as math; +import 'package:flutter/widgets.dart'; + +import '../config/tilt_config.dart'; +import '../enums.dart'; +import '../models/tilt_data_model.dart'; +import '../utils/utils.dart'; + +/// 倾斜状态 +@immutable +class TiltState { + /// 倾斜状态 + const TiltState({ + required this.isInit, + required this.tiltConfig, + required this.width, + required this.height, + required this.areaProgress, + required this.isActive, + required this.currentGesturesType, + }); + + /// 是否初始化 + final bool isInit; + + /// 倾斜配置 + final TiltConfig tiltConfig; + + /// 尺寸 width + final double width; + + /// 尺寸 height + final double height; + + /// 区域进度 + final Offset areaProgress; + + /// 是否处于活动状态(进入或移动) + final bool isActive; + + /// 当前手势类型 + final GesturesType currentGesturesType; + + /// 当前坐标 + Offset get position => Utils.progressPosition(width, height, areaProgress); + + /// Transform + Matrix4 get transform => + isInit && !disable ? tiltTransform() : Matrix4.identity(); + + /// 实际旋转角度 + /// + /// {@macro tilt.TiltDataModel.angle} + Offset get angle => Utils.rotateAxis( + areaProgress * tiltConfig.angle, + tiltConfig.enableReverse, + ); + + /// 禁用 + bool get disable => tiltConfig.disable; + + /// 复制当前倾斜数据,并按需覆盖部分字段 + TiltState copyWith({ + bool? isInit, + TiltConfig? tiltConfig, + double? width, + double? height, + Offset? areaProgress, + bool? isActive, + GesturesType? currentGesturesType, + }) { + return TiltState( + isInit: isInit ?? this.isInit, + tiltConfig: tiltConfig ?? this.tiltConfig, + width: width ?? this.width, + height: height ?? this.height, + areaProgress: areaProgress ?? this.areaProgress, + isActive: isActive ?? this.isActive, + currentGesturesType: currentGesturesType ?? this.currentGesturesType, + ); + } + + /// 根据当前位置生成 “移动” 的下一份倾斜数据 + /// + /// 仅负责纯数据转换,不处理节流、边界判断等 + TiltState moveTo(Offset position, GesturesType gesturesType) { + return copyWith( + areaProgress: Utils.p2cAreaProgress( + width, + height, + position, + tiltConfig.direction, + ), + isActive: true, + currentGesturesType: gesturesType, + ); + } + + /// 根据当前位置生成 “复原” 的下一份倾斜数据 + /// + /// 仅负责纯数据转换,不决定是否应该执行复原 + TiltState revertTo(Offset position, GesturesType gesturesType) { + return copyWith( + areaProgress: Utils.p2cAreaProgress( + width, + height, + position, + tiltConfig.direction, + ), + isActive: false, + currentGesturesType: gesturesType, + ); + } + + /// 解析复原时应回到的目标坐标 + /// + /// 当启用复原时,返回由初始区域进度推导出的初始坐标; + /// 否则保持当前位置不变。 + Offset resolveRevertPosition({ + required Offset currentPosition, + required Offset initialAreaProgress, + }) { + if (!tiltConfig.enableRevert) { + return currentPosition; + } + return Utils.progressPosition(width, height, initialAreaProgress); + } + + /// 计算当前坐标进度的倾斜 + Matrix4 tiltTransform() { + return tiltTransformFor(areaProgress); + } + + /// 计算指定区域进度的倾斜 + Matrix4 tiltTransformFor(Offset targetAreaProgress) { + final rotate = Utils.rotateAxis( + /// 旋转大小:区域进度 * 弧度 + targetAreaProgress * Utils.radian(tiltConfig.angle), + tiltConfig.enableReverse, + ); + final rotateX = rotate.dx, rotateY = rotate.dy; + final maxSize = math.max(width, height); + + return Matrix4.identity() + // 近大远小效果(适配不同尺寸的组件) + ..setEntry(3, 2, 0.5 / maxSize) + // 旋转轴 + ..rotateX(rotateX) + ..rotateY(rotateY); + } + + /// 转换为 TiltDataModel + /// 仅包含当前状态的核心数据,供外部使用 + TiltDataModel toModel() => TiltDataModel( + width: width, + height: height, + position: position, + transform: transform, + areaProgress: areaProgress, + angle: angle, + ); + + @override + bool operator ==(Object other) { + if (identical(this, other)) { + return true; + } + if (other.runtimeType != runtimeType) { + return false; + } + return other is TiltState && + isInit == other.isInit && + width == other.width && + height == other.height && + areaProgress == other.areaProgress && + isActive == other.isActive && + currentGesturesType == other.currentGesturesType && + tiltConfig == other.tiltConfig; + } + + @override + int get hashCode => Object.hash( + isInit, + width, + height, + areaProgress, + isActive, + currentGesturesType, + tiltConfig, + ); +} diff --git a/lib/src/models/tilt_data_model.dart b/lib/src/models/tilt_data_model.dart index 59a1383..872c207 100644 --- a/lib/src/models/tilt_data_model.dart +++ b/lib/src/models/tilt_data_model.dart @@ -16,7 +16,6 @@ class TiltDataModel { }); final double width; - final double height; /// 当前坐标 diff --git a/lib/src/models/tilt_stream_model.dart b/lib/src/models/tilt_stream_model.dart index f72ad51..30fcd59 100644 --- a/lib/src/models/tilt_stream_model.dart +++ b/lib/src/models/tilt_stream_model.dart @@ -10,9 +10,10 @@ class TiltStreamModel { const TiltStreamModel({ required this.position, this.gesturesType = GesturesType.controller, - this.gestureUse = true, + this.isActive = true, }); + /// {@template tilt.TiltStreamModel.position.en} /// The current trigger position, /// It will have the tilt effect of the corresponding position. /// @@ -20,9 +21,11 @@ class TiltStreamModel { /// There is a widget size, width: 10, height: 10, /// - (0, 0): Maximum tilt top left. /// - (10, 10): Maximum tilt bottom right. + /// {@endtemplate} /// /// ------ /// + /// {@template tilt.TiltStreamModel.position.zh} /// 当前触发的坐标位置, /// 会触发对应位置的倾斜效果。 /// @@ -30,15 +33,13 @@ class TiltStreamModel { /// 有一个 widget 尺寸为 width: 10, height: 10, /// - (0, 0):会触发最左上的倾斜。 /// - (10, 10):会触发最右下的倾斜。 - /// + /// {@endtemplate} final Offset position; + /// {@template tilt.TiltStreamModel.gesturesType.en} /// Trigger gesture type. /// - /// It is triggered according to the `gesture priority`. - /// - /// Gesture Priority: - /// {@macro tilt.GesturesType.gesturePriority} + /// It is triggered according to the `gesture priority` of [GesturesType]. /// /// If you need to customize the control with animation or other means. /// @@ -52,15 +53,14 @@ class TiltStreamModel { /// /// If `TiltConfig.enableSensorRevert` is configured to be false, /// it will also not revert to the initial state. + /// {@endtemplate} /// /// ------ /// + /// {@template tilt.TiltStreamModel.gesturesType.zh} /// 触发手势类型。 /// - /// 会根据 `手势优先级` 进行触发。 - /// - /// 手势优先级: - /// {@macro tilt.GesturesType.gesturePriority} + /// 会根据 [GesturesType] 的 `手势优先级` 进行触发。 /// /// 如果需要自定义动画或其他方式自行控制, /// @@ -74,29 +74,30 @@ class TiltStreamModel { /// /// 配置 `TiltConfig.enableSensorRevert` 为 false 的情况下, /// 将同样不会复原至初始状态。 - /// + /// {@endtemplate} final GesturesType gesturesType; - /// Whether the gesture is being used. + /// Whether the gesture is currently active. /// - /// It is used to determine if the gesture is being used and - /// will be processed according to the gesture priority. + /// Indicates whether the gesture is currently active, + /// and is used to determine processing based on gesture priority. /// /// Gesture Priority: /// {@macro tilt.GesturesType.gesturePriority} /// /// e.g. - /// If [GesturesType.touch] is never assigned false when triggered, + /// If [GesturesType.touch] is never set to false when triggered, /// gestures with a lower priority than [GesturesType.touch] will never be triggered. /// - /// - true : Gesture being used. - /// - false : Gestures to leave or no longer be used. + /// - true: Gesture is active. + /// - false: Gesture has ended or is no longer active. /// /// ------ /// - /// 手势是否正在使用。 + /// 手势当前是否处于活动状态。 /// - /// 用于确定手势是否正在使用,并根据手势优先级进行处理。 + /// 表明手势当前是否处于活动状态, + /// 并根据手势优先级进行处理判断。 /// /// 手势优先级: /// {@macro tilt.GesturesType.gesturePriority} @@ -105,10 +106,9 @@ class TiltStreamModel { /// 如果在触发 [GesturesType.touch] 的时候永远不赋值为 false, /// 那么优先级低于 [GesturesType.touch] 的手势将永远不会被触发。 /// - /// - true : 手势正在使用。 - /// - false : 手势离开或不再使用。 - /// - final bool gestureUse; + /// - true:手势处于活动状态。 + /// - false:手势已结束或不再处于活动状态。 + final bool isActive; @override bool operator ==(Object other) { @@ -121,13 +121,13 @@ class TiltStreamModel { return other is TiltStreamModel && other.position == position && other.gesturesType == gesturesType && - other.gestureUse == gestureUse; + other.isActive == isActive; } @override int get hashCode => Object.hash( position.hashCode, gesturesType.hashCode, - gestureUse.hashCode, + isActive.hashCode, ); } diff --git a/lib/src/tilt.dart b/lib/src/tilt.dart index 2e37621..a926d7d 100644 --- a/lib/src/tilt.dart +++ b/lib/src/tilt.dart @@ -24,7 +24,7 @@ class Tilt extends TiltWidget { const Tilt({ super.key, required super.child, - super.tiltStreamController, + super.tiltController, super.disable, super.fps, super.tiltConfig, @@ -52,7 +52,7 @@ class Tilt extends TiltWidget { Tilt.base({ super.key, required Widget child, - super.tiltStreamController, + super.tiltController, super.disable, super.fps, super.tiltConfig, @@ -102,7 +102,7 @@ class Tilt extends TiltWidget { Tilt.projector({ super.key, required Widget child, - super.tiltStreamController, + super.tiltController, super.disable, super.fps, super.tiltConfig, diff --git a/lib/src/internal/controllers/fps_timer_controller.dart b/lib/src/utils/fps_throttle.dart similarity index 89% rename from lib/src/internal/controllers/fps_timer_controller.dart rename to lib/src/utils/fps_throttle.dart index 9149737..d81ec9c 100644 --- a/lib/src/internal/controllers/fps_timer_controller.dart +++ b/lib/src/utils/fps_throttle.dart @@ -1,7 +1,7 @@ import 'dart:async' as async show Timer; -class FpsTimerController { - FpsTimerController(this.fps); +class FpsThrottle { + FpsThrottle(this.fps); final int fps; diff --git a/lib/src/utils.dart b/lib/src/utils/utils.dart similarity index 99% rename from lib/src/utils.dart rename to lib/src/utils/utils.dart index aa3cc1c..439d33d 100644 --- a/lib/src/utils.dart +++ b/lib/src/utils/utils.dart @@ -4,7 +4,7 @@ import 'package:flutter/foundation.dart' show kIsWeb, defaultTargetPlatform, TargetPlatform; import 'package:flutter/widgets.dart'; -import 'config/tilt_config.dart'; +import '../config/tilt_config.dart'; abstract final class Utils { /// 传感器平台支持 diff --git a/lib/src/widgets/containers/tilt_base_container.dart b/lib/src/widgets/containers/tilt_base_container.dart index 8681a81..fd370ee 100644 --- a/lib/src/widgets/containers/tilt_base_container.dart +++ b/lib/src/widgets/containers/tilt_base_container.dart @@ -101,19 +101,21 @@ class TiltBaseContainer extends StatelessWidget { @override Widget build(BuildContext context) { return TiltAnimatedBuilder( - builder: (context, tiltData, tiltConfig, child) { + builder: (context, tiltAnimatedState, child) { + final animatedTiltData = tiltAnimatedState.animatedTiltData; + return Transform( alignment: AlignmentDirectional.center, filterQuality: filterQuality, - transform: tiltData.transform, + transform: animatedTiltData.transform, child: Stack( alignment: AlignmentDirectional.center, clipBehavior: Clip.none, children: _buildChildren( child: child, - width: tiltData.width, - height: tiltData.height, - areaProgress: tiltData.areaProgress, + width: animatedTiltData.width, + height: animatedTiltData.height, + areaProgress: animatedTiltData.areaProgress, ), ), ); diff --git a/lib/src/widgets/containers/tilt_parallax_container.dart b/lib/src/widgets/containers/tilt_parallax_container.dart index fee4778..f850f0f 100644 --- a/lib/src/widgets/containers/tilt_parallax_container.dart +++ b/lib/src/widgets/containers/tilt_parallax_container.dart @@ -43,11 +43,11 @@ class TiltParallaxContainer extends StatelessWidget { @override Widget build(BuildContext context) { return TiltAnimatedBuilder( - builder: (context, tiltData, tiltConfig, child) { + builder: (context, tiltAnimatedState, child) { final tiltParallaxTransform = this.tiltParallaxTransform( - tiltData.areaProgress, + tiltAnimatedState.animatedTiltData.areaProgress, size, - tiltConfig.enableReverse, + tiltAnimatedState.tiltConfig.enableReverse, ); return Transform( diff --git a/lib/src/widgets/containers/tilt_projector_container.dart b/lib/src/widgets/containers/tilt_projector_container.dart index 6bb6efb..495af0c 100644 --- a/lib/src/widgets/containers/tilt_projector_container.dart +++ b/lib/src/widgets/containers/tilt_projector_container.dart @@ -130,19 +130,21 @@ class TiltProjectorContainer extends StatelessWidget { @override Widget build(BuildContext context) { return TiltAnimatedBuilder( - builder: (context, tiltData, tiltConfig, child) { + builder: (context, tiltAnimatedState, child) { + final animatedTiltData = tiltAnimatedState.animatedTiltData; + return Transform( alignment: AlignmentDirectional.center, filterQuality: filterQuality, - transform: tiltData.transform, + transform: animatedTiltData.transform, child: Stack( alignment: AlignmentDirectional.center, clipBehavior: Clip.none, children: _buildChildren( child: child, - width: tiltData.width, - height: tiltData.height, - areaProgress: tiltData.areaProgress, + width: animatedTiltData.width, + height: animatedTiltData.height, + areaProgress: animatedTiltData.areaProgress, ), ), ); diff --git a/lib/src/widgets/core/gestures_listener.dart b/lib/src/widgets/core/gestures_listener.dart index d2eba50..9f98e5c 100644 --- a/lib/src/widgets/core/gestures_listener.dart +++ b/lib/src/widgets/core/gestures_listener.dart @@ -1,111 +1,54 @@ -import 'package:flutter/gestures.dart'; import 'package:flutter/widgets.dart'; -import '../../enums.dart'; -import '../../internal/controllers/tilt_gestures_controller.dart'; -import '../../models/tilt_stream_model.dart'; +import '../../config/tilt_config.dart'; +import '../../internal/action_input/sources/tilt_pointer_input_source.dart'; /// 手势监听 -class GesturesListener extends StatefulWidget { +class GesturesListener extends StatelessWidget { /// 手势监听 /// /// 对 Touch [Listener] 和 Mouse [MouseRegion] 的监听触发 const GesturesListener({ super.key, required this.child, - required this.tiltGesturesController, + required this.disable, + required this.tiltConfig, + required this.pointerInputSource, }); final Widget child; + final bool disable; + final TiltConfig tiltConfig; + final TiltPointerInputSource pointerInputSource; - /// 倾斜手势控制器 - final TiltGesturesController tiltGesturesController; - - @override - State createState() => _GesturesListenerState(); -} - -class _GesturesListenerState extends State { - bool get _enableGestureTouch => - widget.tiltGesturesController.tiltConfig.enableGestureTouch; - bool get _enableGestureHover => - widget.tiltGesturesController.tiltConfig.enableGestureHover; - - /// 避免 touch 和 hover 同时触发,导致 hover 离开的时候会“闪现” - bool _isTouch = false; - - /// 避免 hover 未 Enter 时就触发 - bool _isHover = false; + bool get _enableGestureTouch => tiltConfig.enableGestureTouch; + bool get _enableGestureHover => tiltConfig.enableGestureHover; @override Widget build(BuildContext context) { - if (widget.tiltGesturesController.disable) return widget.child; + if (disable) return child; /// 不受滑动影响 return GestureDetector( onVerticalDragUpdate: _enableGestureTouch ? (_) {} : null, onHorizontalDragUpdate: _enableGestureTouch ? (_) {} : null, child: Listener( - onPointerMove: _enableGestureTouch ? _handlePointerMove : null, - onPointerUp: _enableGestureTouch ? _handlePointerUp : null, - onPointerCancel: _enableGestureTouch ? _handlePointerCancel : null, + onPointerMove: + _enableGestureTouch ? pointerInputSource.handlePointerMove : null, + onPointerUp: + _enableGestureTouch ? pointerInputSource.handlePointerUp : null, + onPointerCancel: + _enableGestureTouch ? pointerInputSource.handlePointerCancel : null, child: MouseRegion( - onEnter: _enableGestureHover ? _handleMouseEnter : null, - onHover: _enableGestureHover ? _handleMouseHover : null, - onExit: _enableGestureHover ? _handleMouseExit : null, - child: widget.child, + onEnter: + _enableGestureHover ? pointerInputSource.handleMouseEnter : null, + onHover: + _enableGestureHover ? pointerInputSource.handleMouseHover : null, + onExit: + _enableGestureHover ? pointerInputSource.handleMouseExit : null, + child: child, ), ), ); } - - void _handlePointerMove(PointerMoveEvent e) { - _isTouch = true; - _addToTiltStream(e.localPosition, GesturesType.touch, true); - } - - void _handlePointerUp(PointerUpEvent e) { - _isTouch = false; - _addToTiltStream(e.localPosition, GesturesType.touch, false); - } - - void _handlePointerCancel(PointerCancelEvent e) { - _isTouch = false; - _addToTiltStream(e.localPosition, GesturesType.touch, false); - } - - void _handleMouseEnter(PointerEnterEvent e) { - if (_isTouch) return; - _isHover = true; - } - - void _handleMouseHover(PointerHoverEvent e) { - if (!_isHover || _isTouch) return; - _addToTiltStream(e.localPosition, GesturesType.hover, true); - } - - void _handleMouseExit(PointerExitEvent e) { - if (_isTouch) return; - _isHover = false; - _addToTiltStream(e.localPosition, GesturesType.hover, false); - } - - /// 添加到 Stream - /// - /// - [position] 当前位置 - /// - [gesturesType] 手势类型 - /// - [gestureUse] 手势是否使用 - void _addToTiltStream( - Offset position, - GesturesType gesturesType, - bool gestureUse, - ) { - widget.tiltGesturesController.tiltStreamController.sink.add( - TiltStreamModel( - position: position, - gesturesType: gesturesType, - gestureUse: gestureUse, - ), - ); - } } diff --git a/lib/src/widgets/core/tilt_animated_builder.dart b/lib/src/widgets/core/tilt_animated_builder.dart index c9cd2cc..504c634 100644 --- a/lib/src/widgets/core/tilt_animated_builder.dart +++ b/lib/src/widgets/core/tilt_animated_builder.dart @@ -1,17 +1,18 @@ import 'package:flutter/widgets.dart'; import '../../config/tilt_config.dart'; +import '../../enums.dart'; import '../../internal/provider/tilt_animation_provider.dart'; import '../../internal/provider/tilt_provider.dart'; -import '../../internal/tilt_data.dart'; +import '../../internal/tilt_state.dart'; import '../../models/tilt_data_model.dart'; +import '../../utils/utils.dart'; /// {@template tilt.TiltAnimatedWidgetBuilder.en} /// A builder callback for [TiltAnimatedBuilder] that is called on every animation frame. /// /// - [context]: The build context. -/// - [tiltData]: The current tilt data snapshot, updated on every animation tick. -/// - [tiltConfig]: The current tilt configuration. +/// - [tiltAnimatedState]: The current animation snapshot containing the animated tilt data and target tilt data. /// - [child]: The pre-built subtree passed to [TiltAnimatedBuilder.child], or `null`. /// {@endtemplate} /// @@ -21,17 +22,57 @@ import '../../models/tilt_data_model.dart'; /// [TiltAnimatedBuilder] 的 builder 回调,每帧动画更新时触发。 /// /// - [context]:当前 build context。 -/// - [tiltData]:当前帧的倾斜数据快照,每帧动画更新时变化。 -/// - [tiltConfig]:当前的倾斜配置。 +/// - [tiltAnimatedState]:当前动画快照,包含动画倾斜数据和目标倾斜数据。 /// - [child]:传入 [TiltAnimatedBuilder.child] 的预构建子树,可能为 `null`。 /// {@endtemplate} typedef TiltAnimatedWidgetBuilder = Widget Function( BuildContext context, - TiltDataModel tiltData, - TiltConfig tiltConfig, + TiltAnimatedState tiltAnimatedState, Widget? child, ); +@immutable +class TiltAnimatedState { + const TiltAnimatedState({ + required this.tiltConfig, + required this.animatedTiltData, + required this.targetTiltData, + required this.currentGesturesType, + }); + + /// The current tilt configuration. + /// + /// ------ + /// + /// 当前倾斜配置。 + final TiltConfig tiltConfig; + + /// The current animated tilt data, + /// which represents the interpolated result from the initial state to [targetTiltData] + /// and is used for rendering the current animation progress. + /// + /// ------ + /// + /// 当前动画倾斜数据, + /// 表示从初始状态到 [targetTiltData] 的插值结果,用于渲染当前动画进度。 + final TiltDataModel animatedTiltData; + + /// The current target tilt data, + /// which is the final target data of the animation. + /// + /// ------ + /// + /// 当前目标倾斜数据,动画最终的目标数据。 + final TiltDataModel targetTiltData; + + /// The current gestures type. + /// + /// ------ + /// + /// 当前手势类型。 + final GesturesType currentGesturesType; +} + /// TiltAnimatedBuilder class TiltAnimatedBuilder extends StatelessWidget { /// It can only be used inside the Tilt widget tree. @@ -53,11 +94,14 @@ class TiltAnimatedBuilder extends StatelessWidget { /// ```dart /// Tilt( /// child: TiltAnimatedBuilder( - /// builder: (context, tiltData, tiltConfig, child) { + /// builder: (context, tiltAnimatedState, child) { /// return Transform( /// alignment: AlignmentDirectional.center, - /// transform: tiltData.transform, - /// child: MyCustomWidget(progress: tiltData.areaProgress, child: child), + /// transform: animatedState.animatedTiltData.transform, + /// child: MyCustomWidget( + /// progress: animatedState.animatedTiltData.areaProgress, + /// child: child, + /// ), /// ); /// }, /// child: SomeWidget(), @@ -110,25 +154,49 @@ class TiltAnimatedBuilder extends StatelessWidget { @override Widget build(BuildContext context) { - final tiltProvider = TiltProvider.of(context); + final tiltState = TiltProvider.of(context); final tiltAnimationProvider = TiltAnimationProvider.of(context); final tiltTweenAnimation = tiltAnimationProvider.tiltTweenAnimation; - final tiltConfig = tiltProvider.tiltConfig; return AnimatedBuilder( animation: tiltTweenAnimation, builder: (BuildContext context, Widget? child) { - final areaProgress = tiltTweenAnimation.value; - final tiltData = TiltData( - isInit: tiltProvider.isInit, - width: tiltProvider.width, - height: tiltProvider.height, - areaProgress: areaProgress, - tiltConfig: tiltConfig, - ).toModel(); - return builder(context, tiltData, tiltConfig, child); + final tiltAnimatedState = TiltAnimatedState( + tiltConfig: tiltState.tiltConfig, + animatedTiltData: _animatedTiltDataModel( + tiltState, + tiltTweenAnimation.value, + ), + targetTiltData: tiltState.toModel(), + currentGesturesType: tiltState.currentGesturesType, + ); + + return builder(context, tiltAnimatedState, child); }, child: child, ); } } + +TiltDataModel _animatedTiltDataModel( + TiltState tiltState, + Offset animatedAreaProgress, +) { + return TiltDataModel( + width: tiltState.width, + height: tiltState.height, + position: Utils.progressPosition( + tiltState.width, + tiltState.height, + animatedAreaProgress, + ), + transform: tiltState.isInit && !tiltState.disable + ? tiltState.tiltTransformFor(animatedAreaProgress) + : Matrix4.identity(), + areaProgress: animatedAreaProgress, + angle: Utils.rotateAxis( + animatedAreaProgress * tiltState.tiltConfig.angle, + tiltState.tiltConfig.enableReverse, + ), + ); +} diff --git a/lib/src/widgets/core/tilt_stream_builder.dart b/lib/src/widgets/core/tilt_stream_builder.dart deleted file mode 100644 index a15ad6b..0000000 --- a/lib/src/widgets/core/tilt_stream_builder.dart +++ /dev/null @@ -1,63 +0,0 @@ -import 'package:flutter/widgets.dart'; - -import '../../internal/controllers/tilt_gestures_controller.dart'; -import '../../models/tilt_stream_model.dart'; - -/// Tilt StreamBuilder -class TiltStreamBuilder extends StatefulWidget { - /// Tilt StreamBuilder - /// - /// 手势相关输入操作 - const TiltStreamBuilder({ - super.key, - required this.tiltGesturesController, - required this.builder, - }); - - /// 倾斜手势控制器 - final TiltGesturesController tiltGesturesController; - - /// Stream builder - final Widget Function(BuildContext, AsyncSnapshot) builder; - - @override - State createState() => _TiltStreamBuilderState(); -} - -class _TiltStreamBuilderState extends State { - late final Widget Function(BuildContext, AsyncSnapshot) - _builder = widget.builder; - - @override - void initState() { - super.initState(); - widget.tiltGesturesController.initSensors(context); - } - - @override - void dispose() { - widget.tiltGesturesController.dispose(); - super.dispose(); - } - - @override - void didUpdateWidget(TiltStreamBuilder oldWidget) { - super.didUpdateWidget(oldWidget); - if (!identical( - oldWidget.tiltGesturesController, - widget.tiltGesturesController, - )) { - oldWidget.tiltGesturesController.disposeSensors(); - widget.tiltGesturesController.initSensors(context); - } - } - - @override - Widget build(BuildContext context) { - return StreamBuilder( - stream: widget.tiltGesturesController.tiltStream, - initialData: widget.tiltGesturesController.initialTiltStreamModel, - builder: _builder, - ); - } -} diff --git a/lib/src/widgets/core/tilt_widget.dart b/lib/src/widgets/core/tilt_widget.dart index da9f930..b351b5c 100644 --- a/lib/src/widgets/core/tilt_widget.dart +++ b/lib/src/widgets/core/tilt_widget.dart @@ -1,19 +1,20 @@ -import 'dart:async' as async; +import 'dart:async'; +import 'package:flutter/material.dart'; import 'package:flutter/widgets.dart'; import '../../config/tilt_config.dart'; +import '../../controllers/tilt_controller.dart'; import '../../enums.dart'; -import '../../internal/controllers/fps_timer_controller.dart'; -import '../../internal/controllers/tilt_gestures_controller.dart'; +import '../../internal/action_input/tilt_action_input_coordinator.dart'; import '../../internal/mixin/tilt_tween_animation_mixin.dart'; import '../../internal/provider/tilt_animation_provider.dart'; import '../../internal/provider/tilt_provider.dart'; -import '../../internal/tilt_data.dart'; +import '../../internal/tilt_state.dart'; import '../../models/tilt_stream_model.dart'; -import '../../utils.dart'; +import '../../utils/fps_throttle.dart'; +import '../../utils/utils.dart'; import 'gestures_listener.dart'; -import 'tilt_stream_builder.dart'; import 'widget_size_getter.dart'; /// TiltWidget @@ -27,7 +28,7 @@ class TiltWidget extends StatefulWidget { /// {@endtemplate} /// /// {@template tilt.TiltWidget.param.en} - /// - [tiltStreamController] : `StreamController.broadcast()` to control the tilt. + /// - [tiltController] : Controller for custom tilt input. /// - [disable] : Disable all effects. /// - [fps] : Gesture triggered frames. /// - [tiltConfig] : Tilt effect config. @@ -42,7 +43,7 @@ class TiltWidget extends StatefulWidget { /// {@endtemplate} /// /// {@template tilt.TiltWidget.param.zh} - /// - [tiltStreamController]:通过 `StreamController.broadcast()` 来自定义控制倾斜。 + /// - [tiltController]:用于自定义倾斜输入的控制器。 /// - [disable]:禁用所有效果。 /// - [fps]:手势触发的帧数。 /// - [tiltConfig]:倾斜效果配置。 @@ -53,7 +54,7 @@ class TiltWidget extends StatefulWidget { const TiltWidget({ super.key, required this.child, - this.tiltStreamController, + this.tiltController, this.disable = false, this.fps = 120, this.tiltConfig = const TiltConfig(), @@ -63,15 +64,14 @@ class TiltWidget extends StatefulWidget { final Widget child; - /// Tilt Stream Controller + /// Tilt Controller /// - /// `StreamController.broadcast()` to control the tilt. + /// Controller for custom tilt input. /// /// ------ /// - /// 通过 `StreamController.broadcast()` 来自定义控制倾斜。 - /// - final async.StreamController? tiltStreamController; + /// 用于自定义倾斜输入的控制器。 + final TiltController? tiltController; /// Disable all effects. /// @@ -113,116 +113,113 @@ class TiltWidget extends StatefulWidget { } class _TiltWidgetState extends State { - /// 初始坐标区域进度 - Offset get _initAreaProgress => widget.tiltConfig.initial ?? Offset.zero; - - /// 是否初始化 - bool _isInit = false; - - /// 尺寸 - double _width = 0.0, _height = 0.0; - - /// 当前坐标的区域进度 - late Offset _areaProgress = _initAreaProgress; + late TiltState _tiltState; + late FpsThrottle _fpsThrottle; + late TiltActionInputCoordinator _tiltActionInputCoordinator; - /// 是否正在移动 - bool _isMove = false; + StreamSubscription? _tiltStreamSubscription; - /// 当前手势类型 - GesturesType _currentGesturesType = GesturesType.none; - - /// 倾斜手势控制器 - late TiltGesturesController _tiltGesturesController; - - /// 默认 TiltStreamController + /// 默认 TiltController /// - /// [widget.tiltStreamController] 为 null 时使用 - final async.StreamController _kDefaultTiltStreamController = - async.StreamController.broadcast(); - - late FpsTimerController _fpsTimerController; + /// [widget.tiltController] 为 null 时使用 + final TiltController _kDefaultTiltController = TiltController(); /// 当前坐标 - late Offset _currentPosition = Utils.progressPosition( - _width, - _height, - _initAreaProgress, - ); + late Offset _currentPosition; + + /// 初始坐标区域进度 + Offset get _initAreaProgress => widget.tiltConfig.initial ?? Offset.zero; + + TiltController get _effectiveTiltController => + widget.tiltController ?? _kDefaultTiltController; @override void initState() { super.initState(); - _initControllers(); + _tiltState = TiltState( + isInit: false, + width: 0.0, + height: 0.0, + areaProgress: _initAreaProgress, + isActive: false, + currentGesturesType: GesturesType.none, + tiltConfig: widget.tiltConfig, + ); + _currentPosition = _tiltState.position; + + /// 初始化 FpsThrottle + _fpsThrottle = FpsThrottle(widget.fps); + + _initCoordinator(); } @override void dispose() { - _kDefaultTiltStreamController.close(); - _fpsTimerController.dispose(); - _tiltGesturesController.dispose(); + _tiltStreamSubscription?.cancel(); + _kDefaultTiltController.dispose(); + _fpsThrottle.dispose(); + _tiltActionInputCoordinator.dispose(); super.dispose(); } @override void didUpdateWidget(TiltWidget oldWidget) { super.didUpdateWidget(oldWidget); - if (_shouldReinitControllers(oldWidget)) { - _initControllers(oldWidget: oldWidget); + + if (oldWidget.fps != widget.fps) { + _fpsThrottle.dispose(); + _fpsThrottle = FpsThrottle(widget.fps); + } + + if (_shouldReinit(oldWidget)) { + _tiltState = _tiltState.copyWith(tiltConfig: widget.tiltConfig); + _reinitCoordinator(); } } @override Widget build(BuildContext context) { return GesturesListener( - tiltGesturesController: _tiltGesturesController, - child: TiltStreamBuilder( - tiltGesturesController: _tiltGesturesController, - builder: (context, snapshot) { - _handleGesturesStream(snapshot.data); - return TiltProvider( - isInit: _isInit, - width: _width, - height: _height, - areaProgress: _areaProgress, - isMove: _isMove, - currentGesturesType: _currentGesturesType, - tiltConfig: widget.tiltConfig, - child: _TiltAnimationProviderWrapper( - child: WidgetSizeGetter(onSize: _onResize, child: widget.child), - ), - ); - }, + disable: widget.disable, + tiltConfig: widget.tiltConfig, + pointerInputSource: _tiltActionInputCoordinator.pointerInputSource, + child: TiltProvider( + data: _tiltState, + child: _TiltAnimationProviderWrapper( + child: WidgetSizeGetter(onSize: _onResize, child: widget.child), + ), ), ); } - /// 初始化控制器 - /// - /// - [oldWidget] 旧 Wdiget 一般用于 [didUpdateWidget] - void _initControllers({TiltWidget? oldWidget}) { - /// 是否需要取消 [TiltGesturesController] 中的手势协调器,避免泄露 - if (oldWidget != null && oldWidget.tiltConfig != widget.tiltConfig) { - _tiltGesturesController.cancelGesturesHarmonizerTimer(); - } - - /// 初始化 TiltGesturesController - final tiltStreamController = - widget.tiltStreamController ?? _kDefaultTiltStreamController; - _tiltGesturesController = TiltGesturesController( - tiltStreamController: tiltStreamController, + /// 初始化 [TiltActionInputCoordinator] 和输入源 + void _initCoordinator() { + _tiltActionInputCoordinator = TiltActionInputCoordinator( + tiltController: _effectiveTiltController, disable: widget.disable, fps: widget.fps, tiltConfig: widget.tiltConfig, initialPosition: _currentPosition, ); + _tiltActionInputCoordinator.init(context); + _listenToTiltStream(); + } + + /// 重新初始化 Coordinator + void _reinitCoordinator() { + final oldCoordinator = _tiltActionInputCoordinator; + + /// 销毁旧的监听和协调器 + _tiltStreamSubscription?.cancel(); + oldCoordinator.dispose(); - /// 初始化 FpsTimerController - _fpsTimerController = FpsTimerController(widget.fps); + /// 创建并初始化全新的协调器 + _initCoordinator(); } - /// 判断是否需要重新初始化控制器 - bool _shouldReinitControllers(TiltWidget oldWidget) { - return oldWidget.tiltStreamController != widget.tiltStreamController || + /// 判断是否需要重新初始化 + bool _shouldReinit(TiltWidget oldWidget) { + return oldWidget.tiltController != widget.tiltController || oldWidget.disable != widget.disable || oldWidget.fps != widget.fps || oldWidget.tiltConfig != widget.tiltConfig; @@ -230,31 +227,42 @@ class _TiltWidgetState extends State { /// 调整尺寸及初始参数 void _onResize(Size size) { - final oldSize = Size(_width, _height); + final oldSize = Size(_tiltState.width, _tiltState.height); if (oldSize != size) { - _isInit = true; - _width = size.width; - _height = size.height; setState(() { + _tiltState = _tiltState.copyWith( + isInit: true, + width: size.width, + height: size.height, + ); _currentPosition = Utils.progressPosition( - _width, - _height, + _tiltState.width, + _tiltState.height, _initAreaProgress, ); }); } } + /// 监听 Tilt 输入流 + void _listenToTiltStream() { + _tiltStreamSubscription?.cancel(); + _tiltStreamSubscription = + _tiltActionInputCoordinator.stream?.listen((data) { + _handleGesturesStream(data); + }); + } + /// 处理手势 Stream 触发 void _handleGesturesStream(TiltStreamModel? tiltStreamModel) { if (tiltStreamModel == null) return; if (tiltStreamModel.gesturesType == GesturesType.none) return; - if (!_isInit || widget.disable) return; + if (!_tiltState.isInit || widget.disable) return; switch (tiltStreamModel.gesturesType) { case GesturesType.none: break; case GesturesType.touch || GesturesType.hover || GesturesType.controller: - if (tiltStreamModel.gestureUse) { + if (tiltStreamModel.isActive) { _onGesturesMove( tiltStreamModel.position, tiltStreamModel.gesturesType, @@ -271,8 +279,8 @@ class _TiltWidgetState extends State { tiltStreamModel.position * widget.tiltConfig.sensorFactor; _onGesturesSensorsRevert(); _currentPosition = Utils.constraintsPosition( - _width, - _height, + _tiltState.width, + _tiltState.height, _currentPosition, ); _onGesturesMove(_currentPosition, tiltStreamModel.gesturesType); @@ -283,20 +291,16 @@ class _TiltWidgetState extends State { /// /// [offset] 当前坐标 void _onGesturesMove(Offset offset, GesturesType gesturesType) { - if (!_isInit || widget.disable) return; - if (!_fpsTimerController.shouldTrigger()) return; + if (!_tiltState.isInit || widget.disable) return; + if (!_fpsThrottle.shouldTrigger()) return; if (widget.tiltConfig.enableOutsideAreaMove || - Utils.isInRange(_width, _height, offset)) { - _currentPosition = offset; - _areaProgress = Utils.p2cAreaProgress( - _width, - _height, - offset, - widget.tiltConfig.direction, - ); - _isMove = true; - _currentGesturesType = gesturesType; - _onGestureMove(_areaProgress, gesturesType); + Utils.isInRange(_tiltState.width, _tiltState.height, offset)) { + setState(() { + _currentPosition = offset; + _tiltState = _tiltState.moveTo(offset, gesturesType); + }); + + _onGestureMove(_tiltState.areaProgress, gesturesType); } else { _onGesturesRevert(offset, gesturesType); } @@ -306,22 +310,18 @@ class _TiltWidgetState extends State { /// /// [offset] 当前坐标 void _onGesturesRevert(Offset offset, GesturesType gesturesType) { - if (!_isInit || widget.disable || !_isMove) return; + if (!_tiltState.isInit || widget.disable || !_tiltState.isActive) return; /// 是否还原的取值 - final position = widget.tiltConfig.enableRevert - ? Utils.progressPosition(_width, _height, _initAreaProgress) - : _currentPosition; - _currentPosition = position; - _areaProgress = Utils.p2cAreaProgress( - _width, - _height, - position, - widget.tiltConfig.direction, + final position = _tiltState.resolveRevertPosition( + currentPosition: _currentPosition, + initialAreaProgress: _initAreaProgress, ); - _isMove = false; - _currentGesturesType = gesturesType; - _onGestureLeave(_areaProgress, gesturesType); + setState(() { + _currentPosition = position; + _tiltState = _tiltState.revertTo(position, gesturesType); + }); + _onGestureLeave(_tiltState.areaProgress, gesturesType); } /// 手势传感器复原触发 @@ -332,8 +332,8 @@ class _TiltWidgetState extends State { /// 默认坐标 final initPosition = Utils.progressPosition( - _width, - _height, + _tiltState.width, + _tiltState.height, _initAreaProgress, ); @@ -377,21 +377,16 @@ class _TiltWidgetState extends State { Offset areaProgress, GesturesType gesturesType, ) { - if (callback != null) { - WidgetsBinding.instance.addPostFrameCallback((_) { - if (mounted) { - callback( - TiltData( - isInit: _isInit, - width: _width, - height: _height, + if (callback != null && mounted) { + callback( + _tiltState + .copyWith( areaProgress: areaProgress, - tiltConfig: widget.tiltConfig, - ).toModel(), - gesturesType, - ); - } - }); + currentGesturesType: gesturesType, + ) + .toModel(), + gesturesType, + ); } } } diff --git a/lib/src/widgets/effects/tilt_light.dart b/lib/src/widgets/effects/tilt_light.dart index 346a35b..890cc94 100644 --- a/lib/src/widgets/effects/tilt_light.dart +++ b/lib/src/widgets/effects/tilt_light.dart @@ -3,7 +3,7 @@ import 'package:flutter/widgets.dart'; import '../../config/tilt_light_config.dart'; import '../../enums.dart'; import '../../internal/mixin/tilt_decoration_mixin.dart'; -import '../../utils.dart'; +import '../../utils/utils.dart'; /// 光源 class TiltLight extends StatelessWidget with TiltDecorationMixin { diff --git a/lib/src/widgets/effects/tilt_shadow.dart b/lib/src/widgets/effects/tilt_shadow.dart index 0d656fe..810f30f 100644 --- a/lib/src/widgets/effects/tilt_shadow.dart +++ b/lib/src/widgets/effects/tilt_shadow.dart @@ -7,7 +7,7 @@ import '../../config/tilt_light_config.dart'; import '../../config/tilt_shadow_config.dart'; import '../../enums.dart'; import '../../internal/mixin/tilt_decoration_mixin.dart'; -import '../../utils.dart'; +import '../../utils/utils.dart'; abstract class TiltShadow extends StatelessWidget with TiltDecorationMixin { diff --git a/test/internal/action_input/sources/tilt_pointer_input_source_test.dart b/test/internal/action_input/sources/tilt_pointer_input_source_test.dart new file mode 100644 index 0000000..294fa8a --- /dev/null +++ b/test/internal/action_input/sources/tilt_pointer_input_source_test.dart @@ -0,0 +1,101 @@ +import 'package:flutter/gestures.dart'; +import 'package:flutter_test/flutter_test.dart'; +import 'package:flutter_tilt/src/controllers/tilt_controller.dart'; +import 'package:flutter_tilt/src/enums.dart'; +import 'package:flutter_tilt/src/internal/action_input/sources/tilt_pointer_input_source.dart'; +import 'package:flutter_tilt/src/models/tilt_stream_model.dart'; + +void main() { + group('TiltPointerInputSource ::', () { + late TiltController tiltController; + late TiltPointerInputSource inputSource; + + setUp(() { + tiltController = TiltController(); + inputSource = TiltPointerInputSource( + tiltController: tiltController, + ); + }); + + tearDown(() { + tiltController.dispose(); + }); + + test('emits touch move and leave events', () async { + final events = []; + final subscription = tiltController.stream.listen(events.add); + + inputSource.handlePointerMove( + const PointerMoveEvent(position: Offset(3, 4)), + ); + inputSource.handlePointerUp( + const PointerUpEvent(position: Offset(3, 4)), + ); + + await Future.delayed(Duration.zero); + + expect(events, hasLength(2)); + expect(events.first.gesturesType, GesturesType.touch); + expect(events.first.isActive, isTrue); + expect(events.first.position, const Offset(3, 4)); + expect(events.last.gesturesType, GesturesType.touch); + expect(events.last.isActive, isFalse); + + await subscription.cancel(); + }); + + test('requires hover enter and blocks hover while touching', () async { + final events = []; + final subscription = tiltController.stream.listen(events.add); + + /// Hover events should be blocked until a hover enter is received + inputSource.handleMouseHover( + const PointerHoverEvent(position: Offset(1, 1)), + ); + + /// Hover enter, should be accepted + inputSource.handleMouseEnter( + const PointerEnterEvent(position: Offset(1, 1)), + ); + + /// Now hover events should be accepted + inputSource.handleMouseHover( + const PointerHoverEvent(position: Offset(2, 2)), + ); + + /// Touch events should still be accepted + inputSource.handlePointerMove( + const PointerMoveEvent(position: Offset(9, 9)), + ); + + /// While touching, hover should be blocked + inputSource.handleMouseHover( + const PointerHoverEvent(position: Offset(4, 4)), + ); + + /// Touch leave + inputSource.handlePointerCancel( + const PointerCancelEvent(position: Offset(9, 9)), + ); + + /// Now hover should be accepted again + inputSource.handleMouseExit( + const PointerExitEvent(position: Offset(2, 2)), + ); + + await Future.delayed(Duration.zero); + expect(events, hasLength(4)); + expect(events[0].gesturesType, GesturesType.hover); + expect(events[0].isActive, isTrue); + expect(events[0].position, const Offset(2, 2)); + expect(events[1].gesturesType, GesturesType.touch); + expect(events[1].isActive, isTrue); + expect(events[2].gesturesType, GesturesType.touch); + expect(events[2].isActive, isFalse); + expect(events[3].gesturesType, GesturesType.hover); + expect(events[3].isActive, isFalse); + + await subscription.cancel(); + }); + }); +} diff --git a/test/internal/controllers/tilt_gestures_controller_test.dart b/test/internal/action_input/sources/tilt_sensor_input_source_test.dart similarity index 52% rename from test/internal/controllers/tilt_gestures_controller_test.dart rename to test/internal/action_input/sources/tilt_sensor_input_source_test.dart index 1725a98..35ac8b9 100644 --- a/test/internal/controllers/tilt_gestures_controller_test.dart +++ b/test/internal/action_input/sources/tilt_sensor_input_source_test.dart @@ -5,53 +5,36 @@ import 'package:flutter/foundation.dart' import 'package:flutter/services.dart' show DeviceOrientation; import 'package:flutter/widgets.dart'; import 'package:flutter_test/flutter_test.dart'; -import 'package:flutter_tilt/src/config/tilt_config.dart'; -import 'package:flutter_tilt/src/enums.dart'; -import 'package:flutter_tilt/src/internal/controllers/tilt_gestures_controller.dart'; -import 'package:flutter_tilt/src/models/tilt_stream_model.dart'; +import 'package:flutter_tilt/flutter_tilt.dart'; +import 'package:flutter_tilt/src/internal/action_input/sources/tilt_sensor_input_source.dart'; import 'package:sensors_plus/sensors_plus.dart' show AccelerometerEvent; -import '../../sensors_mock.dart'; +import '../../../sensors_mock.dart'; void main() { TestWidgetsFlutterBinding.ensureInitialized(); - group('TiltGesturesController ::', () { - late StreamController tiltStreamController; - late TiltGesturesController controller; - late TiltConfig tiltConfig; - late Offset initialPosition; + group('TiltSensorInputSource ::', () { + const fps = 60; + late TiltController tiltController; + late TiltSensorInputSource inputSource; late int frameDurationMs; setUp(() { - tiltStreamController = StreamController.broadcast(); - tiltConfig = const TiltConfig( - enableGestureSensors: true, - leaveDuration: Duration(milliseconds: 300), - controllerLeaveDuration: Duration(milliseconds: 500), - ); - initialPosition = const Offset(10, 10); - controller = TiltGesturesController( - tiltStreamController: tiltStreamController, + tiltController = TiltController(); + inputSource = TiltSensorInputSource( + tiltController: tiltController, disable: false, - fps: 60, - tiltConfig: tiltConfig, - initialPosition: initialPosition, + fps: fps, + tiltConfig: const TiltConfig(enableGestureSensors: true), ); - frameDurationMs = (1000 / controller.fps) ~/ 1; + frameDurationMs = (1000 / fps) ~/ 1; }); tearDown(() { - controller.dispose(); - tiltStreamController.close(); - }); - - test('Initial position', () { - expect(controller.enableTiltStream, isTrue); - expect( - controller.latestTiltStreamModel.position, - equals(initialPosition), - ); + inputSource.dispose(); + tiltController.dispose(); + debugDefaultTargetPlatformOverride = null; }); testWidgets('Sensors stream subscriptions', (WidgetTester tester) async { @@ -90,13 +73,13 @@ void main() { Builder( builder: (BuildContext context) { /// 初始化传感器 - controller.initSensors(context); + inputSource.init(context); return const SizedBox(); }, ), ); - firstSubscription = tiltStreamController.stream.listen( + firstSubscription = tiltController.stream.listen( (TiltStreamModel tiltStreamModel) { currentPosition = tiltStreamModel.position; currentGesturesType = tiltStreamModel.gesturesType; @@ -117,7 +100,7 @@ void main() { Duration(milliseconds: frameDurationMs * 2), ); - final replaySubscription = tiltStreamController.stream.listen( + final replaySubscription = tiltController.stream.listen( (TiltStreamModel tiltStreamModel) { if (tiltStreamModel.gesturesType == GesturesType.sensors) { replayedFrameCount++; @@ -141,108 +124,48 @@ void main() { expect(sensorFrameCount, 3); expect(replayedFrameCount, 3); - expect(controller.gyroscopeSubscription, isNotNull); - expect(controller.accelerometerSubscription, isNotNull); + expect(inputSource.gyroscopeSubscription, isNotNull); + expect(inputSource.accelerometerSubscription, isNotNull); await tester.runAsync(() async { /// 取消传感器订阅 - controller.disposeSensors(); + inputSource.dispose(); }); - expect(controller.gyroscopeSubscription, isNull); - expect(controller.accelerometerSubscription, isNull); + expect(inputSource.gyroscopeSubscription, isNull); + expect(inputSource.accelerometerSubscription, isNull); debugDefaultTargetPlatformOverride = null; }); - testWidgets('initSensors only subscribes once', - (WidgetTester tester) async { + testWidgets('initSensors only subscribes once', ( + WidgetTester tester, + ) async { debugDefaultTargetPlatformOverride = TargetPlatform.android; - SensorsMock.initMockSensorsMethodChannel([ - SensorsMock.accelerometerMethodName, - SensorsMock.gyroscopeMethodName, - ]); - final date = DateTime.now(); - SensorsMock.initMockSensorChannelData( - SensorsMock.accelerometerChannelName, - >[ - [1.0, 2.0, 3.0, date.microsecondsSinceEpoch.toDouble()], - ], - ); - SensorsMock.initMockSensorChannelData( - SensorsMock.gyroscopeChannelName, - >[ - [3.0, 4.0, 5.0, date.microsecondsSinceEpoch.toDouble()], - ], - ); - await tester.runAsync( - () async { - await tester.pumpWidget( - Builder( - builder: (BuildContext context) { - controller.initSensors(context); - controller.initSensors(context); - return const SizedBox(); - }, - ), - ); - }, - ); + await tester.runAsync(() async { + await tester.pumpWidget( + Builder( + builder: (BuildContext context) { + expect(inputSource.sensorsInitialized, isFalse); - expect(controller.gyroscopeSubscription, isNotNull); - expect(controller.accelerometerSubscription, isNotNull); - debugDefaultTargetPlatformOverride = null; - }); + inputSource.init(context); + expect(inputSource.sensorsInitialized, isTrue); + expect(inputSource.gyroscopeSubscription, isNotNull); + expect(inputSource.accelerometerSubscription, isNotNull); - test('Tilt stream updates (sensors)', () async { - const deviceOrientationList = DeviceOrientation.values; - const testModel = TiltStreamModel( - position: Offset(10, 10), - gesturesType: GesturesType.sensors, - ); - final testSensorsX = testModel.position.dx; - final testSensorsY = testModel.position.dy; - - controller.tiltStream?.listen((_) => {}); - for (final deviceOrientation in deviceOrientationList) { - controller.deviceOrientation = deviceOrientation; - controller.tiltStreamController.sink.add(testModel); - await Future.delayed(Duration.zero); - - final testPosition = switch (deviceOrientation) { - DeviceOrientation.portraitUp => Offset(testSensorsX, testSensorsY), - DeviceOrientation.portraitDown => -Offset(testSensorsX, testSensorsY), - DeviceOrientation.landscapeLeft => - Offset(testSensorsY, -testSensorsX), - DeviceOrientation.landscapeRight => - Offset(-testSensorsY, testSensorsX), - }; - - expect(controller.latestTiltStreamModel.position, testPosition); - } - - controller.tiltStreamController.sink.add( - const TiltStreamModel( - position: Offset(10, 10), - gesturesType: GesturesType.controller, - ), - ); - await Future.delayed(Duration.zero); - expect( - controller.latestTiltStreamModel.gesturesType == - GesturesType.controller, - isTrue, - ); + inputSource.init(context); + inputSource.dispose(); + expect(inputSource.sensorsInitialized, isFalse); + return const SizedBox(); + }, + ), + ); + }); - controller.tiltStreamController.sink.add(testModel); - await Future.delayed(Duration.zero); - expect( - controller.latestTiltStreamModel.gesturesType == GesturesType.sensors, - isFalse, - ); + debugDefaultTargetPlatformOverride = null; }); - testWidgets('Update device orientation', (WidgetTester tester) async { + testWidgets('updates device orientation', (WidgetTester tester) async { const landscapeMediaQueryData = MediaQueryData(size: Size(800.0, 600.0)); const portraitMediaQueryData = MediaQueryData(size: Size(600.0, 800.0)); @@ -254,7 +177,7 @@ void main() { data: landscapeMediaQueryData, child: Builder( builder: (BuildContext context) { - controller.handleDeviceOrientationEvent( + inputSource.handleDeviceOrientationEvent( context, landscapeLeftEvent, ); @@ -265,7 +188,7 @@ void main() { ); expect( - controller.deviceOrientation, + inputSource.deviceOrientation, equals(DeviceOrientation.landscapeLeft), ); @@ -277,7 +200,7 @@ void main() { data: landscapeMediaQueryData, child: Builder( builder: (BuildContext context) { - controller.handleDeviceOrientationEvent( + inputSource.handleDeviceOrientationEvent( context, landscapeLRightEvent, ); @@ -288,7 +211,7 @@ void main() { ); expect( - controller.deviceOrientation, + inputSource.deviceOrientation, equals(DeviceOrientation.landscapeRight), ); @@ -299,7 +222,10 @@ void main() { data: portraitMediaQueryData, child: Builder( builder: (BuildContext context) { - controller.handleDeviceOrientationEvent(context, portraitUpEvent); + inputSource.handleDeviceOrientationEvent( + context, + portraitUpEvent, + ); return const SizedBox(); }, ), @@ -307,7 +233,7 @@ void main() { ); expect( - controller.deviceOrientation, + inputSource.deviceOrientation, equals(DeviceOrientation.portraitUp), ); @@ -319,7 +245,7 @@ void main() { data: portraitMediaQueryData, child: Builder( builder: (BuildContext context) { - controller.handleDeviceOrientationEvent( + inputSource.handleDeviceOrientationEvent( context, portraitDownEvent, ); @@ -330,7 +256,7 @@ void main() { ); expect( - controller.deviceOrientation, + inputSource.deviceOrientation, equals(DeviceOrientation.portraitDown), ); }); diff --git a/test/internal/action_input/tilt_action_input_resolver_test.dart b/test/internal/action_input/tilt_action_input_resolver_test.dart new file mode 100644 index 0000000..34f80ae --- /dev/null +++ b/test/internal/action_input/tilt_action_input_resolver_test.dart @@ -0,0 +1,124 @@ +import 'package:flutter/services.dart' show DeviceOrientation; +import 'package:flutter_test/flutter_test.dart'; +import 'package:flutter_tilt/src/config/tilt_config.dart'; +import 'package:flutter_tilt/src/enums.dart'; +import 'package:flutter_tilt/src/internal/action_input/tilt_action_input_resolver.dart'; +import 'package:flutter_tilt/src/models/tilt_stream_model.dart'; + +void main() { + group('TiltActionInputResolver ::', () { + late TiltActionInputResolver resolver; + + setUp(() { + resolver = TiltActionInputResolver( + initialTiltStreamModel: const TiltStreamModel( + position: Offset(10, 10), + gesturesType: GesturesType.none, + isActive: false, + ), + tiltConfig: const TiltConfig( + leaveDuration: Duration(milliseconds: 10), + controllerLeaveDuration: Duration(milliseconds: 15), + ), + ); + }); + + tearDown(() { + resolver.dispose(); + }); + + test('keeps higher priority input active over sensors', () { + resolver.resolve( + const TiltStreamModel( + position: Offset(1, 1), + gesturesType: GesturesType.controller, + ), + deviceOrientation: DeviceOrientation.portraitUp, + ); + + final result = resolver.resolve( + const TiltStreamModel( + position: Offset(2, 2), + gesturesType: GesturesType.sensors, + ), + deviceOrientation: DeviceOrientation.portraitUp, + ); + + expect(result.gesturesType, GesturesType.controller); + expect(result.position, const Offset(1, 1)); + }); + + test('allows sensors again after gesture leave conflict window', () async { + resolver.resolve( + const TiltStreamModel( + position: Offset(3, 3), + gesturesType: GesturesType.touch, + isActive: true, + ), + deviceOrientation: DeviceOrientation.portraitUp, + ); + + resolver.resolve( + const TiltStreamModel( + position: Offset(3, 3), + gesturesType: GesturesType.touch, + isActive: false, + ), + deviceOrientation: DeviceOrientation.portraitUp, + ); + + /// Attempt to use sensors immediately after touch leave, should be blocked + final blocked = resolver.resolve( + const TiltStreamModel( + position: Offset(5, 6), + gesturesType: GesturesType.sensors, + ), + deviceOrientation: DeviceOrientation.portraitUp, + ); + + expect(blocked.gesturesType, GesturesType.touch); + expect(blocked.isActive, isFalse); + + /// Wait for the leave duration to pass before trying again + await Future.delayed(const Duration(milliseconds: 20)); + + /// Now sensors should be accepted again + final accepted = resolver.resolve( + const TiltStreamModel( + position: Offset(5, 6), + gesturesType: GesturesType.sensors, + ), + deviceOrientation: DeviceOrientation.portraitUp, + ); + + expect(accepted.gesturesType, GesturesType.sensors); + expect(accepted.position, const Offset(5, 6)); + }); + + test('maps sensor positions by device orientation (all directions)', () { + const input = Offset(4, 7); + final cases = { + DeviceOrientation.portraitUp: input, + DeviceOrientation.portraitDown: const Offset(-4, -7), + DeviceOrientation.landscapeLeft: const Offset(7, -4), + DeviceOrientation.landscapeRight: const Offset(-7, 4), + }; + for (final entry in cases.entries) { + final result = resolver.resolve( + const TiltStreamModel( + position: input, + gesturesType: GesturesType.sensors, + ), + deviceOrientation: entry.key, + ); + expect( + result.position, + entry.value, + reason: 'orientation: ${entry.key}', + ); + expect(result.gesturesType, GesturesType.sensors); + expect(result.isActive, isTrue); + } + }); + }); +} diff --git a/test/internal/mixin/tilt_tween_animation_mixin_test.dart b/test/internal/mixin/tilt_tween_animation_mixin_test.dart index 5d62487..0fa580a 100644 --- a/test/internal/mixin/tilt_tween_animation_mixin_test.dart +++ b/test/internal/mixin/tilt_tween_animation_mixin_test.dart @@ -4,6 +4,7 @@ import 'package:flutter_tilt/src/config/tilt_config.dart'; import 'package:flutter_tilt/src/enums.dart'; import 'package:flutter_tilt/src/internal/mixin/tilt_tween_animation_mixin.dart'; import 'package:flutter_tilt/src/internal/provider/tilt_provider.dart'; +import 'package:flutter_tilt/src/internal/tilt_state.dart'; void main() { group('TiltTweenAnimationMixin ::', () { @@ -218,13 +219,15 @@ class TiltTweenAnimationMixinTestWidget extends StatelessWidget { @override Widget build(BuildContext context) { return const TiltProvider( - tiltConfig: TiltConfig(), - isInit: true, - width: 10.0, - height: 10.0, - areaProgress: Offset.zero, - isMove: true, - currentGesturesType: GesturesType.touch, + data: TiltState( + tiltConfig: TiltConfig(), + isInit: true, + width: 10.0, + height: 10.0, + areaProgress: Offset.zero, + isActive: true, + currentGesturesType: GesturesType.touch, + ), child: TiltTweenAnimationMixinTest(), ); } diff --git a/test/internal/tilt_data_test.dart b/test/internal/tilt_data_test.dart deleted file mode 100644 index 841e6a7..0000000 --- a/test/internal/tilt_data_test.dart +++ /dev/null @@ -1,73 +0,0 @@ -import 'package:flutter/widgets.dart'; -import 'package:flutter_test/flutter_test.dart'; -import 'package:flutter_tilt/src/config/tilt_config.dart'; -import 'package:flutter_tilt/src/internal/tilt_data.dart'; - -void main() { - group('TiltData ::', () { - test('tiltTransform', () { - expect( - const TiltData( - width: 100, - height: 100, - isInit: true, - areaProgress: Offset(1.0, 1.0), - tiltConfig: TiltConfig( - angle: 10.0, - enableReverse: false, - ), - ).tiltTransform(), - Matrix4( - 0.984807753012208, - -0.030153689607045803, - 0.17101007166283433, - 0.0008550503583141717, - 0.0, - 0.984807753012208, - 0.17364817766693033, - 0.0008682408883346517, - -0.17364817766693033, - -0.17101007166283433, - 0.9698463103929541, - 0.004849231551964771, - 0.0, - 0.0, - 0.0, - 1.0, - ), - reason: 'enableReverse = false', - ); - expect( - const TiltData( - width: 100, - height: 100, - isInit: true, - areaProgress: Offset(1.0, 1.0), - tiltConfig: TiltConfig( - angle: 10.0, - enableReverse: true, - ), - ).tiltTransform(), - Matrix4( - 0.984807753012208, - -0.030153689607045803, - -0.17101007166283433, - -0.0008550503583141717, - 0.0, - 0.984807753012208, - -0.17364817766693033, - -0.0008682408883346517, - 0.17364817766693033, - 0.17101007166283433, - 0.9698463103929541, - 0.004849231551964771, - 0.0, - 0.0, - 0.0, - 1.0, - ), - reason: 'enableReverse = true', - ); - }); - }); -} diff --git a/test/internal/tilt_state_test.dart b/test/internal/tilt_state_test.dart new file mode 100644 index 0000000..facc08e --- /dev/null +++ b/test/internal/tilt_state_test.dart @@ -0,0 +1,166 @@ +import 'package:flutter/widgets.dart'; +import 'package:flutter_test/flutter_test.dart'; +import 'package:flutter_tilt/src/config/tilt_config.dart'; +import 'package:flutter_tilt/src/enums.dart'; +import 'package:flutter_tilt/src/internal/tilt_state.dart'; +import 'package:flutter_tilt/src/utils/utils.dart'; + +void main() { + group('TiltState ::', () { + const tiltState = TiltState( + width: 100, + height: 100, + isInit: true, + areaProgress: Offset.zero, + tiltConfig: TiltConfig( + angle: 10.0, + enableReverse: false, + ), + isActive: false, + currentGesturesType: GesturesType.none, + ); + + test('hashCode', () { + final tiltState1 = const TiltState( + isInit: true, + tiltConfig: TiltConfig( + angle: 10.0, + enableReverse: false, + ), + width: 1, + height: 1, + areaProgress: Offset.zero, + isActive: true, + currentGesturesType: GesturesType.touch, + ); + final tiltState2 = const TiltState( + isInit: true, + tiltConfig: TiltConfig( + angle: 10.0, + enableReverse: false, + ), + width: 1, + height: 1, + areaProgress: Offset.zero, + isActive: true, + currentGesturesType: GesturesType.touch, + ); + expect(tiltState1, tiltState2); + expect(tiltState1.hashCode, tiltState2.hashCode); + }); + + test('tiltTransform', () { + expect( + const TiltState( + width: 100, + height: 100, + isInit: true, + areaProgress: Offset(1.0, 1.0), + tiltConfig: TiltConfig( + angle: 10.0, + enableReverse: false, + ), + isActive: true, + currentGesturesType: GesturesType.touch, + ).tiltTransform(), + Matrix4( + 0.984807753012208, + -0.030153689607045803, + 0.17101007166283433, + 0.0008550503583141717, + 0.0, + 0.984807753012208, + 0.17364817766693033, + 0.0008682408883346517, + -0.17364817766693033, + -0.17101007166283433, + 0.9698463103929541, + 0.004849231551964771, + 0.0, + 0.0, + 0.0, + 1.0, + ), + reason: 'enableReverse = false', + ); + expect( + const TiltState( + width: 100, + height: 100, + isInit: true, + areaProgress: Offset(1.0, 1.0), + tiltConfig: TiltConfig( + angle: 10.0, + enableReverse: true, + ), + isActive: true, + currentGesturesType: GesturesType.touch, + ).tiltTransform(), + Matrix4( + 0.984807753012208, + -0.030153689607045803, + -0.17101007166283433, + -0.0008550503583141717, + 0.0, + 0.984807753012208, + -0.17364817766693033, + -0.0008682408883346517, + 0.17364817766693033, + 0.17101007166283433, + 0.9698463103929541, + 0.004849231551964771, + 0.0, + 0.0, + 0.0, + 1.0, + ), + reason: 'enableReverse = true', + ); + }); + + test('moveTo updates pure interaction fields', () { + final nextTiltData = tiltState.moveTo( + const Offset(100, 0), + GesturesType.touch, + ); + + expect(nextTiltData.areaProgress, const Offset(-1.0, 1.0)); + expect(nextTiltData.isActive, isTrue); + expect(nextTiltData.currentGesturesType, GesturesType.touch); + expect(nextTiltData.width, tiltState.width); + expect(nextTiltData.height, tiltState.height); + }); + + test('resolveRevertPosition respects enableRevert', () { + expect( + tiltState.resolveRevertPosition( + currentPosition: const Offset(20, 30), + initialAreaProgress: const Offset(0.25, -0.25), + ), + Utils.progressPosition( + tiltState.width, + tiltState.height, + const Offset(0.25, -0.25), + ), + ); + + const nonRevertingTiltData = TiltState( + width: 100, + height: 100, + isInit: true, + areaProgress: Offset.zero, + tiltConfig: TiltConfig(enableRevert: false), + isActive: true, + currentGesturesType: GesturesType.touch, + ); + + expect( + nonRevertingTiltData.resolveRevertPosition( + currentPosition: const Offset(20, 30), + initialAreaProgress: const Offset(0.25, -0.25), + ), + const Offset(20, 30), + ); + }); + }); +} diff --git a/test/models/tilt_stream_model_test.dart b/test/models/tilt_stream_model_test.dart index 9a02c57..03fec79 100644 --- a/test/models/tilt_stream_model_test.dart +++ b/test/models/tilt_stream_model_test.dart @@ -8,12 +8,10 @@ void main() { const tiltStreamModel = TiltStreamModel( position: Offset(5, 5), gesturesType: GesturesType.controller, - gestureUse: true, ); const tiltStreamModel2 = TiltStreamModel( position: Offset(5, 5), gesturesType: GesturesType.controller, - gestureUse: true, ); expect(tiltStreamModel, tiltStreamModel2); expect(tiltStreamModel.hashCode, tiltStreamModel2.hashCode); diff --git a/test/tilt/shared_widgets/tilt_widget.dart b/test/tilt/shared_widgets/tilt_widget.dart index f331f58..39f0d90 100644 --- a/test/tilt/shared_widgets/tilt_widget.dart +++ b/test/tilt/shared_widgets/tilt_widget.dart @@ -1,5 +1,3 @@ -import 'dart:async'; - import 'package:flutter/material.dart'; import 'package:flutter_tilt/flutter_tilt.dart'; @@ -7,7 +5,7 @@ class TiltWidget extends StatelessWidget { const TiltWidget({ super.key, this.childLayout = const ChildLayout(), - this.tiltStreamController, + this.tiltController, this.disable = false, this.fps = 120, this.border, @@ -21,7 +19,7 @@ class TiltWidget extends StatelessWidget { }); final ChildLayout childLayout; - final StreamController? tiltStreamController; + final TiltController? tiltController; final bool disable; final int fps; final BoxBorder? border; @@ -40,7 +38,7 @@ class TiltWidget extends StatelessWidget { key: const Key('tilt_scaffold'), body: Tilt( key: const Key('tilt_widget'), - tiltStreamController: tiltStreamController, + tiltController: tiltController, disable: disable, fps: fps, tiltConfig: tiltConfig.copyWith(enableGestureSensors: false), @@ -69,7 +67,7 @@ class TiltBaseWidget extends StatelessWidget { const TiltBaseWidget({ super.key, this.childLayout = const ChildLayout(), - this.tiltStreamController, + this.tiltController, this.disable = false, this.fps = 120, this.border, @@ -83,7 +81,7 @@ class TiltBaseWidget extends StatelessWidget { }); final ChildLayout childLayout; - final StreamController? tiltStreamController; + final TiltController? tiltController; final bool disable; final int fps; final BoxBorder? border; @@ -102,7 +100,7 @@ class TiltBaseWidget extends StatelessWidget { key: const Key('tilt_scaffold'), body: Tilt.base( key: const Key('tilt_widget'), - tiltStreamController: tiltStreamController, + tiltController: tiltController, disable: disable, fps: fps, tiltConfig: tiltConfig.copyWith(enableGestureSensors: false), @@ -129,7 +127,7 @@ class TiltProjectorWidget extends StatelessWidget { const TiltProjectorWidget({ super.key, this.childLayout = const ChildLayout(), - this.tiltStreamController, + this.tiltController, this.disable = false, this.fps = 120, this.border, @@ -143,7 +141,7 @@ class TiltProjectorWidget extends StatelessWidget { }); final ChildLayout childLayout; - final StreamController? tiltStreamController; + final TiltController? tiltController; final bool disable; final int fps; final BoxBorder? border; @@ -162,7 +160,7 @@ class TiltProjectorWidget extends StatelessWidget { key: const Key('tilt_scaffold'), body: Tilt.projector( key: const Key('tilt_widget'), - tiltStreamController: tiltStreamController, + tiltController: tiltController, disable: disable, fps: fps, tiltConfig: tiltConfig.copyWith(enableGestureSensors: false), diff --git a/test/tilt/tilt_config_tilt_test.dart b/test/tilt/tilt_config_tilt_test.dart index 57332f6..c9a032d 100644 --- a/test/tilt/tilt_config_tilt_test.dart +++ b/test/tilt/tilt_config_tilt_test.dart @@ -3,21 +3,23 @@ import 'package:flutter/gestures.dart'; import 'package:flutter/material.dart'; import 'package:flutter_test/flutter_test.dart'; import 'package:flutter_tilt/flutter_tilt.dart'; -import 'package:flutter_tilt/src/internal/tilt_data.dart'; +import 'package:flutter_tilt/src/internal/tilt_state.dart'; import 'shared_widgets/tilt_widget.dart'; void main() { const tiltConfig = TiltConfig(); - TiltData tiltDataTestCalculate({ + TiltState tiltDataTestCalculate({ required Offset areaProgress, TiltConfig tiltConfig = tiltConfig, }) => - TiltData( + TiltState( isInit: true, width: 10, height: 10, areaProgress: areaProgress, tiltConfig: tiltConfig, + isActive: true, + currentGesturesType: GesturesType.touch, ); final tiltWidgetFinder = find.byKey(const Key('tilt_widget')); @@ -320,6 +322,12 @@ void main() { expect(moveGesturesTypeTest, GesturesType.touch); expect(moveTiltDataTest, tiltDataExpect); + moveTiltDataTest = null; + leaveTiltDataTest = null; + moveGesturesTypeTest = null; + leaveGesturesTypeTest = null; + leaveCountTest = 0; + /// 倾斜-超范围 await tester.fling(tiltWidgetFinder, const Offset(-6.0, -6.0), 0.1); expect(childFinder, findsOneWidget); diff --git a/test/tilt/tilt_widget_test.dart b/test/tilt/tilt_widget_test.dart index 67d551c..d05c1bc 100644 --- a/test/tilt/tilt_widget_test.dart +++ b/test/tilt/tilt_widget_test.dart @@ -1,5 +1,3 @@ -import 'dart:async' show StreamController; - import 'package:flutter/material.dart'; import 'package:flutter_test/flutter_test.dart'; import 'package:flutter_tilt/flutter_tilt.dart'; @@ -88,30 +86,34 @@ void main() { frequency: fpsList[0].toDouble(), ); await tester.pumpAndSettle(); - expect(count, fps); + expect(count, fps + 1); } }); }); group('Tilt didUpdateWidget ::', () { - testWidgets('tiltStreamController', (WidgetTester tester) async { - final tiltStreamController1 = - StreamController.broadcast(); - final tiltStreamController2 = - StreamController.broadcast(); - final dataList = >[ - tiltStreamController1, - tiltStreamController2, + testWidgets('tiltController', (WidgetTester tester) async { + final tiltController1 = TiltController(); + final tiltController2 = TiltController(); + final dataList = [ + tiltController1, + tiltController2, ]; for (final data in dataList) { await tester.pumpWidget( - TiltWidget(tiltStreamController: data), + TiltWidget(tiltController: data), ); await tester.pumpAndSettle(); expect(data.hasListener, true); } + + await tiltController1.dispose(); + await tiltController2.dispose(); + + expect(tiltController1.hasListener, false); + expect(tiltController2.hasListener, false); }); testWidgets('disable', (WidgetTester tester) async { @@ -141,14 +143,14 @@ void main() { }); testWidgets('fps', (WidgetTester tester) async { - final dataList = [120, 60]; + final fpsList = [120, 60]; - for (final data in dataList) { + for (final fps in fpsList) { var count = 0; await tester.pumpWidget( TiltWidget( - fps: data, + fps: fps, onGestureMove: (TiltDataModel tiltData, GesturesType gesturesType) { count++; }, @@ -159,16 +161,15 @@ void main() { tiltWidgetFinder, const Offset(0.0, 5.0), const Duration(milliseconds: 1000), - frequency: dataList[0].toDouble(), + frequency: fpsList[0].toDouble(), ); await tester.pumpAndSettle(); - expect(count, data); + expect(count, fps + 1); } }); testWidgets('tiltConfig', (WidgetTester tester) async { - final tiltStreamController = - StreamController.broadcast(); + final tiltController = TiltController(); const tiltConfig1 = TiltConfig(leaveDuration: Duration(milliseconds: 4000)); const tiltConfig2 = TiltConfig(leaveDuration: Duration.zero); @@ -180,7 +181,7 @@ void main() { await tester.pumpWidget( TiltWidget( - tiltStreamController: tiltStreamController, + tiltController: tiltController, tiltConfig: data, onGestureMove: (TiltDataModel tiltData, GesturesType gesturesType) { currentGesturesType = gesturesType; @@ -191,23 +192,17 @@ void main() { /// 事件 1,触发一次 touch leave(此时总体时间在 1000ms) if (i == 0) { /// 倾斜 touch move - tiltStreamController.sink.add( - const TiltStreamModel( - position: Offset(0.0, 5.0), - gesturesType: GesturesType.touch, - gestureUse: true, - ), + tiltController.move( + position: const Offset(0.0, 5.0), + gesturesType: GesturesType.touch, ); await tester.pump(const Duration(milliseconds: 500)); expect(currentGesturesType, GesturesType.touch); /// 倾斜 touch leave - tiltStreamController.sink.add( - const TiltStreamModel( - position: Offset(0.0, 5.0), - gesturesType: GesturesType.touch, - gestureUse: false, - ), + tiltController.leave( + position: const Offset(0.0, 5.0), + gesturesType: GesturesType.touch, ); await tester.pump(const Duration(milliseconds: 500)); } @@ -219,12 +214,9 @@ void main() { /// /// 事件 2,触发一次 controller move, /// 由于重新赋值 [tiltConfig2.leaveDuration],此时应结束持续时间。 - tiltStreamController.sink.add( - const TiltStreamModel( - position: Offset(0.0, 5.0), - gesturesType: GesturesType.sensors, - gestureUse: true, - ), + tiltController.move( + position: const Offset(0.0, 5.0), + gesturesType: GesturesType.sensors, ); await tester.pump(const Duration(milliseconds: 500)); @@ -238,6 +230,8 @@ void main() { expect(currentGesturesType, GesturesType.sensors); } } + + await tiltController.dispose(); }); }); } diff --git a/test/utils_test.dart b/test/utils/utils_test.dart similarity index 98% rename from test/utils_test.dart rename to test/utils/utils_test.dart index 6967bd3..d100e1e 100644 --- a/test/utils_test.dart +++ b/test/utils/utils_test.dart @@ -1,7 +1,7 @@ import 'package:flutter/foundation.dart'; import 'package:flutter_test/flutter_test.dart'; import 'package:flutter_tilt/flutter_tilt.dart'; -import 'package:flutter_tilt/src/utils.dart'; +import 'package:flutter_tilt/src/utils/utils.dart'; void main() { group('utils ::', () { diff --git a/test/widgets/containers/tilt_base_container/tilt_stream_controller_test.dart b/test/widgets/containers/tilt_base_container/tilt_controller_test.dart similarity index 74% rename from test/widgets/containers/tilt_base_container/tilt_stream_controller_test.dart rename to test/widgets/containers/tilt_base_container/tilt_controller_test.dart index eb2d062..3fb6ce1 100644 --- a/test/widgets/containers/tilt_base_container/tilt_stream_controller_test.dart +++ b/test/widgets/containers/tilt_base_container/tilt_controller_test.dart @@ -1,84 +1,72 @@ -import 'dart:async'; - import 'package:flutter_test/flutter_test.dart'; import 'package:flutter_tilt/flutter_tilt.dart'; + import '../../shared_widgets/tilt_base_container_widget.dart'; void main() { final childFinder = find.text('Tilt'); - group('TiltBaseContainer :: tilt TiltStreamController ::', () { - testWidgets('stream listen', (WidgetTester tester) async { + + group('TiltBaseContainer :: tilt TiltController ::', () { + testWidgets('controller stream listen', (WidgetTester tester) async { var tiltStreamModelTest = const TiltStreamModel( position: Offset(1, 1), gesturesType: GesturesType.touch, - gestureUse: false, + isActive: false, ); const tiltStreamModelExpect = TiltStreamModel( position: Offset.zero, gesturesType: GesturesType.controller, - gestureUse: true, ); - /// 基础 - final tiltStreamController = - StreamController.broadcast(); + final tiltController = TiltController(); await tester.pumpWidget( - TiltBaseContainerWidget(tiltStreamController: tiltStreamController), + TiltBaseContainerWidget(tiltController: tiltController), ); await tester.pumpAndSettle(); expect(childFinder, findsOneWidget); - - /// 测试值不同 expect(tiltStreamModelTest != tiltStreamModelExpect, true); - await tester.pumpAndSettle(); - - /// 监听 - tiltStreamController.stream.listen((TiltStreamModel tiltStreamModel) { + tiltController.stream.listen((TiltStreamModel tiltStreamModel) { tiltStreamModelTest = tiltStreamModel; }); - tiltStreamController.sink.add(tiltStreamModelExpect); + tiltController.emit(tiltStreamModelExpect); await tester.pumpAndSettle(); - /// 测试值相同 expect(tiltStreamModelTest, tiltStreamModelExpect); + await tiltController.dispose(); }); - testWidgets('disable all gestures and use controller triggers', ( + testWidgets('disable all gestures and drive with controller', ( WidgetTester tester, ) async { var tiltStreamModelTest = const TiltStreamModel( position: Offset(1, 1), gesturesType: GesturesType.controller, - gestureUse: true, + isActive: true, ); var tiltStreamModelExpect = const TiltStreamModel( position: Offset.zero, gesturesType: GesturesType.controller, - gestureUse: false, + isActive: false, ); TiltStreamModel? gestureMoveExpect; TiltStreamModel? gestureLeaveExpect; + final tiltController = TiltController(); - /// 基础 回调赋值 - final tiltStreamController = - StreamController.broadcast(); await tester.pumpWidget( TiltBaseContainerWidget( - tiltStreamController: tiltStreamController, + tiltController: tiltController, tiltConfig: const TiltConfig( enableGestureTouch: false, enableGestureHover: false, enableGestureSensors: false, ), - onGestureMove: ( - TiltDataModel tiltDataModel, - GesturesType gesturesType, - ) { + onGestureMove: + (TiltDataModel tiltDataModel, GesturesType gesturesType) { gestureMoveExpect = TiltStreamModel( position: tiltDataModel.position, gesturesType: gesturesType, - gestureUse: true, + isActive: true, ); }, onGestureLeave: ( @@ -88,7 +76,7 @@ void main() { gestureLeaveExpect = TiltStreamModel( position: tiltDataModel.position, gesturesType: gesturesType, - gestureUse: false, + isActive: false, ); }, ), @@ -98,7 +86,7 @@ void main() { expect(tiltStreamModelTest != tiltStreamModelExpect, true); /// stream 监听 - tiltStreamController.stream.listen((TiltStreamModel tiltStreamModel) { + tiltController.stream.listen((TiltStreamModel tiltStreamModel) { tiltStreamModelTest = tiltStreamModel; }); @@ -107,9 +95,9 @@ void main() { tiltStreamModelExpect = const TiltStreamModel( position: Offset(10, 10), gesturesType: GesturesType.touch, - gestureUse: true, + isActive: true, ); - tiltStreamController.sink.add(tiltStreamModelExpect); + tiltController.emit(tiltStreamModelExpect); await tester.pumpAndSettle(); expect(tiltStreamModelTest, tiltStreamModelExpect); expect(tiltStreamModelTest, gestureMoveExpect); @@ -119,16 +107,16 @@ void main() { tiltStreamModelExpect = const TiltStreamModel( position: Offset(10, 10), gesturesType: GesturesType.touch, - gestureUse: false, + isActive: false, ); - tiltStreamController.sink.add(tiltStreamModelExpect); + tiltController.emit(tiltStreamModelExpect); await tester.pumpAndSettle(); expect(tiltStreamModelTest, tiltStreamModelExpect); expect( const TiltStreamModel( position: Offset(5, 5), gesturesType: GesturesType.touch, - gestureUse: false, + isActive: false, ), gestureLeaveExpect, ); @@ -138,9 +126,9 @@ void main() { tiltStreamModelExpect = const TiltStreamModel( position: Offset(10, 10), gesturesType: GesturesType.hover, - gestureUse: true, + isActive: true, ); - tiltStreamController.sink.add(tiltStreamModelExpect); + tiltController.emit(tiltStreamModelExpect); await tester.pumpAndSettle(); expect(tiltStreamModelTest, tiltStreamModelExpect); expect(tiltStreamModelTest, gestureMoveExpect); @@ -150,9 +138,9 @@ void main() { tiltStreamModelExpect = const TiltStreamModel( position: Offset(10, 10), gesturesType: GesturesType.hover, - gestureUse: false, + isActive: false, ); - tiltStreamController.sink.add(tiltStreamModelExpect); + tiltController.emit(tiltStreamModelExpect); await tester.pumpAndSettle(); expect(tiltStreamModelTest, tiltStreamModelExpect); expect( @@ -160,7 +148,7 @@ void main() { const TiltStreamModel( position: Offset(5, 5), gesturesType: GesturesType.hover, - gestureUse: false, + isActive: false, ), ); @@ -169,9 +157,9 @@ void main() { tiltStreamModelExpect = const TiltStreamModel( position: Offset(10, 10), gesturesType: GesturesType.controller, - gestureUse: true, + isActive: true, ); - tiltStreamController.sink.add(tiltStreamModelExpect); + tiltController.emit(tiltStreamModelExpect); await tester.pumpAndSettle(); expect(tiltStreamModelTest, tiltStreamModelExpect); expect(tiltStreamModelTest, gestureMoveExpect); @@ -181,9 +169,9 @@ void main() { tiltStreamModelExpect = const TiltStreamModel( position: Offset(10, 10), gesturesType: GesturesType.controller, - gestureUse: false, + isActive: false, ); - tiltStreamController.sink.add(tiltStreamModelExpect); + tiltController.emit(tiltStreamModelExpect); await tester.pumpAndSettle(); expect(tiltStreamModelTest, tiltStreamModelExpect); expect( @@ -191,7 +179,7 @@ void main() { const TiltStreamModel( position: Offset(5, 5), gesturesType: GesturesType.controller, - gestureUse: false, + isActive: false, ), ); @@ -200,9 +188,9 @@ void main() { tiltStreamModelExpect = const TiltStreamModel( position: Offset(10, 10), gesturesType: GesturesType.sensors, - gestureUse: true, + isActive: true, ); - tiltStreamController.sink.add(tiltStreamModelExpect); + tiltController.emit(tiltStreamModelExpect); await tester.pumpAndSettle(); expect(tiltStreamModelTest, tiltStreamModelExpect); expect(tiltStreamModelTest, gestureMoveExpect); @@ -212,9 +200,9 @@ void main() { tiltStreamModelExpect = const TiltStreamModel( position: Offset(10, 10), gesturesType: GesturesType.sensors, - gestureUse: false, + isActive: false, ); - tiltStreamController.sink.add(tiltStreamModelExpect); + tiltController.emit(tiltStreamModelExpect); await tester.pumpAndSettle(); // sensors 不会触发 onGestureLeave expect(gestureLeaveExpect, null); @@ -222,50 +210,46 @@ void main() { tiltStreamModelExpect = const TiltStreamModel( position: Offset.zero, gesturesType: GesturesType.sensors, - gestureUse: true, + isActive: true, ); - tiltStreamController.sink.add(tiltStreamModelExpect); + tiltController.emit(tiltStreamModelExpect); await tester.pumpAndSettle(); expect( gestureMoveExpect, const TiltStreamModel( position: Offset(9.75, 9.75), gesturesType: GesturesType.sensors, - gestureUse: true, + isActive: true, ), ); + + await tiltController.dispose(); }); - testWidgets('gesture priority', ( - WidgetTester tester, - ) async { + testWidgets('gesture priority', (WidgetTester tester) async { var tiltStreamModelTest = const TiltStreamModel( position: Offset(1, 1), gesturesType: GesturesType.controller, - gestureUse: true, + isActive: true, ); var tiltStreamModelExpect = const TiltStreamModel( position: Offset.zero, gesturesType: GesturesType.controller, - gestureUse: false, + isActive: false, ); TiltStreamModel? gestureMoveExpect; TiltStreamModel? gestureLeaveExpect; + final tiltController = TiltController(); - /// 基础 回调赋值 - final tiltStreamController = - StreamController.broadcast(); await tester.pumpWidget( TiltBaseContainerWidget( - tiltStreamController: tiltStreamController, - onGestureMove: ( - TiltDataModel tiltDataModel, - GesturesType gesturesType, - ) { + tiltController: tiltController, + onGestureMove: + (TiltDataModel tiltDataModel, GesturesType gesturesType) { gestureMoveExpect = TiltStreamModel( position: tiltDataModel.position, gesturesType: gesturesType, - gestureUse: true, + isActive: true, ); }, onGestureLeave: ( @@ -275,7 +259,7 @@ void main() { gestureLeaveExpect = TiltStreamModel( position: tiltDataModel.position, gesturesType: gesturesType, - gestureUse: false, + isActive: false, ); }, ), @@ -285,7 +269,7 @@ void main() { expect(tiltStreamModelTest != tiltStreamModelExpect, true); /// stream 监听 - tiltStreamController.stream.listen((TiltStreamModel tiltStreamModel) { + tiltController.stream.listen((TiltStreamModel tiltStreamModel) { tiltStreamModelTest = tiltStreamModel; }); @@ -294,9 +278,9 @@ void main() { tiltStreamModelExpect = const TiltStreamModel( position: Offset(10, 10), gesturesType: GesturesType.sensors, - gestureUse: true, + isActive: true, ); - tiltStreamController.sink.add(tiltStreamModelExpect); + tiltController.emit(tiltStreamModelExpect); await tester.pumpAndSettle(); expect(tiltStreamModelTest, tiltStreamModelExpect); expect(tiltStreamModelTest, gestureMoveExpect); @@ -306,9 +290,9 @@ void main() { tiltStreamModelExpect = const TiltStreamModel( position: Offset(10, 10), gesturesType: GesturesType.controller, - gestureUse: true, + isActive: true, ); - tiltStreamController.sink.add(tiltStreamModelExpect); + tiltController.emit(tiltStreamModelExpect); await tester.pumpAndSettle(); expect(tiltStreamModelTest, tiltStreamModelExpect); expect(tiltStreamModelTest, gestureMoveExpect); @@ -318,9 +302,9 @@ void main() { tiltStreamModelExpect = const TiltStreamModel( position: Offset(10, 10), gesturesType: GesturesType.hover, - gestureUse: true, + isActive: true, ); - tiltStreamController.sink.add(tiltStreamModelExpect); + tiltController.emit(tiltStreamModelExpect); await tester.pumpAndSettle(); expect(tiltStreamModelTest, tiltStreamModelExpect); expect(tiltStreamModelTest, gestureMoveExpect); @@ -330,9 +314,9 @@ void main() { tiltStreamModelExpect = const TiltStreamModel( position: Offset(10, 10), gesturesType: GesturesType.touch, - gestureUse: true, + isActive: true, ); - tiltStreamController.sink.add(tiltStreamModelExpect); + tiltController.emit(tiltStreamModelExpect); await tester.pumpAndSettle(); expect(tiltStreamModelTest, tiltStreamModelExpect); expect(tiltStreamModelTest, gestureMoveExpect); @@ -341,7 +325,7 @@ void main() { const lowPriorityData = TiltStreamModel( position: Offset(10, 10), gesturesType: GesturesType.touch, - gestureUse: true, + isActive: true, ); /// hover 手势移动 @@ -349,9 +333,9 @@ void main() { tiltStreamModelExpect = const TiltStreamModel( position: Offset(10, 10), gesturesType: GesturesType.hover, - gestureUse: true, + isActive: true, ); - tiltStreamController.sink.add(tiltStreamModelExpect); + tiltController.emit(tiltStreamModelExpect); await tester.pumpAndSettle(); expect(gestureMoveExpect, lowPriorityData); @@ -361,9 +345,9 @@ void main() { tiltStreamModelExpect = const TiltStreamModel( position: Offset(10, 10), gesturesType: GesturesType.touch, - gestureUse: false, + isActive: false, ); - tiltStreamController.sink.add(tiltStreamModelExpect); + tiltController.emit(tiltStreamModelExpect); await tester.pumpAndSettle(); expect(tiltStreamModelTest, tiltStreamModelExpect); expect(gestureMoveExpect, null); @@ -372,7 +356,7 @@ void main() { const TiltStreamModel( position: Offset(5, 5), gesturesType: GesturesType.touch, - gestureUse: false, + isActive: false, ), ); @@ -381,12 +365,14 @@ void main() { tiltStreamModelExpect = const TiltStreamModel( position: Offset(10, 10), gesturesType: GesturesType.hover, - gestureUse: true, + isActive: true, ); - tiltStreamController.sink.add(tiltStreamModelExpect); + tiltController.emit(tiltStreamModelExpect); await tester.pumpAndSettle(); expect(tiltStreamModelTest, tiltStreamModelExpect); expect(tiltStreamModelTest, gestureMoveExpect); + + await tiltController.dispose(); }); }); } diff --git a/test/widgets/containers/tilt_base_container/tilt_gestures_hover_test.dart b/test/widgets/containers/tilt_base_container/tilt_gestures_hover_test.dart index 5b3f59e..8b04235 100644 --- a/test/widgets/containers/tilt_base_container/tilt_gestures_hover_test.dart +++ b/test/widgets/containers/tilt_base_container/tilt_gestures_hover_test.dart @@ -2,18 +2,20 @@ import 'package:flutter/gestures.dart'; import 'package:flutter/widgets.dart'; import 'package:flutter_test/flutter_test.dart'; import 'package:flutter_tilt/flutter_tilt.dart'; -import 'package:flutter_tilt/src/internal/tilt_data.dart'; +import 'package:flutter_tilt/src/internal/tilt_state.dart'; import '../../shared_widgets/tilt_base_container_widget.dart'; void main() { group('TiltBaseContainer :: tilt gestures hover ::', () { const tiltConfig = TiltConfig(); - TiltData tiltDataTestCalculate(Offset areaProgress) => TiltData( + TiltState tiltDataTestCalculate(Offset areaProgress) => TiltState( isInit: true, width: 10, height: 10, areaProgress: areaProgress, tiltConfig: tiltConfig, + isActive: true, + currentGesturesType: GesturesType.hover, ); final tiltWidgetFinder = find.byKey(const Key('tilt_widget')); diff --git a/test/widgets/containers/tilt_base_container/tilt_gestures_touch_test.dart b/test/widgets/containers/tilt_base_container/tilt_gestures_touch_test.dart index 80c9f0c..796c515 100644 --- a/test/widgets/containers/tilt_base_container/tilt_gestures_touch_test.dart +++ b/test/widgets/containers/tilt_base_container/tilt_gestures_touch_test.dart @@ -1,18 +1,20 @@ import 'package:flutter/widgets.dart'; import 'package:flutter_test/flutter_test.dart'; import 'package:flutter_tilt/flutter_tilt.dart'; -import 'package:flutter_tilt/src/internal/tilt_data.dart'; +import 'package:flutter_tilt/src/internal/tilt_state.dart'; import '../../shared_widgets/tilt_base_container_widget.dart'; void main() { group('TiltBaseContainer :: tilt gestures touch ::', () { const tiltConfig = TiltConfig(); - TiltData tiltDataTestCalculate(Offset areaProgress) => TiltData( + TiltState tiltDataTestCalculate(Offset areaProgress) => TiltState( isInit: true, width: 10, height: 10, areaProgress: areaProgress, tiltConfig: tiltConfig, + isActive: true, + currentGesturesType: GesturesType.touch, ); final tiltScaffoldFinder = find.byKey(const Key('tilt_scaffold')); diff --git a/test/widgets/containers/tilt_projector_container/tilt_stream_controller_test.dart b/test/widgets/containers/tilt_projector_container/tilt_controller_test.dart similarity index 74% rename from test/widgets/containers/tilt_projector_container/tilt_stream_controller_test.dart rename to test/widgets/containers/tilt_projector_container/tilt_controller_test.dart index 664bddf..a3cf628 100644 --- a/test/widgets/containers/tilt_projector_container/tilt_stream_controller_test.dart +++ b/test/widgets/containers/tilt_projector_container/tilt_controller_test.dart @@ -1,86 +1,72 @@ -import 'dart:async'; - import 'package:flutter_test/flutter_test.dart'; import 'package:flutter_tilt/flutter_tilt.dart'; + import '../../shared_widgets/tilt_projector_container_widget.dart'; void main() { final childFinder = find.text('Tilt'); - group('TiltProjectorContainer :: tilt TiltStreamController ::', () { - testWidgets('stream listen', (WidgetTester tester) async { + + group('TiltProjectorContainer :: tilt TiltController ::', () { + testWidgets('controller stream listen', (WidgetTester tester) async { var tiltStreamModelTest = const TiltStreamModel( position: Offset(1, 1), gesturesType: GesturesType.touch, - gestureUse: false, + isActive: false, ); const tiltStreamModelExpect = TiltStreamModel( position: Offset.zero, gesturesType: GesturesType.controller, - gestureUse: true, ); - /// 基础 - final tiltStreamController = - StreamController.broadcast(); + final tiltController = TiltController(); await tester.pumpWidget( - TiltProjectorContainerWidget( - tiltStreamController: tiltStreamController, - ), + TiltProjectorContainerWidget(tiltController: tiltController), ); await tester.pumpAndSettle(); expect(childFinder, findsNWidgets(2)); - - /// 测试值不同 expect(tiltStreamModelTest != tiltStreamModelExpect, true); - await tester.pumpAndSettle(); - - /// 监听 - tiltStreamController.stream.listen((TiltStreamModel tiltStreamModel) { + tiltController.stream.listen((TiltStreamModel tiltStreamModel) { tiltStreamModelTest = tiltStreamModel; }); - tiltStreamController.sink.add(tiltStreamModelExpect); + tiltController.emit(tiltStreamModelExpect); await tester.pumpAndSettle(); - /// 测试值相同 expect(tiltStreamModelTest, tiltStreamModelExpect); + await tiltController.dispose(); }); - testWidgets('disable all gestures and use controller triggers', ( + testWidgets('disable all gestures and drive with controller', ( WidgetTester tester, ) async { var tiltStreamModelTest = const TiltStreamModel( position: Offset(1, 1), gesturesType: GesturesType.controller, - gestureUse: true, + isActive: true, ); var tiltStreamModelExpect = const TiltStreamModel( position: Offset.zero, gesturesType: GesturesType.controller, - gestureUse: false, + isActive: false, ); TiltStreamModel? gestureMoveExpect; TiltStreamModel? gestureLeaveExpect; + final tiltController = TiltController(); - /// 基础 回调赋值 - final tiltStreamController = - StreamController.broadcast(); await tester.pumpWidget( TiltProjectorContainerWidget( - tiltStreamController: tiltStreamController, + tiltController: tiltController, tiltConfig: const TiltConfig( enableGestureTouch: false, enableGestureHover: false, enableGestureSensors: false, ), - onGestureMove: ( - TiltDataModel tiltDataModel, - GesturesType gesturesType, - ) { + onGestureMove: + (TiltDataModel tiltDataModel, GesturesType gesturesType) { gestureMoveExpect = TiltStreamModel( position: tiltDataModel.position, gesturesType: gesturesType, - gestureUse: true, + isActive: true, ); }, onGestureLeave: ( @@ -90,7 +76,7 @@ void main() { gestureLeaveExpect = TiltStreamModel( position: tiltDataModel.position, gesturesType: gesturesType, - gestureUse: false, + isActive: false, ); }, ), @@ -100,7 +86,7 @@ void main() { expect(tiltStreamModelTest != tiltStreamModelExpect, true); /// stream 监听 - tiltStreamController.stream.listen((TiltStreamModel tiltStreamModel) { + tiltController.stream.listen((TiltStreamModel tiltStreamModel) { tiltStreamModelTest = tiltStreamModel; }); @@ -109,9 +95,9 @@ void main() { tiltStreamModelExpect = const TiltStreamModel( position: Offset(10, 10), gesturesType: GesturesType.touch, - gestureUse: true, + isActive: true, ); - tiltStreamController.sink.add(tiltStreamModelExpect); + tiltController.emit(tiltStreamModelExpect); await tester.pumpAndSettle(); expect(tiltStreamModelTest, tiltStreamModelExpect); expect(tiltStreamModelTest, gestureMoveExpect); @@ -121,16 +107,16 @@ void main() { tiltStreamModelExpect = const TiltStreamModel( position: Offset(10, 10), gesturesType: GesturesType.touch, - gestureUse: false, + isActive: false, ); - tiltStreamController.sink.add(tiltStreamModelExpect); + tiltController.emit(tiltStreamModelExpect); await tester.pumpAndSettle(); expect(tiltStreamModelTest, tiltStreamModelExpect); expect( const TiltStreamModel( position: Offset(5, 5), gesturesType: GesturesType.touch, - gestureUse: false, + isActive: false, ), gestureLeaveExpect, ); @@ -140,9 +126,9 @@ void main() { tiltStreamModelExpect = const TiltStreamModel( position: Offset(10, 10), gesturesType: GesturesType.hover, - gestureUse: true, + isActive: true, ); - tiltStreamController.sink.add(tiltStreamModelExpect); + tiltController.emit(tiltStreamModelExpect); await tester.pumpAndSettle(); expect(tiltStreamModelTest, tiltStreamModelExpect); expect(tiltStreamModelTest, gestureMoveExpect); @@ -152,9 +138,9 @@ void main() { tiltStreamModelExpect = const TiltStreamModel( position: Offset(10, 10), gesturesType: GesturesType.hover, - gestureUse: false, + isActive: false, ); - tiltStreamController.sink.add(tiltStreamModelExpect); + tiltController.emit(tiltStreamModelExpect); await tester.pumpAndSettle(); expect(tiltStreamModelTest, tiltStreamModelExpect); expect( @@ -162,7 +148,7 @@ void main() { const TiltStreamModel( position: Offset(5, 5), gesturesType: GesturesType.hover, - gestureUse: false, + isActive: false, ), ); @@ -171,9 +157,9 @@ void main() { tiltStreamModelExpect = const TiltStreamModel( position: Offset(10, 10), gesturesType: GesturesType.controller, - gestureUse: true, + isActive: true, ); - tiltStreamController.sink.add(tiltStreamModelExpect); + tiltController.emit(tiltStreamModelExpect); await tester.pumpAndSettle(); expect(tiltStreamModelTest, tiltStreamModelExpect); expect(tiltStreamModelTest, gestureMoveExpect); @@ -183,9 +169,9 @@ void main() { tiltStreamModelExpect = const TiltStreamModel( position: Offset(10, 10), gesturesType: GesturesType.controller, - gestureUse: false, + isActive: false, ); - tiltStreamController.sink.add(tiltStreamModelExpect); + tiltController.emit(tiltStreamModelExpect); await tester.pumpAndSettle(); expect(tiltStreamModelTest, tiltStreamModelExpect); expect( @@ -193,7 +179,7 @@ void main() { const TiltStreamModel( position: Offset(5, 5), gesturesType: GesturesType.controller, - gestureUse: false, + isActive: false, ), ); @@ -202,9 +188,9 @@ void main() { tiltStreamModelExpect = const TiltStreamModel( position: Offset(10, 10), gesturesType: GesturesType.sensors, - gestureUse: true, + isActive: true, ); - tiltStreamController.sink.add(tiltStreamModelExpect); + tiltController.emit(tiltStreamModelExpect); await tester.pumpAndSettle(); expect(tiltStreamModelTest, tiltStreamModelExpect); expect(tiltStreamModelTest, gestureMoveExpect); @@ -214,9 +200,9 @@ void main() { tiltStreamModelExpect = const TiltStreamModel( position: Offset(10, 10), gesturesType: GesturesType.sensors, - gestureUse: false, + isActive: false, ); - tiltStreamController.sink.add(tiltStreamModelExpect); + tiltController.emit(tiltStreamModelExpect); await tester.pumpAndSettle(); // sensors 不会触发 onGestureLeave expect(gestureLeaveExpect, null); @@ -224,50 +210,46 @@ void main() { tiltStreamModelExpect = const TiltStreamModel( position: Offset.zero, gesturesType: GesturesType.sensors, - gestureUse: true, + isActive: true, ); - tiltStreamController.sink.add(tiltStreamModelExpect); + tiltController.emit(tiltStreamModelExpect); await tester.pumpAndSettle(); expect( gestureMoveExpect, const TiltStreamModel( position: Offset(9.75, 9.75), gesturesType: GesturesType.sensors, - gestureUse: true, + isActive: true, ), ); + + await tiltController.dispose(); }); - testWidgets('gesture priority', ( - WidgetTester tester, - ) async { + testWidgets('gesture priority', (WidgetTester tester) async { var tiltStreamModelTest = const TiltStreamModel( position: Offset(1, 1), gesturesType: GesturesType.controller, - gestureUse: true, + isActive: true, ); var tiltStreamModelExpect = const TiltStreamModel( position: Offset.zero, gesturesType: GesturesType.controller, - gestureUse: false, + isActive: false, ); TiltStreamModel? gestureMoveExpect; TiltStreamModel? gestureLeaveExpect; + final tiltController = TiltController(); - /// 基础 回调赋值 - final tiltStreamController = - StreamController.broadcast(); await tester.pumpWidget( TiltProjectorContainerWidget( - tiltStreamController: tiltStreamController, - onGestureMove: ( - TiltDataModel tiltDataModel, - GesturesType gesturesType, - ) { + tiltController: tiltController, + onGestureMove: + (TiltDataModel tiltDataModel, GesturesType gesturesType) { gestureMoveExpect = TiltStreamModel( position: tiltDataModel.position, gesturesType: gesturesType, - gestureUse: true, + isActive: true, ); }, onGestureLeave: ( @@ -277,7 +259,7 @@ void main() { gestureLeaveExpect = TiltStreamModel( position: tiltDataModel.position, gesturesType: gesturesType, - gestureUse: false, + isActive: false, ); }, ), @@ -287,7 +269,7 @@ void main() { expect(tiltStreamModelTest != tiltStreamModelExpect, true); /// stream 监听 - tiltStreamController.stream.listen((TiltStreamModel tiltStreamModel) { + tiltController.stream.listen((TiltStreamModel tiltStreamModel) { tiltStreamModelTest = tiltStreamModel; }); @@ -296,9 +278,9 @@ void main() { tiltStreamModelExpect = const TiltStreamModel( position: Offset(10, 10), gesturesType: GesturesType.sensors, - gestureUse: true, + isActive: true, ); - tiltStreamController.sink.add(tiltStreamModelExpect); + tiltController.emit(tiltStreamModelExpect); await tester.pumpAndSettle(); expect(tiltStreamModelTest, tiltStreamModelExpect); expect(tiltStreamModelTest, gestureMoveExpect); @@ -308,9 +290,9 @@ void main() { tiltStreamModelExpect = const TiltStreamModel( position: Offset(10, 10), gesturesType: GesturesType.controller, - gestureUse: true, + isActive: true, ); - tiltStreamController.sink.add(tiltStreamModelExpect); + tiltController.emit(tiltStreamModelExpect); await tester.pumpAndSettle(); expect(tiltStreamModelTest, tiltStreamModelExpect); expect(tiltStreamModelTest, gestureMoveExpect); @@ -320,9 +302,9 @@ void main() { tiltStreamModelExpect = const TiltStreamModel( position: Offset(10, 10), gesturesType: GesturesType.hover, - gestureUse: true, + isActive: true, ); - tiltStreamController.sink.add(tiltStreamModelExpect); + tiltController.emit(tiltStreamModelExpect); await tester.pumpAndSettle(); expect(tiltStreamModelTest, tiltStreamModelExpect); expect(tiltStreamModelTest, gestureMoveExpect); @@ -332,9 +314,9 @@ void main() { tiltStreamModelExpect = const TiltStreamModel( position: Offset(10, 10), gesturesType: GesturesType.touch, - gestureUse: true, + isActive: true, ); - tiltStreamController.sink.add(tiltStreamModelExpect); + tiltController.emit(tiltStreamModelExpect); await tester.pumpAndSettle(); expect(tiltStreamModelTest, tiltStreamModelExpect); expect(tiltStreamModelTest, gestureMoveExpect); @@ -343,7 +325,7 @@ void main() { const lowPriorityData = TiltStreamModel( position: Offset(10, 10), gesturesType: GesturesType.touch, - gestureUse: true, + isActive: true, ); /// hover 手势移动 @@ -351,9 +333,9 @@ void main() { tiltStreamModelExpect = const TiltStreamModel( position: Offset(10, 10), gesturesType: GesturesType.hover, - gestureUse: true, + isActive: true, ); - tiltStreamController.sink.add(tiltStreamModelExpect); + tiltController.emit(tiltStreamModelExpect); await tester.pumpAndSettle(); expect(gestureMoveExpect, lowPriorityData); @@ -363,9 +345,9 @@ void main() { tiltStreamModelExpect = const TiltStreamModel( position: Offset(10, 10), gesturesType: GesturesType.touch, - gestureUse: false, + isActive: false, ); - tiltStreamController.sink.add(tiltStreamModelExpect); + tiltController.emit(tiltStreamModelExpect); await tester.pumpAndSettle(); expect(tiltStreamModelTest, tiltStreamModelExpect); expect(gestureMoveExpect, null); @@ -374,7 +356,7 @@ void main() { const TiltStreamModel( position: Offset(5, 5), gesturesType: GesturesType.touch, - gestureUse: false, + isActive: false, ), ); @@ -383,12 +365,14 @@ void main() { tiltStreamModelExpect = const TiltStreamModel( position: Offset(10, 10), gesturesType: GesturesType.hover, - gestureUse: true, + isActive: true, ); - tiltStreamController.sink.add(tiltStreamModelExpect); + tiltController.emit(tiltStreamModelExpect); await tester.pumpAndSettle(); expect(tiltStreamModelTest, tiltStreamModelExpect); expect(tiltStreamModelTest, gestureMoveExpect); + + await tiltController.dispose(); }); }); } diff --git a/test/widgets/containers/tilt_projector_container/tilt_gestures_hover_test.dart b/test/widgets/containers/tilt_projector_container/tilt_gestures_hover_test.dart index 61cdde4..bd32da5 100644 --- a/test/widgets/containers/tilt_projector_container/tilt_gestures_hover_test.dart +++ b/test/widgets/containers/tilt_projector_container/tilt_gestures_hover_test.dart @@ -2,18 +2,20 @@ import 'package:flutter/gestures.dart'; import 'package:flutter/widgets.dart'; import 'package:flutter_test/flutter_test.dart'; import 'package:flutter_tilt/flutter_tilt.dart'; -import 'package:flutter_tilt/src/internal/tilt_data.dart'; +import 'package:flutter_tilt/src/internal/tilt_state.dart'; import '../../shared_widgets/tilt_projector_container_widget.dart'; void main() { group('TiltProjectorContainer :: tilt gestures hover ::', () { const tiltConfig = TiltConfig(); - TiltData tiltDataTestCalculate(Offset areaProgress) => TiltData( + TiltState tiltDataTestCalculate(Offset areaProgress) => TiltState( isInit: true, width: 10, height: 10, areaProgress: areaProgress, tiltConfig: tiltConfig, + isActive: true, + currentGesturesType: GesturesType.hover, ); final tiltWidgetFinder = find.byKey(const Key('tilt_widget')); diff --git a/test/widgets/containers/tilt_projector_container/tilt_gestures_touch_test.dart b/test/widgets/containers/tilt_projector_container/tilt_gestures_touch_test.dart index c77522f..3982478 100644 --- a/test/widgets/containers/tilt_projector_container/tilt_gestures_touch_test.dart +++ b/test/widgets/containers/tilt_projector_container/tilt_gestures_touch_test.dart @@ -1,18 +1,20 @@ import 'package:flutter/widgets.dart'; import 'package:flutter_test/flutter_test.dart'; import 'package:flutter_tilt/flutter_tilt.dart'; -import 'package:flutter_tilt/src/internal/tilt_data.dart'; +import 'package:flutter_tilt/src/internal/tilt_state.dart'; import '../../shared_widgets/tilt_projector_container_widget.dart'; void main() { group('TiltProjectorContainer :: tilt gestures touch ::', () { const tiltConfig = TiltConfig(); - TiltData tiltDataTestCalculate(Offset areaProgress) => TiltData( + TiltState tiltDataTestCalculate(Offset areaProgress) => TiltState( isInit: true, width: 10, height: 10, areaProgress: areaProgress, tiltConfig: tiltConfig, + isActive: true, + currentGesturesType: GesturesType.touch, ); final tiltScaffoldFinder = find.byKey(const Key('tilt_scaffold')); diff --git a/test/widgets/core/tilt_animated_builder_test.dart b/test/widgets/core/tilt_animated_builder_test.dart index 1dbf75d..8410391 100644 --- a/test/widgets/core/tilt_animated_builder_test.dart +++ b/test/widgets/core/tilt_animated_builder_test.dart @@ -1,3 +1,4 @@ +import 'package:flutter/gestures.dart'; import 'package:flutter/material.dart'; import 'package:flutter_test/flutter_test.dart'; import 'package:flutter_tilt/flutter_tilt.dart'; @@ -17,19 +18,33 @@ void main() { width: 100, height: 100, child: TiltAnimatedBuilder( - builder: (context, tiltData, tiltConfig, child) { - reportedSize = Size(tiltData.width, tiltData.height); + builder: (context, animatedState, child) { + final tiltConfig = animatedState.tiltConfig; + final targetTiltData = animatedState.targetTiltData; + final animatedTiltData = animatedState.animatedTiltData; + final currentGesturesType = + animatedState.currentGesturesType; + + reportedSize = Size( + animatedTiltData.width, + animatedTiltData.height, + ); /// 检查 builder 数据是否可用 - expect(tiltData, isNotNull); - expect(tiltData.areaProgress, initialProgress); expect(tiltConfig, isNotNull); + expect(targetTiltData, isNotNull); + expect(animatedTiltData, isNotNull); + expect(targetTiltData.areaProgress, initialProgress); + expect(animatedTiltData.areaProgress, initialProgress); + expect(currentGesturesType, GesturesType.none); /// 渲染 child return Column( children: [ Expanded( - child: Text('progress: ${tiltData.areaProgress}'), + child: Text( + 'progress: ${animatedTiltData.areaProgress}', + ), ), if (child != null) child, ], @@ -55,5 +70,83 @@ void main() { /// 检查尺寸回调是否正确 expect(reportedSize, const Size(100, 100)); }); + + testWidgets('uses animated progress during hover move', ( + WidgetTester tester, + ) async { + var latestTargetAreaProgress = Offset.zero; + var latestAnimatedAreaProgress = Offset.zero; + final testPointer = TestPointer(1, PointerDeviceKind.mouse); + + await tester.pumpWidget( + MaterialApp( + home: Scaffold( + body: Center( + child: Tilt( + key: const Key('tilt_widget'), + tiltConfig: const TiltConfig( + enableGestureSensors: false, + enterDuration: Duration(milliseconds: 100), + moveDuration: Duration(milliseconds: 100), + enterToMoveDuration: Duration.zero, + ), + child: SizedBox( + width: 100, + height: 100, + child: TiltAnimatedBuilder( + builder: (context, animatedState, child) { + final targetTiltData = animatedState.targetTiltData; + final animatedTiltData = animatedState.animatedTiltData; + + latestTargetAreaProgress = targetTiltData.areaProgress; + latestAnimatedAreaProgress = + animatedTiltData.areaProgress; + return child ?? const SizedBox(); + }, + child: const SizedBox.expand(), + ), + ), + ), + ), + ), + ), + ); + + final tiltWidgetFinder = find.byKey(const Key('tilt_widget')); + final hoverEventLocation = tester.getCenter(tiltWidgetFinder); + + await tester.sendEventToBinding( + testPointer.hover(hoverEventLocation + const Offset(-25.0, -25.0)), + ); + await tester.pump(); + await tester.pump(const Duration(milliseconds: 50)); + + final intermediateTargetAreaProgress = latestTargetAreaProgress; + final intermediateAnimatedAreaProgress = latestAnimatedAreaProgress; + + await tester.pumpAndSettle(); + + final finalTargetAreaProgress = latestTargetAreaProgress; + final finalAnimatedAreaProgress = latestAnimatedAreaProgress; + + expect(intermediateTargetAreaProgress, isNot(Offset.zero)); + expect(intermediateTargetAreaProgress, finalTargetAreaProgress); + expect(intermediateAnimatedAreaProgress, isNot(Offset.zero)); + expect( + intermediateAnimatedAreaProgress, + isNot(finalAnimatedAreaProgress), + ); + expect( + intermediateAnimatedAreaProgress.dx.abs(), + lessThan(finalAnimatedAreaProgress.dx.abs()), + ); + expect( + intermediateAnimatedAreaProgress.dy.abs(), + lessThan(finalAnimatedAreaProgress.dy.abs()), + ); + expect(finalTargetAreaProgress, finalAnimatedAreaProgress); + + await tester.sendEventToBinding(testPointer.removePointer()); + }); }); } diff --git a/test/widgets/shared_widgets/tilt_base_container_widget.dart b/test/widgets/shared_widgets/tilt_base_container_widget.dart index 764670d..0505b5f 100644 --- a/test/widgets/shared_widgets/tilt_base_container_widget.dart +++ b/test/widgets/shared_widgets/tilt_base_container_widget.dart @@ -1,5 +1,3 @@ -import 'dart:async'; - import 'package:flutter/material.dart'; import 'package:flutter_tilt/flutter_tilt.dart'; @@ -7,7 +5,7 @@ class TiltBaseContainerWidget extends StatelessWidget { const TiltBaseContainerWidget({ super.key, this.childLayout = const ChildLayout(), - this.tiltStreamController, + this.tiltController, this.disable = false, this.fps = 60, this.border, @@ -21,7 +19,7 @@ class TiltBaseContainerWidget extends StatelessWidget { }); final ChildLayout childLayout; - final StreamController? tiltStreamController; + final TiltController? tiltController; final bool disable; final int fps; final BoxBorder? border; @@ -40,7 +38,7 @@ class TiltBaseContainerWidget extends StatelessWidget { key: const Key('tilt_scaffold'), body: Tilt( key: const Key('tilt_widget'), - tiltStreamController: tiltStreamController, + tiltController: tiltController, disable: disable, fps: fps, tiltConfig: tiltConfig.copyWith(enableGestureSensors: false), diff --git a/test/widgets/shared_widgets/tilt_parallax_container_widget.dart b/test/widgets/shared_widgets/tilt_parallax_container_widget.dart index 7d5690f..6d88621 100644 --- a/test/widgets/shared_widgets/tilt_parallax_container_widget.dart +++ b/test/widgets/shared_widgets/tilt_parallax_container_widget.dart @@ -1,5 +1,3 @@ -import 'dart:async'; - import 'package:flutter/material.dart'; import 'package:flutter_tilt/flutter_tilt.dart'; import 'package:flutter_tilt/src/widgets/containers/tilt_parallax_container.dart'; @@ -8,7 +6,7 @@ class TiltParallaxContainerWidget extends StatelessWidget { const TiltParallaxContainerWidget({ super.key, this.childLayout, - this.tiltStreamController, + this.tiltController, this.disable = false, this.fps = 60, this.border, @@ -22,7 +20,7 @@ class TiltParallaxContainerWidget extends StatelessWidget { }); final ChildLayout? childLayout; - final StreamController? tiltStreamController; + final TiltController? tiltController; final bool disable; final int fps; final BoxBorder? border; @@ -45,7 +43,7 @@ class TiltParallaxContainerWidget extends StatelessWidget { child: Center( child: Tilt( key: const Key('tilt_widget'), - tiltStreamController: tiltStreamController, + tiltController: tiltController, disable: disable, fps: fps, tiltConfig: tiltConfig.copyWith(enableGestureSensors: false), diff --git a/test/widgets/shared_widgets/tilt_projector_container_widget.dart b/test/widgets/shared_widgets/tilt_projector_container_widget.dart index a3612f5..1ac3e05 100644 --- a/test/widgets/shared_widgets/tilt_projector_container_widget.dart +++ b/test/widgets/shared_widgets/tilt_projector_container_widget.dart @@ -1,5 +1,3 @@ -import 'dart:async'; - import 'package:flutter/material.dart'; import 'package:flutter_tilt/flutter_tilt.dart'; @@ -7,7 +5,7 @@ class TiltProjectorContainerWidget extends StatelessWidget { const TiltProjectorContainerWidget({ super.key, this.childLayout = const ChildLayout(), - this.tiltStreamController, + this.tiltController, this.disable = false, this.fps = 60, this.border, @@ -21,7 +19,7 @@ class TiltProjectorContainerWidget extends StatelessWidget { }); final ChildLayout childLayout; - final StreamController? tiltStreamController; + final TiltController? tiltController; final bool disable; final int fps; final BoxBorder? border; @@ -40,7 +38,7 @@ class TiltProjectorContainerWidget extends StatelessWidget { key: const Key('tilt_scaffold'), body: Tilt( key: const Key('tilt_widget'), - tiltStreamController: tiltStreamController, + tiltController: tiltController, disable: disable, fps: fps, tiltConfig: tiltConfig.copyWith(enableGestureSensors: false), From 745d0406474b3c6e7e6dd6fe6ff3fa47f56b045e Mon Sep 17 00:00:00 2001 From: Amos Date: Thu, 23 Apr 2026 15:13:35 +0800 Subject: [PATCH 05/10] test: child rebuild count --- test/tilt/tilt_base_widget_test.dart | 41 +++++++++++++ test/tilt/tilt_parallax_test.dart | 49 +++++++++++++++- test/tilt/tilt_projector_widget_test.dart | 41 +++++++++++++ test/tilt/tilt_widget_test.dart | 47 +++++++++++++++ .../core/tilt_animated_builder_test.dart | 57 +++++++++++++++++++ 5 files changed, 234 insertions(+), 1 deletion(-) diff --git a/test/tilt/tilt_base_widget_test.dart b/test/tilt/tilt_base_widget_test.dart index 16694ac..4995115 100644 --- a/test/tilt/tilt_base_widget_test.dart +++ b/test/tilt/tilt_base_widget_test.dart @@ -38,5 +38,46 @@ void main() { .reversed; expect(allTextKeys, [outerKey, innerKey, childKey, behindKey]); }); + + testWidgets('child rebuild count', (WidgetTester tester) async { + const fps = 120; + var moveCount = 0; + var childBuildCount = 0; + + await tester.pumpWidget( + MaterialApp( + home: Scaffold( + body: Tilt.base( + key: const Key('tilt_widget'), + fps: fps, + onGestureMove: (_, __) { + moveCount++; + }, + child: Builder( + builder: (_) { + childBuildCount++; + return const SizedBox( + width: 10, + height: 10, + child: Text('Tilt'), + ); + }, + ), + ), + ), + ), + ); + + await tester.timedDrag( + find.byKey(const Key('tilt_widget')), + const Offset(0.0, 5.0), + const Duration(milliseconds: 1000), + frequency: fps.toDouble(), + ); + await tester.pumpAndSettle(); + + expect(moveCount, fps + 1); + expect(childBuildCount, 1); + }); }); } diff --git a/test/tilt/tilt_parallax_test.dart b/test/tilt/tilt_parallax_test.dart index 3c29fc5..9b45ea0 100644 --- a/test/tilt/tilt_parallax_test.dart +++ b/test/tilt/tilt_parallax_test.dart @@ -1,9 +1,11 @@ -import 'package:flutter/widgets.dart'; +import 'package:flutter/material.dart'; import 'package:flutter_test/flutter_test.dart'; import 'package:flutter_tilt/flutter_tilt.dart'; import 'shared_widgets/tilt_parallax_widget.dart'; void main() { + final tiltWidgetFinder = find.byKey(const Key('tilt_widget')); + group('TiltParallax ::', () { testWidgets('default', (WidgetTester tester) async { final outerFinder = find.text('outer'); @@ -67,5 +69,50 @@ void main() { const Offset(24.061335345163766, 14.999999999999998), ); }); + + testWidgets('child rebuild count', (WidgetTester tester) async { + const fps = 120; + var moveCount = 0; + var childBuildCount = 0; + + await tester.pumpWidget( + TiltParallaxWidget( + fps: fps, + tiltConfig: const TiltConfig(initial: Offset(1, 0)), + onGestureMove: (_, __) { + moveCount++; + }, + childLayout: ChildLayout( + outer: [ + Positioned( + child: TiltParallax( + child: Builder( + builder: (_) { + childBuildCount++; + return const SizedBox( + width: 10, + height: 10, + child: Text('outer'), + ); + }, + ), + ), + ), + ], + ), + ), + ); + + await tester.timedDrag( + tiltWidgetFinder, + const Offset(0.0, 5.0), + const Duration(milliseconds: 1000), + frequency: fps.toDouble(), + ); + await tester.pumpAndSettle(); + + expect(moveCount, fps + 1); + expect(childBuildCount, 1); + }); }); } diff --git a/test/tilt/tilt_projector_widget_test.dart b/test/tilt/tilt_projector_widget_test.dart index 09bf140..f2d0eba 100644 --- a/test/tilt/tilt_projector_widget_test.dart +++ b/test/tilt/tilt_projector_widget_test.dart @@ -44,5 +44,46 @@ void main() { ...layerOrder, ]); }); + + testWidgets('child rebuild count', (WidgetTester tester) async { + const fps = 120; + var moveCount = 0; + var childBuildCount = 0; + + await tester.pumpWidget( + MaterialApp( + home: Scaffold( + body: Tilt.projector( + key: const Key('tilt_widget'), + fps: fps, + onGestureMove: (_, __) { + moveCount++; + }, + child: Builder( + builder: (_) { + childBuildCount++; + return const SizedBox( + width: 10, + height: 10, + child: Text('Tilt'), + ); + }, + ), + ), + ), + ), + ); + + await tester.timedDrag( + find.byKey(const Key('tilt_widget')), + const Offset(0.0, 5.0), + const Duration(milliseconds: 1000), + frequency: fps.toDouble(), + ); + await tester.pumpAndSettle(); + + expect(moveCount, fps + 1); + expect(childBuildCount, 2); + }); }); } diff --git a/test/tilt/tilt_widget_test.dart b/test/tilt/tilt_widget_test.dart index d05c1bc..976f87e 100644 --- a/test/tilt/tilt_widget_test.dart +++ b/test/tilt/tilt_widget_test.dart @@ -7,6 +7,49 @@ void main() { final tiltWidgetFinder = find.byKey(const Key('tilt_widget')); final childFinder = find.text('Tilt'); + group('Tilt Widget ::', () { + testWidgets('child rebuild count', (WidgetTester tester) async { + const fps = 120; + var moveCount = 0; + var childBuildCount = 0; + + await tester.pumpWidget( + MaterialApp( + home: Scaffold( + body: Tilt( + key: const Key('tilt_widget'), + fps: fps, + onGestureMove: (_, __) { + moveCount++; + }, + child: Builder( + builder: (_) { + childBuildCount++; + return const SizedBox( + width: 10, + height: 10, + child: Text('Tilt'), + ); + }, + ), + ), + ), + ), + ); + + await tester.timedDrag( + find.byKey(const Key('tilt_widget')), + const Offset(0.0, 5.0), + const Duration(milliseconds: 1000), + frequency: fps.toDouble(), + ); + await tester.pumpAndSettle(); + + expect(moveCount, fps + 1); + expect(childBuildCount, 1); + }); + }); + group('Tilt config ::', () { testWidgets('default', (WidgetTester tester) async { await tester.pumpWidget(const TiltWidget()); @@ -86,6 +129,8 @@ void main() { frequency: fpsList[0].toDouble(), ); await tester.pumpAndSettle(); + + /// timedDrag 会多 1 次触发 expect(count, fps + 1); } }); @@ -164,6 +209,8 @@ void main() { frequency: fpsList[0].toDouble(), ); await tester.pumpAndSettle(); + + /// timedDrag 会多 1 次触发 expect(count, fps + 1); } }); diff --git a/test/widgets/core/tilt_animated_builder_test.dart b/test/widgets/core/tilt_animated_builder_test.dart index 8410391..be43e6a 100644 --- a/test/widgets/core/tilt_animated_builder_test.dart +++ b/test/widgets/core/tilt_animated_builder_test.dart @@ -148,5 +148,62 @@ void main() { await tester.sendEventToBinding(testPointer.removePointer()); }); + + testWidgets('child rebuild count', (WidgetTester tester) async { + const fps = 120; + var moveCount = 0; + var tiltAnimatedBuildCount = 0; + var childBuildCount = 0; + + await tester.pumpWidget( + MaterialApp( + home: Scaffold( + body: Tilt( + key: const Key('tilt_widget'), + fps: fps, + tiltConfig: const TiltConfig( + enterDuration: Duration.zero, + moveDuration: Duration(milliseconds: 1000), + enterToMoveDuration: Duration.zero, + leaveDuration: Duration.zero, + ), + onGestureMove: (_, __) { + moveCount++; + }, + child: TiltAnimatedBuilder( + builder: (context, animatedState, child) { + if (animatedState.currentGesturesType != GesturesType.none) { + tiltAnimatedBuildCount++; + } + return child!; + }, + child: Builder( + builder: (_) { + childBuildCount++; + return const SizedBox( + width: 10, + height: 10, + child: Text('Tilt'), + ); + }, + ), + ), + ), + ), + ), + ); + + await tester.timedDrag( + find.byKey(const Key('tilt_widget')), + const Offset(0.0, 5.0), + const Duration(milliseconds: 1000), + frequency: fps.toDouble(), + ); + await tester.pumpAndSettle(); + + expect(moveCount, fps + 1); + expect(tiltAnimatedBuildCount, fps + 1); + expect(childBuildCount, 1); + }); }); } From 64a01e30b14c329414663eeae7a26a2312254b1b Mon Sep 17 00:00:00 2001 From: Amos Date: Thu, 23 Apr 2026 15:32:29 +0800 Subject: [PATCH 06/10] chore(example): upgrade Kotlin, Java version --- example/android/app/build.gradle.kts | 8 ++++---- example/android/settings.gradle.kts | 2 +- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/example/android/app/build.gradle.kts b/example/android/app/build.gradle.kts index 037a746..60cb5d9 100644 --- a/example/android/app/build.gradle.kts +++ b/example/android/app/build.gradle.kts @@ -8,15 +8,15 @@ plugins { android { namespace = "com.example.flutter_tilt_example" compileSdk = flutter.compileSdkVersion - ndkVersion = "27.0.12077973" // flutter.ndkVersion + ndkVersion = flutter.ndkVersion compileOptions { - sourceCompatibility = JavaVersion.VERSION_11 - targetCompatibility = JavaVersion.VERSION_11 + sourceCompatibility = JavaVersion.VERSION_17 + targetCompatibility = JavaVersion.VERSION_17 } kotlinOptions { - jvmTarget = JavaVersion.VERSION_11.toString() + jvmTarget = JavaVersion.VERSION_17.toString() } defaultConfig { diff --git a/example/android/settings.gradle.kts b/example/android/settings.gradle.kts index fb605bc..9dcd140 100644 --- a/example/android/settings.gradle.kts +++ b/example/android/settings.gradle.kts @@ -20,7 +20,7 @@ pluginManagement { plugins { id("dev.flutter.flutter-plugin-loader") version "1.0.0" id("com.android.application") version "8.9.1" apply false - id("org.jetbrains.kotlin.android") version "2.1.0" apply false + id("org.jetbrains.kotlin.android") version "2.1.21" apply false } include(":app") From 3880c4f45d87df2c850e5578aac0d19594fe94ed Mon Sep 17 00:00:00 2001 From: Amos Date: Fri, 24 Apr 2026 16:22:57 +0800 Subject: [PATCH 07/10] feat: add isCurrentGesturesTypeActive to TiltAnimatedState --- lib/src/internal/tilt_state.dart | 13 ++++-- lib/src/models/tilt_stream_model.dart | 14 +++---- .../widgets/core/tilt_animated_builder.dart | 17 ++++++++ .../core/tilt_animated_builder_test.dart | 42 ++++++++++++++++++- 4 files changed, 73 insertions(+), 13 deletions(-) diff --git a/lib/src/internal/tilt_state.dart b/lib/src/internal/tilt_state.dart index 8adceb1..f066a41 100644 --- a/lib/src/internal/tilt_state.dart +++ b/lib/src/internal/tilt_state.dart @@ -35,10 +35,18 @@ class TiltState { /// 区域进度 final Offset areaProgress; - /// 是否处于活动状态(进入或移动) + /// {@macro tilt.TiltStreamModel.isActive.en} + /// + /// ------ + /// + /// {@macro tilt.TiltStreamModel.isActive.zh} final bool isActive; - /// 当前手势类型 + /// {@macro tilt.TiltStreamModel.gesturesType.en} + /// + /// ------ + /// + /// {@macro tilt.TiltStreamModel.gesturesType.zh} final GesturesType currentGesturesType; /// 当前坐标 @@ -59,7 +67,6 @@ class TiltState { /// 禁用 bool get disable => tiltConfig.disable; - /// 复制当前倾斜数据,并按需覆盖部分字段 TiltState copyWith({ bool? isInit, TiltConfig? tiltConfig, diff --git a/lib/src/models/tilt_stream_model.dart b/lib/src/models/tilt_stream_model.dart index 30fcd59..18fb9d5 100644 --- a/lib/src/models/tilt_stream_model.dart +++ b/lib/src/models/tilt_stream_model.dart @@ -77,13 +77,11 @@ class TiltStreamModel { /// {@endtemplate} final GesturesType gesturesType; + /// {@template tilt.TiltStreamModel.isActive.en} /// Whether the gesture is currently active. /// /// Indicates whether the gesture is currently active, - /// and is used to determine processing based on gesture priority. - /// - /// Gesture Priority: - /// {@macro tilt.GesturesType.gesturePriority} + /// and is used to determine processing based on `gesture priority` of [GesturesType]. /// /// e.g. /// If [GesturesType.touch] is never set to false when triggered, @@ -91,16 +89,15 @@ class TiltStreamModel { /// /// - true: Gesture is active. /// - false: Gesture has ended or is no longer active. + /// {@endtemplate} /// /// ------ /// + /// {@template tilt.TiltStreamModel.isActive.zh} /// 手势当前是否处于活动状态。 /// /// 表明手势当前是否处于活动状态, - /// 并根据手势优先级进行处理判断。 - /// - /// 手势优先级: - /// {@macro tilt.GesturesType.gesturePriority} + /// 并根据 [GesturesType] 的 `手势优先级` 进行处理判断。 /// /// 例如: /// 如果在触发 [GesturesType.touch] 的时候永远不赋值为 false, @@ -108,6 +105,7 @@ class TiltStreamModel { /// /// - true:手势处于活动状态。 /// - false:手势已结束或不再处于活动状态。 + /// {@endtemplate} final bool isActive; @override diff --git a/lib/src/widgets/core/tilt_animated_builder.dart b/lib/src/widgets/core/tilt_animated_builder.dart index 504c634..3a88925 100644 --- a/lib/src/widgets/core/tilt_animated_builder.dart +++ b/lib/src/widgets/core/tilt_animated_builder.dart @@ -38,6 +38,7 @@ class TiltAnimatedState { required this.animatedTiltData, required this.targetTiltData, required this.currentGesturesType, + required this.isCurrentGesturesTypeActive, }); /// The current tilt configuration. @@ -51,18 +52,26 @@ class TiltAnimatedState { /// which represents the interpolated result from the initial state to [targetTiltData] /// and is used for rendering the current animation progress. /// + /// During the animation, [animatedTiltData] will gradually change from the initial state and approach [targetTiltData]. + /// /// ------ /// /// 当前动画倾斜数据, /// 表示从初始状态到 [targetTiltData] 的插值结果,用于渲染当前动画进度。 + /// + /// 动画过程中 [animatedTiltData] 会逐渐从初始状态变化并趋近于 [targetTiltData]。 final TiltDataModel animatedTiltData; /// The current target tilt data, /// which is the final target data of the animation. /// + /// During the animation, [animatedTiltData] will gradually change from the initial state and approach [targetTiltData]. + /// /// ------ /// /// 当前目标倾斜数据,动画最终的目标数据。 + /// + /// 动画过程中 [animatedTiltData] 会逐渐从初始状态变化并趋近于 [targetTiltData]。 final TiltDataModel targetTiltData; /// The current gestures type. @@ -71,6 +80,13 @@ class TiltAnimatedState { /// /// 当前手势类型。 final GesturesType currentGesturesType; + + /// {@macro tilt.TiltStreamModel.isActive.en} + /// + /// ------ + /// + /// {@macro tilt.TiltStreamModel.isActive.zh}。 + final bool isCurrentGesturesTypeActive; } /// TiltAnimatedBuilder @@ -169,6 +185,7 @@ class TiltAnimatedBuilder extends StatelessWidget { ), targetTiltData: tiltState.toModel(), currentGesturesType: tiltState.currentGesturesType, + isCurrentGesturesTypeActive: tiltState.isActive, ); return builder(context, tiltAnimatedState, child); diff --git a/test/widgets/core/tilt_animated_builder_test.dart b/test/widgets/core/tilt_animated_builder_test.dart index be43e6a..f9f5b20 100644 --- a/test/widgets/core/tilt_animated_builder_test.dart +++ b/test/widgets/core/tilt_animated_builder_test.dart @@ -154,6 +154,12 @@ void main() { var moveCount = 0; var tiltAnimatedBuildCount = 0; var childBuildCount = 0; + TiltDataModel? moveTiltData; + TiltDataModel? leaveTiltData; + TiltDataModel? moveAnimatedTiltData; + TiltDataModel? moveTargetTiltData; + TiltDataModel? leaveAnimatedTiltData; + TiltDataModel? leaveTargetTiltData; await tester.pumpWidget( MaterialApp( @@ -163,17 +169,34 @@ void main() { fps: fps, tiltConfig: const TiltConfig( enterDuration: Duration.zero, - moveDuration: Duration(milliseconds: 1000), + // 确保存在动画时常,以便在测试过程中捕获动画状态 + moveDuration: Duration(milliseconds: 1), enterToMoveDuration: Duration.zero, leaveDuration: Duration.zero, ), - onGestureMove: (_, __) { + onGestureMove: (tiltDataModel, _) { moveCount++; + if (moveCount == fps + 1) { + moveTiltData = tiltDataModel; + } + }, + onGestureLeave: (tiltDataModel, _) { + leaveTiltData = tiltDataModel; }, child: TiltAnimatedBuilder( builder: (context, animatedState, child) { if (animatedState.currentGesturesType != GesturesType.none) { tiltAnimatedBuildCount++; + + if (animatedState.isCurrentGesturesTypeActive && + tiltAnimatedBuildCount == fps) { + moveAnimatedTiltData = animatedState.animatedTiltData; + moveTargetTiltData = animatedState.targetTiltData; + } + if (!animatedState.isCurrentGesturesTypeActive) { + leaveAnimatedTiltData = animatedState.animatedTiltData; + leaveTargetTiltData = animatedState.targetTiltData; + } } return child!; }, @@ -204,6 +227,21 @@ void main() { expect(moveCount, fps + 1); expect(tiltAnimatedBuildCount, fps + 1); expect(childBuildCount, 1); + + expect(moveTiltData, isNotNull); + expect(leaveTiltData, isNotNull); + expect(moveAnimatedTiltData, isNotNull); + expect(moveTargetTiltData, isNotNull); + expect(leaveAnimatedTiltData, isNotNull); + expect(leaveTargetTiltData, isNotNull); + + expect( + moveAnimatedTiltData!.areaProgress.dy, + closeTo(moveTargetTiltData!.areaProgress.dy, 0.1), + ); + expect(moveTiltData, moveTargetTiltData); + expect(leaveAnimatedTiltData, leaveTargetTiltData); + expect(leaveTiltData, leaveTargetTiltData); }); }); } From 228e942518999efd2ceb230b7faa14b201bd3f8f Mon Sep 17 00:00:00 2001 From: Amos Date: Fri, 24 Apr 2026 16:35:08 +0800 Subject: [PATCH 08/10] refactor: rename TiltParallax parameter `size` to `offset` --- CHANGELOG.md | 1 + example/lib/main.dart | 2 +- lib/src/tilt.dart | 2 +- .../containers/tilt_parallax_container.dart | 28 +++++++++++-------- .../tilt_parallax_container_widget.dart | 6 ++-- 5 files changed, 22 insertions(+), 17 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 9defc8b..a0e634a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -15,6 +15,7 @@ Migration Guides: [Migrate to v4.0.0](#) To reproduce the previous style, you need to compose them inside the `Tilt` widget, or use `Tilt.base` and `Tilt.projector` directly. - `ShadowConfig` has been split into `ShadowBaseConfig` and `ShadowProjectorConfig`. +- Rename `TiltParallaxContainer` parameter `size` to `offset`. **Deprecations** diff --git a/example/lib/main.dart b/example/lib/main.dart index de22955..c486a2c 100644 --- a/example/lib/main.dart +++ b/example/lib/main.dart @@ -34,7 +34,7 @@ class TiltExample extends StatelessWidget { for (var i = 1; i <= 10; i++) { innerBox.add( TiltParallax( - size: Offset(-20.0 * i, -30.0 * i), + offset: Offset(-20.0 * i, -30.0 * i), child: Container( width: 200 * (1 - i * 0.05), height: 200 * (1 - i * 0.05), diff --git a/lib/src/tilt.dart b/lib/src/tilt.dart index a926d7d..64d99c4 100644 --- a/lib/src/tilt.dart +++ b/lib/src/tilt.dart @@ -153,7 +153,7 @@ class TiltParallax extends TiltParallaxContainer { const TiltParallax({ super.key, required super.child, - super.size = const Offset(10.0, 10.0), + super.offset = const Offset(10.0, 10.0), super.filterQuality, }); } diff --git a/lib/src/widgets/containers/tilt_parallax_container.dart b/lib/src/widgets/containers/tilt_parallax_container.dart index f850f0f..b18d0c6 100644 --- a/lib/src/widgets/containers/tilt_parallax_container.dart +++ b/lib/src/widgets/containers/tilt_parallax_container.dart @@ -10,7 +10,7 @@ class TiltParallaxContainer extends StatelessWidget { /// {@endtemplate} /// /// {@template tilt.TiltParallaxContainer.param.en} - /// - [size]: Parallax size. + /// - [offset]: Parallax offset. /// - [filterQuality]: Flutter FilterQuality. /// {@endtemplate} /// @@ -23,20 +23,24 @@ class TiltParallaxContainer extends StatelessWidget { /// {@endtemplate} /// /// {@template tilt.TiltParallaxContainer.param.zh} - /// - [size]:视差大小。 + /// - [offset]:视差偏移量。 /// - [filterQuality]:Flutter FilterQuality。 /// {@endtemplate} const TiltParallaxContainer({ super.key, required this.child, - required this.size, + required this.offset, this.filterQuality, }); final Widget child; - /// 视差大小 (x, y) - final Offset size; + /// Parallax offset + /// + /// ------ + /// + /// 视差偏移量 (x, y) + final Offset offset; final FilterQuality? filterQuality; @@ -46,7 +50,7 @@ class TiltParallaxContainer extends StatelessWidget { builder: (context, tiltAnimatedState, child) { final tiltParallaxTransform = this.tiltParallaxTransform( tiltAnimatedState.animatedTiltData.areaProgress, - size, + offset, tiltAnimatedState.tiltConfig.enableReverse, ); @@ -63,18 +67,18 @@ class TiltParallaxContainer extends StatelessWidget { /// 计算当前倾斜视差 /// /// - [areaProgress] 当前坐标的区域进度 - /// - [size] 视差大小 + /// - [offset] 视差偏移量 /// - [enableReverse] 开启倾斜反向,向上或向下倾斜 Matrix4 tiltParallaxTransform( Offset areaProgress, - Offset size, + Offset offset, bool enableReverse, ) { - final dx = size.dx * areaProgress.dx; - final dy = size.dy * areaProgress.dy; - final offset = enableReverse ? Offset(-dx, -dy) : Offset(dx, dy); + final dx = offset.dx * areaProgress.dx; + final dy = offset.dy * areaProgress.dy; + final parallaxOffset = enableReverse ? Offset(-dx, -dy) : Offset(dx, dy); // TODO: 兼容低版本开发者,未来完全弃用时再替换为新的方法(Flutter 3.35 开始标记为弃用) // ignore: deprecated_member_use - return Matrix4.identity()..translate(offset.dx, offset.dy); + return Matrix4.identity()..translate(parallaxOffset.dx, parallaxOffset.dy); } } diff --git a/test/widgets/shared_widgets/tilt_parallax_container_widget.dart b/test/widgets/shared_widgets/tilt_parallax_container_widget.dart index 6d88621..66973b1 100644 --- a/test/widgets/shared_widgets/tilt_parallax_container_widget.dart +++ b/test/widgets/shared_widgets/tilt_parallax_container_widget.dart @@ -60,7 +60,7 @@ class TiltParallaxContainerWidget extends StatelessWidget { outer: [ Positioned( child: TiltParallaxContainer( - size: Offset(10, 10), + offset: Offset(10, 10), child: SizedBox( width: 10, height: 10, @@ -72,7 +72,7 @@ class TiltParallaxContainerWidget extends StatelessWidget { inner: [ Positioned( child: TiltParallaxContainer( - size: Offset(10, 10), + offset: Offset(10, 10), child: SizedBox( width: 10, height: 10, @@ -84,7 +84,7 @@ class TiltParallaxContainerWidget extends StatelessWidget { behind: [ Positioned( child: TiltParallaxContainer( - size: Offset(10, 10), + offset: Offset(10, 10), child: SizedBox( width: 10, height: 10, From 9fcb78290f4833bfe44710b24b715ff2f8d1cd38 Mon Sep 17 00:00:00 2001 From: Amos Date: Fri, 24 Apr 2026 16:45:10 +0800 Subject: [PATCH 09/10] update: CHANGELOG.md --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index a0e634a..3898455 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -15,7 +15,7 @@ Migration Guides: [Migrate to v4.0.0](#) To reproduce the previous style, you need to compose them inside the `Tilt` widget, or use `Tilt.base` and `Tilt.projector` directly. - `ShadowConfig` has been split into `ShadowBaseConfig` and `ShadowProjectorConfig`. -- Rename `TiltParallaxContainer` parameter `size` to `offset`. +- Rename `TiltParallax` parameter `size` to `offset`. **Deprecations** From c0092670c6127a56d59254002bfa844f6fab7dc5 Mon Sep 17 00:00:00 2001 From: Amos Date: Sat, 25 Apr 2026 18:48:28 +0800 Subject: [PATCH 10/10] update: cleanup --- example/example.md | 27 +++++++++++--------- example/lib/main.dart | 20 +++++++++------ lib/src/widgets/effects/tilt_light.dart | 34 +++++++++++++------------ 3 files changed, 45 insertions(+), 36 deletions(-) diff --git a/example/example.md b/example/example.md index 6528f26..bd21d41 100644 --- a/example/example.md +++ b/example/example.md @@ -21,7 +21,6 @@ class MyApp extends StatelessWidget { return MaterialApp( title: 'Flutter Tilt Example', theme: ThemeData( - useMaterial3: true, colorScheme: ColorScheme.fromSwatch(primarySwatch: Colors.brown), appBarTheme: const AppBarTheme( backgroundColor: Colors.brown, @@ -42,15 +41,17 @@ class TiltExample extends StatelessWidget { for (var i = 1; i <= 10; i++) { innerBox.add( TiltParallax( - size: Offset(-20.0 * i, -30.0 * i), - child: Container( + offset: Offset(-20.0 * i, -30.0 * i), + child: SizedBox( width: 200 * (1 - i * 0.05), height: 200 * (1 - i * 0.05), - decoration: BoxDecoration( - shape: BoxShape.circle, - border: Border.all( - width: 4 * (1 - i * 0.05), - color: Colors.white.withValues(alpha: 1 - (i - 1) * 0.1), + child: DecoratedBox( + decoration: BoxDecoration( + shape: BoxShape.circle, + border: Border.all( + width: 4 * (1 - i * 0.05), + color: Colors.white.withValues(alpha: 1 - (i - 1) * 0.1), + ), ), ), ), @@ -61,7 +62,7 @@ class TiltExample extends StatelessWidget { return Scaffold( backgroundColor: const Color(0xFFFFFFFF), body: Center( - child: Tilt( + child: Tilt.base( borderRadius: BorderRadius.circular(24.0), tiltConfig: const TiltConfig( angle: 20, @@ -69,7 +70,7 @@ class TiltExample extends StatelessWidget { leaveDuration: Duration(milliseconds: 1200), ), lightConfig: const LightConfig(disable: true), - shadowConfig: const ShadowConfig(disable: true), + shadowConfig: const ShadowBaseConfig(disable: true), childLayout: ChildLayout( inner: [ ...innerBox, @@ -109,10 +110,12 @@ class TiltExample extends StatelessWidget { ), ], ), - child: Container( + child: SizedBox( width: 300, height: 500, - decoration: const BoxDecoration(color: Colors.black), + child: DecoratedBox( + decoration: const BoxDecoration(color: Colors.black), + ), ), ), ), diff --git a/example/lib/main.dart b/example/lib/main.dart index c486a2c..ba46822 100644 --- a/example/lib/main.dart +++ b/example/lib/main.dart @@ -35,14 +35,16 @@ class TiltExample extends StatelessWidget { innerBox.add( TiltParallax( offset: Offset(-20.0 * i, -30.0 * i), - child: Container( + child: SizedBox( width: 200 * (1 - i * 0.05), height: 200 * (1 - i * 0.05), - decoration: BoxDecoration( - shape: BoxShape.circle, - border: Border.all( - width: 4 * (1 - i * 0.05), - color: Colors.white.withValues(alpha: 1 - (i - 1) * 0.1), + child: DecoratedBox( + decoration: BoxDecoration( + shape: BoxShape.circle, + border: Border.all( + width: 4 * (1 - i * 0.05), + color: Colors.white.withValues(alpha: 1 - (i - 1) * 0.1), + ), ), ), ), @@ -101,10 +103,12 @@ class TiltExample extends StatelessWidget { ), ], ), - child: Container( + child: SizedBox( width: 300, height: 500, - decoration: const BoxDecoration(color: Colors.black), + child: DecoratedBox( + decoration: const BoxDecoration(color: Colors.black), + ), ), ), ), diff --git a/lib/src/widgets/effects/tilt_light.dart b/lib/src/widgets/effects/tilt_light.dart index 890cc94..d0f1f8a 100644 --- a/lib/src/widgets/effects/tilt_light.dart +++ b/lib/src/widgets/effects/tilt_light.dart @@ -86,24 +86,26 @@ class TiltLight extends StatelessWidget with TiltDecorationMixin { right: !enableReverse ? positionX : null, bottom: !enableReverse ? positionY : null, child: IgnorePointer( - child: Container( + child: SizedBox( width: spreadWidth, height: spreadHeight, - decoration: BoxDecoration( - gradient: RadialGradient( - center: Alignment.center, - colors: [ - /// TODO: Flutter v3.27.0 之后需要迁移,在这之前暂时使用 withAlpha, - /// (目前为了兼容更多低版本 Flutter 以及对于非主要 Tilt 效果的 P3 广色域优先级很低,未来再迁移为 withValues) - /// 以下 withAlpha 内的计算方式和 withOpacity 内部的计算方式一致, - /// 所以还不支持 P3 广色域,目前依旧是 sRGB。 - /// https://docs.flutter.dev/release/breaking-changes/wide-gamut-framework - lightConfig.color.withAlpha((alphaProgress * 0.95).round()), - lightConfig.color.withAlpha((alphaProgress * 0.85).round()), - lightConfig.color.withAlpha((alphaProgress * 0.5).round()), - lightConfig.color.withAlpha(0), - ], - stops: const [0.01, 0.4, 0.75, 0.99], + child: DecoratedBox( + decoration: BoxDecoration( + gradient: RadialGradient( + center: Alignment.center, + colors: [ + /// TODO: Flutter v3.27.0 之后需要迁移,在这之前暂时使用 withAlpha, + /// (目前为了兼容更多低版本 Flutter 以及对于非主要 Tilt 效果的 P3 广色域优先级很低,未来再迁移为 withValues) + /// 以下 withAlpha 内的计算方式和 withOpacity 内部的计算方式一致, + /// 所以还不支持 P3 广色域,目前依旧是 sRGB。 + /// https://docs.flutter.dev/release/breaking-changes/wide-gamut-framework + lightConfig.color.withAlpha((alphaProgress * 0.95).round()), + lightConfig.color.withAlpha((alphaProgress * 0.85).round()), + lightConfig.color.withAlpha((alphaProgress * 0.5).round()), + lightConfig.color.withAlpha(0), + ], + stops: const [0.01, 0.4, 0.75, 0.99], + ), ), ), ),