Flutter-自适用高度PageView

需求

在 Flutter 中,PageView 是一个非常常用的组件,能够实现多个页面的滑动切换。然而,默认的 PageView 高度是固定的,这在展示不同高度的页面时,可能会导致不必要的空白或内容裁剪问题。为了使 PageView 能够根据每个页面的内容高度动态调整,我们需要一个自适应高度的 PageView 实现。

效果

在这里插入图片描述

本方案的 PageView 可以根据每个页面内容的高度自动调整,保证每个页面的内容在其实际高度内完整显示,并且在页面滑动时,使用平滑的过渡效果。具体来说:

  • 每个页面的内容高度不同,PageView 能够动态调整高度。
  • 页面切换时,高度变化平滑,不会造成突兀的视觉效果。

实现思路

1. 测量每个页面的高度

首先,我们需要为每个页面添加一个高度测量的机制,测量页面内容的高度。在 Flutter 中,我们可以通过 GlobalKeyRenderBox 获取每个 Widget 的实际高度。

2. 动态调整 PageView 高度

在页面滑动时,我们需要根据滑动的进度动态计算当前 PageView 的高度。这就需要在 PageView 的滑动过程中实时更新高度,使得高度随滑动位置逐步过渡。

3. 动态高度过渡

我们可以使用 AnimatedContainer 来平滑过渡 PageView 的高度,避免切换时高度变化过于突兀。通过监听 PageView 的滑动状态,实时调整容器高度。

实现代码

1. 高度测量组件 HeightMeasureWidget

这个组件负责测量每个页面的高度,并将测量结果通过回调传递出去。

import 'package:flutter/material.dart';

class HeightMeasureWidget extends StatefulWidget {
  final Widget child;
  final Function(double height) onHeightChanged;

  const HeightMeasureWidget(
      {super.key, required this.child, required this.onHeightChanged});

  
  HeightMeasureState createState() => HeightMeasureState();
}

class HeightMeasureState extends State<HeightMeasureWidget> {
  final GlobalKey _key = GlobalKey();

  
  void initState() {
    super.initState();
    WidgetsBinding.instance.addPostFrameCallback((_) {
      _measureHeight();
    });
  }

  void _measureHeight() {
    final RenderBox? renderBox =
        _key.currentContext!.findRenderObject() as RenderBox?;
    if (renderBox != null) {
      widget.onHeightChanged(renderBox.size.height);
    }
  }

  
  Widget build(BuildContext context) {
    return Container(
      key: _key,
      child: widget.child,
    );
  }
}

2. 自适应高度的 AutoHeightPageView

这个组件使用了前面创建的 HeightMeasureWidget 来测量每个页面的高度,然后根据滑动进度调整高度。

import 'package:flutter/material.dart';
import 'measure_height_widget.dart';

class AutoHeightPageView extends StatefulWidget {
  final List<Widget> children;
  final PageController pageController;

  const AutoHeightPageView({
    Key? key,
    required this.children,
    required this.pageController,
  }) : super(key: key);

  
  AutoHeightPageViewState createState() => AutoHeightPageViewState();
}

class AutoHeightPageViewState extends State<AutoHeightPageView> {
  final List<double> _heights = [];
  double _currentHeight = 0;

  
  void initState() {
    super.initState();
    widget.pageController.addListener(_updateHeight);
  }

  void _updateHeight() {
    if (widget.pageController.position.haveDimensions && _heights.isNotEmpty) {
      double page = widget.pageController.page ?? 0.0;
      int index = page.floor();
      int nextIndex = (index + 1) < _heights.length ? index + 1 : index;
      double percent = page - index;
      double height =
          _heights[index] + (_heights[nextIndex] - _heights[index]) * percent;
      setState(() {
        _currentHeight = height;
      });
    }
  }

  
  Widget build(BuildContext context) {
    var isMeasureHeight =
        _heights.length == widget.children.length ? false : true;
    return Column(
      children: [
        Stack(
          children: [
            Visibility(
              visible: isMeasureHeight,
              child: Stack(
                children: widget.children
                    .map((e) => HeightMeasureWidget(
                          child: e,
                          onHeightChanged: (height) {
                            _heights.add(height);
                            if (_heights.length == widget.children.length) {
                              setState(() {
                                _currentHeight = _heights[0];
                              });
                            }
                          },
                        ))
                    .toList(),
              ),
            ),
            if (!isMeasureHeight)
              AnimatedContainer(
                duration: const Duration(milliseconds: 200),
                height: _currentHeight,
                curve: Curves.easeOut,
                child: PageView(
                  controller: widget.pageController,
                  children: widget.children,
                ),
              ),
          ],
        )
      ],
    );
  }

  
  void dispose() {
    widget.pageController.dispose();
    super.dispose();
  }
}

3. 使用示例 AutoHeightPageViewPage

该页面演示了如何使用自适应高度的 PageView,通过内容高度的动态调整,确保 PageView 始终适应当前页面的高度。

import 'package:flutter/material.dart';
import 'package:flutter_xy/r.dart';
import 'package:flutter_xy/xydemo/vp/pageview/auto_height_page_view.dart';

class AutoHeightPageViewPage extends StatefulWidget {
  const AutoHeightPageViewPage({Key? key}) : super(key: key);

  
  State<StatefulWidget> createState() => AutoHeightPageViewState();
}

class AutoHeightPageViewState extends State<AutoHeightPageViewPage> {
  final PageController _pageController = PageController();

  
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text('自适用高度PageView'),
      ),
      body: Container(
        color: Colors.white,
        child: SingleChildScrollView(
          child: Column(
            children: [
              AutoHeightPageView(
                pageController: _pageController,
                children: [
                  Container(
                    color: Colors.white,
                    child: ListView(
                      shrinkWrap: true,
                      physics: const NeverScrollableScrollPhysics(),
                      children: [
                        Container(
                          color: Colors.red,
                          height: 50,
                          alignment: Alignment.center,
                          child: const Text("第一个界面"),
                        ),
                        Container(
                          color: Colors.yellow,
                          height: 50,
                          alignment: Alignment.center,
                          child: const Text("第一个界面"),
                        ),
                        Container(
                          color: Colors.blue,
                          height: 50,
                          alignment: Alignment.center,
                          child: const Text("第一个界面"),
                        ),
                      ],
                    ),
                  ),
                  Container(
                      color: Colors.green,
                      height: 250,
                      child: const Center(child: Text('第二个界面'))),
                ],
              ),
              Image.asset(
                R.vp_content_jpg,
                width: MediaQuery.of(context).size.width,
                fit: BoxFit.fill,
              ),
            ],
          ),
        ),
      ),
    );
  }
}

总结

通过本方案,我们实现了一个自适应高度的 PageView,它能够根据每个页面的内容高度进行动态调整。该实现依赖于对每个页面内容的测量,并使用 AnimatedContainer 来平滑地过渡高度变化。这样可以确保页面切换时,用户体验更加自然流畅,避免了内容被裁剪或空白区域的出现。这种自适应高度的 PageView 非常适合用于不同页面内容高度不一致的场景。

详情:github.com/yixiaolunhui/flutter_xy

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值