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

Flutter中WebViewWidget无法垂直居中的问题排查

解决Flutter中Stack内WebViewWidget无法垂直居中的问题

问题原因

  1. 尺寸单位异常:你定义的320.0.w和250.0.w如果是基于屏幕宽度的百分比(比如.w=屏幕宽度/100),320.w会等于3.2倍屏幕宽度,导致WebView和外层Container尺寸远超屏幕范围,布局错乱后无法正常居中。
  2. 冗余布局嵌套:加载状态的CircularProgressIndicator额外嵌套了Center,虽然不影响居中,但属于冗余代码。
  3. HTML内容适配问题:iframe使用固定宽高,和WebView尺寸不匹配,导致HTML内部的居中逻辑干扰整体布局视觉效果。

解决方案

1. 修正尺寸单位

将WebView的尺寸改为合理值,要么用固定像素匹配iframe的300x250,要么用屏幕百分比并保持比例:

// 方案1:用固定像素匹配iframe尺寸
final double webViewWidth = 300.0;
final double webViewHeight = 250.0;

// 方案2:用屏幕百分比并保持比例
// final double webViewWidth = 80.0.w;
// final double webViewHeight = (webViewWidth * 250) / 300;

2. 优化Stack子元素布局

移除CircularProgressIndicator外层的Center,直接利用Stack的alignment: Alignment.center实现居中:

if (isLoading) const CircularProgressIndicator(),

3. 适配HTML内容

将iframe的固定宽高改为100%,让它完全填充WebView空间,避免内部居中逻辑干扰:

<iframe id='a0659fc9' name='a0659fc9' src='https://ads.thitsarparami.org/www/delivery/afr.php?zoneid=1&cb=INSERT_RANDOM_NUMBER_HERE' frameborder='0' scrolling='no' width='100%' height='100%' allow='autoplay'>...</iframe>

修改后的完整代码

import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import 'package:thitsarparami/size_config.dart';
import 'package:webview_flutter/webview_flutter.dart';
import 'package:url_launcher/url_launcher.dart';

class CustomAdWidget extends StatefulWidget {
  const CustomAdWidget({super.key});

  @override
  State<CustomAdWidget> createState() => _CustomAdWidgetState();
}

class _CustomAdWidgetState extends State<CustomAdWidget> {
  WebViewController? controller;
  bool isLoading = true;
  bool isInitialLoad = true;
  bool isAdFailed = false;

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

  void initializeWebView() {
    controller ??= WebViewController()
      ..setJavaScriptMode(JavaScriptMode.unrestricted)
      ..setBackgroundColor(const Color(0x00000000))
      ..setNavigationDelegate(
        NavigationDelegate(
          onPageStarted: (String url) {
            if (mounted) {
              setState(() {
                isLoading = true;
                isAdFailed = false;
              });
            }
          },
          onPageFinished: (String url) {
            if (mounted) {
              setState(() {
                isLoading = false;
                isInitialLoad = false;
              });
            }
          },
          onWebResourceError: (WebResourceError error) {
            if (mounted) {
              setState(() {
                isLoading = false;
                isAdFailed = true;
              });
            }
            if (kDebugMode) {
              print("Failed to load ad: ${error.description}");
            }
          },
          onNavigationRequest: (NavigationRequest request) {
            if (!isInitialLoad && request.url.startsWith('http')) {
              _launchURL(request.url);
              return NavigationDecision.prevent;
            }
            return NavigationDecision.navigate;
          },
        ),
      );

    if (controller != null) {
      controller!.loadRequest(
        Uri.dataFromString(
          '''
          <!DOCTYPE html>
          <html lang="en">
            <head>
              <meta charset="UTF-8" />
              <meta name="viewport" content="width=device-width, initial-scale=1.0" />
              <title>Ad</title>
              <style>
                body,
                html {
                  margin: 0;
                  padding: 0;
                  overflow: hidden;
                  background-color: transparent;
                  height: 100%;
                }
                iframe {
                  width: 100%;
                  height: 100%;
                  border: 0;
                }
              </style>
            </head>
            <body>
              <iframe id='a0659fc9' name='a0659fc9' src='https://ads.thitsarparami.org/www/delivery/afr.php?zoneid=1&cb=INSERT_RANDOM_NUMBER_HERE' frameborder='0' scrolling='no' width='100%' height='100%' allow='autoplay'><a href='https://ads.localhost.org/www/delivery/ck.php?n=a64097b3&cb=INSERT_RANDOM_NUMBER_HERE' target='_blank'><img src='https://ads.thitsarparami.org/www/delivery/avw.php?zoneid=1&cb=INSERT_RANDOM_NUMBER_HERE&n=a64097b3' border='0' alt='' /></a></iframe>
            </body>
          </html>
          ''',
          mimeType: 'text/html',
        ),
      );
    }
  }

  @override
  Widget build(BuildContext context) {
    // 修正尺寸:使用固定像素值匹配iframe,或改用合理的百分比单位
    final double webViewWidth = 300.0;
    final double webViewHeight = 250.0;
    final double padding = 16.0.w;

    final double containerWidth = webViewWidth + (2 * padding);
    final double containerHeight = webViewHeight + (2 * padding);

    return Container(
      width: containerWidth,
      height: containerHeight,
      decoration: BoxDecoration(
        border: Border.all(color: Colors.grey),
        borderRadius: BorderRadius.circular(8.0.w),
      ),
      child: Stack(
        alignment: Alignment.center,
        children: [
          SizedBox(
            width: webViewWidth,
            height: webViewHeight,
            child: controller != null && !isAdFailed
                ? WebViewWidget(controller: controller!)
                : (isAdFailed
                    ? Center(
                        child: Text(
                          "Ad could not be loaded. Please check your internet connection.",
                          style: Theme.of(context).textTheme.titleMedium,
                          textAlign: TextAlign.center,
                        ),
                      )
                    : const SizedBox.shrink()),
          ),

          // 移除冗余的Center,利用Stack的alignment居中
          if (isLoading) const CircularProgressIndicator(),

          Positioned(
            top: 5.0.w,
            right: 5.0.w,
            child: Container(
              padding: EdgeInsets.symmetric(horizontal: 4.0.w, vertical: 2.0.w),
              decoration: BoxDecoration(
                color: Theme.of(context).colorScheme.secondary,
                borderRadius: BorderRadius.circular(4.0.w),
              ),
              child: Text(
                'Ad',
                style: TextStyle(
                  color: Colors.white,
                  fontSize: 12.0.w,
                  fontWeight: FontWeight.bold,
                ),
              ),
            ),
          ),
        ],
      ),
    );
  }

  void _launchURL(String url) async {
    var targetURL = Uri.parse(url);
    if (await canLaunchUrl(targetURL)) {
      await launchUrl(targetURL);
    } else {
      if (kDebugMode) {
        print('Could not launch $url');
      }
    }
  }
}

内容的提问来源于stack exchange,提问作者Alex Aung

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.16 10:09:54