Column里面有一个PageView.这PageView是一个块,它应该具有任何活动页面所要求的最小高度.

例如,如果第一个页面的高度为300px,剩余的页面为500px,并且活动页面是第一个页面,那么PageView本身的高度应该为300px.

值得一提的是,为了让PageView存在于Column中,必须将固定高度指定为PageView,否则Ffltter SDK将抛出未绑定的高度异常.

如何才能做到这一点呢?

推荐答案

Disclaimer:这是我(一小时)对这个小挑战的看法.有可能有更好的解决方案来做到这一点,或者我没有考虑到的东西.确保测试好这个解决方案.如果其他读者发现了问题,请务必在 comments 中指出,或者提出替代解决方案作为另一个答案.


我创建了一个或多或少通用的小部件来解决这个问题.它有两个限制,您需要注意:

  1. 如果您将一个不支持getDryLayout的小部件放入子元素中,它将不起作用.
  2. 它 for each 布局做了两次布局.如果您在子树中有更复杂的小部件(或者更确切地说是呈现对象)树,那么这个解决方案很可能对您来说不够好(性能).
    • 对于第一次运行,它将子元素们放置在某个常数(_firstLayoutMaxHeight)的最大高度,以便子元素们可以告诉他们的大小.如果你的子元素比这个高,你需要相应地调整它(或者更好地找到一个更好的,更集中的解决方案).

如果您已经将问题缩小到特定的范围,比如特定的图像页面视图,那么更好地解决特定的问题会容易得多.

以下是我编写的(feel free to use it)小部件的解决方案视频、它的用法和代码,以及一些解释:

enter image description here

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.

Flutter相关问答推荐

未处理异常:字符串类型不是值类型子类型的子类型'

如何在不使用NULL-check(!)的情况下缩小`FutureBuilder.Builder()`内部的`Snaphot`范围

在Flutter 中将参数从一个类传递到另一个类

为什么PUT方法没有发出任何响应?

带有图标抖动的自定义下拉按钮

有没有方法可以从当前时间轻松计算TimeStamp?

Flutter:无法构建iOS应用程序-找不到PigeonParser.h文件

将自定义容器附加到屏幕底部

Dart 和 flutter 错误无法在您的 PATH 中找到 git

GridView 渲染项目之间有微小的间隙

如何使 flutter 2 手指zoom ?

我想在文本结束后显示分隔线.它会在第一行、第二行或第三行结束

如何解决需要一个标识符,但得到的是:.try 在:之前插入一个标识符.dart 错误

为什么 ref.watch(otherProvider.stream) 在 Riverpod 2.x 中被弃用并且将在 3.x 中被删除?

Show Snackbar above to showModalBottomSheet Flutter

如何在 flutter 中创建渐变按钮

Firestore:缓存和读取数据

如何获取 android 和 Ios (flutter) 的 CircularProgress 指示器?

NoSuchMethodError:方法 '[]' 在 null 上被调用.我的信息流中的错误

Flutter Web Responsiveness 效率不高