提问人:Tour4x 提问时间:11/10/2023 最后编辑:Tour4x 更新时间:11/17/2023 访问量:52
带有 WebView iOS 的 Flutter showModalBottomSheet 无法滚动
Flutter showModalBottomSheet with WebView iOS can't scroll
问:
我用 Webview 显示 url 显示 ModalBottomSheet,https://www.youtube.com 它显示时无法滚动,必须等待片刻才能滚动,如果我在 web 末尾添加任何字符,它可以随时滚动,或者我没有设置插图,使 webview 的宽度等于屏幕,它始终可以随时滚动,我尝试使用 webview_flutter 或flutter_inappwebview, 有同样的问题,
设备为iPhone 12 iOS 16.1。
代码
// Copyright 2013 The Flutter Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
// ignore_for_file: public_member_api_docs
import 'package:flutter_inappwebview/flutter_inappwebview.dart';
import 'package:flutter/material.dart';
import 'package:web_test/widget_show_card_rule.dart';
import 'package:webview_flutter/webview_flutter.dart';
void main() => runApp(const MaterialApp(home: WebViewExample()));
class WebViewExample extends StatefulWidget {
const WebViewExample({super.key});
@override
State<WebViewExample> createState() => _WebViewExampleState();
}
class _WebViewExampleState extends State<WebViewExample> {
late final WebViewController _controller;
@override
void initState() {
super.initState();
}
@override
Widget build(BuildContext context) {
return Scaffold(
backgroundColor: Colors.green,
appBar: AppBar(
title: const Text('Flutter WebView example'),
),
body: Container(
height: 300,
padding: const EdgeInsets.only(left: 15, right: 15),
child: InAppWebView(
initialUrlRequest: URLRequest(
url: Uri.parse(
'https://www.youtube.com')),
),
),
floatingActionButton: favoriteButton(),
);
}
Widget favoriteButton() {
return FloatingActionButton(
onPressed: () async {
_checkRule();
},
child: const Icon(Icons.favorite),
);
}
_checkRule() {
showModalBottomSheet(
enableDrag: false,
isScrollControlled: true,
context: context,
backgroundColor: Colors.transparent,
builder: (context) {
return const ShowCardRule();
});
}
}
import 'package:flutter/foundation.dart';
import 'package:flutter/gestures.dart';
import 'package:flutter/material.dart';
import 'package:hexcolor/hexcolor.dart';
import 'package:webview_flutter/webview_flutter.dart';
import 'package:webview_flutter_android/webview_flutter_android.dart';
import 'package:webview_flutter_wkwebview/webview_flutter_wkwebview.dart';
class ShowCardRule extends StatefulWidget {
const ShowCardRule({super.key});
@override
ShowCardRuleState createState() => ShowCardRuleState();
}
class ShowCardRuleState extends State<ShowCardRule> {
late final WebViewController _controller;
final Set<Factory<OneSequenceGestureRecognizer>> gestureRecognizers = {
Factory(() => EagerGestureRecognizer())
};
UniqueKey _key = UniqueKey();
@override
void initState() {
super.initState();
// #docregion platform_features
late final PlatformWebViewControllerCreationParams params;
if (WebViewPlatform.instance is WebKitWebViewPlatform) {
params = WebKitWebViewControllerCreationParams(
allowsInlineMediaPlayback: true,
mediaTypesRequiringUserAction: const <PlaybackMediaTypes>{},
);
} else {
params = const PlatformWebViewControllerCreationParams();
}
final WebViewController controller =
WebViewController.fromPlatformCreationParams(params);
// #enddocregion platform_features
controller
..setJavaScriptMode(JavaScriptMode.unrestricted)
..setBackgroundColor(const Color(0x00000000))
..setNavigationDelegate(
NavigationDelegate(
onProgress: (int progress) {
debugPrint('WebView is loading (progress : $progress%)');
},
onPageStarted: (String url) {
debugPrint('Page started loading: $url');
},
onPageFinished: (String url) {
debugPrint('Page finished loading: $url');
},
onWebResourceError: (WebResourceError error) {
debugPrint('''
Page resource error:
code: ${error.errorCode}
description: ${error.description}
errorType: ${error.errorType}
isForMainFrame: ${error.isForMainFrame}
''');
},
onNavigationRequest: (NavigationRequest request) {
if (request.url.startsWith('https://www.youtube.com/')) {
debugPrint('blocking navigation to ${request.url}');
return NavigationDecision.prevent;
}
debugPrint('allowing navigation to ${request.url}');
return NavigationDecision.navigate;
},
onUrlChange: (UrlChange change) {
debugPrint('url change to ${change.url}');
},
),
)
..addJavaScriptChannel(
'Toaster',
onMessageReceived: (JavaScriptMessage message) {
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(content: Text(message.message)),
);
},
)
..loadRequest(Uri.parse('https://www.youtube.com'));
// #docregion platform_features
if (controller.platform is AndroidWebViewController) {
AndroidWebViewController.enableDebugging(true);
(controller.platform as AndroidWebViewController)
.setMediaPlaybackRequiresUserGesture(false);
}
// #enddocregion platform_features
_controller = controller;
}
@override
Widget build(BuildContext context) {
double webHeight = 400;
double contentTop = 24;
double titleHeight = 25;
double topBlankHeight = MediaQuery.of(context).size.height - webHeight - titleHeight - contentTop;
return Align(
alignment: Alignment.topCenter,
child: Column(children: [
GestureDetector(
onTap: () {
Navigator.pop(context);
},
child: Container(height: topBlankHeight, color: Colors.transparent),
),
Container(
decoration: const BoxDecoration(
borderRadius: BorderRadius.vertical(top: Radius.circular(8)),
color: Colors.white,
),
padding: EdgeInsets.only(top: contentTop, left: 18, right: 18),
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
Container(
height: titleHeight,
child: Row(
children: [
Text('使用须知',
style: TextStyle(
color: HexColor('#333333'),
fontSize: 18,
fontWeight: FontWeight.w600)),
Expanded(child: SizedBox()),
Container(
alignment: Alignment.centerRight,
width: 60,
child: GestureDetector(
onTap: () {
Navigator.pop(context);
},
),
)
],
)),
Container(
height: webHeight,
padding: EdgeInsets.only(top: 15),
child: WebViewWidget(key: _key,controller: _controller, gestureRecognizers: gestureRecognizers,))
],
),
)
]),
);
}
}
答:
1赞
Mathis Fouques
11/10/2023
#1
我看了一下你的问题,滚动最初不起作用的原因是你的图像在webview上的加载时间。
即使触发了回调 onPageFinished,它似乎也不会加载所有图像,这就是为什么您可以查看 webview 中的大部分文本,但不能查看图像,因此您不能滚动太多。
问题实际上不是来自 webview_flutter 或 inappwebview 插件,而是来自网页本身。 如果你能设法在所有图像加载后触发一些操作,你将能够通过从你的网页向 flutter webview 控制器发送一条消息,让用户知道图像正在加载。
我使用了这篇文章的组合 https://stackoverflow.com/a/11071687/14227800;使用名为 imageLoadCompleter 的 Completer 和来自 webview 的 onConsoleMessage 回调,以: (0). 定义 imageLoad 完成器。
- 检测 webview 上所有图像的加载。
- 发送控制台消息(更喜欢使用 javascript 通道,我只是无法让它与你一起工作)
- 使用完成器完成
- 使用 FutureBuilder 和堆栈,在顶部显示 webview + 加载器,直到加载图像。
以下是使用加载器更新的“ShowCardRule”小部件:
class ShowCardRule extends StatefulWidget {
const ShowCardRule({super.key});
@override
ShowCardRuleState createState() => ShowCardRuleState();
}
class ShowCardRuleState extends State<ShowCardRule> {
late final WebViewController _controller;
final Completer imageLoadCompleter = Completer();
// COMMENT_MATHIS_FOUQUES : Completer that will complete once all images have loaded.
final Set<Factory<OneSequenceGestureRecognizer>> gestureRecognizers = {
Factory(() => EagerGestureRecognizer())
};
final UniqueKey _key = UniqueKey();
@override
void initState() {
super.initState();
// #docregion platform_features
late final PlatformWebViewControllerCreationParams params;
if (WebViewPlatform.instance is WebKitWebViewPlatform) {
params = WebKitWebViewControllerCreationParams(
allowsInlineMediaPlayback: true,
mediaTypesRequiringUserAction: const <PlaybackMediaTypes>{},
);
} else {
params = const PlatformWebViewControllerCreationParams();
}
final WebViewController controller =
WebViewController.fromPlatformCreationParams(params);
// #enddocregion platform_features
controller
..clearCache() // COMMENT_MATHIS_FOUQUES : /!\ TO remove ! Put there for testing purposes.
..setJavaScriptMode(JavaScriptMode.unrestricted)
..setBackgroundColor(const Color(0x00000000))
..setNavigationDelegate(
NavigationDelegate(
onProgress: (int progress) {
debugPrint('WebView is loading (progress : $progress%)');
},
onPageStarted: (String url) {
debugPrint('Page started loading: $url');
},
onPageFinished: (String url) {
_controller.runJavaScript(
""" var imgs = document.images,
len = imgs.length,
counter = 0;
console.log(imgs);
[].forEach.call( imgs, function( img ) {
if(img.complete) {
incrementCounter();
} else {
img.addEventListener( 'load', incrementCounter, false );
}
} );
function incrementCounter() {
counter++;
if ( counter === len ) {
console.log( 'LOADED' );
}
}""",
);
// COMMENT_MATHIS_FOUQUES : This will run on pageFinished, on the _controller, that will have been initialised.
debugPrint('Page finished loading: $url');
},
onWebResourceError: (WebResourceError error) {
debugPrint('''
Page resource error:
code: ${error.errorCode}
description: ${error.description}
errorType: ${error.errorType}
isForMainFrame: ${error.isForMainFrame}
''');
},
onNavigationRequest: (NavigationRequest request) {
if (request.url.startsWith('https://www.youtube.com/')) {
debugPrint('blocking navigation to ${request.url}');
return NavigationDecision.prevent;
}
debugPrint('allowing navigation to ${request.url}');
return NavigationDecision.navigate;
},
onUrlChange: (UrlChange change) {
debugPrint('url change to ${change.url}');
},
),
)
..setJavaScriptMode(JavaScriptMode.unrestricted)
..addJavaScriptChannel(
'Toaster',
onMessageReceived: (JavaScriptMessage message) {
print(message);
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(content: Text(message.message)),
);
},
)
..setOnConsoleMessage((message) {
if (message.message == "LOADED") {
imageLoadCompleter.complete();
}
print(message.message);
}) // COMMENT_MATHIS_FOUQUES : This console message callback can be replaced by proper use of javascriptChannels, it's just that I couldn't make it work quick enough with js channels.
..loadRequest(
Uri.parse(
'https://image.fangte.com/TestPro/UploadFiles/H5/RichText/index.html?id=BDB1A583206FED5F975D6D89D9CCB8E1'),
);
// #docregion platform_features
if (controller.platform is AndroidWebViewController) {
AndroidWebViewController.enableDebugging(true);
(controller.platform as AndroidWebViewController)
.setMediaPlaybackRequiresUserGesture(false);
}
// #enddocregion platform_features
_controller = controller;
}
@override
Widget build(BuildContext context) {
double webHeight = 400;
double contentTop = 24;
double titleHeight = 25;
double topBlankHeight = MediaQuery.of(context).size.height -
webHeight -
titleHeight -
contentTop;
return Align(
alignment: Alignment.topCenter,
child: Column(children: [
GestureDetector(
onTap: () {
Navigator.pop(context);
},
child: Container(height: topBlankHeight, color: Colors.transparent),
),
Container(
decoration: const BoxDecoration(
borderRadius: BorderRadius.vertical(top: Radius.circular(8)),
color: Colors.white,
),
padding: EdgeInsets.only(top: contentTop, left: 18, right: 18),
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
SizedBox(
height: titleHeight,
child: Row(
children: [
Text('使用须知',
style: TextStyle(
color: HexColor('#333333'),
fontSize: 18,
fontWeight: FontWeight.w600)),
const Expanded(child: SizedBox()),
Container(
alignment: Alignment.centerRight,
width: 60,
child: GestureDetector(
onTap: () {
Navigator.pop(context);
},
),
)
],
)),
Container(
height: webHeight,
padding: const EdgeInsets.only(top: 15),
child: FutureBuilder(
future: imageLoadCompleter
.future, // COMMENT_MATHIS_FOUQUES : This will complete when we receive the js message that images have loaded.
builder: (context, snapshot) {
return Stack(
children: [
WebViewWidget(
key: _key,
controller: _controller,
gestureRecognizers: gestureRecognizers,
),
if (snapshot.connectionState != ConnectionState.done)
const Center(
child: CircularProgressIndicator
.adaptive(), // COMMENT_MATHIS_FOUQUES : Shows a loader until images are ok, but we can do anything we want.
// COMMENT_MATHIS_FOUQUES : The only thing is that the webview still has to be showed, even if images are not loaded.
)
],
);
}),
)
],
),
)
]),
);
}
}
我确实用适当的标签放置了评论来解释这一点。
希望这会有所帮助
评论
0赞
Tour4x
11/10/2023
嗨,马蒂斯 感谢您的帮助。我已经尝试了解决方案,它仍然有同样的问题,我不明白为什么我不设置插图(现在插图是 EdgeInsets.only(top: contentTop, left: 18, right: 18)) 它会起作用,另一个问题是我可以编辑 Web 内容,如果我在末尾添加空格或任何字符(现在内容的结尾是图片), 它总是没有滚动问题。
0赞
Mathis Fouques
11/11/2023
是的,如果您在图像后添加一个字符,滚动问题就会消失,因为阻止您滚动的是图像的加载时间。
0赞
Mathis Fouques
11/11/2023
因此,如果您想让滚动一直工作,只需在带有图像的 div 后面添加“<p> </p>”,您的问题就解决了。问题并不真正出在 flutter webview 上,你可以用一个小窗口在你的电脑上测试,也会有同样的问题。
0赞
Tour4x
11/13/2023
谢谢马蒂斯,问题与网络内容格式有关,我已经编辑了它,网络中的滚动问题消失了。
评论