【问题标题】:Flutter: adjust height of PageView/Horizontal ListView based on current childFlutter:根据当前子级调整PageView/Horizo​​ntal ListView的高度
【发布时间】:2019-06-28 14:20:06
【问题描述】:

我正在尝试创建一个带有可变高度项目的轮播。当使用 PageView 或 ListView 进行水平滚动时,我需要给它一个恒定的高度,像这样:

class CarouselVariableHightState extends State<CarouselVariableHight> {
  double height = 200;

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: Text('Carousel')),
      body: ListView(
        children: <Widget>[
          Text('My Carousel'),
          Container(
            height: height,
            child: PageView(
              children: <Widget>[
                _buildCarouselItem(),
                _buildCarouselItem(),
                _buildCarouselItem(),
                _buildCarouselItem(),
              ],
            ),
          ),
          Text('end of List')
        ],
      ),
    );
  }

  Widget _buildCarouselItem() {
    return Column(
      children: [
        Container(
          color: Colors.red,
          height: Random().nextInt(200).toDouble(),
          child: Text('Text with random length')
        )
      ]
    );
  }
}

但我的项目包含文本,我不知道它的长度。我不想限制 CarouselItem 的高度,因为它可能会切断文本。我怎么会有像 PageView 这样的东西,它会根据当前显示的项目调整自己的大小?

我认为一个解决方案可能是获取 CarouselItem 的高度,然后将其设置为 PageView 的高度。但是我想不出一种方法来获取我当前的 Carousel 项目的高度。

有什么想法吗?

【问题讨论】:

  • 你找到解决办法了吗?
  • @Johnykutty 不,我打开了这个问题:github.com/flutter/flutter/issues/29749
  • 我是从那个问题来到这里的,也没有任何回应:(
  • 是的,不幸的是我认为现在没有解决方案

标签: dart flutter


【解决方案1】:

您可以在下面找到一个 PageView 示例,该示例将其高度调整为当前显示的子项。带动画:

想法是:

  1. 我们使用自定义小部件包装 PageView 的每个子级,该小部件可以在每一帧之后报告小部件的大小。这里我称之为SizeReportingWidget
  2. 每个高度都保存在一个列表中,称为_heights,位于与子索引对应的索引上。
  3. 附加一个 PageController 可以让您确定当前显示的页面的索引。在这里,我们将其保存到名为_currentPage 的字段中
  4. PageView 包裹在SizedBox 中,并为其指定当前显示的孩子的高度。我添加了一个方便的 getter,名为 _currentHeight 来检索高度。
  5. (可选)将SizedBox 包裹在TweenAnimationBuilder 中,这样高度变化就变得平滑而生动,而不是铿锵的跳跃。

ExpandablePageView

class ExpandablePageView extends StatefulWidget {
  final List<Widget> children;

  const ExpandablePageView({
    Key key,
    @required this.children,
  }) : super(key: key);

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

class _ExpandablePageViewState extends State<ExpandablePageView> with TickerProviderStateMixin {
  PageController _pageController;
  List<double> _heights;
  int _currentPage = 0;

  double get _currentHeight => _heights[_currentPage];

  @override
  void initState() {
    _heights = widget.children.map((e) => 0.0).toList();
    super.initState();
    _pageController = PageController() //
      ..addListener(() {
        final _newPage = _pageController.page.round();
        if (_currentPage != _newPage) {
          setState(() => _currentPage = _newPage);
        }
      });
  }


 @override
 void dispose() {
   _pageController.dispose();
   super.dispose();
 }


  @override
  Widget build(BuildContext context) {
    return TweenAnimationBuilder<double>(
      curve: Curves.easeInOutCubic,
      duration: const Duration(milliseconds: 100),
      tween: Tween<double>(begin: _heights[0], end: _currentHeight),
      builder: (context, value, child) => SizedBox(height: value, child: child),
      child: PageView(
        controller: _pageController,
        children: _sizeReportingChildren
            .asMap() //
            .map((index, child) => MapEntry(index, child))
            .values
            .toList(),
      ),
    );
  }

  List<Widget> get _sizeReportingChildren => widget.children
      .asMap() //
      .map(
        (index, child) => MapEntry(
          index,
          OverflowBox(
            //needed, so that parent won't impose its constraints on the children, thus skewing the measurement results.
            minHeight: 0,
            maxHeight: double.infinity,
            alignment: Alignment.topCenter,
            child: SizeReportingWidget(
              onSizeChange: (size) => setState(() => _heights[index] = size?.height ?? 0),
              child: Align(child: child),
            ),
          ),
        ),
      )
      .values
      .toList();
}

SizeReportingWidget

class SizeReportingWidget extends StatefulWidget {
  final Widget child;
  final ValueChanged<Size> onSizeChange;

  const SizeReportingWidget({
    Key key,
    @required this.child,
    @required this.onSizeChange,
  }) : super(key: key);

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

class _SizeReportingWidgetState extends State<SizeReportingWidget> {
  Size _oldSize;

  @override
  Widget build(BuildContext context) {
    WidgetsBinding.instance.addPostFrameCallback((_) => _notifySize());
    return widget.child;
  }

  void _notifySize() {
    if (!this.mounted) {
      return;
    }
    final size = context?.size;
    if (_oldSize != size) {
      _oldSize = size;
      widget.onSizeChange(size);
    }
  }
}

【讨论】:

  • 这应该被接受的答案..你救了我兄弟
  • 我猜这是他发布的包:pub.dev/packages/expandable_page_view
  • 如果您想在其中包含动画高度,您可以使用 ValueListenableBuilder 覆盖 ExpandablePageView。之后创建一个 ValueNotifier 并将其从父级传递给动画小部件,并在该动画中调整 ValueNotifier。最后,您将在页面更改时获得蹩脚的动画。因为同时有两个动画(你的小部件的动画和 ExpandablePageView 的)。为防止这种情况,请在 ExpandablePageView 的 TweenAnimationBuilder _currentPage == 0 &amp;&amp; _pageController.page == 0 ? const Duration(milliseconds: 0) : const Duration(milliseconds: 300) 中使用此持续时间属性
【解决方案2】:

有 2 种可能的解决方案:正确的一种和 hacky 的一种。

  1. 正确的解决方案: 因为 PageView 试图获得所有可能的高度,所以我们不能在任何没有高度的小部件中使用它。因此,如果我们没有高度,我们需要使用另一个小部件,例如 StackWidget 并重新创建 PageView 行为:滑动、动画等。它不会是一个大库,如果你只想制作简单的东西,小于我猜是 200-300 行。

  2. hacky 解决方案是在构建 PageView 小部件之前检查所有元素的高度。构建 PageView 时,您无法更改元素的高度。因此,如果您需要在渲染轮播之前进行例如服务器请求。

要获取高度,使用 IndexedStack 更容易,因为它具有最高元素的高度,但只显示第一个元素(默认情况下)。

  • 使用keys获取IndexedStack的高度
  • 使用 setState 来保存它。
  • 使用获取的高度作为PageView的高度。

class CarouselVariableHight extends StatefulWidget {
  @override
  CarouselVariableHightState createState() => CarouselVariableHightState();
}

final widgetHeights = <double>[
  Random().nextInt(300).toDouble(),
  Random().nextInt(300).toDouble(),
  Random().nextInt(300).toDouble(),
  Random().nextInt(300).toDouble(),
];

class CarouselVariableHightState extends State<CarouselVariableHight> {
  double height;
  GlobalKey stackKey = GlobalKey();
  bool widgetHasHeigh;

  @override
  void initState() {
    WidgetsBinding.instance.addPostFrameCallback(_afterLayout);
    super.initState();
  }

  _afterLayout(_) {
    final RenderBox renderBoxRed = stackKey.currentContext.findRenderObject();
    final sizeRed = renderBoxRed.size;
    setState(() {
      height = sizeRed.height;
    });
  }

  final caruselItems = widgetHeights
      .map((height) => Column(children: [
            Container(
                color: Colors.red,
                height: Random().nextInt(300).toDouble(),
                child: Text('Text with random length'))
          ]))
      .toList();

  _buildStack() {
    Widget firstElement;
    if (height == null) {
      firstElement = Container();
    } else {
      firstElement = Container(
        height: height,
        child: PageView(
          children: caruselItems,
        ),
      );
    }

    return IndexedStack(
      key: stackKey,
      children: <Widget>[
        firstElement,
        ...caruselItems,
      ],
    );
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: Text('Carousel')),
      body: ListView(
        children: <Widget>[
          Text('My Carousel'),
          _buildStack(),
          Text('end of List'),
        ],
      ),
    );
  }
}

【讨论】:

  • 我遵循这个并得到错误方法'findRenderObject'被调用为null。接收者:null 尝试调用:findRenderObject() 你有什么想法吗?
  • @QuyenAnhNguyen 你设置了密钥吗?
  • 是的,我做到了。我没有这个错误了,但高度没有改变:(
  • 不看你的代码很难说是什么问题
  • 堆栈不会有动态高度,所以它不会按预期工作,只有孩子会有正确的高度
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2022-11-05
  • 2021-07-22
  • 1970-01-01
  • 2010-11-28
  • 2020-06-19
  • 2020-03-28
  • 2019-07-13
相关资源
最近更新 更多