Flutter:根据当前子项调整PageView / Horizo​​ntal ListView的高度

Jon*_*nas 11 dart flutter

我正在尝试创建高度可变的项目的轮播。将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')
        )
      ]
    );
  }
}
Run Code Online (Sandbox Code Playgroud)

但是我的物品包含文字,我不知道它的长度。我不想限制CarouselItem的高度,因为它可能会截断文本。我该如何使用PageView之类的东西,根据当前显示的项目调整其自身尺寸?

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

有任何想法吗?

And*_*ski 28

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

ExpandablePageView 示例

这个想法是:

  1. 我们使用自定义小部件包装 PageView 的每个子项,该小部件可以在每帧后报告小部件大小。我在这里称它SizeReportingWidget。
  2. 每个高度都保存在一个列表中,_heights在与子项索引对应的索引上调用。
  3. 附加一个 PageController 可以让您确定当前显示的页面的索引。这里我们将它保存到一个名为_currentPage
  4. 包裹PageView在 a 中SizedBox并为其指定当前显示的孩子的高度。我添加了一个方便的 getter 调用_currentHeight来检索高度。
  5. (可选)将SizedBoxin包裹起来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();
}
Run Code Online (Sandbox Code Playgroud)

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);
    }
  }
}
Run Code Online (Sandbox Code Playgroud)

  • 我想这是他发布的包:https://pub.dev/packages/expandable_page_view (8认同)
  • 这应该被接受的答案..你救了我兄弟 (3认同)

Khe*_*rel 11

有两种可能的解决方案:正确的一种和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'),
        ],
      ),
    );
  }
}
Run Code Online (Sandbox Code Playgroud)