hyper_effects 0.1.1 hyper_effects: ^0.1.1 copied to clipboard
Create beautiful effects and animations with just a few lines of code.
import 'dart:math';
import 'package:adaptive_theme/adaptive_theme.dart';
import 'package:flutter/material.dart';
import 'package:flutter/scheduler.dart';
import 'package:flutter/services.dart';
import 'package:flutter_box_transform/flutter_box_transform.dart';
import 'package:hyper_effects_demo/stories/spring_animation.dart';
import 'package:hyper_effects_demo/stories/color_filter_scroll_transition.dart';
import 'package:hyper_effects_demo/stories/scroll_phase_transition.dart';
import 'package:hyper_effects_demo/stories/scroll_wheel_blur.dart';
import 'package:hyper_effects_demo/stories/scroll_wheel_transition.dart';
import 'package:hyper_effects_demo/stories/windows_settings_transition.dart';
import 'story.dart';
void main() {
runApp(const MyApp());
}
class MyApp extends StatelessWidget {
const MyApp({super.key});
@override
Widget build(BuildContext context) {
return AdaptiveTheme(
light: ThemeData(
useMaterial3: true,
brightness: Brightness.light,
colorSchemeSeed: Colors.blue,
inputDecorationTheme: InputDecorationTheme(
isDense: true,
contentPadding:
const EdgeInsets.symmetric(horizontal: 10, vertical: 12),
border: OutlineInputBorder(
borderRadius: BorderRadius.circular(6),
),
),
),
dark: ThemeData(
useMaterial3: true,
brightness: Brightness.dark,
colorSchemeSeed: Colors.blue,
inputDecorationTheme: InputDecorationTheme(
isDense: true,
contentPadding:
const EdgeInsets.symmetric(horizontal: 10, vertical: 12),
border: OutlineInputBorder(
borderRadius: BorderRadius.circular(6),
),
),
),
initial: AdaptiveThemeMode.system,
builder: (theme, darkTheme) => MaterialApp(
title: 'Hyper Effects Storyboard',
debugShowCheckedModeBanner: false,
theme: theme,
darkTheme: darkTheme,
home: const Storyboard(),
),
);
}
}
class Storyboard extends StatefulWidget {
const Storyboard({super.key});
@override
State<Storyboard> createState() => _StoryboardState();
}
class _StoryboardState extends State<Storyboard> {
final List<Story> animationStories = [
const Story(
title: 'Spring Animation',
child: SpringAnimation(),
),
// const Story(
// title: 'Chained Animation',
// child: ChainedAnimation(),
// )
];
final List<Story> transitionStories = [
const Story(
title: 'Scroll Phase Transition',
child: ScrollPhaseTransition(),
),
const Story(
title: 'Scroll Wheel Blur Transition',
child: ScrollWheelBlurTransition(),
),
const Story(
title: 'Scroll Wheel Transition',
child: ScrollWheelTransition(),
),
const Story(
title: 'Windows Settings Effect',
child: WindowsSettingsTransition(),
),
const Story(
title: 'Color Filter Scroll Transition',
child: FashionScrollTransition(),
),
];
int? selectedAnimation;
int? selectedTransition;
int? selectedCategory;
@override
Widget build(BuildContext context) {
return Scaffold(
body: Row(
children: [
SizedBox(
width: 300,
child: Column(
children: [
Padding(
padding: const EdgeInsets.all(16),
child: Text(
'Hyper Effects Storyboard',
style: Theme.of(context).textTheme.titleLarge,
),
),
const Divider(height: 0),
CustomExpansionTile(
title: const Text('Animations'),
isExpanded: selectedCategory == 0,
onExpansionChanged: () {
setState(() {
if (selectedCategory == 0) {
selectedCategory = null;
} else {
selectedCategory = 0;
}
});
},
children: [
for (final Story story in animationStories)
ColoredBox(
color: (animationStories.indexOf(story) ==
selectedAnimation)
? Theme.of(context)
.colorScheme
.primary
.withOpacity(0.1)
: Colors.transparent,
child: ListTile(
title: Padding(
padding: const EdgeInsets.only(left: 16),
child: Text(story.title),
),
onTap: () {
setState(() {
selectedAnimation =
animationStories.indexOf(story);
selectedTransition = null;
});
},
selected: animationStories.indexOf(story) ==
selectedAnimation,
),
),
],
),
const Divider(height: 0),
CustomExpansionTile(
title: const Text('Transitions'),
isExpanded: selectedCategory == 1,
onExpansionChanged: () {
setState(() {
if (selectedCategory == 1) {
selectedCategory = null;
} else {
selectedCategory = 1;
}
});
},
children: [
for (final Story story in transitionStories)
ColoredBox(
color: (transitionStories.indexOf(story) ==
selectedTransition)
? Theme.of(context)
.colorScheme
.primary
.withOpacity(0.1)
: Colors.transparent,
child: ListTile(
title: Padding(
padding: const EdgeInsets.only(left: 16),
child: Text(story.title),
),
onTap: () {
setState(() {
selectedTransition =
transitionStories.indexOf(story);
selectedAnimation = null;
});
},
selected: transitionStories.indexOf(story) ==
selectedTransition,
),
),
],
),
const Divider(height: 0),
],
),
),
const VerticalDivider(width: 2),
Expanded(
flex: 3,
child: ContentView(
child: AnimatedSwitcher(
duration: const Duration(milliseconds: 300),
child: selectedAnimation != null
? animationStories[selectedAnimation!].child
: selectedTransition != null
? transitionStories[selectedTransition!].child
: const Center(
child: Text('Select a story to view.'),
),
),
),
),
],
),
);
}
}
class ContentView extends StatefulWidget {
final Widget child;
const ContentView({super.key, required this.child});
@override
State<ContentView> createState() => _ContentViewState();
}
class _ContentViewState extends State<ContentView> with WidgetsBindingObserver {
final GlobalKey _key = GlobalKey();
final TransformableBoxController controller = TransformableBoxController(
resizeModeResolver: () {
final pressedKeys = WidgetsBinding.instance.keyboard.logicalKeysPressed;
final isShiftPressed =
pressedKeys.contains(LogicalKeyboardKey.shiftLeft) ||
pressedKeys.contains(LogicalKeyboardKey.shiftRight);
if (isShiftPressed) {
return ResizeMode.symmetricScale;
} else {
return ResizeMode.symmetric;
}
},
allowFlippingWhileResizing: false,
);
@override
void initState() {
super.initState();
controller
.setConstraints(const BoxConstraints(minHeight: 200, minWidth: 200));
SchedulerBinding.instance.addPostFrameCallback((_) {
controller.setClampingRect(getArea(), notify: false);
controller.setRect(Rect.fromCenter(
center: controller.clampingRect.center,
width: min(500, controller.clampingRect.width),
height: controller.clampingRect.height,
));
if (mounted) setState(() {});
});
WidgetsBinding.instance.addObserver(this);
}
@override
void didChangeMetrics() {
super.didChangeMetrics();
controller.setClampingRect(getArea());
controller.setRect(getRect(), recalculate: true);
if (mounted) setState(() {});
}
@override
void didUpdateWidget(covariant ContentView oldWidget) {
super.didUpdateWidget(oldWidget);
controller.setRect(getRect(), recalculate: true);
}
Rect getArea() {
final RenderBox renderBox =
_key.currentContext?.findRenderObject() as RenderBox;
final size = renderBox.size;
return Rect.fromLTWH(0, 0, size.width, size.height).deflate(16);
}
Rect getRect() {
Rect rect = controller.rect;
rect = Rect.fromCenter(
center: controller.clampingRect.center,
width: min(controller.clampingRect.width, rect.width),
height: min(controller.clampingRect.height, rect.height),
);
return rect;
}
@override
void dispose() {
WidgetsBinding.instance.removeObserver(this);
controller.dispose();
super.dispose();
}
@override
Widget build(BuildContext context) {
return Stack(
key: _key,
children: [
TransformableBox(
controller: controller,
draggable: false,
allowContentFlipping: false,
contentBuilder: (BuildContext context, Rect rect, Flip flip) {
return DecoratedBox(
decoration: BoxDecoration(
border: Border.all(
color: Theme.of(context).colorScheme.primary,
width: 2,
strokeAlign: BorderSide.strokeAlignOutside,
),
),
child: Center(child: widget.child),
);
},
),
],
);
}
}
class CustomExpansionTile extends StatefulWidget {
final Widget title;
final List<Widget> children;
final bool isExpanded;
final VoidCallback onExpansionChanged;
const CustomExpansionTile({
super.key,
required this.title,
required this.isExpanded,
required this.onExpansionChanged,
required this.children,
});
@override
State<CustomExpansionTile> createState() => _CustomExpansionTileState();
}
class _CustomExpansionTileState extends State<CustomExpansionTile> {
@override
Widget build(BuildContext context) {
return Column(
children: [
ListTile(
title: widget.title,
trailing: const Icon(Icons.expand_more),
onTap: widget.onExpansionChanged,
),
ClipRect(
child: AnimatedAlign(
duration: const Duration(milliseconds: 300),
curve: Curves.easeOutQuart,
heightFactor: widget.isExpanded ? 1 : 0,
alignment: Alignment.topCenter,
child: ColoredBox(
color: Theme.of(context)
.colorScheme
.primaryContainer
.withOpacity(0.05),
child: Column(
children: widget.children,
),
),
),
),
],
);
}
}