Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Flutter vertical PageView Dynamic height

I'm trying to do onboarding screen that has vertical snapping scroll. The problem is widget's are not equal in height and i want to make it take minimum space as posible. Result i'm trying to get

I found this package Expandable_page_view but it does not support vertical scroll.

In short i want this Expandable_page_view but in vertical.

like image 511
uujiguu Avatar asked Jul 11 '26 19:07

uujiguu


1 Answers

for vertical dynamic item height with snap scroll i have worked around with this

import 'dart:math';

import 'package:flutter/material.dart';

void main() => runApp(const MyApp());

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

  @override
  Widget build(BuildContext context) {
    return const MaterialApp(
      title: 'Snap List Demo',
      home: MyHomePage(),
    );
  }
}

final List<GlobalKey> itemKeys = List.generate(20, (index) => GlobalKey());

class MyHomePage extends StatefulWidget {
  const MyHomePage({super.key});
  @override
  MyHomePageState createState() => MyHomePageState();
}

class MyHomePageState extends State<MyHomePage> {
  final List<GlobalKey> itemKeys = List.generate(20, (index) => GlobalKey());
  final List<double> itemOffsets = [];
  double totalHeight = 0;

  @override
  Widget build(BuildContext context) {
    final size = MediaQuery.of(context).size;
    return Scaffold(
      appBar: AppBar(
        title: const Text('Snap List with Dynamic Heights'),
      ),
      body: ListView.builder(
        physics: SnapScrollPhysics(itemOffsets: itemOffsets),
        itemCount: 20,
        itemBuilder: (context, index) {
          return MeasureSize(
            key: itemKeys[index],
            onSizeChanged: (size) {
              if (index >= itemOffsets.length) {
                itemOffsets.add(totalHeight);
                totalHeight += size.height;
              }
            },
            child: Container(
              height: (index % 3 == 0)
                  ? size.height * 0.5
                  : (index % 2 == 0)
                      ? size.height * 0.25
                      : size.height * 0.8,
              color: Colors.grey[(index % 10 + 1) * 100],
              child: Center(child: Text('Item $index')),
            ),
          );
        },
      ),
    );
  }
}

class SnapScrollPhysics extends ScrollPhysics {
  final List<double> itemOffsets;

  const SnapScrollPhysics({required this.itemOffsets, ScrollPhysics? parent})
      : super(parent: parent);

  @override
  ScrollPhysics applyTo(ScrollPhysics? ancestor) {
    return SnapScrollPhysics(itemOffsets: itemOffsets, parent: parent);
  }

  double _getSnapTarget(double offset) {
    if (itemOffsets.isEmpty) return 0.0;

    final distances = itemOffsets.map((e) => (e - offset).abs()).toList();
    final minDistance = distances.reduce(min);
    final closestIndex = distances.indexOf(minDistance);

    return itemOffsets[closestIndex];
  }

  @override
  Simulation createBallisticSimulation(
      ScrollMetrics position, double velocity) {
    final tolerance = toleranceFor(position);
    final targetPosition = _getSnapTarget(position.pixels);

    if (velocity.abs() < tolerance.velocity) {
      return ScrollSpringSimulation(
        spring,
        position.pixels,
        targetPosition,
        velocity,
        tolerance: tolerance,
      );
    }
    return super.createBallisticSimulation(position, velocity) ??
        ScrollSpringSimulation(
          spring,
          position.pixels,
          targetPosition,
          velocity,
          tolerance: tolerance,
        );
  }
}

typedef OnSizeChanged = void Function(Size size);
class MeasureSize extends StatefulWidget {
  final OnSizeChanged onSizeChanged;
  final Widget child;

  const MeasureSize({
    Key? key,
    required this.onSizeChanged,
    required this.child,
  }) : super(key: key);

  @override
  MeasureSizeState createState() => MeasureSizeState();
}

class MeasureSizeState extends State<MeasureSize> {
  Size? oldSize;
  @override
  Widget build(BuildContext context) {
    WidgetsBinding.instance.addPostFrameCallback((_) => _checkSize());
    return widget.child;
  }

  _checkSize() {
    final size = (context.findRenderObject() as RenderBox).size;
    if (oldSize != size) {
      widget.onSizeChanged(size);
    }
    oldSize = size;
  }
}

like image 92
bharat Avatar answered Jul 14 '26 12:07

bharat



Donate For Us

If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!