Skip to content

Commit ed11b14

Browse files
feat: Add overlay animation duration and curve for showcase barrier
1 parent 0d13c41 commit ed11b14

7 files changed

Lines changed: 173 additions & 24 deletions

File tree

CHANGELOG.md

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,7 @@
1+
## Unreleased
2+
3+
- Feature [#616](https://github.com/SimformSolutionsPvtLtd/showcaseview/issues/616) - Add overlay animation duration and curve for showcase barrier. Fixed by @[vasu-nageshri](https://github.com/vasu-nageshri)
4+
15
## 5.1.0
26

37
- Fixed [#650](https://github.com/SimformSolutionsPvtLtd/showcaseview/issues/650) - Fix null-check crash in ShowcaseService.getController during didUpdateWidget. Fixed by @[vatsaltanna-simformsolutions](https://github.com/vatsaltanna-simformsolutions)
@@ -6,7 +10,7 @@
610
- Fixed [#645](https://github.com/SimformSolutionsPvtLtd/showcaseview/issues/645) - Prevent re-entrant calls to _onComplete during rapid barrier taps. Fixed by @[apizon](https://github.com/apizon)
711
- Fixed [#639](https://github.com/SimformSolutionsPvtLtd/showcaseview/issues/639) - Add null-safety guards for async sequence transitions. Fixed by @[vasu-nageshri](https://github.com/vasu-nageshri)
812
- Fixed [#622](https://github.com/SimformSolutionsPvtLtd/showcaseview/issues/622) - Resolve Semantics issue when using `go_router` and `showSemanticsDebugger` flag. Fixed by @[Sahil-Simform](https://github.com/Sahil-Simform)
9-
- Fix [#620](https://github.com/SimformSolutionsPvtLtd/showcaseview/pull/620)- Set MouseRegion opaque property to false for improved interaction in TargetWidget, TooltipWrapper, and FloatingActionWidget. Fixed by @[RuslanTsitser](https://github.com/RuslanTsitser)
13+
- Fixed [#620](https://github.com/SimformSolutionsPvtLtd/showcaseview/pull/620)- Set MouseRegion opaque property to false for improved interaction in TargetWidget, TooltipWrapper, and FloatingActionWidget. Fixed by @[RuslanTsitser](https://github.com/RuslanTsitser)
1014

1115
## [5.0.2]
1216

doc/documentation.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -130,6 +130,10 @@ Reference](https://pub.dev/documentation/showcaseview/latest/showcaseview/).
130130
ShowcaseView.register(
131131
autoPlayDelay: const Duration(seconds: 3),
132132
semanticEnable: true, // Enable accessibility support globally
133+
// Overlay barrier fades in when showcase first appears (default 200ms).
134+
// Set overlayAnimationDuration to Duration.zero to disable.
135+
overlayAnimationDuration: const Duration(milliseconds: 200),
136+
overlayAnimationCurve: Curves.easeInOut,
133137
globalFloatingActionWidget: (showcaseContext) => FloatingActionWidget(
134138
left: 16,
135139
bottom: 16,

lib/src/showcase/showcase_view.dart

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -79,6 +79,8 @@ class ShowcaseView {
7979
this.blurValue = 0,
8080
this.overlayColor,
8181
this.overlayOpacity,
82+
this.overlayAnimationDuration = Constants.defaultOverlayAnimationDuration,
83+
this.overlayAnimationCurve = Constants.defaultOverlayAnimationCurve,
8284
this.globalTooltipActionConfig,
8385
this.globalTooltipActions,
8486
this.globalFloatingActionWidget,
@@ -161,6 +163,24 @@ class ShowcaseView {
161163
/// Opacity apply on [overlayColor] (which ranges from 0.0 to 1.0)
162164
final double? overlayOpacity;
163165

166+
/// Duration of the fade-in animation applied to the overlay barrier
167+
/// (background color, opacity and blur) when the showcase first appears.
168+
///
169+
/// This animates only the initial appearance of the barrier and not the
170+
/// transition between individual showcase steps.
171+
///
172+
/// Defaults to 200 milliseconds. Set to [Duration.zero] to show the barrier
173+
/// instantly.
174+
final Duration overlayAnimationDuration;
175+
176+
/// Curve used for the overlay barrier fade-in animation.
177+
///
178+
/// Only applies when [overlayAnimationDuration] is greater than
179+
/// [Duration.zero].
180+
///
181+
/// Defaults to [Curves.easeInOut].
182+
final Curve overlayAnimationCurve;
183+
164184
/// Whether to enable semantic properties for accessibility.
165185
///
166186
/// When set to true, semantic widgets will be added to improve

lib/src/utils/constants.dart

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -66,4 +66,12 @@ class Constants {
6666

6767
static const Duration defaultAutoPlayDelay = Duration(milliseconds: 2000);
6868
static const Duration defaultScrollDuration = Duration(milliseconds: 300);
69+
70+
/// Default duration for the overlay barrier fade-in animation.
71+
/// Set to [Duration.zero] to make the barrier appear instantly.
72+
static const Duration defaultOverlayAnimationDuration =
73+
Duration(milliseconds: 200);
74+
75+
/// Default curve for the overlay barrier fade-in animation.
76+
static const Curve defaultOverlayAnimationCurve = Curves.easeInOut;
6977
}

lib/src/utils/overlay_manager.dart

Lines changed: 48 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@ import '../showcase/showcase.dart';
2929
import '../showcase/showcase_controller.dart';
3030
import '../showcase/showcase_service.dart';
3131
import '../showcase/showcase_view.dart';
32+
import '../widget/animated_overlay_barrier.dart';
3233
import 'extensions.dart';
3334
import 'shape_clipper.dart';
3435

@@ -181,30 +182,57 @@ class OverlayManager {
181182
child: const Align(),
182183
);
183184

185+
Widget barrier = GestureDetector(
186+
onTap: firstController.handleBarrierTap,
187+
child: ClipPath(
188+
clipper: ShapeClipper(
189+
linkedObjectData: _getLinkedShowcasesData(controllers),
190+
),
191+
child: firstController.blur <= 0.2
192+
? backgroundContainer
193+
: BackdropFilter(
194+
filter: ImageFilter.blur(
195+
sigmaX: firstController.blur,
196+
sigmaY: firstController.blur,
197+
),
198+
child: backgroundContainer,
199+
),
200+
),
201+
);
202+
203+
final overlayAnimationDuration =
204+
firstController.showcaseView.overlayAnimationDuration;
205+
if (overlayAnimationDuration > Duration.zero) {
206+
// The barrier is kept outside the per-step keyed stack and given a stable
207+
// key so its fade-in animation runs only once when the showcase first
208+
// appears, instead of restarting (and flickering) on every step.
209+
barrier = AnimatedOverlayBarrier(
210+
key: const ValueKey('showcase_overlay_barrier'),
211+
duration: overlayAnimationDuration,
212+
curve: firstController.showcaseView.overlayAnimationCurve,
213+
child: barrier,
214+
);
215+
}
216+
184217
final overlayChild = Stack(
185-
// This key is used to force rebuild the overlay when needed.
186-
// this key enables `_overlayEntry?.markNeedsBuild();` to detect that
187-
// output of the builder has changed.
188-
key: ValueKey(firstController.id),
189218
children: [
190-
GestureDetector(
191-
onTap: firstController.handleBarrierTap,
192-
child: ClipPath(
193-
clipper: ShapeClipper(
194-
linkedObjectData: _getLinkedShowcasesData(controllers),
195-
),
196-
child: firstController.blur <= 0.2
197-
? backgroundContainer
198-
: BackdropFilter(
199-
filter: ImageFilter.blur(
200-
sigmaX: firstController.blur,
201-
sigmaY: firstController.blur,
202-
),
203-
child: backgroundContainer,
204-
),
219+
barrier,
220+
// Tooltips are kept in their own stack so the barrier (and its
221+
// optional fade-in) lives outside this keyed subtree. `Positioned.fill`
222+
// is required because the inner stack has only `Positioned` children
223+
// and would otherwise collapse, clipping the tooltips.
224+
Positioned.fill(
225+
child: Stack(
226+
// This key forces the tooltips to rebuild when needed. It enables
227+
// `_overlayEntry?.markNeedsBuild();` to detect that the output of
228+
// the builder has changed and restarts the per-step tooltip
229+
// animations between showcase steps.
230+
key: ValueKey(firstController.id),
231+
children: [
232+
...controllers.expand((object) => object.tooltipWidgets),
233+
],
205234
),
206235
),
207-
...controllers.expand((object) => object.tooltipWidgets),
208236
],
209237
);
210238

Lines changed: 84 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,84 @@
1+
/*
2+
* Copyright (c) 2021 Simform Solutions
3+
*
4+
* Permission is hereby granted, free of charge, to any person obtaining a copy
5+
* of this software and associated documentation files (the "Software"), to deal
6+
* in the Software without restriction, including without limitation the rights
7+
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
8+
* copies of the Software, and to permit persons to whom the Software is
9+
* furnished to do so, subject to the following conditions:
10+
*
11+
* The above copyright notice and this permission notice shall be
12+
* included in all copies or substantial portions of the Software.
13+
*
14+
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
15+
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
16+
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
17+
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
18+
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
19+
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
20+
* SOFTWARE.
21+
*/
22+
import 'package:flutter/material.dart';
23+
24+
/// Fades the showcase overlay barrier (background color, opacity and blur) in
25+
/// when the showcase first appears.
26+
///
27+
/// The fade only runs once for the lifetime of this widget's [State]. The
28+
/// overlay rebuilds on every showcase step to reposition the highlight cut-out,
29+
/// so this widget is kept outside the per-step keyed subtree in
30+
/// `OverlayManager` to preserve its animation state across steps and avoid the
31+
/// barrier flickering between showcases.
32+
class AnimatedOverlayBarrier extends StatefulWidget {
33+
const AnimatedOverlayBarrier({
34+
required this.duration,
35+
required this.curve,
36+
required this.child,
37+
super.key,
38+
});
39+
40+
/// Duration of the fade-in animation.
41+
final Duration duration;
42+
43+
/// Curve of the fade-in animation.
44+
final Curve curve;
45+
46+
/// The barrier widget (background color/blur) to fade in.
47+
final Widget child;
48+
49+
@override
50+
State<AnimatedOverlayBarrier> createState() => _AnimatedOverlayBarrierState();
51+
}
52+
53+
class _AnimatedOverlayBarrierState extends State<AnimatedOverlayBarrier>
54+
with SingleTickerProviderStateMixin {
55+
late final AnimationController _controller = AnimationController(
56+
vsync: this,
57+
duration: widget.duration,
58+
);
59+
60+
late final Animation<double> _opacity = CurvedAnimation(
61+
parent: _controller,
62+
curve: widget.curve,
63+
);
64+
65+
@override
66+
void initState() {
67+
super.initState();
68+
_controller.forward();
69+
}
70+
71+
@override
72+
void dispose() {
73+
_controller.dispose();
74+
super.dispose();
75+
}
76+
77+
@override
78+
Widget build(BuildContext context) {
79+
return FadeTransition(
80+
opacity: _opacity,
81+
child: widget.child,
82+
);
83+
}
84+
}

lib/src/widget/floating_action_widget.dart

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -172,9 +172,10 @@ class FloatingActionWidget extends StatelessWidget {
172172
width: width,
173173
height: height,
174174
child: Material(
175-
type: MaterialType.transparency,
176-
color: Colors.transparent,
177-
child: child),
175+
type: MaterialType.transparency,
176+
color: Colors.transparent,
177+
child: child,
178+
),
178179
);
179180
}
180181
}

0 commit comments

Comments
 (0)