Skip to content

Commit 4e76ad3

Browse files
authored
Added snake_animation example (#8)
* Added `snake_animation` demo source * Added snake demo GIF * Updated example READMEs
1 parent 1f9dc14 commit 4e76ad3

10 files changed

Lines changed: 433 additions & 7 deletions

File tree

.github/workflows/example.yaml

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ name: "example"
22

33
on:
44
schedule:
5-
- cron: "0 12 * * 3"
5+
- cron: "0 12 * * 3"
66
pull_request:
77
push:
88
branches:
@@ -23,7 +23,9 @@ jobs:
2323
include:
2424
- package: auto_layout_frame
2525
example: editor
26+
- package: auto_layout_frame
27+
example: snake_animation
2628
name: "${{ matrix.package }} (${{ matrix.example }})"
2729
uses: kerberjg/dart_flutter_ci_experiments/.github/workflows/pipeline.yaml@4224e695167e35d90952e21953b2f933b0f045b0
2830
with:
29-
path: "example/${{ matrix.example }}"
31+
path: "example/${{ matrix.example }}"

README.md

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,8 @@ flutter pub add auto_layout_frame
2626
- 📱 **Responsive UI**: Build flexible, responsive layouts that adapt to different screen sizes
2727
- 🚀 **Rapid prototyping**: Quickly create complex layouts with an intuitive, declarative syntax
2828

29-
<img style="width: 100%;" src="https://github.com/kerberjg/auto_layout_frame.dart/blob/0.1.2/screenshots/editor.gif?raw=true"/>
29+
<img style="width: 100%;" src="https://github.com/kerberjg/auto_layout_frame.dart/blob/0.1.3/screenshots/editor.gif?raw=true"/>
30+
<img style="width: 50%;" src="https://github.com/kerberjg/auto_layout_frame.dart/blob/0.1.3/screenshots/snake-demo.gif?raw=true"/>
3031

3132

3233
## ✨ Features

example/README.md

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,8 @@
22

33
This directory contains runnable examples that showcase how to use the package.
44

5-
## basic/
6-
A minimal, straightforward demonstration of the package API. Use this example to see the simplest setup and typical usage patterns.
5+
## editor/
6+
An interactive layout editor that allows you to experiment with the package's features in real-time. You can adjust properties, add/remove children, and see the layout update instantly.
77

8-
## advanced_example/
9-
An advanced example that applies the package API in an interactive CLI. It demonstrates more complex flows, user interaction, and extended features.
8+
## snake_animation/
9+
A fun animation that demonstrates `AutoLayoutFrame`'s dynamic capabilities. A snake-shaped container moves around the screen and its children follow, showcasing how the layout adapts to changing positions and sizes.

example/snake_animation/.gitignore

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
# Miscellaneous
2+
*.class
3+
*.log
4+
*.pyc
5+
*.swp
6+
.DS_Store
7+
.atom/
8+
.build/
9+
.buildlog/
10+
.history
11+
.svn/
12+
.swiftpm/
13+
migrate_working_dir/
14+
15+
# IntelliJ related
16+
*.iml
17+
*.ipr
18+
*.iws
19+
.idea/
20+
21+
# The .vscode folder contains launch configuration and tasks you configure in
22+
# VS Code which you may wish to be included in version control, so this line
23+
# is commented out by default.
24+
#.vscode/
25+
26+
# Flutter/Dart/Pub related
27+
**/doc/api/
28+
**/ios/Flutter/.last_build_id
29+
.dart_tool/
30+
.flutter-plugins-dependencies
31+
.pub-cache/
32+
.pub/
33+
/build/
34+
/coverage/
35+
36+
# Symbolication related
37+
app.*.symbols
38+
39+
# Obfuscation related
40+
app.*.map.json
41+
42+
# Android Studio will place build artifacts here
43+
/android/app/debug
44+
/android/app/profile
45+
/android/app/release

example/snake_animation/.metadata

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
# This file tracks properties of this Flutter project.
2+
# Used by Flutter tool to assess capabilities and perform upgrades etc.
3+
#
4+
# This file should be version controlled and should not be manually edited.
5+
6+
version:
7+
revision: "90673a4eef275d1a6692c26ac80d6d746d41a73a"
8+
channel: "stable"
9+
10+
project_type: app
11+
12+
# Tracks metadata for the flutter migrate command
13+
migration:
14+
platforms:
15+
- platform: root
16+
create_revision: 90673a4eef275d1a6692c26ac80d6d746d41a73a
17+
base_revision: 90673a4eef275d1a6692c26ac80d6d746d41a73a
18+
- platform: macos
19+
create_revision: 90673a4eef275d1a6692c26ac80d6d746d41a73a
20+
base_revision: 90673a4eef275d1a6692c26ac80d6d746d41a73a
21+
22+
# User provided section
23+
24+
# List of Local paths (relative to this file) that should be
25+
# ignored by the migrate tool.
26+
#
27+
# Files that are not part of the templates will be ignored by default.
28+
unmanaged_files:
29+
- 'lib/main.dart'
30+
- 'ios/Runner.xcodeproj/project.pbxproj'

example/snake_animation/README.md

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
# snake_animation
2+
3+
A demo showcasing smooth snake-like animation built with [`auto_layout_frame`](https://pub.dev/packages/auto_layout_frame). A row of colored squares animates fluidly as the `AutoLayoutFrame` reflows its children in response to the frame's changing position and size.
4+
5+
<div align="center">
6+
<img src="https://github.com/kerberjg/auto_layout_frame.dart/blob/0.1.3/screenshots/snake-demo.gif?raw=true" width="50%"/>
7+
</div>
8+
9+
## Running
10+
11+
```bash
12+
flutter run
13+
```
14+
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
# This file configures the analyzer, which statically analyzes Dart code to
2+
# check for errors, warnings, and lints.
3+
#
4+
# The issues identified by the analyzer are surfaced in the UI of Dart-enabled
5+
# IDEs (https://dart.dev/tools#ides-and-editors). The analyzer can also be
6+
# invoked from the command line by running `flutter analyze`.
7+
8+
# The following line activates a set of recommended lints for Flutter apps,
9+
# packages, and plugins designed to encourage good coding practices.
10+
include: package:flutter_lints/flutter.yaml
11+
12+
linter:
13+
# The lint rules applied to this project can be customized in the
14+
# section below to disable rules from the `package:flutter_lints/flutter.yaml`
15+
# included above or to enable additional rules. A list of all available lints
16+
# and their documentation is published at https://dart.dev/lints.
17+
#
18+
# Instead of disabling a lint rule for the entire project in the
19+
# section below, it can also be suppressed for a single line of code
20+
# or a specific dart file by using the `// ignore: name_of_lint` and
21+
# `// ignore_for_file: name_of_lint` syntax on the line or in the file
22+
# producing the lint.
23+
rules:
24+
# avoid_print: false # Uncomment to disable the `avoid_print` rule
25+
# prefer_single_quotes: true # Uncomment to enable the `prefer_single_quotes` rule
26+
27+
# Additional information about this file can be found at
28+
# https://dart.dev/guides/language/analysis-options
Lines changed: 215 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,215 @@
1+
import 'dart:ui';
2+
3+
import 'package:flutter/material.dart';
4+
import 'package:auto_layout_frame/auto_layout_frame.dart';
5+
6+
final double kChildSize = 48;
7+
final double kPadding = 12;
8+
9+
void main() {
10+
runApp(const SnakeAnimationApp());
11+
}
12+
13+
class SnakeAnimationApp extends StatelessWidget {
14+
const SnakeAnimationApp({super.key});
15+
16+
@override
17+
Widget build(BuildContext context) {
18+
return const MaterialApp(
19+
title: 'Snake Animation',
20+
home: SnakeAnimationPage(),
21+
);
22+
}
23+
}
24+
25+
class SnakeAnimationPage extends StatefulWidget {
26+
const SnakeAnimationPage({super.key});
27+
28+
@override
29+
State<SnakeAnimationPage> createState() => _SnakeAnimationPageState();
30+
}
31+
32+
class _SnakeAnimationPageState extends State<SnakeAnimationPage>
33+
with TickerProviderStateMixin {
34+
// Animation controller
35+
late AnimationController _controller;
36+
late Animation<double> _animation;
37+
38+
// Number of children (colored squares) - constant throughout animation
39+
static const int _childCount = 11;
40+
41+
// Pre-created children - never rebuilt
42+
final List<Container> _children = [];
43+
44+
// Animation duration
45+
static const Duration _duration = Duration(milliseconds: 6000);
46+
47+
@override
48+
void initState() {
49+
super.initState();
50+
51+
// Initialize children once - never rebuild
52+
for (int i = 0; i < _childCount; i++) {
53+
_children.add(
54+
Container(
55+
width: kChildSize,
56+
height: kChildSize,
57+
decoration: BoxDecoration(
58+
color: _getColor(i),
59+
borderRadius: BorderRadius.circular(4),
60+
),
61+
child: Center(child: Text("$i")),
62+
),
63+
);
64+
}
65+
66+
// Setup animation
67+
_controller = AnimationController(duration: _duration, vsync: this);
68+
69+
// Animation goes from 0 to 4 (representing 4 corners with interpolation)
70+
_animation = Tween<double>(begin: 0, end: 4).animate(
71+
CurvedAnimation(
72+
parent: _controller, //
73+
curve: Curves.linear, //
74+
),
75+
);
76+
77+
// Start the animation
78+
_controller.repeat();
79+
}
80+
81+
Color _getColor(int index) {
82+
final hue = (index / _childCount).toDouble();
83+
// Use HSL color with fixed saturation and lightness
84+
final color = Color.fromARGB(
85+
255,
86+
(255 * hue).toInt(),
87+
(255 * (1 - hue)).toInt(),
88+
(128 + 127 * (1 - hue)).toInt(),
89+
);
90+
return color;
91+
}
92+
93+
// Get alignment based on current corner
94+
Alignment _getAlignment(int corner) {
95+
switch (corner) {
96+
case 0:
97+
return Alignment.topLeft;
98+
case 1:
99+
return Alignment.topRight;
100+
case 2:
101+
return Alignment.bottomRight;
102+
case 3:
103+
return Alignment.bottomLeft;
104+
default:
105+
return Alignment.topLeft;
106+
}
107+
}
108+
109+
// Get direction based on current corner for layout
110+
AutoLayoutDirection _getDirection(int corner) {
111+
switch (corner) {
112+
case 0:
113+
case 2:
114+
return AutoLayoutDirection.horizontal;
115+
case 1:
116+
case 3:
117+
return AutoLayoutDirection.vertical;
118+
default:
119+
return AutoLayoutDirection.horizontal;
120+
}
121+
}
122+
123+
// Calculate snake size based on animation value
124+
// Value goes from 0 to 4, where each integer is a corner
125+
// Sequence per corner: extend (25%) -> switch align (25%) -> shrink (25%) -> switch align (25%)
126+
double _getSnakeSize(double value, double maxSize, double minSize) {
127+
// Get the fractional part within the current corner's phase
128+
final cornerProgress = value - value.floor();
129+
130+
double size;
131+
132+
// First 25%: extend from 1 to max
133+
if (cornerProgress <= 0.25) {
134+
size = cornerProgress / 0.25;
135+
}
136+
// Next 25%: stay at max (align switch happens here)
137+
else if (cornerProgress <= 0.50) {
138+
size = 1;
139+
}
140+
// Next 25%: shrink from max to 1
141+
else if (cornerProgress <= 0.75) {
142+
size = 1 - ((cornerProgress - 0.50) / 0.25);
143+
}
144+
// Last 25%: stay at 1 (align switch happens here before next corner)
145+
else {
146+
size = 0;
147+
}
148+
149+
return lerpDouble(minSize, maxSize, size)!;
150+
}
151+
152+
@override
153+
void dispose() {
154+
_controller.dispose();
155+
super.dispose();
156+
}
157+
158+
@override
159+
Widget build(BuildContext context) {
160+
return Scaffold(
161+
backgroundColor: Colors.grey.shade900,
162+
body: AnimatedBuilder(
163+
animation: _animation,
164+
builder: (context, child) {
165+
final value = _animation.value;
166+
167+
// Get current corner and interpolation
168+
final int currentCorner = value.floor();
169+
170+
final Alignment currentAlignment = _getAlignment(currentCorner);
171+
final Alignment nextAlignment = _getAlignment(
172+
(currentCorner + 1) % 4,
173+
);
174+
175+
final Alignment alignment = value % 1 < 0.5
176+
? currentAlignment
177+
: nextAlignment; // Switch alignment at halfway point of corner phase
178+
179+
// Get screen size
180+
final Size screenSize = MediaQuery.of(context).size;
181+
// Get snake size
182+
final double snakeSize = _getSnakeSize(
183+
value,
184+
// max size: width/height of screen (depends on direction) minus padding
185+
(currentCorner % 2 == 0 ? screenSize.width : screenSize.height),
186+
// min size: just the child size (when fully shrunk)
187+
kChildSize + kPadding * 2, // child size + padding
188+
);
189+
190+
return Align(
191+
alignment: alignment,
192+
child: AutoLayoutFrame(
193+
direction: _getDirection(currentCorner),
194+
gap: 8,
195+
width: currentCorner % 2 == 0
196+
? snakeSize
197+
: kChildSize + kPadding * 2, // child size + padding
198+
height: currentCorner % 2 == 1
199+
? snakeSize
200+
: kChildSize + kPadding * 2, // child size + padding
201+
padding: EdgeInsets.all(kPadding),
202+
alignChildren: Alignment
203+
.center, // Keep children centered during alignment switch
204+
horizontalResizing: AutoLayoutResizing.fixed,
205+
verticalResizing: AutoLayoutResizing.fixed,
206+
overflow: AutoLayoutOverflowBehavior.clip,
207+
backgroundColor: Colors.lime.withValues(alpha: 0.9),
208+
children: _children,
209+
),
210+
);
211+
},
212+
),
213+
);
214+
}
215+
}

0 commit comments

Comments
 (0)