import 'dart:ui'; import 'package:flutter/material.dart'; import 'package:flutter/rendering.dart'; const _firstLayoutMaxHeight = 10000.0; class PageViewHeightAdaptable extends StatefulWidget { const PageViewHeightAdaptable({ super.key, required this.controller, required this.children, }) : assert(children.length > 0, 'children must not be empty'); final PageController controller; final List children; @override State createState() => _PageViewHeightAdaptableState(); } class _PageViewHeightAdaptableState extends State { final _sizes = {}; @override void didUpdateWidget(PageViewHeightAdaptable oldWidget) { super.didUpdateWidget(oldWidget); _sizes.clear(); } @override Widget build(BuildContext context) { return ListenableBuilder( listenable: widget.controller, builder: (context, child) => _SizingContainer( sizes: _sizes, page: widget.controller.hasClients ? widget.controller.page ?? 0 : 0, child: child!, ), child: LayoutBuilder( builder: (context, constraints) => PageView( controller: widget.controller, children: [ for (final (i, child) in widget.children.indexed) Stack( alignment: Alignment.topCenter, clipBehavior: Clip.hardEdge, children: [ SizedBox.fromSize(size: _sizes[i]), Positioned( left: 0, top: 0, right: 0, child: _SizeAware( child: child, // don't setState, we'll use it in the layout phase onSizeLaidOut: (size) { _sizes[i] = size; }, ), ), ], ), ], ), ), ); } } typedef _OnSizeLaidOutCallback = void Function(Size); class _SizingContainer extends SingleChildRenderObjectWidget { const _SizingContainer({ super.child, required this.sizes, required this.page, }); final Map sizes; final double page; @override _RenderSizingContainer createRenderObject(BuildContext context) { return _RenderSizingContainer( sizes: sizes, page: page, ); } @override void updateRenderObject( BuildContext context, _RenderSizingContainer renderObject, ) { renderObject ..sizes = sizes ..page = page; } } class _RenderSizingContainer extends RenderProxyBox { _RenderSizingContainer({ RenderBox? child, required Map sizes, required double page, }) : _sizes = sizes, _page = page, super(child); Map _sizes; Map get sizes => _sizes; set sizes(Map value) { if (_sizes == value) return; _sizes = value; markNeedsLayout(); } double _page; double get page => _page; set page(double value) { if (_page == value) return; _page = value; markNeedsLayout(); } @override void performLayout() { if (child case final child?) { child.layout( constraints.copyWith( minWidth: constraints.maxWidth, minHeight: 0, maxHeight: constraints.hasBoundedHeight ? null : _firstLayoutMaxHeight, ), parentUsesSize: true, ); final int floorPage = page.floor(); final int ceilPage = page.ceil(); Size? a = sizes[floorPage]; Size? b = sizes[ceilPage]; if (a == null && sizes.isNotEmpty) { a = sizes.values.first; } if (b == null && sizes.isNotEmpty) { b = sizes.values.first; } a ??= child.getDryLayout(constraints); b ??= a; final double t = (page - floorPage).clamp(0.0, 1.0); final double height = lerpDouble(a.height, b.height, t) ?? a.height; child.layout( constraints.copyWith(minHeight: height, maxHeight: height), parentUsesSize: true, ); size = child.size; } else { size = computeSizeForNoChild(constraints); } } } class _SizeAware extends SingleChildRenderObjectWidget { const _SizeAware({ required Widget child, required this.onSizeLaidOut, }) : super(child: child); final _OnSizeLaidOutCallback onSizeLaidOut; @override _RenderSizeAware createRenderObject(BuildContext context) { return _RenderSizeAware( onSizeLaidOut: onSizeLaidOut, ); } @override void updateRenderObject(BuildContext context, _RenderSizeAware renderObject) { renderObject.onSizeLaidOut = onSizeLaidOut; } } class _RenderSizeAware extends RenderProxyBox { _RenderSizeAware({ RenderBox? child, required _OnSizeLaidOutCallback onSizeLaidOut, }) : _onSizeLaidOut = onSizeLaidOut, super(child); _OnSizeLaidOutCallback? _onSizeLaidOut; _OnSizeLaidOutCallback get onSizeLaidOut => _onSizeLaidOut!; set onSizeLaidOut(_OnSizeLaidOutCallback value) { if (_onSizeLaidOut == value) return; _onSizeLaidOut = value; markNeedsLayout(); } @override void performLayout() { super.performLayout(); onSizeLaidOut( getDryLayout( constraints.copyWith(maxHeight: double.infinity), ), ); } }