Skip to content

Commit 5f4b282

Browse files
committed
❇️ Pointer Transitions.
1 parent b191c31 commit 5f4b282

14 files changed

+636
-84
lines changed

example/lib/main.dart

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import 'package:example/stories/windows_settings_transition.dart';
12
import 'package:example/stories/scroll_phase_transition.dart';
23
import 'package:example/stories/scroll_wheel_transition.dart';
34
import 'package:flutter/material.dart';
@@ -31,9 +32,15 @@ class MyApp extends StatelessWidget {
3132
),
3233
Story(
3334
name: 'Scroll Phase Blur',
34-
description: 'A focus effect where elements outside the view are blurred',
35+
description:
36+
'A focus effect where elements outside the view are blurred',
3537
builder: (context) => const ScrollWheelBlurTransition(),
3638
),
39+
Story(
40+
name: 'Pointer Transition',
41+
description: 'Moves elements slightly with the pointer',
42+
builder: (context) => const WindowsSettingsTransition(),
43+
),
3744
],
3845
),
3946
);

example/lib/stories/scroll_phase_transition.dart

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,6 @@ class _ScrollPhaseTransitionState extends State<ScrollPhaseTransition> {
3939
),
4040
),
4141
).scrollTransition(
42-
index,
4342
(context, widget, event) =>
4443
widget.scale(event.phase.isIdentity ? 1 : 0.7).translateX(
4544
switch (event.phase) {

example/lib/stories/scroll_wheel_blur.dart

Lines changed: 15 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -40,20 +40,21 @@ class _ScrollWheelBlurTransitionState extends State<ScrollWheelBlurTransition> {
4040
),
4141
),
4242
).scrollTransition(
43-
index,
44-
(context, widget, event) => widget.blur(
45-
switch (event.phase) {
46-
ScrollPhase.identity => 0,
47-
ScrollPhase.topLeading => 10,
48-
ScrollPhase.bottomTrailing => 10,
49-
},
50-
).scale(
51-
switch (event.phase) {
52-
ScrollPhase.identity => 1,
53-
ScrollPhase.topLeading => 0.9,
54-
ScrollPhase.bottomTrailing => 0.9,
55-
},
56-
),
43+
(context, widget, event) => widget
44+
.blur(
45+
switch (event.phase) {
46+
ScrollPhase.identity => 0,
47+
ScrollPhase.topLeading => 10,
48+
ScrollPhase.bottomTrailing => 10,
49+
},
50+
)
51+
.scale(
52+
switch (event.phase) {
53+
ScrollPhase.identity => 1,
54+
ScrollPhase.topLeading => 0.9,
55+
ScrollPhase.bottomTrailing => 0.9,
56+
},
57+
),
5758
);
5859
},
5960
);

example/lib/stories/scroll_wheel_transition.dart

Lines changed: 7 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -38,15 +38,13 @@ class _ScrollWheelTransitionState extends State<ScrollWheelTransition> {
3838
color: Colors.white,
3939
),
4040
),
41-
).scrollTransition(
42-
index,
43-
(context, widget, event) => TransformEffect(
44-
rotateX: -90 * event.screenOffsetFraction * pi / 180,
45-
translateY: (event.screenOffsetFraction * -1) * 200,
46-
translateZ: event.screenOffsetFraction.abs() * 100,
47-
scaleX: 1 - (event.screenOffsetFraction.abs() / 2),
48-
depth: 0.002,
49-
).apply(context, widget));
41+
).scrollTransition((context, widget, event) => TransformEffect(
42+
rotateX: -90 * event.screenOffsetFraction * pi / 180,
43+
translateY: (event.screenOffsetFraction * -1) * 200,
44+
translateZ: event.screenOffsetFraction.abs() * 100,
45+
scaleX: 1 - (event.screenOffsetFraction.abs() / 2),
46+
depth: 0.002,
47+
).apply(context, widget));
5048
},
5149
);
5250
}
Lines changed: 119 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,119 @@
1+
import 'package:flutter/material.dart';
2+
import 'package:hyper_effects/hyper_effects.dart';
3+
4+
class WindowsSettingsTransition extends StatefulWidget {
5+
const WindowsSettingsTransition({super.key});
6+
7+
@override
8+
State<WindowsSettingsTransition> createState() =>
9+
_WindowsSettingsTransitionState();
10+
}
11+
12+
class _WindowsSettingsTransitionState extends State<WindowsSettingsTransition> {
13+
@override
14+
Widget build(BuildContext context) {
15+
return Center(
16+
child: GridView.builder(
17+
gridDelegate: const SliverGridDelegateWithFixedCrossAxisCount(
18+
crossAxisCount: 3,
19+
crossAxisSpacing: 4,
20+
mainAxisSpacing: 4,
21+
),
22+
padding: const EdgeInsets.all(8),
23+
itemBuilder: (context, i) => Stack(
24+
alignment: Alignment.center,
25+
children: [
26+
Container(
27+
clipBehavior: Clip.antiAlias,
28+
decoration: const BoxDecoration(
29+
color: Color(0xFF404040),
30+
),
31+
child: Transform.scale(
32+
scale: 2,
33+
child: Container(
34+
decoration: BoxDecoration(
35+
gradient: RadialGradient(
36+
focalRadius: 10000,
37+
colors: [
38+
Colors.white.withOpacity(0.5),
39+
Colors.white.withOpacity(0),
40+
],
41+
),
42+
),
43+
),
44+
).pointerTransition(
45+
transitionBetweenBounds: false,
46+
resetOnExitBounds: false,
47+
(context, child, event) => child
48+
.opacity(
49+
event.isInsideBounds ? 1 : 0,
50+
)
51+
.animate(
52+
toggle: event.isInsideBounds,
53+
duration: const Duration(milliseconds: 150),
54+
)
55+
.translateXY(
56+
event.valueOffset.dx / 2,
57+
event.valueOffset.dy / 2,
58+
fractional: true,
59+
),
60+
),
61+
),
62+
Container(
63+
margin: const EdgeInsets.all(1),
64+
decoration: const BoxDecoration(
65+
color: Color(0xFF404040),
66+
),
67+
clipBehavior: Clip.antiAlias,
68+
child: Transform.scale(
69+
scale: 2,
70+
child: Container(
71+
decoration: BoxDecoration(
72+
gradient: RadialGradient(
73+
colors: [
74+
Colors.white.withOpacity(0.2),
75+
Colors.white.withOpacity(0),
76+
],
77+
),
78+
),
79+
),
80+
).pointerTransition(
81+
transitionBetweenBounds: false,
82+
resetOnExitBounds: false,
83+
(context, child, event) => child
84+
.opacity(
85+
event.isInsideBounds ? 1 : 0,
86+
)
87+
.animate(
88+
toggle: event.isInsideBounds,
89+
duration: const Duration(milliseconds: 150),
90+
)
91+
.translateXY(
92+
event.valueOffset.dx / 2,
93+
event.valueOffset.dy / 2,
94+
fractional: true,
95+
),
96+
),
97+
),
98+
Container(
99+
width: 56,
100+
height: 56,
101+
alignment: Alignment.center,
102+
decoration: BoxDecoration(
103+
color: Colors.blue,
104+
borderRadius: BorderRadius.circular(4),
105+
),
106+
child: Text(
107+
'$i',
108+
style: const TextStyle(
109+
fontSize: 32,
110+
color: Colors.white,
111+
),
112+
),
113+
),
114+
],
115+
),
116+
),
117+
);
118+
}
119+
}

lib/hyper_effects.dart

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,3 +6,5 @@ export 'src/effects/effects.dart';
66
export 'src/extensions.dart';
77
export 'src/scroll_phase.dart';
88
export 'src/scroll_transition.dart';
9+
export 'src/effect_animation_value.dart';
10+
export 'src/pointer_transition.dart';

lib/src/animated_effect.dart

Lines changed: 12 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,7 @@
11
import 'package:flutter/widgets.dart';
2+
import 'package:hyper_effects/src/apple_curve.dart';
3+
4+
import 'effect_animation_value.dart';
25

36
/// Provides extension methods for [Widget] to animate it's appearance.
47
extension AnimatedEffectExt on Widget {
@@ -13,7 +16,7 @@ extension AnimatedEffectExt on Widget {
1316
Widget animate({
1417
required Object? toggle,
1518
Duration duration = const Duration(milliseconds: 350),
16-
Curve curve = Curves.easeInOut,
19+
Curve curve = appleEaseInOut,
1720
}) {
1821
return AnimatedEffect(
1922
toggle: toggle,
@@ -45,7 +48,7 @@ class AnimatedEffect extends StatefulWidget {
4548
required this.child,
4649
required this.toggle,
4750
required this.duration,
48-
this.curve = Curves.easeInOut,
51+
this.curve = appleEaseInOut,
4952
});
5053

5154
@override
@@ -65,6 +68,11 @@ class _AnimatedEffectState extends State<AnimatedEffect>
6568
duration: widget.duration,
6669
);
6770

71+
late final Animation<double> _animation = CurvedAnimation(
72+
parent: _controller,
73+
curve: widget.curve,
74+
);
75+
6876
@override
6977
void didUpdateWidget(covariant AnimatedEffect oldWidget) {
7078
super.didUpdateWidget(oldWidget);
@@ -75,9 +83,9 @@ class _AnimatedEffectState extends State<AnimatedEffect>
7583
@override
7684
Widget build(BuildContext context) {
7785
return AnimatedBuilder(
78-
animation: _controller,
86+
animation: _animation,
7987
builder: (context, child) => EffectAnimationValue(
80-
value: _controller.value,
88+
value: _animation.value,
8189
isTransition: false,
8290
child: child!,
8391
),
@@ -91,30 +99,3 @@ class _AnimatedEffectState extends State<AnimatedEffect>
9199
super.dispose();
92100
}
93101
}
94-
95-
/// An inherited widget that provides the animation value to it's descendants.
96-
///
97-
/// This widget is used by [AnimatedEffect] and [ScrollTransition] widgets to
98-
/// provide the animation value to it's descendants in order to animate them.
99-
class EffectAnimationValue extends InheritedWidget {
100-
/// The animation value. It's value is between 0 and 1.
101-
final double value;
102-
103-
/// Whether the animation is in scroll transition or not. Animations behaves
104-
/// differently in scroll transition. This flag is used to determine the
105-
/// behavior of the animation.
106-
final bool isTransition;
107-
108-
/// Creates [EffectAnimationValue] widget.
109-
const EffectAnimationValue({
110-
super.key,
111-
required super.child,
112-
required this.value,
113-
required this.isTransition,
114-
});
115-
116-
@override
117-
bool updateShouldNotify(covariant EffectAnimationValue oldWidget) {
118-
return oldWidget.value != value || oldWidget.isTransition != isTransition;
119-
}
120-
}

lib/src/apple_curve.dart

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
import 'package:flutter/animation.dart';
2+
3+
/// A cubic Bézier curve that replicates Apple's easeIn curve.
4+
/// Ref: https://developer.apple.com/documentation/quartzcore/camediatimingfunctionname/1521971-easein
5+
const Cubic appleEaseIn = Cubic(0.42, 0.0, 1.0, 1.0);
6+
7+
/// A cubic Bézier curve that replicates Apple's easeOut curve.
8+
/// Ref: https://developer.apple.com/documentation/quartzcore/camediatimingfunctionname/1522178-easeout
9+
const Cubic appleEaseOut = Cubic(0.0, 0.0, 0.58, 1.0);
10+
11+
/// A cubic Bézier curve that replicates Apple's easeInOut curve.
12+
/// Ref: https://developer.apple.com/documentation/quartzcore/camediatimingfunctionname/1522173-easeineaseout
13+
const Cubic appleEaseInOut = Cubic(0.42, 0.0, 0.58, 1.0);
Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
import 'package:flutter/widgets.dart';
2+
import 'package:hyper_effects/src/pointer_transition.dart';
3+
4+
/// An inherited widget that provides the animation value to it's descendants.
5+
///
6+
/// This widget is used by [AnimatedEffect], [ScrollTransition], and
7+
/// [PointerTransition] widgets to provide their animation values to their
8+
/// descendants in order to animate them.
9+
class EffectAnimationValue extends InheritedWidget {
10+
/// The animation value. It's value is between 0 and 1.
11+
final double value;
12+
13+
/// Whether the animation is in scroll transition or not. Animations behaves
14+
/// differently in scroll transition. This flag is used to determine the
15+
/// behavior of the animation.
16+
final bool isTransition;
17+
18+
/// Whether the animation should be lerped or not. If set to false, the
19+
/// animation value is used as is. If set to true, the animation value is
20+
/// interpolated between 0 and 1.
21+
final bool lerpValues;
22+
23+
/// Creates [EffectAnimationValue] widget.
24+
const EffectAnimationValue({
25+
super.key,
26+
required super.child,
27+
required this.value,
28+
required this.isTransition,
29+
this.lerpValues = true,
30+
});
31+
32+
@override
33+
bool updateShouldNotify(covariant EffectAnimationValue oldWidget) {
34+
return oldWidget.value != value ||
35+
oldWidget.isTransition != isTransition ||
36+
oldWidget.lerpValues != lerpValues;
37+
}
38+
}

lib/src/effect_builder.dart

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import 'package:flutter/widgets.dart';
22

3-
import 'animated_effect.dart';
3+
import 'effect_animation_value.dart';
44
import 'effects/effect.dart';
55

66
/// A widget that applies given [Effect] to a [Widget]. This widget is hardly
@@ -15,7 +15,6 @@ import 'effects/effect.dart';
1515
/// interpolate between two [Effect]s. The resulting [Effect] is then applied
1616
/// to the [child] by calling [Effect.apply].
1717
class AnimatableEffect extends StatefulWidget {
18-
1918
/// The effect to apply to the [child].
2019
final Effect effect;
2120

@@ -72,7 +71,11 @@ class _AnimatableEffectState extends State<AnimatableEffect> {
7271

7372
@override
7473
Widget build(BuildContext context) {
75-
final Effect newEffect = begin.lerp(end, animationValue);
76-
return newEffect.apply(context, widget.child);
74+
if (effectAnimationValue?.lerpValues == false) {
75+
return end.apply(context, widget.child);
76+
} else {
77+
final Effect newEffect = begin.lerp(end, animationValue);
78+
return newEffect.apply(context, widget.child);
79+
}
7780
}
7881
}

0 commit comments

Comments
 (0)