flutter_path_layout 0.1.2 copy "flutter_path_layout: ^0.1.2" to clipboard
flutter_path_layout: ^0.1.2 copied to clipboard

Reusable Flutter layouts for learning, activity, roadmap, and progression paths.

example/lib/main.dart

import 'dart:math' as math;

import 'package:flutter/material.dart';
import 'package:flutter_path_layout/flutter_path_layout.dart';

void main() {
  runApp(const PathLayoutDemoApp());
}

class PathLayoutDemoApp extends StatelessWidget {
  const PathLayoutDemoApp({super.key});

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Path Layout Demo',
      theme: ThemeData(useMaterial3: true),
      home: const PathLayoutDemoScreen(),
    );
  }
}

class PathLayoutDemoScreen extends StatelessWidget {
  const PathLayoutDemoScreen({super.key});

  static const demoItems = <DemoItem>[
    DemoItem(1, 'Lesson', Size.square(64)),
    DemoItem(2, 'Practice', Size.square(64)),
    DemoItem(3, 'Reward', Size(108, 72)),
    DemoItem(4, 'Character', Size(132, 112)),
    DemoItem(5, 'Review', Size.square(76)),
    DemoItem(6, 'Lesson', Size.square(64)),
    DemoItem(7, 'Boss', Size(96, 96)),
  ];

  static const keyedProgressItems = <DemoItem>[
    DemoItem(5, 'Five', Size.square(64)),
    DemoItem(4, 'Four', Size.square(76)),
    DemoItem(3, 'Three', Size.square(64)),
    DemoItem(2, 'Two', Size.square(76)),
    DemoItem(1, 'One', Size.square(64)),
  ];

  @override
  Widget build(BuildContext context) {
    return DefaultTabController(
      length: 8,
      child: Scaffold(
        appBar: AppBar(
          title: const Text('Path Layout Demo'),
          bottom: const TabBar(
            isScrollable: true,
            tabs: [
              Tab(text: 'Vertical wave'),
              Tab(text: 'Horizontal wave'),
              Tab(text: 'Zigzag'),
              Tab(text: 'Straight'),
              Tab(text: 'Sizes'),
              Tab(text: 'Custom'),
              Tab(text: 'Connectors'),
              Tab(text: 'Progress'),
            ],
          ),
        ),
        body: TabBarView(
          children: [
            _PathDemo(
              items: demoItems,
              shape: PathShape.wave,
              connectorStyle: const PathConnectorStyle.solid(curved: true),
            ),
            _PathDemo(
              items: demoItems,
              direction: Axis.horizontal,
              shape: PathShape.wave,
              connectorStyle: const PathConnectorStyle.solid(curved: true),
            ),
            _PathDemo(
              items: demoItems,
              shape: PathShape.zigzag,
              amplitude: 0.85,
              connectorStyle: const PathConnectorStyle.dashed(),
            ),
            _PathDemo(items: demoItems, shape: PathShape.straight),
            _PathDemo(items: demoItems, shape: PathShape.alternating),
            _PathDemo(
              items: demoItems,
              strategy: CustomPathStrategy(
                positionBuilder: (context) {
                  final middle = (context.itemCount - 1) / 2;
                  if (middle == 0) {
                    return 0;
                  }
                  final distance = (context.index - middle).abs() / middle;
                  return math.cos(distance * math.pi) * 0.65;
                },
              ),
              connectorStyle: const PathConnectorStyle.solid(curved: true),
            ),
            ListView(
              padding: const EdgeInsets.all(16),
              children: const [
                SizedBox(
                  height: 360,
                  child: _PathDemo(
                    items: demoItems,
                    connectorStyle: PathConnectorStyle.none,
                  ),
                ),
                SizedBox(
                  height: 360,
                  child: _PathDemo(
                    items: demoItems,
                    connectorStyle: PathConnectorStyle.solid(),
                  ),
                ),
                SizedBox(
                  height: 360,
                  child: _PathDemo(
                    items: demoItems,
                    connectorStyle: PathConnectorStyle.dashed(curved: true),
                  ),
                ),
              ],
            ),
            _PathDemo(
              items: keyedProgressItems,
              shape: PathShape.wave,
              isItemCompleted: (item, index) => item.id <= 3,
              isItemCurrent: (item, index) => item.id == 4,
              connectorStyleBuilder: (context, segment) {
                if (segment.isCompleted) {
                  return const PathConnectorStyle.solid(
                    color: Colors.green,
                    width: 8,
                    curved: true,
                  );
                }
                return const PathConnectorStyle.solid(
                  color: Colors.black26,
                  width: 5,
                  curved: true,
                );
              },
            ),
          ],
        ),
      ),
    );
  }
}

class _PathDemo extends StatelessWidget {
  const _PathDemo({
    required this.items,
    this.direction = Axis.vertical,
    this.shape = PathShape.wave,
    this.strategy,
    this.amplitude = 0.45,
    this.connectorStyle = PathConnectorStyle.none,
    this.isItemCompleted,
    this.isItemCurrent,
    this.connectorStyleBuilder,
  });

  final List<DemoItem> items;
  final Axis direction;
  final PathShape shape;
  final PathLayoutStrategy? strategy;
  final double amplitude;
  final PathConnectorStyle connectorStyle;
  final PathItemStateResolver<DemoItem>? isItemCompleted;
  final PathItemStateResolver<DemoItem>? isItemCurrent;
  final PathConnectorStyleBuilder<DemoItem>? connectorStyleBuilder;

  @override
  Widget build(BuildContext context) {
    return FlutterPathLayout<DemoItem>(
      items: items,
      direction: direction,
      shape: shape,
      strategy: strategy,
      amplitude: amplitude,
      itemSpacing: 124,
      padding: const EdgeInsets.all(32),
      physics: const BouncingScrollPhysics(),
      connectorStyle: connectorStyle,
      isItemCompleted: isItemCompleted,
      isItemCurrent: isItemCurrent,
      connectorStyleBuilder: connectorStyleBuilder,
      contextItemBuilder: (context, item, pathContext) {
        return DemoNode(
          item: item,
          index: pathContext.index,
          isCompleted: pathContext.isCompleted,
          isCurrent: pathContext.isCurrent,
        );
      },
    );
  }
}

class DemoNode extends StatelessWidget {
  const DemoNode({
    super.key,
    required this.item,
    required this.index,
    this.isCompleted = false,
    this.isCurrent = false,
  });

  final DemoItem item;
  final int index;
  final bool isCompleted;
  final bool isCurrent;

  @override
  Widget build(BuildContext context) {
    final colors = [
      Colors.blueGrey,
      Colors.teal,
      Colors.deepPurple,
      Colors.orange,
      Colors.indigo,
    ];
    final color = isCompleted ? Colors.green : colors[index % colors.length];
    final borderWidth = isCurrent ? 5.0 : 2.0;
    return Semantics(
      label: '${item.label} ${index + 1}',
      button: true,
      child: Stack(
        clipBehavior: Clip.none,
        children: [
          DecoratedBox(
            decoration: BoxDecoration(
              color: isCompleted ? Colors.green.shade50 : color.shade100,
              border: Border.all(color: color.shade600, width: borderWidth),
              borderRadius: BorderRadius.circular(18),
            ),
            child: SizedBox.fromSize(
              size: item.size,
              child: Center(
                child: Text(
                  item.label,
                  textAlign: TextAlign.center,
                  style: TextStyle(
                    color: color.shade900,
                    fontWeight: FontWeight.w700,
                  ),
                ),
              ),
            ),
          ),
          if (isCurrent)
            Positioned(
              right: -8,
              top: -8,
              child: DecoratedBox(
                decoration: BoxDecoration(
                  color: Colors.amber,
                  border: Border.all(color: Colors.white, width: 2),
                  shape: BoxShape.circle,
                ),
                child: const SizedBox.square(
                  dimension: 24,
                  child: Icon(Icons.location_on, size: 14),
                ),
              ),
            ),
        ],
      ),
    );
  }
}

class DemoItem {
  const DemoItem(this.id, this.label, this.size);

  final int id;
  final String label;
  final Size size;
}
1
likes
160
points
204
downloads
screenshot

Documentation

API reference

Publisher

unverified uploader

Weekly Downloads

Reusable Flutter layouts for learning, activity, roadmap, and progression paths.

Repository (GitHub)
View/report issues

Topics

#flutter #layout #path #roadmap #learning

License

BSD-3-Clause (license)

Dependencies

flutter

More

Packages that depend on flutter_path_layout