dev
dart 216 lines 5.26 KB
Raw
1 import 'dart:ui';
2
3 import 'package:flutter/material.dart';
4 import 'package:flutter/rendering.dart';
5
6 const _firstLayoutMaxHeight = 10000.0;
7
8 class PageViewHeightAdaptable extends StatefulWidget {
9 const PageViewHeightAdaptable({
10 super.key,
11 required this.controller,
12 required this.children,
13 }) : assert(children.length > 0, 'children must not be empty');
14
15 final PageController controller;
16 final List<Widget> children;
17
18 @override
19 State<PageViewHeightAdaptable> createState() => _PageViewHeightAdaptableState();
20 }
21
22 class _PageViewHeightAdaptableState extends State<PageViewHeightAdaptable> {
23 final _sizes = <int, Size>{};
24
25 @override
26 void didUpdateWidget(PageViewHeightAdaptable oldWidget) {
27 super.didUpdateWidget(oldWidget);
28
29 _sizes.clear();
30 }
31
32 @override
33 Widget build(BuildContext context) {
34 return ListenableBuilder(
35 listenable: widget.controller,
36 builder: (context, child) => _SizingContainer(
37 sizes: _sizes,
38 page: widget.controller.hasClients ? widget.controller.page ?? 0 : 0,
39 child: child!,
40 ),
41 child: LayoutBuilder(
42 builder: (context, constraints) => PageView(
43 controller: widget.controller,
44 children: [
45 for (final (i, child) in widget.children.indexed)
46 Stack(
47 alignment: Alignment.topCenter,
48 clipBehavior: Clip.hardEdge,
49 children: [
50 SizedBox.fromSize(size: _sizes[i]),
51 Positioned(
52 left: 0,
53 top: 0,
54 right: 0,
55 child: _SizeAware(
56 child: child,
57 // don't setState, we'll use it in the layout phase
58 onSizeLaidOut: (size) {
59 _sizes[i] = size;
60 },
61 ),
62 ),
63 ],
64 ),
65 ],
66 ),
67 ),
68 );
69 }
70 }
71
72 typedef _OnSizeLaidOutCallback = void Function(Size);
73
74 class _SizingContainer extends SingleChildRenderObjectWidget {
75 const _SizingContainer({
76 super.child,
77 required this.sizes,
78 required this.page,
79 });
80
81 final Map<int, Size> sizes;
82 final double page;
83
84 @override
85 _RenderSizingContainer createRenderObject(BuildContext context) {
86 return _RenderSizingContainer(
87 sizes: sizes,
88 page: page,
89 );
90 }
91
92 @override
93 void updateRenderObject(
94 BuildContext context,
95 _RenderSizingContainer renderObject,
96 ) {
97 renderObject
98 ..sizes = sizes
99 ..page = page;
100 }
101 }
102
103 class _RenderSizingContainer extends RenderProxyBox {
104 _RenderSizingContainer({
105 RenderBox? child,
106 required Map<int, Size> sizes,
107 required double page,
108 }) : _sizes = sizes,
109 _page = page,
110 super(child);
111
112 Map<int, Size> _sizes;
113 Map<int, Size> get sizes => _sizes;
114 set sizes(Map<int, Size> value) {
115 if (_sizes == value) return;
116 _sizes = value;
117 markNeedsLayout();
118 }
119
120 double _page;
121 double get page => _page;
122 set page(double value) {
123 if (_page == value) return;
124 _page = value;
125 markNeedsLayout();
126 }
127
128 @override
129 void performLayout() {
130 if (child case final child?) {
131 child.layout(
132 constraints.copyWith(
133 minWidth: constraints.maxWidth,
134 minHeight: 0,
135 maxHeight: constraints.hasBoundedHeight ? null : _firstLayoutMaxHeight,
136 ),
137 parentUsesSize: true,
138 );
139
140 final int floorPage = page.floor();
141 final int ceilPage = page.ceil();
142
143 Size? a = sizes[floorPage];
144 Size? b = sizes[ceilPage];
145
146 if (a == null && sizes.isNotEmpty) {
147 a = sizes.values.first;
148 }
149 if (b == null && sizes.isNotEmpty) {
150 b = sizes.values.first;
151 }
152
153 a ??= child.getDryLayout(constraints);
154 b ??= a;
155
156 final double t = (page - floorPage).clamp(0.0, 1.0);
157 final double height = lerpDouble(a.height, b.height, t) ?? a.height;
158
159 child.layout(
160 constraints.copyWith(minHeight: height, maxHeight: height),
161 parentUsesSize: true,
162 );
163 size = child.size;
164 } else {
165 size = computeSizeForNoChild(constraints);
166 }
167 }
168 }
169
170 class _SizeAware extends SingleChildRenderObjectWidget {
171 const _SizeAware({
172 required Widget child,
173 required this.onSizeLaidOut,
174 }) : super(child: child);
175
176 final _OnSizeLaidOutCallback onSizeLaidOut;
177
178 @override
179 _RenderSizeAware createRenderObject(BuildContext context) {
180 return _RenderSizeAware(
181 onSizeLaidOut: onSizeLaidOut,
182 );
183 }
184
185 @override
186 void updateRenderObject(BuildContext context, _RenderSizeAware renderObject) {
187 renderObject.onSizeLaidOut = onSizeLaidOut;
188 }
189 }
190
191 class _RenderSizeAware extends RenderProxyBox {
192 _RenderSizeAware({
193 RenderBox? child,
194 required _OnSizeLaidOutCallback onSizeLaidOut,
195 }) : _onSizeLaidOut = onSizeLaidOut,
196 super(child);
197
198 _OnSizeLaidOutCallback? _onSizeLaidOut;
199 _OnSizeLaidOutCallback get onSizeLaidOut => _onSizeLaidOut!;
200 set onSizeLaidOut(_OnSizeLaidOutCallback value) {
201 if (_onSizeLaidOut == value) return;
202 _onSizeLaidOut = value;
203 markNeedsLayout();
204 }
205
206 @override
207 void performLayout() {
208 super.performLayout();
209
210 onSizeLaidOut(
211 getDryLayout(
212 constraints.copyWith(maxHeight: double.infinity),
213 ),
214 );
215 }
216 }