Column
里面有一个PageView
.这PageView
是一个块,它应该具有任何活动页面所要求的最小高度.
例如,如果第一个页面的高度为300px,剩余的页面为500px,并且活动页面是第一个页面,那么PageView本身的高度应该为300px.
值得一提的是,为了让PageView
存在于Column
中,必须将固定高度指定为PageView
,否则Ffltter SDK将抛出未绑定的高度异常.
如何才能做到这一点呢?
Column
里面有一个PageView
.这PageView
是一个块,它应该具有任何活动页面所要求的最小高度.
例如,如果第一个页面的高度为300px,剩余的页面为500px,并且活动页面是第一个页面,那么PageView本身的高度应该为300px.
值得一提的是,为了让PageView
存在于Column
中,必须将固定高度指定为PageView
,否则Ffltter SDK将抛出未绑定的高度异常.
如何才能做到这一点呢?
Disclaimer:这是我(一小时)对这个小挑战的看法.有可能有更好的解决方案来做到这一点,或者我没有考虑到的东西.确保测试好这个解决方案.如果其他读者发现了问题,请务必在 comments 中指出,或者提出替代解决方案作为另一个答案.
我创建了一个或多或少通用的小部件来解决这个问题.它有两个限制,您需要注意:
getDryLayout
的小部件放入子元素中,它将不起作用._firstLayoutMaxHeight
)的最大高度,以便子元素们可以告诉他们的大小.如果你的子元素比这个高,你需要相应地调整它(或者更好地找到一个更好的,更集中的解决方案).如果您已经将问题缩小到特定的范围,比如特定的图像页面视图,那么更好地解决特定的问题会容易得多.
以下是我编写的(feel free to use it)小部件的解决方案视频、它的用法和代码,以及一些解释:
import 'dart:ui';
import 'package:flutter/material.dart';
import 'package:flutter/rendering.dart';
void main() {
runApp(
const MaterialApp(
home: Home(),
),
);
}
class Home extends StatefulWidget {
const Home({super.key});
@override
State<Home> createState() => _HomeState();
}
class _HomeState extends State<Home> {
final _controller = PageController();
@override
void dispose() {
_controller.dispose();
super.dispose();
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text('PageView adaptive height'),
),
body: Column(
children: [
PageViewHeightAdaptable(
controller: _controller,
children: [
Container(height: 100, width: 50, color: Colors.red),
Container(
height: 300,
color: Colors.green,
child: const AspectRatio(
aspectRatio: 1,
child: Placeholder(),
),
),
Container(height: 200, color: Colors.blue),
Container(height: 400, color: Colors.red),
Container(height: 600, color: Colors.blue),
],
),
const FlutterLogo(),
],
),
);
}
}
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<Widget> children;
@override
State<PageViewHeightAdaptable> createState() =>
_PageViewHeightAdaptableState();
}
class _PageViewHeightAdaptableState extends State<PageViewHeightAdaptable> {
final _sizes = <int, Size>{};
@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<int, Size> 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<int, Size> sizes,
required double page,
}) : _sizes = sizes,
_page = page,
super(child);
Map<int, Size> _sizes;
Map<int, Size> get sizes => _sizes;
set sizes(Map<int, Size> 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 a = sizes[page.floor()]!;
final b = sizes[page.ceil()]!;
final height = lerpDouble(a.height, b.height, page - page.floor());
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),
),
);
}
}
是时候给出一些解释了.
我在里面创建了一个PageView
,将它们各自的子项堆叠在一起,其中SizedBox
定义子对象的大小,第二个Positioned
个子对象是实际的PageView
‘S子项,已剪裁.我用_SizeAware
收集子元素们想要的高度(从无限高度限制的干燥布局),保存到 map 上._SizingContainer
在第一次运行时将子元素放在一边,导致回调被触发. map 将使用子尺寸进行更新._SizingContainer
有这些新的尺寸,因为我们总是通过引用来修改和传递这个 map .然后,我们执行layout
的第二次运行,这一次我们获取当前的滑动/动画页面进度,并使用该进度在所示的两个子元素的大小之间进行线性内插,并使用该内插值作为我们子元素的高度-PageView
.