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

时间:2019-02-04 19:23:56

标签: 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')
        )
      ]
    );
  }
}

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

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

有什么想法吗?

2 个答案:

答案 0 :(得分:4)

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

  1. 正确的解决方案: 因为PageView试图获取所有可能的高度,所以我们不能在没有高度的任何小部件内使用它。因此,如果我们没有高度,则需要使用其他小部件,例如StackWidget并重新创建PageView行为:滑动,动画等。如果您只想制作简单的东西,而不会比我猜大概200-300行。

  2. 有问题的解决方案是在构建PageView小部件之前检查所有元素的高度。构建PageView时,不能更改元素的高度。因此,例如,如果您需要服务器请求,请在渲染转盘之前发出请求。

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

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

enter image description here

void OnActivate(RouterState previous, RouterState current) {
    var search = Search.fromJson(current.queryParameters);
}

答案 1 :(得分:0)

下面您可以找到一个PageView的示例,该示例可以使其页面高度适应当前显示的子级。带有动画:

ExpandablePageView example

想法是:

  1. 我们使用自定义窗口小部件包装PageView的每个子级,该窗口小部件可以在每一帧之后报告窗口小部件的大小。在这里,我将其称为SizeReportingWidget
  2. 每个身高都保存在一个列表中,该列表称为_heights,该列表对应于孩子的索引。
  3. 附加一个PageController,它使您可以确定当前显示的页面的索引。在这里,我们将其保存到名为_currentPage
  4. 的字段中
  5. PageView包裹在SizedBox中,并赋予其当前显示子项的高度。我添加了一个方便的名为_currentHeight的吸气剂来获取高度。
  6. (可选)将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
  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() {
    final size = context?.size;
    if (_oldSize != size) {
      _oldSize = size;
      widget.onSizeChange(size);
    }
  }
}