问题 webview_flutter 直接使用会小米8会出现顶部空白
解决方案 用Container包裹,给Container设置宽高
1.依赖
webview_flutter: ^2.0.13
2.自定义WebViewWidget控件
WebViewWidget
import 'dart:async'
import 'dart:io'
import 'package:flutter/cupertino.dart'
import 'package:flutter/foundation.dart'
import 'package:flutter/gestures.dart'
import 'package:flutter/material.dart'
import 'package:flutter/services.dart'
import 'package:webview_flutter/webview_flutter.dart'
import 'javascript_bridge.dart'
class WebViewWidget extends StatefulWidget {
final String initialUrl
final JavascriptBridge? bridge
final Completer<WebViewController>? controller
final Function(String)? onPageFinished
final bool canRefresh
//可以自定义webview的宽高,0 的时候设置成屏幕的宽高,默认值是 0
final double width
final double height
final Function(WebViewController controller)? onRefreshByBtn
WebViewWidget(
{required this.initialUrl,
this.bridge,
this.controller,
this.onPageFinished,
this.canRefresh = false,
this.width = 0,
this.height = 0,
this.onRefreshByBtn,
Key? key})
: super(key: key)
@override
State<StatefulWidget> createState() {
return WebViewWidgetState()
}
}
class WebViewWidgetState extends State<WebViewWidget> {
Completer<WebViewController>? _controller
bool _loading = true
String? _errorMsg
String? _errorDetail
@override
void initState() {
super.initState()
if (Platform.isAndroid) {
WebView.platform = SurfaceAndroidWebView()
}
_controller = widget.controller ?? Completer<WebViewController>()
}
Widget? webView
String _getUrl() {
String url = widget.initialUrl
return Platform.isIOS ? Uri.encodeFull(url) : url
}
@override
Widget build(BuildContext context) {
webView = WebView(
debuggingEnabled: !kReleaseMode,
initialUrl: _getUrl(),
javascriptChannels: widget.bridge?.channels.values.toSet() ?? Set(),
javascriptMode: JavascriptMode.unrestricted,
gestureRecognizers: Set()
..add(Factory<VerticalDragGestureRecognizer>(() {
return VerticalDragGestureRecognizer()
..onStart = (DragStartDetails details) {
print("@drag_start")
}
..onUpdate = (DragUpdateDetails details) {
print("@drag_update: $details")
}
..onDown = (DragDownDetails details) {
if (Platform.isIOS) {
SystemChannels.textInput.invokeMethod('TextInput.hide')
}
print("@drag_down: $details")
}
..onCancel = () {
print("@drag_cancel")
}
..onEnd = (DragEndDetails details) {
print("@drag_end")
}
})),
onWebViewCreated: (webViewController) {
var isCompleted = _controller?.isCompleted ?? false
print("@web_created:$isCompleted")
if (!isCompleted) {
_controller?.complete(webViewController)
}
},
onWebResourceError: (error) {
StringBuffer sb = StringBuffer('onWebResourceError:\n')
if (error.failingUrl != null && error.failingUrl!.isNotEmpty) {
sb.writeln('url:${error.failingUrl}')
}
if (error.errorType != null) {
sb.writeln('errorType:${error.errorType}')
}
if (error.domain != null && error.domain!.isNotEmpty) {
sb.writeln('domain:${error.domain}')
}
sb.writeln('errorCode:${error.errorCode}')
if (error.description.isNotEmpty) {
_errorDetail = error.description
sb.writeln('description:${error.description}')
}
setState(() {
_loading = false
_errorMsg = '发生错误:${error.errorCode}'
})
print(sb.toString())
},
onPageStarted: (value) {
setState(() {
_loading = true
_errorMsg = null
_errorDetail = null
})
},
onPageFinished: (value) {
if (widget.onPageFinished != null) {
widget.onPageFinished!(value)
}
if (widget.canRefresh) {}
setState(() {
_loading = false
_errorMsg = null
_errorDetail = null
})
_controller?.future.then((value) {
value.currentUrl().then((url) {
print("web_finished: $url")
})
})
},
)
return Stack(
children: [
Container(
width: widget.width <= 0
? MediaQuery.of(context).size.width
: widget.width,
height: widget.height <= 0
? MediaQuery.of(context).size.height
: widget.height,
child: webView,
),
Visibility(
visible: _loading,
child: Container(
color: Colors.white,
child: Center(
child: CupertinoActivityIndicator(),
),
)),
Visibility(
visible: _errorMsg != null,
child: Container(
color: Colors.white,
width: MediaQuery.of(context).size.width,
height: MediaQuery.of(context).size.height,
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Text(_errorMsg ?? ''),
RaisedButton(
onPressed: () async {
var webViewController = await _controller?.future;
var url = await webViewController?.currentUrl();
webViewController?.loadUrl(url ?? widget.initialUrl);
setState(() {
_loading = true;
_errorMsg = null;
_errorDetail = null;
});
},
child: Text('重新加载'),
),
Visibility(
visible: _errorDetail != null,
child: TextButton(
onPressed: () {
Clipboard.setData(ClipboardData(text: _errorDetail));
},
child: Text('复制错误信息'),
),
),
],
),
)),
Positioned(
right: 20,
bottom: 80,
child: Visibility(
visible: widget.canRefresh,
child: FloatingActionButton(
mini: true,
onPressed: () {
_controller?.future.then((value) {
if (null != widget.onRefreshByBtn) {
widget.onRefreshByBtn!(value)
} else {
value.reload()
}
})
},
child: Icon(Icons.refresh),
),
))
],
)
}
}
JavascriptBridge
import 'dart:convert';
import 'package:webview_flutter/webview_flutter.dart';
typedef JavascriptMessageReceiver = void Function(BridgeData data);
class JavascriptBridge {
Map<String, JavascriptChannel> _channels = {};
Map<String, JavascriptChannel> get channels => _channels;
void add(String name, JavascriptMessageReceiver receiver,
{bool parse = true}) {
assert(name != null && receiver != null);
if (_channels.containsKey(name)) {
return;
}
_channels[name] = JavascriptChannel(
name: name,
onMessageReceived: (message) {
if (parse) {
try {
if (message.message != null && message.message.isNotEmpty) {
receiver(_parseJSMessage(message));
return;
}
} catch (e, s) {
print(e.toString());
print(s.toString());
}
receiver(BridgeData());
} else {
receiver(BridgeData(data: message.message));
}
},
);
}
BridgeData _parseJSMessage(JavascriptMessage message) {
print('Parse JavascriptMessage=>${message.message}');
var object = json.decode(message.message);
if (object['data'] != null &&
object['data'] is Map &&
object['data']['bridgeName'] != null) {
return BridgeData.fromJson(object['data']);
}
return BridgeData.fromJson(object);
}
}
class BridgeData {
String? bridgeName;
String? callbackKey;
dynamic data;
BridgeData({this.bridgeName, this.callbackKey, this.data});
BridgeData.fromJson(Map<String, dynamic> json) {
bridgeName = json['bridgeName'];
callbackKey = json['callbackKey'];
data = json['data'];
}
Map<String, dynamic> toJson() {
final Map<String, dynamic> data = new Map<String, dynamic>();
data['bridgeName'] = this.bridgeName;
data['callbackKey'] = this.callbackKey;
data['data'] = this.data;
return data;
}
}
工具类
class StringUtils {
static isDouble(String str) {
print("${double.tryParse(str)}");
return null != double.tryParse(str);
}
static isNotDouble(String str) {
return !isDouble(str);
}
static double stringToDouble(String str) {
if (isDouble(str)) {
return double.parse(str);
} else {
return 0;
}
}
}
3.使用
import 'package:flutter/material.dart';
import 'package:uilib/webview/web_view_widget.dart';
void main() {
runApp(MyApp());
}
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'WebView',
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: MyHomePage(title: 'Flutter Demo Home Page'),
);
}
}
class MyHomePage extends StatefulWidget {
MyHomePage({Key? key, required this.title}) : super(key: key);
final String title;
@override
_MyHomePageState createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text(widget.title),
),
body: WebViewWidget(
initialUrl: "https://www.baidu.com",
canRefresh: true,
));
}
}