Flutter中WebViewWidget无法垂直居中的问题排查
解决Flutter中Stack内WebViewWidget无法垂直居中的问题
问题原因
- 尺寸单位异常:你定义的
320.0.w和250.0.w如果是基于屏幕宽度的百分比(比如.w=屏幕宽度/100),320.w会等于3.2倍屏幕宽度,导致WebView和外层Container尺寸远超屏幕范围,布局错乱后无法正常居中。 - 冗余布局嵌套:加载状态的
CircularProgressIndicator额外嵌套了Center,虽然不影响居中,但属于冗余代码。 - 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
相关产品推荐
相关产品推荐

