You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Flutter垂直PageView如何在WebView加载完成后适配子项高度

Flutter竖向PageView动态适配WebView内容高度解决方案

核心逻辑

默认PageView会强制所有子项占满主轴(此处为垂直方向)的视口高度,要实现子项高度随WebView内容动态适配,需要完成两个核心步骤:

  1. WebView加载完成后注入JS获取页面实际内容高度
  2. 改造PageView使其支持动态子项高度

实现步骤

1. 改造WebView获取内容高度

以官方webview_flutter包为例,在页面加载完成的回调中注入JS获取内容高度,存储到高度列表中:

class _YourPageState extends State<YourPage> {
  // 存储三个页面的高度,初始值设为当前屏幕高度
  final ValueNotifier<List<double>> pageHeights = ValueNotifier([]);
  final PageController pageController = PageController(initialPage: 0);

  @override
  void initState() {
    super.initState();
    final screenHeight = MediaQuery.of(context).size.height;
    pageHeights.value = [screenHeight, screenHeight, screenHeight];
  }

  Widget createWebView(String url, int pageIndex) {
    late WebViewController webController;
    return WebView(
      initialUrl: url,
      javascriptMode: JavascriptMode.unrestricted,
      onWebViewCreated: (controller) => webController = controller,
      onPageFinished: (_) async {
        // 注入JS获取页面实际滚动高度
        final heightStr = await webController.runJavascriptReturningResult(
          "document.body.scrollHeight"
        );
        double contentHeight = double.parse(heightStr);
        // 若返回值为物理像素,需除以设备像素比转成逻辑像素
        // contentHeight = contentHeight / MediaQuery.of(context).devicePixelRatio;

        // 更新对应页面的高度
        pageHeights.value[pageIndex] = contentHeight;
        pageHeights.notifyListeners();
      },
    );
  }
}

注意:如果网页存在懒加载资源(如图片、异步渲染内容),可添加适当延迟后再获取高度,或者监听网页内容变化事件实时更新高度。

2. 改造PageView支持动态高度

方案1:使用第三方适配包(推荐,最简实现)

使用expandable_page_view包,该包原生支持子项高度自动适配,直接替换原生PageView即可:

@override
Widget build(BuildContext context) {
  return ValueListenableBuilder<List<double>>(
    valueListenable: pageHeights,
    builder: (context, heights, child) {
      return ExpandablePageView(
        scrollDirection: Axis.vertical,
        controller: pageController,
        // 开启页面切换时的高度动画过渡
        animatePageTransition: true,
        children: [
          SizedBox(height: heights[0], child: createWebView("/book/page1.html", 0)),
          SizedBox(height: heights[1], child: createWebView("/book/page2.html", 1)),
          SizedBox(height: heights[2], child: createWebView("/book/page3.html", 2)),
        ],
      );
    },
  );
}

方案2:自定义实现(无需引入第三方包)

如果不想引入额外依赖,可以用SingleChildScrollView+手势监听模拟PageView的竖向翻页效果:

@override
Widget build(BuildContext context) {
  return GestureDetector(
    // 监听竖向滑动手势,实现翻页逻辑
    onVerticalDragEnd: (details) {
      if (details.primaryVelocity! > 0) {
        // 上滑,切到上一页
        pageController.previousPage(duration: const Duration(milliseconds: 300), curve: Curves.ease);
      } else if (details.primaryVelocity! < 0) {
        // 下滑,切到下一页
        pageController.nextPage(duration: const Duration(milliseconds: 300), curve: Curves.ease);
      }
    },
    child: SingleChildScrollView(
      physics: const NeverScrollableScrollPhysics(),
      child: ValueListenableBuilder<List<double>>(
        valueListenable: pageHeights,
        builder: (context, heights, child) {
          return Column(
            children: [
              SizedBox(height: heights[0], child: createWebView("/book/page1.html", 0)),
              SizedBox(height: heights[1], child: createWebView("/book/page2.html", 1)),
              SizedBox(height: heights[2], child: createWebView("/book/page3.html", 2)),
            ],
          );
        },
      ),
    ),
  );
}

冲突处理

如果WebView内容高度超过屏幕高度,需要处理WebView自身滚动和PageView翻页的手势冲突:

  • 当WebView滚动到顶部/底部时,再将手势事件交给PageView处理
  • 或者设置只有在WebView滚动到边界时才允许触发PageView的翻页逻辑

内容的提问来源于stack exchange,提问作者noname.cs

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.10.02 18:09:04