diff --git a/CHANGELOG.md b/CHANGELOG.md index 99021d1..3898455 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,6 +3,30 @@ > [!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... +- 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. +- `ShadowConfig` has been split into `ShadowBaseConfig` and `ShadowProjectorConfig`. +- Rename `TiltParallax` parameter `size` to `offset`. + +**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/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") 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 f217597..ba46822 100644 --- a/example/lib/main.dart +++ b/example/lib/main.dart @@ -34,15 +34,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), + ), ), ), ), @@ -53,7 +55,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 +63,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, @@ -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/flutter_tilt.dart b/lib/flutter_tilt.dart index caf44e1..e539cff 100644 --- a/lib/flutter_tilt.dart +++ b/lib/flutter_tilt.dart @@ -1,15 +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 export 'src/config/tilt_config.dart'; export 'src/config/tilt_light_config.dart'; export 'src/config/tilt_shadow_config.dart'; export 'src/enums.dart'; + +/// 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; -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/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/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/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 e253af7..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,54 +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.onResize, + 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; - - /// 调整尺寸 - final void Function(Size) onResize; - - 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 02bdcd4..0000000 --- a/lib/src/internal/tilt_data.dart +++ /dev/null @@ -1,78 +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; - - /// 倾斜数据 - TiltDataModel get data => TiltDataModel( - position: position, - transform: transform, - areaProgress: areaProgress, - angle: angle, - ); - - /// 当前坐标 - 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); - } -} diff --git a/lib/src/internal/tilt_state.dart b/lib/src/internal/tilt_state.dart new file mode 100644 index 0000000..f066a41 --- /dev/null +++ b/lib/src/internal/tilt_state.dart @@ -0,0 +1,198 @@ +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; + + /// {@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; + + /// 当前坐标 + 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 2d776c4..872c207 100644 --- a/lib/src/models/tilt_data_model.dart +++ b/lib/src/models/tilt_data_model.dart @@ -7,12 +7,17 @@ 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 +61,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 +71,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..18fb9d5 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,25 +21,25 @@ 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} /// 当前触发的坐标位置, /// 会触发对应位置的倾斜效果。 /// /// 例如: - /// 有一个组件尺寸为 width: 10, height: 10, + /// 有一个 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,41 +74,39 @@ class TiltStreamModel { /// /// 配置 `TiltConfig.enableSensorRevert` 为 false 的情况下, /// 将同样不会复原至初始状态。 - /// + /// {@endtemplate} final GesturesType gesturesType; - /// Whether the gesture is being used. + /// {@template tilt.TiltStreamModel.isActive.en} + /// 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. - /// - /// Gesture Priority: - /// {@macro tilt.GesturesType.gesturePriority} + /// Indicates whether the gesture is currently active, + /// and is used to determine processing based on `gesture priority` of [GesturesType]. /// /// 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. + /// {@endtemplate} /// /// ------ /// - /// 手势是否正在使用。 - /// - /// 用于确定手势是否正在使用,并根据手势优先级进行处理。 + /// {@template tilt.TiltStreamModel.isActive.zh} + /// 手势当前是否处于活动状态。 /// - /// 手势优先级: - /// {@macro tilt.GesturesType.gesturePriority} + /// 表明手势当前是否处于活动状态, + /// 并根据 [GesturesType] 的 `手势优先级` 进行处理判断。 /// /// 例如: /// 如果在触发 [GesturesType.touch] 的时候永远不赋值为 false, /// 那么优先级低于 [GesturesType.touch] 的手势将永远不会被触发。 /// - /// - true : 手势正在使用。 - /// - false : 手势离开或不再使用。 - /// - final bool gestureUse; + /// - true:手势处于活动状态。 + /// - false:手势已结束或不再处于活动状态。 + /// {@endtemplate} + final bool isActive; @override bool operator ==(Object other) { @@ -121,13 +119,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 e61f96f..64d99c4 100644 --- a/lib/src/tilt.dart +++ b/lib/src/tilt.dart @@ -1,408 +1,138 @@ -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.tiltController, + 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.tiltController, + 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.tiltController, + super.disable, + super.fps, + super.tiltConfig, + 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, + 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,22 +141,19 @@ 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, - super.size = const Offset(10.0, 10.0), + super.offset = const Offset(10.0, 10.0), super.filterQuality, }); } 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 new file mode 100644 index 0000000..fd370ee --- /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'; + +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, tiltAnimatedState, child) { + final animatedTiltData = tiltAnimatedState.animatedTiltData; + + return Transform( + alignment: AlignmentDirectional.center, + filterQuality: filterQuality, + transform: animatedTiltData.transform, + child: Stack( + alignment: AlignmentDirectional.center, + clipBehavior: Clip.none, + children: _buildChildren( + child: child, + width: animatedTiltData.width, + height: animatedTiltData.height, + areaProgress: animatedTiltData.areaProgress, + ), + ), + ); + }, + child: child, + ); + } + + List _buildChildren({ + Widget? child, + required double width, + required double height, + required Offset areaProgress, + }) { + 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, + ), + + /// TODO: 已被 [WidgetSizeGetter] 替代,后续删除 + /// 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..14d6898 --- /dev/null +++ b/lib/src/widgets/containers/tilt_container_utils.dart @@ -0,0 +1,25 @@ +import 'package:flutter/widgets.dart'; +import '../core/widget_size_getter.dart'; + +abstract final class TiltContainerUtils { + /// TODO: 已被 [WidgetSizeGetter] 替代,后续删除 + /// + /// 尺寸监听 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/containers/tilt_parallax_container.dart b/lib/src/widgets/containers/tilt_parallax_container.dart new file mode 100644 index 0000000..b18d0c6 --- /dev/null +++ b/lib/src/widgets/containers/tilt_parallax_container.dart @@ -0,0 +1,84 @@ +import 'package:flutter/widgets.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]. + /// + /// Create a parallax layer within the Tilt widget tree. + /// {@endtemplate} + /// + /// {@template tilt.TiltParallaxContainer.param.en} + /// - [offset]: Parallax offset. + /// - [filterQuality]: Flutter FilterQuality. + /// {@endtemplate} + /// + /// ------ + /// + /// {@template tilt.TiltParallaxContainer.desc.zh} + /// 倾斜视差,只能在 [Tilt] 中使用的视差效果 widget。 + /// + /// 在 Tilt widget 树内创建视差效果层。 + /// {@endtemplate} + /// + /// {@template tilt.TiltParallaxContainer.param.zh} + /// - [offset]:视差偏移量。 + /// - [filterQuality]:Flutter FilterQuality。 + /// {@endtemplate} + const TiltParallaxContainer({ + super.key, + required this.child, + required this.offset, + this.filterQuality, + }); + + final Widget child; + + /// Parallax offset + /// + /// ------ + /// + /// 视差偏移量 (x, y) + final Offset offset; + + final FilterQuality? filterQuality; + + @override + Widget build(BuildContext context) { + return TiltAnimatedBuilder( + builder: (context, tiltAnimatedState, child) { + final tiltParallaxTransform = this.tiltParallaxTransform( + tiltAnimatedState.animatedTiltData.areaProgress, + offset, + tiltAnimatedState.tiltConfig.enableReverse, + ); + + return Transform( + filterQuality: filterQuality, + transform: tiltParallaxTransform, + child: child, + ); + }, + child: child, + ); + } + + /// 计算当前倾斜视差 + /// + /// - [areaProgress] 当前坐标的区域进度 + /// - [offset] 视差偏移量 + /// - [enableReverse] 开启倾斜反向,向上或向下倾斜 + Matrix4 tiltParallaxTransform( + Offset areaProgress, + Offset offset, + bool enableReverse, + ) { + 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(parallaxOffset.dx, parallaxOffset.dy); + } +} 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..495af0c --- /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'; + +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, tiltAnimatedState, child) { + final animatedTiltData = tiltAnimatedState.animatedTiltData; + + return Transform( + alignment: AlignmentDirectional.center, + filterQuality: filterQuality, + transform: animatedTiltData.transform, + child: Stack( + alignment: AlignmentDirectional.center, + clipBehavior: Clip.none, + children: _buildChildren( + child: child, + width: animatedTiltData.width, + height: animatedTiltData.height, + areaProgress: animatedTiltData.areaProgress, + ), + ), + ); + }, + child: child, + ); + } + + List _buildChildren({ + Widget? child, + required double width, + required double height, + required Offset areaProgress, + }) { + 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, + + /// TODO: 已被 [WidgetSizeGetter] 替代,后续删除 + /// 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/core/gestures_listener.dart b/lib/src/widgets/core/gestures_listener.dart new file mode 100644 index 0000000..9f98e5c --- /dev/null +++ b/lib/src/widgets/core/gestures_listener.dart @@ -0,0 +1,54 @@ +import 'package:flutter/widgets.dart'; + +import '../../config/tilt_config.dart'; +import '../../internal/action_input/sources/tilt_pointer_input_source.dart'; + +/// 手势监听 +class GesturesListener extends StatelessWidget { + /// 手势监听 + /// + /// 对 Touch [Listener] 和 Mouse [MouseRegion] 的监听触发 + const GesturesListener({ + super.key, + required this.child, + required this.disable, + required this.tiltConfig, + required this.pointerInputSource, + }); + + final Widget child; + final bool disable; + final TiltConfig tiltConfig; + final TiltPointerInputSource pointerInputSource; + + bool get _enableGestureTouch => tiltConfig.enableGestureTouch; + bool get _enableGestureHover => tiltConfig.enableGestureHover; + + @override + Widget build(BuildContext context) { + if (disable) return child; + + /// 不受滑动影响 + return GestureDetector( + onVerticalDragUpdate: _enableGestureTouch ? (_) {} : null, + onHorizontalDragUpdate: _enableGestureTouch ? (_) {} : null, + child: Listener( + onPointerMove: + _enableGestureTouch ? pointerInputSource.handlePointerMove : null, + onPointerUp: + _enableGestureTouch ? pointerInputSource.handlePointerUp : null, + onPointerCancel: + _enableGestureTouch ? pointerInputSource.handlePointerCancel : null, + child: MouseRegion( + onEnter: + _enableGestureHover ? pointerInputSource.handleMouseEnter : null, + onHover: + _enableGestureHover ? pointerInputSource.handleMouseHover : null, + onExit: + _enableGestureHover ? pointerInputSource.handleMouseExit : null, + child: child, + ), + ), + ); + } +} 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..3a88925 --- /dev/null +++ b/lib/src/widgets/core/tilt_animated_builder.dart @@ -0,0 +1,219 @@ +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_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. +/// - [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} +/// +/// ------ +/// +/// {@template tilt.TiltAnimatedWidgetBuilder.zh} +/// [TiltAnimatedBuilder] 的 builder 回调,每帧动画更新时触发。 +/// +/// - [context]:当前 build context。 +/// - [tiltAnimatedState]:当前动画快照,包含动画倾斜数据和目标倾斜数据。 +/// - [child]:传入 [TiltAnimatedBuilder.child] 的预构建子树,可能为 `null`。 +/// {@endtemplate} +typedef TiltAnimatedWidgetBuilder = Widget Function( + BuildContext context, + TiltAnimatedState tiltAnimatedState, + Widget? child, +); + +@immutable +class TiltAnimatedState { + const TiltAnimatedState({ + required this.tiltConfig, + required this.animatedTiltData, + required this.targetTiltData, + required this.currentGesturesType, + required this.isCurrentGesturesTypeActive, + }); + + /// 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. + /// + /// 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. + /// + /// ------ + /// + /// 当前手势类型。 + final GesturesType currentGesturesType; + + /// {@macro tilt.TiltStreamModel.isActive.en} + /// + /// ------ + /// + /// {@macro tilt.TiltStreamModel.isActive.zh}。 + final bool isCurrentGesturesTypeActive; +} + +/// 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, tiltAnimatedState, child) { + /// return Transform( + /// alignment: AlignmentDirectional.center, + /// transform: animatedState.animatedTiltData.transform, + /// child: MyCustomWidget( + /// progress: animatedState.animatedTiltData.areaProgress, + /// child: child, + /// ), + /// ); + /// }, + /// child: SomeWidget(), + /// ), + /// ) + /// ``` + /// + const TiltAnimatedBuilder({ + super.key, + required this.builder, + 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 tiltState = TiltProvider.of(context); + final tiltAnimationProvider = TiltAnimationProvider.of(context); + final tiltTweenAnimation = tiltAnimationProvider.tiltTweenAnimation; + + return AnimatedBuilder( + animation: tiltTweenAnimation, + builder: (BuildContext context, Widget? child) { + final tiltAnimatedState = TiltAnimatedState( + tiltConfig: tiltState.tiltConfig, + animatedTiltData: _animatedTiltDataModel( + tiltState, + tiltTweenAnimation.value, + ), + targetTiltData: tiltState.toModel(), + currentGesturesType: tiltState.currentGesturesType, + isCurrentGesturesTypeActive: tiltState.isActive, + ); + + 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_widget.dart b/lib/src/widgets/core/tilt_widget.dart new file mode 100644 index 0000000..b351b5c --- /dev/null +++ b/lib/src/widgets/core/tilt_widget.dart @@ -0,0 +1,419 @@ +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/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_state.dart'; +import '../../models/tilt_stream_model.dart'; +import '../../utils/fps_throttle.dart'; +import '../../utils/utils.dart'; +import 'gestures_listener.dart'; +import 'widget_size_getter.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} + /// - [tiltController] : Controller for custom tilt input. + /// - [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} + /// - [tiltController]:用于自定义倾斜输入的控制器。 + /// - [disable]:禁用所有效果。 + /// - [fps]:手势触发的帧数。 + /// - [tiltConfig]:倾斜效果配置。 + /// - [onGestureMove]:手势移动的回调触发。 + /// - [onGestureLeave]:手势离开的回调触发。 + /// {@endtemplate} + /// + const TiltWidget({ + super.key, + required this.child, + this.tiltController, + this.disable = false, + this.fps = 120, + this.tiltConfig = const TiltConfig(), + this.onGestureMove, + this.onGestureLeave, + }); + + final Widget child; + + /// Tilt Controller + /// + /// Controller for custom tilt input. + /// + /// ------ + /// + /// 用于自定义倾斜输入的控制器。 + final TiltController? tiltController; + + /// 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 { + late TiltState _tiltState; + late FpsThrottle _fpsThrottle; + late TiltActionInputCoordinator _tiltActionInputCoordinator; + + StreamSubscription? _tiltStreamSubscription; + + /// 默认 TiltController + /// + /// [widget.tiltController] 为 null 时使用 + final TiltController _kDefaultTiltController = TiltController(); + + /// 当前坐标 + late Offset _currentPosition; + + /// 初始坐标区域进度 + Offset get _initAreaProgress => widget.tiltConfig.initial ?? Offset.zero; + + TiltController get _effectiveTiltController => + widget.tiltController ?? _kDefaultTiltController; + + @override + void initState() { + super.initState(); + _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() { + _tiltStreamSubscription?.cancel(); + _kDefaultTiltController.dispose(); + _fpsThrottle.dispose(); + _tiltActionInputCoordinator.dispose(); + super.dispose(); + } + + @override + void didUpdateWidget(TiltWidget oldWidget) { + super.didUpdateWidget(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( + disable: widget.disable, + tiltConfig: widget.tiltConfig, + pointerInputSource: _tiltActionInputCoordinator.pointerInputSource, + child: TiltProvider( + data: _tiltState, + child: _TiltAnimationProviderWrapper( + child: WidgetSizeGetter(onSize: _onResize, child: widget.child), + ), + ), + ); + } + + /// 初始化 [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(); + + /// 创建并初始化全新的协调器 + _initCoordinator(); + } + + /// 判断是否需要重新初始化 + bool _shouldReinit(TiltWidget oldWidget) { + return oldWidget.tiltController != widget.tiltController || + oldWidget.disable != widget.disable || + oldWidget.fps != widget.fps || + oldWidget.tiltConfig != widget.tiltConfig; + } + + /// 调整尺寸及初始参数 + void _onResize(Size size) { + final oldSize = Size(_tiltState.width, _tiltState.height); + if (oldSize != size) { + setState(() { + _tiltState = _tiltState.copyWith( + isInit: true, + width: size.width, + height: size.height, + ); + _currentPosition = Utils.progressPosition( + _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 (!_tiltState.isInit || widget.disable) return; + switch (tiltStreamModel.gesturesType) { + case GesturesType.none: + break; + case GesturesType.touch || GesturesType.hover || GesturesType.controller: + if (tiltStreamModel.isActive) { + _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( + _tiltState.width, + _tiltState.height, + _currentPosition, + ); + _onGesturesMove(_currentPosition, tiltStreamModel.gesturesType); + } + } + + /// 手势移动触发 + /// + /// [offset] 当前坐标 + void _onGesturesMove(Offset offset, GesturesType gesturesType) { + if (!_tiltState.isInit || widget.disable) return; + if (!_fpsThrottle.shouldTrigger()) return; + if (widget.tiltConfig.enableOutsideAreaMove || + Utils.isInRange(_tiltState.width, _tiltState.height, offset)) { + setState(() { + _currentPosition = offset; + _tiltState = _tiltState.moveTo(offset, gesturesType); + }); + + _onGestureMove(_tiltState.areaProgress, gesturesType); + } else { + _onGesturesRevert(offset, gesturesType); + } + } + + /// 手势复原触发 + /// + /// [offset] 当前坐标 + void _onGesturesRevert(Offset offset, GesturesType gesturesType) { + if (!_tiltState.isInit || widget.disable || !_tiltState.isActive) return; + + /// 是否还原的取值 + final position = _tiltState.resolveRevertPosition( + currentPosition: _currentPosition, + initialAreaProgress: _initAreaProgress, + ); + setState(() { + _currentPosition = position; + _tiltState = _tiltState.revertTo(position, gesturesType); + }); + _onGestureLeave(_tiltState.areaProgress, gesturesType); + } + + /// 手势传感器复原触发 + /// + /// Sensors 只会触发 onGestureMove,不会触发 onGestureLeave + void _onGesturesSensorsRevert() { + if (!widget.tiltConfig.enableSensorRevert) return; + + /// 默认坐标 + final initPosition = Utils.progressPosition( + _tiltState.width, + _tiltState.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 && mounted) { + callback( + _tiltState + .copyWith( + areaProgress: areaProgress, + currentGesturesType: gesturesType, + ) + .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/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/lib/src/widgets/tilt_light.dart b/lib/src/widgets/effects/tilt_light.dart similarity index 64% rename from lib/src/widgets/tilt_light.dart rename to lib/src/widgets/effects/tilt_light.dart index 802b65a..d0f1f8a 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/utils.dart'; /// 光源 class TiltLight extends StatelessWidget with TiltDecorationMixin { @@ -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], + ), ), ), ), 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..810f30f 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/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/gestures_listener.dart b/lib/src/widgets/gestures_listener.dart deleted file mode 100644 index 9a456cb..0000000 --- a/lib/src/widgets/gestures_listener.dart +++ /dev/null @@ -1,111 +0,0 @@ -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'; - -/// 手势监听 -class GesturesListener extends StatefulWidget { - /// 手势监听 - /// - /// 对 Touch [Listener] 和 Mouse [MouseRegion] 的监听触发 - const GesturesListener({ - super.key, - required this.child, - required this.tiltGesturesController, - }); - - final Widget child; - - /// 倾斜手势控制器 - 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; - - @override - Widget build(BuildContext context) { - if (widget.tiltGesturesController.disable) return widget.child; - - /// 不受滑动影响 - return GestureDetector( - onVerticalDragUpdate: _enableGestureTouch ? (_) {} : null, - onHorizontalDragUpdate: _enableGestureTouch ? (_) {} : null, - child: Listener( - onPointerMove: _enableGestureTouch ? _handlePointerMove : null, - onPointerUp: _enableGestureTouch ? _handlePointerUp : null, - onPointerCancel: _enableGestureTouch ? _handlePointerCancel : null, - child: MouseRegion( - onEnter: _enableGestureHover ? _handleMouseEnter : null, - onHover: _enableGestureHover ? _handleMouseHover : null, - onExit: _enableGestureHover ? _handleMouseExit : null, - child: widget.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/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/lib/src/widgets/tilt_parallax_container.dart b/lib/src/widgets/tilt_parallax_container.dart deleted file mode 100644 index e9d3292..0000000 --- a/lib/src/widgets/tilt_parallax_container.dart +++ /dev/null @@ -1,70 +0,0 @@ -import 'package:flutter/widgets.dart'; - -import '../internal/provider/tilt_animation_provider.dart'; -import '../internal/provider/tilt_provider.dart'; - -/// 倾斜视差 -class TiltParallaxContainer extends StatelessWidget { - /// 倾斜视差 - /// - /// 用作视差的 Widget - /// - /// 只能在 [Tilt.childLayout] 中使用 - const TiltParallaxContainer({ - super.key, - required this.child, - required this.size, - this.filterQuality, - }); - - final Widget child; - - /// 视差大小 (x, y) - final Offset size; - - final FilterQuality? filterQuality; - - @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; - final tiltParallaxTransform = this.tiltParallaxTransform( - areaProgress, - size, - tiltProvider.tiltConfig.enableReverse, - ); - - return Transform( - filterQuality: filterQuality, - transform: tiltParallaxTransform, - child: child, - ); - }, - child: child, - ); - } - - /// 计算当前倾斜视差 - /// - /// - [areaProgress] 当前坐标的区域进度 - /// - [size] 视差大小 - /// - [enableReverse] 开启倾斜反向,向上或向下倾斜 - Matrix4 tiltParallaxTransform( - Offset areaProgress, - Offset size, - bool enableReverse, - ) { - final dx = size.dx * areaProgress.dx; - final dy = size.dy * areaProgress.dy; - final offset = enableReverse ? Offset(-dx, -dy) : Offset(dx, dy); - // TODO: 兼容低版本开发者,未来完全弃用时再替换为新的方法(Flutter 3.35 开始标记为弃用) - // ignore: deprecated_member_use - return Matrix4.identity()..translate(offset.dx, offset.dy); - } -} diff --git a/lib/src/widgets/tilt_stream_builder.dart b/lib/src/widgets/tilt_stream_builder.dart deleted file mode 100644 index 33ca73a..0000000 --- a/lib/src/widgets/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/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..f844f83 100644 --- a/test/config/tilt_light_config_test.dart +++ b/test/config/tilt_light_config_test.dart @@ -31,20 +31,74 @@ void main() { () => LightConfig(spreadFactor: 0.1), throwsAssertionError, ); + }); + test('copyWith', () { + const lightConfig = LightConfig(); + const lightConfigExpect = LightConfig( + disable: true, + color: Color(0xFFFFFFF0), + minIntensity: 0.0, + maxIntensity: 0.5, + spreadFactor: 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, + direction: LightDirection.around, + enableReverse: true, + ); + expect(lightConfig, lightConfig.copyWith()); + expect(lightConfigCopyWith, lightConfigExpect); + expect(lightConfigCopyWith.hashCode, lightConfigExpect.hashCode); + }); + }); + + group('LightProjectorConfig ::', () { + test('assert', () { expect( - () => LightConfig(projectorScale: -1), + () => 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 = LightConfig(); - const lightConfigExpect = LightConfig( + const lightConfig = LightProjectorConfig(); + const lightConfigExpect = LightProjectorConfig( disable: true, color: Color(0xFFFFFFF0), minIntensity: 0.0, maxIntensity: 0.5, spreadFactor: 1.0, - projectorScale: 2.0, + projectorScale: 1.0, direction: LightDirection.around, enableReverse: true, ); @@ -54,7 +108,7 @@ void main() { minIntensity: 0.0, maxIntensity: 0.5, spreadFactor: 1.0, - projectorScale: 2.0, + projectorScale: 1.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/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 2933aa1..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 ::', () { @@ -217,16 +218,17 @@ class TiltTweenAnimationMixinTestWidget extends StatelessWidget { @override Widget build(BuildContext context) { - return TiltProvider( - tiltConfig: const TiltConfig(), - isInit: true, - width: 10.0, - height: 10.0, - areaProgress: Offset.zero, - isMove: true, - currentGesturesType: GesturesType.touch, - onResize: (_) {}, - child: const TiltTweenAnimationMixinTest(), + return const TiltProvider( + 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_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/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_parallax_widget/tilt_parallax_widget.dart b/test/tilt/shared_widgets/tilt_parallax_widget.dart similarity index 73% rename from test/tilt_parallax_widget/tilt_parallax_widget.dart rename to test/tilt/shared_widgets/tilt_parallax_widget.dart index e82d94d..44cc9f0 100644 --- a/test/tilt_parallax_widget/tilt_parallax_widget.dart +++ b/test/tilt/shared_widgets/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/shared_widgets/tilt_widget.dart b/test/tilt/shared_widgets/tilt_widget.dart new file mode 100644 index 0000000..39f0d90 --- /dev/null +++ b/test/tilt/shared_widgets/tilt_widget.dart @@ -0,0 +1,184 @@ +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.tiltController, + 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 TiltController? tiltController; + 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'), + tiltController: tiltController, + 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.tiltController, + 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 TiltController? tiltController; + 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'), + tiltController: tiltController, + 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.tiltController, + 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 TiltController? tiltController; + 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'), + tiltController: tiltController, + 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..4995115 --- /dev/null +++ b/test/tilt/tilt_base_widget_test.dart @@ -0,0 +1,83 @@ +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]); + }); + + 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_widget/tilt_config_tilt_test.dart b/test/tilt/tilt_config_tilt_test.dart similarity index 94% rename from test/tilt_widget/tilt_config_tilt_test.dart rename to test/tilt/tilt_config_tilt_test.dart index 209887b..c9a032d 100644 --- a/test/tilt_widget/tilt_config_tilt_test.dart +++ b/test/tilt/tilt_config_tilt_test.dart @@ -3,28 +3,30 @@ 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 'tilt_widget.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')); 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; @@ -32,7 +34,7 @@ void main() { final tiltDataExpect = tiltDataTestCalculate( areaProgress: const Offset(0.0, 1.0), tiltConfig: tiltConfigTest, - ).data; + ).toModel(); await tester.pumpWidget( TiltWidget( @@ -56,11 +58,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 +109,7 @@ void main() { final tiltDataExpect = tiltDataTestCalculate( areaProgress: const Offset(1.0, 1.0), tiltConfig: tiltConfigTest, - ).data; + ).toModel(); await tester.pumpWidget( TiltWidget( @@ -144,11 +146,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 +184,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 +220,7 @@ void main() { final tiltDataExpect = tiltDataTestCalculate( areaProgress: const Offset(1.0, 1.0), tiltConfig: tiltConfigTest, - ).data; + ).toModel(); await tester.pumpWidget( TiltWidget( @@ -265,7 +267,7 @@ void main() { final tiltDataExpect = tiltDataTestCalculate( areaProgress: const Offset(1.0, 1.0), tiltConfig: tiltConfigTest, - ).data; + ).toModel(); await tester.pumpWidget( TiltWidget( @@ -293,11 +295,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( @@ -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); @@ -335,7 +343,7 @@ void main() { final tiltDataExpect = tiltDataTestCalculate( areaProgress: const Offset(-0.8, -0.8), tiltConfig: tiltConfigTest, - ).data; + ).toModel(); await tester.pumpWidget( TiltWidget( @@ -383,11 +391,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 +398,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 +435,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_parallax_widget/tilt_parallax_test.dart b/test/tilt/tilt_parallax_test.dart similarity index 57% rename from test/tilt_parallax_widget/tilt_parallax_test.dart rename to test/tilt/tilt_parallax_test.dart index 9aab630..9b45ea0 100644 --- a/test/tilt_parallax_widget/tilt_parallax_test.dart +++ b/test/tilt/tilt_parallax_test.dart @@ -1,10 +1,12 @@ -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 'tilt_parallax_widget.dart'; +import 'shared_widgets/tilt_parallax_widget.dart'; void main() { - group('tilt parallax ::', () { + final tiltWidgetFinder = find.byKey(const Key('tilt_widget')); + + group('TiltParallax ::', () { testWidgets('default', (WidgetTester tester) async { final outerFinder = find.text('outer'); final innerFinder = find.text('inner'); @@ -67,44 +69,32 @@ 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'); + + testWidgets('child rebuild count', (WidgetTester tester) async { + const fps = 120; + var moveCount = 0; + var childBuildCount = 0; await tester.pumpWidget( - const TiltParallaxWidget( - tiltConfig: TiltConfig(initial: Offset(1, 0), enableReverse: true), + TiltParallaxWidget( + fps: fps, + tiltConfig: const TiltConfig(initial: Offset(1, 0)), + onGestureMove: (_, __) { + moveCount++; + }, 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'), + child: Builder( + builder: (_) { + childBuildCount++; + return const SizedBox( + width: 10, + height: 10, + child: Text('outer'), + ); + }, ), ), ), @@ -113,22 +103,16 @@ void main() { ), ); - 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), + 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 new file mode 100644 index 0000000..f2d0eba --- /dev/null +++ b/test/tilt/tilt_projector_widget_test.dart @@ -0,0 +1,89 @@ +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, + ]); + }); + + 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_widget/tilt_widget_test.dart b/test/tilt/tilt_widget_test.dart similarity index 69% rename from test/tilt_widget/tilt_widget_test.dart rename to test/tilt/tilt_widget_test.dart index 4cf0c44..976f87e 100644 --- a/test/tilt_widget/tilt_widget_test.dart +++ b/test/tilt/tilt_widget_test.dart @@ -1,15 +1,56 @@ -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 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()); await tester.pumpAndSettle(); @@ -30,11 +71,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 +78,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), @@ -86,30 +129,36 @@ void main() { frequency: fpsList[0].toDouble(), ); await tester.pumpAndSettle(); - expect(count, fps); + + /// timedDrag 会多 1 次触发 + expect(count, fps + 1); } }); }); - group('didUpdateWidget ::', () { - testWidgets('tiltStreamController', (WidgetTester tester) async { - final tiltStreamController1 = - StreamController.broadcast(); - final tiltStreamController2 = - StreamController.broadcast(); - final dataList = >[ - tiltStreamController1, - tiltStreamController2, + group('Tilt didUpdateWidget ::', () { + 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 { @@ -139,14 +188,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++; }, @@ -157,16 +206,17 @@ 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); + + /// timedDrag 会多 1 次触发 + 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); @@ -178,7 +228,7 @@ void main() { await tester.pumpWidget( TiltWidget( - tiltStreamController: tiltStreamController, + tiltController: tiltController, tiltConfig: data, onGestureMove: (TiltDataModel tiltData, GesturesType gesturesType) { currentGesturesType = gesturesType; @@ -189,23 +239,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)); } @@ -217,12 +261,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)); @@ -236,34 +277,8 @@ void main() { expect(currentGesturesType, GesturesType.sensors); } } - }); - }); - - 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]); + await tiltController.dispose(); }); }); } diff --git a/test/tilt_widget/tilt_widget.dart b/test/tilt_widget/tilt_widget.dart deleted file mode 100644 index 4a60d36..0000000 --- a/test/tilt_widget/tilt_widget.dart +++ /dev/null @@ -1,67 +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.lightShadowMode = LightShadowMode.base, - this.lightConfig = const LightConfig(), - this.shadowConfig = const ShadowConfig(), - 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 LightShadowMode lightShadowMode; - final LightConfig lightConfig; - final ShadowConfig 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'), - 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')), - ), - ), - ), - ); - } -} 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/tilt_widget/light_shadow_mode_base_widget/tilt_stream_controller_test.dart b/test/widgets/containers/tilt_base_container/tilt_controller_test.dart similarity index 73% rename from test/tilt_widget/light_shadow_mode_base_widget/tilt_stream_controller_test.dart rename to test/widgets/containers/tilt_base_container/tilt_controller_test.dart index f3d56c3..3fb6ce1 100644 --- a/test/tilt_widget/light_shadow_mode_base_widget/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 'tilt_widget_base.dart'; + +import '../../shared_widgets/tilt_base_container_widget.dart'; void main() { final childFinder = find.text('Tilt'); - group('LightShadowMode.base :: 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( - TiltWidgetBase(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( - TiltWidgetBase( - tiltStreamController: tiltStreamController, + TiltBaseContainerWidget( + 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( - TiltWidgetBase( - tiltStreamController: tiltStreamController, - onGestureMove: ( - TiltDataModel tiltDataModel, - GesturesType gesturesType, - ) { + TiltBaseContainerWidget( + 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/tilt_widget/light_shadow_mode_base_widget/tilt_gestures_drag_test.dart b/test/widgets/containers/tilt_base_container/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/widgets/containers/tilt_base_container/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/widgets/containers/tilt_base_container/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/widgets/containers/tilt_base_container/tilt_gestures_hover_test.dart similarity index 91% rename from test/tilt_widget/light_shadow_mode_base_widget/tilt_gestures_hover_test.dart rename to test/widgets/containers/tilt_base_container/tilt_gestures_hover_test.dart index 560c163..8b04235 100644 --- a/test/tilt_widget/light_shadow_mode_base_widget/tilt_gestures_hover_test.dart +++ b/test/widgets/containers/tilt_base_container/tilt_gestures_hover_test.dart @@ -2,24 +2,26 @@ 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 'tilt_widget_base.dart'; +import 'package:flutter_tilt/src/internal/tilt_state.dart'; +import '../../shared_widgets/tilt_base_container_widget.dart'; void main() { - group('LightShadowMode.base :: tilt gestures hover ::', () { + 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')); 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; @@ -29,7 +31,7 @@ void main() { /// 回调赋值 await tester.pumpWidget( - TiltWidgetBase( + TiltBaseContainerWidget( onGestureMove: (TiltDataModel tiltData, GesturesType gesturesType) { moveTiltDataTest = tiltData; moveGesturesTypeTest = gesturesType; @@ -63,11 +65,12 @@ 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( - TiltWidgetBase( + TiltBaseContainerWidget( onGestureMove: (TiltDataModel tiltData, GesturesType gesturesType) { tiltDataTest = tiltData; gesturesTypeTest = gesturesType; @@ -106,11 +109,11 @@ 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( - TiltWidgetBase( + TiltBaseContainerWidget( onGestureMove: (TiltDataModel tiltData, GesturesType gesturesType) { tiltDataTest = tiltData; gesturesTypeTest = gesturesType; @@ -148,11 +151,12 @@ 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( - TiltWidgetBase( + TiltBaseContainerWidget( onGestureMove: (TiltDataModel tiltData, GesturesType gesturesType) { tiltDataTest = tiltData; gesturesTypeTest = gesturesType; @@ -191,11 +195,11 @@ 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( - TiltWidgetBase( + TiltBaseContainerWidget( onGestureMove: (TiltDataModel tiltData, GesturesType gesturesType) { tiltDataTest = tiltData; gesturesTypeTest = gesturesType; @@ -233,11 +237,12 @@ 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( - TiltWidgetBase( + TiltBaseContainerWidget( onGestureMove: (TiltDataModel tiltData, GesturesType gesturesType) { tiltDataTest = tiltData; gesturesTypeTest = gesturesType; @@ -276,11 +281,11 @@ 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( - TiltWidgetBase( + TiltBaseContainerWidget( onGestureMove: (TiltDataModel tiltData, GesturesType gesturesType) { tiltDataTest = tiltData; gesturesTypeTest = gesturesType; @@ -319,11 +324,11 @@ 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( - TiltWidgetBase( + TiltBaseContainerWidget( onGestureMove: (TiltDataModel tiltData, GesturesType gesturesType) { tiltDataTest = tiltData; gesturesTypeTest = gesturesType; @@ -362,11 +367,11 @@ 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( - TiltWidgetBase( + TiltBaseContainerWidget( onGestureMove: (TiltDataModel tiltData, GesturesType gesturesType) { tiltDataTest = tiltData; gesturesTypeTest = gesturesType; diff --git a/test/tilt_widget/light_shadow_mode_base_widget/tilt_gestures_touch_test.dart b/test/widgets/containers/tilt_base_container/tilt_gestures_touch_test.dart similarity index 87% rename from test/tilt_widget/light_shadow_mode_base_widget/tilt_gestures_touch_test.dart rename to test/widgets/containers/tilt_base_container/tilt_gestures_touch_test.dart index 7f2b1cf..796c515 100644 --- a/test/tilt_widget/light_shadow_mode_base_widget/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 'tilt_widget_base.dart'; +import 'package:flutter_tilt/src/internal/tilt_state.dart'; +import '../../shared_widgets/tilt_base_container_widget.dart'; void main() { - group('LightShadowMode.base :: tilt gestures touch ::', () { + 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')); @@ -25,11 +27,12 @@ void main() { TiltDataModel? leaveTiltDataTest; GesturesType? moveGesturesTypeTest; GesturesType? leaveGesturesTypeTest; - final leaveTiltDataTestExpect = tiltDataTestCalculate(Offset.zero).data; + final leaveTiltDataTestExpect = + tiltDataTestCalculate(Offset.zero).toModel(); /// 回调赋值 await tester.pumpWidget( - TiltWidgetBase( + TiltBaseContainerWidget( onGestureMove: (TiltDataModel tiltData, GesturesType gesturesType) { moveTiltDataTest = tiltData; moveGesturesTypeTest = gesturesType; @@ -54,7 +57,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)); @@ -67,11 +70,12 @@ 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( - TiltWidgetBase( + TiltBaseContainerWidget( onGestureMove: (TiltDataModel tiltData, GesturesType gesturesType) { tiltDataTest = tiltData; gesturesTypeTest = gesturesType; @@ -95,11 +99,11 @@ 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( - TiltWidgetBase( + TiltBaseContainerWidget( onGestureMove: (TiltDataModel tiltData, GesturesType gesturesType) { tiltDataTest = tiltData; gesturesTypeTest = gesturesType; @@ -122,11 +126,12 @@ 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( - TiltWidgetBase( + TiltBaseContainerWidget( onGestureMove: (TiltDataModel tiltData, GesturesType gesturesType) { tiltDataTest = tiltData; gesturesTypeTest = gesturesType; @@ -150,11 +155,11 @@ 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( - TiltWidgetBase( + TiltBaseContainerWidget( onGestureMove: (TiltDataModel tiltData, GesturesType gesturesType) { tiltDataTest = tiltData; gesturesTypeTest = gesturesType; @@ -177,11 +182,12 @@ 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( - TiltWidgetBase( + TiltBaseContainerWidget( onGestureMove: (TiltDataModel tiltData, GesturesType gesturesType) { tiltDataTest = tiltData; gesturesTypeTest = gesturesType; @@ -205,11 +211,11 @@ 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( - TiltWidgetBase( + TiltBaseContainerWidget( onGestureMove: (TiltDataModel tiltData, GesturesType gesturesType) { tiltDataTest = tiltData; gesturesTypeTest = gesturesType; @@ -233,11 +239,11 @@ 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( - TiltWidgetBase( + TiltBaseContainerWidget( onGestureMove: (TiltDataModel tiltData, GesturesType gesturesType) { tiltDataTest = tiltData; gesturesTypeTest = gesturesType; @@ -261,11 +267,11 @@ 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( - TiltWidgetBase( + TiltBaseContainerWidget( onGestureMove: (TiltDataModel tiltData, GesturesType gesturesType) { tiltDataTest = tiltData; gesturesTypeTest = gesturesType; @@ -291,12 +297,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), + TiltBaseContainerWidget( + shadowConfig: const ShadowBaseConfig(disable: true), onGestureMove: (TiltDataModel tiltData, GesturesType gesturesType) { moveTiltDataTest = tiltData; moveGesturesTypeTest = gesturesType; 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/light_shadow_mode_projector_widget/tilt_stream_controller_test.dart b/test/widgets/containers/tilt_projector_container/tilt_controller_test.dart similarity index 73% rename from test/tilt_widget/light_shadow_mode_projector_widget/tilt_stream_controller_test.dart rename to test/widgets/containers/tilt_projector_container/tilt_controller_test.dart index f84e32b..a3cf628 100644 --- a/test/tilt_widget/light_shadow_mode_projector_widget/tilt_stream_controller_test.dart +++ b/test/widgets/containers/tilt_projector_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 'tilt_widget_projector.dart'; + +import '../../shared_widgets/tilt_projector_container_widget.dart'; void main() { final childFinder = find.text('Tilt'); - group('LightShadowMode.projector :: 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( - TiltWidgetProjector(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( - TiltWidgetProjector( - tiltStreamController: tiltStreamController, + TiltProjectorContainerWidget( + 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( - TiltWidgetProjector( - tiltStreamController: tiltStreamController, - onGestureMove: ( - TiltDataModel tiltDataModel, - GesturesType gesturesType, - ) { + TiltProjectorContainerWidget( + 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/tilt_widget/light_shadow_mode_projector_widget/tilt_gestures_drag_test.dart b/test/widgets/containers/tilt_projector_container/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/widgets/containers/tilt_projector_container/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/widgets/containers/tilt_projector_container/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/widgets/containers/tilt_projector_container/tilt_gestures_hover_test.dart similarity index 91% rename from test/tilt_widget/light_shadow_mode_projector_widget/tilt_gestures_hover_test.dart rename to test/widgets/containers/tilt_projector_container/tilt_gestures_hover_test.dart index cb1c836..bd32da5 100644 --- a/test/tilt_widget/light_shadow_mode_projector_widget/tilt_gestures_hover_test.dart +++ b/test/widgets/containers/tilt_projector_container/tilt_gestures_hover_test.dart @@ -2,24 +2,26 @@ 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 'tilt_widget_projector.dart'; +import 'package:flutter_tilt/src/internal/tilt_state.dart'; +import '../../shared_widgets/tilt_projector_container_widget.dart'; void main() { - group('LightShadowMode.projector :: tilt gestures hover ::', () { + 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')); 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; @@ -29,7 +31,7 @@ void main() { /// 回调赋值 await tester.pumpWidget( - TiltWidgetProjector( + TiltProjectorContainerWidget( onGestureMove: (TiltDataModel tiltData, GesturesType gesturesType) { moveTiltDataTest = tiltData; moveGesturesTypeTest = gesturesType; @@ -63,11 +65,12 @@ 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( - TiltWidgetProjector( + TiltProjectorContainerWidget( onGestureMove: (TiltDataModel tiltData, GesturesType gesturesType) { tiltDataTest = tiltData; gesturesTypeTest = gesturesType; @@ -106,11 +109,11 @@ 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( - TiltWidgetProjector( + TiltProjectorContainerWidget( onGestureMove: (TiltDataModel tiltData, GesturesType gesturesType) { tiltDataTest = tiltData; gesturesTypeTest = gesturesType; @@ -148,11 +151,12 @@ 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( - TiltWidgetProjector( + TiltProjectorContainerWidget( onGestureMove: (TiltDataModel tiltData, GesturesType gesturesType) { tiltDataTest = tiltData; gesturesTypeTest = gesturesType; @@ -191,11 +195,11 @@ 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( - TiltWidgetProjector( + TiltProjectorContainerWidget( onGestureMove: (TiltDataModel tiltData, GesturesType gesturesType) { tiltDataTest = tiltData; gesturesTypeTest = gesturesType; @@ -233,11 +237,12 @@ 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( - TiltWidgetProjector( + TiltProjectorContainerWidget( onGestureMove: (TiltDataModel tiltData, GesturesType gesturesType) { tiltDataTest = tiltData; gesturesTypeTest = gesturesType; @@ -276,11 +281,11 @@ 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( - TiltWidgetProjector( + TiltProjectorContainerWidget( onGestureMove: (TiltDataModel tiltData, GesturesType gesturesType) { tiltDataTest = tiltData; gesturesTypeTest = gesturesType; @@ -319,11 +324,11 @@ 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( - TiltWidgetProjector( + TiltProjectorContainerWidget( onGestureMove: (TiltDataModel tiltData, GesturesType gesturesType) { tiltDataTest = tiltData; gesturesTypeTest = gesturesType; @@ -362,11 +367,11 @@ 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( - TiltWidgetProjector( + TiltProjectorContainerWidget( onGestureMove: (TiltDataModel tiltData, GesturesType gesturesType) { tiltDataTest = tiltData; gesturesTypeTest = gesturesType; diff --git a/test/tilt_widget/light_shadow_mode_projector_widget/tilt_gestures_touch_test.dart b/test/widgets/containers/tilt_projector_container/tilt_gestures_touch_test.dart similarity index 86% rename from test/tilt_widget/light_shadow_mode_projector_widget/tilt_gestures_touch_test.dart rename to test/widgets/containers/tilt_projector_container/tilt_gestures_touch_test.dart index e4c7ba0..3982478 100644 --- a/test/tilt_widget/light_shadow_mode_projector_widget/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 'tilt_widget_projector.dart'; +import 'package:flutter_tilt/src/internal/tilt_state.dart'; +import '../../shared_widgets/tilt_projector_container_widget.dart'; void main() { - group('LightShadowMode.projector :: tilt gestures touch ::', () { + 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')); @@ -25,11 +27,12 @@ void main() { TiltDataModel? leaveTiltDataTest; GesturesType? moveGesturesTypeTest; GesturesType? leaveGesturesTypeTest; - final leaveTiltDataTestExpect = tiltDataTestCalculate(Offset.zero).data; + final leaveTiltDataTestExpect = + tiltDataTestCalculate(Offset.zero).toModel(); /// 回调赋值 await tester.pumpWidget( - TiltWidgetProjector( + TiltProjectorContainerWidget( onGestureMove: (TiltDataModel tiltData, GesturesType gesturesType) { moveTiltDataTest = tiltData; moveGesturesTypeTest = gesturesType; @@ -54,7 +57,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)); @@ -67,11 +70,12 @@ 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( - TiltWidgetProjector( + TiltProjectorContainerWidget( onGestureMove: (TiltDataModel tiltData, GesturesType gesturesType) { tiltDataTest = tiltData; gesturesTypeTest = gesturesType; @@ -95,11 +99,11 @@ 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( - TiltWidgetProjector( + TiltProjectorContainerWidget( onGestureMove: (TiltDataModel tiltData, GesturesType gesturesType) { tiltDataTest = tiltData; gesturesTypeTest = gesturesType; @@ -122,11 +126,12 @@ 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( - TiltWidgetProjector( + TiltProjectorContainerWidget( onGestureMove: (TiltDataModel tiltData, GesturesType gesturesType) { tiltDataTest = tiltData; gesturesTypeTest = gesturesType; @@ -150,11 +155,11 @@ 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( - TiltWidgetProjector( + TiltProjectorContainerWidget( onGestureMove: (TiltDataModel tiltData, GesturesType gesturesType) { tiltDataTest = tiltData; gesturesTypeTest = gesturesType; @@ -177,11 +182,12 @@ 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( - TiltWidgetProjector( + TiltProjectorContainerWidget( onGestureMove: (TiltDataModel tiltData, GesturesType gesturesType) { tiltDataTest = tiltData; gesturesTypeTest = gesturesType; @@ -205,11 +211,11 @@ 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( - TiltWidgetProjector( + TiltProjectorContainerWidget( onGestureMove: (TiltDataModel tiltData, GesturesType gesturesType) { tiltDataTest = tiltData; gesturesTypeTest = gesturesType; @@ -233,11 +239,11 @@ 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( - TiltWidgetProjector( + TiltProjectorContainerWidget( onGestureMove: (TiltDataModel tiltData, GesturesType gesturesType) { tiltDataTest = tiltData; gesturesTypeTest = gesturesType; @@ -261,11 +267,11 @@ 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( - TiltWidgetProjector( + TiltProjectorContainerWidget( onGestureMove: (TiltDataModel tiltData, GesturesType gesturesType) { tiltDataTest = tiltData; gesturesTypeTest = gesturesType; @@ -291,12 +297,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), + TiltProjectorContainerWidget( + shadowConfig: const ShadowProjectorConfig(disable: true), onGestureMove: (TiltDataModel tiltData, GesturesType gesturesType) { moveTiltDataTest = tiltData; moveGesturesTypeTest = gesturesType; 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..f9f5b20 --- /dev/null +++ b/test/widgets/core/tilt_animated_builder_test.dart @@ -0,0 +1,247 @@ +import 'package:flutter/gestures.dart'; +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); + Size? reportedSize; + + await tester.pumpWidget( + MaterialApp( + home: Scaffold( + body: Tilt( + tiltConfig: const TiltConfig(initial: initialProgress), + child: SizedBox( + width: 100, + height: 100, + child: TiltAnimatedBuilder( + 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(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: ${animatedTiltData.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); + + /// 检查尺寸回调是否正确 + 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()); + }); + + testWidgets('child rebuild count', (WidgetTester tester) async { + const fps = 120; + 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( + home: Scaffold( + body: Tilt( + key: const Key('tilt_widget'), + fps: fps, + tiltConfig: const TiltConfig( + enterDuration: Duration.zero, + // 确保存在动画时常,以便在测试过程中捕获动画状态 + moveDuration: Duration(milliseconds: 1), + enterToMoveDuration: Duration.zero, + leaveDuration: Duration.zero, + ), + 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!; + }, + 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); + + 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); + }); + }); +} 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/tilt_widget/light_shadow_mode_base_widget/tilt_widget_base.dart b/test/widgets/shared_widgets/tilt_base_container_widget.dart similarity index 61% rename from test/tilt_widget/light_shadow_mode_base_widget/tilt_widget_base.dart rename to test/widgets/shared_widgets/tilt_base_container_widget.dart index 593fcd0..0505b5f 100644 --- a/test/tilt_widget/light_shadow_mode_base_widget/tilt_widget_base.dart +++ b/test/widgets/shared_widgets/tilt_base_container_widget.dart @@ -1,37 +1,33 @@ -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, + this.tiltController, this.disable = false, this.fps = 60, this.border, 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, }); final ChildLayout childLayout; - final StreamController? tiltStreamController; + final TiltController? tiltController; final bool disable; final int fps; final BoxBorder? border; 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 +38,24 @@ class TiltWidgetBase extends StatelessWidget { key: const Key('tilt_scaffold'), body: Tilt( key: const Key('tilt_widget'), - childLayout: childLayout, - tiltStreamController: tiltStreamController, + tiltController: tiltController, 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/widgets/shared_widgets/tilt_parallax_container_widget.dart b/test/widgets/shared_widgets/tilt_parallax_container_widget.dart new file mode 100644 index 0000000..66973b1 --- /dev/null +++ b/test/widgets/shared_widgets/tilt_parallax_container_widget.dart @@ -0,0 +1,109 @@ +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({ + super.key, + this.childLayout, + this.tiltController, + 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 TiltController? tiltController; + 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'), + tiltController: tiltController, + 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: TiltParallaxContainer( + offset: Offset(10, 10), + child: SizedBox( + width: 10, + height: 10, + child: Text('outer'), + ), + ), + ), + ], + inner: [ + Positioned( + child: TiltParallaxContainer( + offset: Offset(10, 10), + child: SizedBox( + width: 10, + height: 10, + child: Text('inner'), + ), + ), + ), + ], + behind: [ + Positioned( + child: TiltParallaxContainer( + offset: Offset(10, 10), + 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/light_shadow_mode_projector_widget/tilt_widget_projector.dart b/test/widgets/shared_widgets/tilt_projector_container_widget.dart similarity index 56% rename from test/tilt_widget/light_shadow_mode_projector_widget/tilt_widget_projector.dart rename to test/widgets/shared_widgets/tilt_projector_container_widget.dart index fba5e60..1ac3e05 100644 --- a/test/tilt_widget/light_shadow_mode_projector_widget/tilt_widget_projector.dart +++ b/test/widgets/shared_widgets/tilt_projector_container_widget.dart @@ -1,37 +1,33 @@ -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, + this.tiltController, this.disable = false, this.fps = 60, this.border, 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, }); final ChildLayout childLayout; - final StreamController? tiltStreamController; + final TiltController? tiltController; final bool disable; final int fps; final BoxBorder? border; 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 +38,24 @@ class TiltWidgetProjector extends StatelessWidget { key: const Key('tilt_scaffold'), body: Tilt( key: const Key('tilt_widget'), - childLayout: childLayout, - tiltStreamController: tiltStreamController, + tiltController: tiltController, 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'), + ), ), ), ),