flutter_path_layout
A reusable Flutter layout component for creating learning paths, progression paths, activity paths, onboarding paths, game maps, roadmaps, and reward paths.
The package is intentionally generic. It handles layout, scrolling, path geometry, optional connectors, and responsive positioning. The consuming app supplies every node widget, including colors, icons, semantics, gestures, and state.
Preview
Basic Usage
Add the package to a consuming Flutter app:
flutter pub add flutter_path_layout
For local monorepo development, use a path dependency instead:
dependencies:
flutter_path_layout:
path: ../../packages/flutter_path_layout
Then import the public barrel:
import 'package:flutter_path_layout/flutter_path_layout.dart';
FlutterPathLayout<Activity>(
items: activities,
direction: Axis.vertical,
shape: PathShape.wave,
itemSpacing: 120,
amplitude: 0.3,
itemBuilder: (context, activity, index) {
return ActivityButton(activity: activity);
},
)
Horizontal Usage
FlutterPathLayout<Activity>(
items: activities,
direction: Axis.horizontal,
shape: PathShape.wave,
physics: const BouncingScrollPhysics(),
itemBuilder: (context, activity, index) {
return ActivityButton(activity: activity);
},
)
Configuration
Common options can be passed directly to FlutterPathLayout:
FlutterPathLayout<Activity>(
items: activities,
direction: Axis.vertical,
shape: PathShape.zigzag,
itemSpacing: 112,
amplitude: 0.75,
padding: const EdgeInsets.all(24),
connectorStyle: const PathConnectorStyle.solid(curved: true),
controller: scrollController,
physics: const BouncingScrollPhysics(),
itemBuilder: (context, activity, index) => ActivityNode(activity),
)
For repeated setups, use PathLayoutConfig:
FlutterPathLayout<Activity>(
items: activities,
config: const PathLayoutConfig(
direction: Axis.vertical,
shape: PathShape.wave,
itemSpacing: 120,
amplitude: 0.45,
),
itemBuilder: (context, activity, index) => ActivityNode(activity),
)
Custom Node Styles
Nodes are normal Flutter widgets. They can use GestureDetector, InkWell,
Semantics, FocusableActionDetector, animations, images, or any other app
composition.
FlutterPathLayout<String>(
items: const ['lesson', 'reward', 'review'],
itemBuilder: (context, item, index) {
return Semantics(
label: item,
button: true,
child: SizedBox.square(
dimension: item == 'reward' ? 96 : 64,
child: Center(child: Text(item)),
),
);
},
)
Custom Strategy
Built-in shapes include straight, wave, zigzag, and alternating.
For arbitrary patterns, pass a strategy:
FlutterPathLayout<int>(
items: List.generate(8, (index) => index),
strategy: CustomPathStrategy(
positionBuilder: (context) {
final middle = (context.itemCount - 1) / 2;
final distance = (context.index - middle).abs() / middle;
return (1 - distance) * 0.8;
},
),
itemBuilder: (context, item, index) => LessonNode(index: index),
)
Wave paths can be tuned with amplitude, frequency, and phase:
FlutterPathLayout<int>(
items: items,
shape: PathShape.wave,
amplitude: 0.65,
frequency: 1.5,
phase: 0.4,
itemBuilder: (context, item, index) => Node(index),
)
Strategies return normalized cross-axis values:
-1.0: far left for vertical paths, or top for horizontal paths.0.0: center.1.0: far right for vertical paths, or bottom for horizontal paths.
The layout engine clamps positions so nodes stay inside the available cross-axis area as much as the parent constraints allow.
Connector Customization
Connectors are optional and use the same calculated geometry as node positioning.
FlutterPathLayout<int>(
items: items,
connectorStyle: const PathConnectorStyle.dashed(
width: 4,
color: Color(0xFF7C8794),
curved: true,
),
itemBuilder: (context, item, index) => Node(item: item),
)
Set curved: true to draw smooth connector segments instead of straight line
segments. Curved connectors work with solid, dashed, progress-based, and
segment-resolved connector styles.
FlutterPathLayout<int>(
items: items,
connectorStyle: const PathConnectorStyle.solid(
width: 5,
color: Color(0xFF6B7280),
curved: true,
),
itemBuilder: (context, item, index) => Node(item: item),
)
Progress Styling
Use PathLayoutProgress for simple paths where visual order and learning order
are the same.
FlutterPathLayout<Activity>(
items: activities,
progress: const PathLayoutProgress(
completedIndex: 4,
currentIndex: 5,
completedConnectorStyle: PathConnectorStyle.solid(
color: Color(0xFF35A66F),
width: 6,
curved: true,
),
pendingConnectorStyle: PathConnectorStyle.solid(
color: Color(0xFFD1D5DB),
width: 4,
curved: true,
),
),
contextItemBuilder: (context, activity, pathContext) {
return ActivityNode(
activity: activity,
isCompleted: pathContext.isCompleted,
isCurrent: pathContext.isCurrent,
isPending: pathContext.isPending,
);
},
)
The package styles connector segments from the same geometry used for node placement. The consuming app still owns node visuals, including check marks, locked states, badges, rings, or current indicators.
Connector styles used by progress APIs can also be curved:
completedConnectorStyle: const PathConnectorStyle.solid(
color: Color(0xFF35A66F),
width: 6,
curved: true,
)
For production paths where progress is keyed by backend IDs, visual order is reversed, or items can be skipped, prefer item-aware callbacks and a segment style resolver:
FlutterPathLayout<Activity>(
items: renderedActivities,
isItemCompleted: (activity, index) {
return completedActivityIds.contains(activity.id);
},
isItemCurrent: (activity, index) {
return activity.id == currentActivityId;
},
connectorStyleBuilder: (context, segment) {
if (segment.isCompleted) {
return const PathConnectorStyle.solid(
color: Color(0xFF35A66F),
width: 6,
curved: true,
);
}
return const PathConnectorStyle.solid(
color: Color(0xFFD1D5DB),
width: 4,
curved: true,
);
},
contextItemBuilder: (context, activity, pathContext) {
return ActivityNode(
activity: activity,
isCompleted: pathContext.isCompleted,
isCurrent: pathContext.isCurrent,
);
},
)
connectorStyleBuilder receives a PathSegmentContext<T> with fromItem,
toItem, fromIndex, toIndex, geometry, direction, and resolved endpoint
states. This lets the app keep progress key-based while the package owns
connector painting.
Progress precedence is:
connectorBuilderconnectorStyleBuilderPathLayoutProgressconnectorStyle
Item state precedence is:
isItemCompleted/isItemCurrentPathLayoutProgress- pending by default
For fully custom connector visuals, use connectorBuilder:
FlutterPathLayout<int>(
items: items,
connectorBuilder: (context, geometry) {
return CustomPaint(painter: MyRoadPainter(geometry));
},
itemBuilder: (context, item, index) => Node(item: item),
)
Performance Guidance
This package uses a regular scroll view and stack. It is simple, predictable, and appropriate for typical learning paths. It currently builds all node widgets, so extremely large paths with hundreds or thousands of expensive nodes should be split into sections or paired with lightweight node widgets.
The package keeps geometry calculation separate from rendering so a future sliver or render-object implementation can reuse the same strategy model.
Architecture Overview
FlutterPathLayoutowns scrolling, measuring children, and widget placement.PathLayoutEngineconverts constraints, padding, child sizes, and strategy output into pixel geometry.PathLayoutStrategyimplementations return normalized cross-axis positions.PathConnectorPainterpaints optional connectors from the same geometry used by the nodes.
Run checks from this package directory:
fvm flutter analyze
fvm flutter test
From the repository root, the package also participates in the pnpm/Turbo workspace through its wrapper scripts:
pnpm --filter @mybaby/flutter-path-layout lint
pnpm --filter @mybaby/flutter-path-layout test
Example App
The example app demonstrates vertical wave, horizontal wave, zigzag, straight, mixed node sizes, a custom strategy, connector styles, and progress styling.
cd packages/flutter_path_layout/example
fvm flutter run -d web-server --web-port 8090