flutter window Pc 项目 富文本编辑器 踩坑
富文本编辑器实现思路
- 嵌入Html网页
- js 方法注入 与原生交互
- 保存编辑 html 代码块 完成编辑
导入webview_windows pub 包
dependencies:
# web view
webview_windows:^0.1.6
#pub.dev webview_windows 0.1.6
Example:
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'dart:async';
import 'package:webview_windows/webview_windows.dart';
final navigatorKey = GlobalKey<NavigatorState>();
void main() {
runApp(MyApp());
}
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(navigatorKey: navigatorKey, home: ExampleBrowser());
}
}
class ExampleBrowser extends StatefulWidget {
@override
State<ExampleBrowser> createState() => _ExampleBrowser();
}
class _ExampleBrowser extends State<ExampleBrowser> {
final _controller = WebviewController();
final _textController = TextEditingController();
bool _isWebviewSuspended = false;
@override
void initState() {
super.initState();
initPlatformState();
}
Future<void> initPlatformState() async {
// Optionally initialize the webview environment using
// a custom user data directory
// and/or a custom browser executable directory
// and/or custom chromium command line flags
//await WebviewController.initializeEnvironment(
// additionalArguments: '--show-fps-counter');
try {
///初始化webViewController
await _controller.initialize();
///设置Url监听
///url 改变会有监听回调
_controller.url.listen((url) {
_textController.text = url;
});
await _controller.setBackgroundColor(Colors.transparent);
await _controller.setPopupWindowPolicy(WebviewPopupWindowPolicy.deny);
///加载网页地址
await _controller.loadUrl('https://flutter.cn');
if (!mounted) return;
setState(() {});
} on PlatformException catch (e) {
WidgetsBinding.instance?.addPostFrameCallback((_) {
showDialog(
context: context,
builder: (_) => AlertDialog(
title: Text('Error'),
content: Column(
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text('Code: ${e.code}'),
Text('Message: ${e.message}'),
],
),
actions: [
TextButton(
child: Text('Continue'),
onPressed: () {
Navigator.of(context).pop();
},
)
],
));
});
}
}
Widget compositeView() {
if (!_controller.value.isInitialized) {
return const Text(
'Not Initialized',
style: TextStyle(
fontSize: 24.0,
fontWeight: FontWeight.w900,
),
);
} else {
return Padding(
padding: EdgeInsets.all(20),
child: Column(
children: [
Card(
elevation: 0,
child: TextField(
decoration: InputDecoration(
hintText: 'URL',
contentPadding: EdgeInsets.all(10.0),
suffixIcon: IconButton(
icon: Icon(Icons.refresh),
onPressed: () {
_controller.reload();
},
)),
textAlignVertical: TextAlignVertical.center,
controller: _textController,
onSubmitted: (val) {
_controller.loadUrl(val);
},
),
),
Expanded(
child: Card(
color: Colors.transparent,
elevation: 0,
clipBehavior: Clip.antiAliasWithSaveLayer,
child: Stack(
children: [
Webview(
_controller,
permissionRequested: _onPermissionRequested,
),
StreamBuilder<LoadingState>(
stream: _controller.loadingState,
builder: (context, snapshot) {
if (snapshot.hasData &&
snapshot.data == LoadingState.loading) {
return LinearProgressIndicator();
} else {
return SizedBox();
}
}),
],
))),
],
),
);
}
}
@override
Widget build(BuildContext context) {
return Scaffold(
floatingActionButton: FloatingActionButton(
tooltip: _isWebviewSuspended ? 'Resume webview' : 'Suspend webview',
onPressed: () async {
if (_isWebviewSuspended) {
await _controller.resume();
} else {
await _controller.suspend();
}
setState(() {
_isWebviewSuspended = !_isWebviewSuspended;
});
},
child: Icon(_isWebviewSuspended ? Icons.play_arrow : Icons.pause),
),
appBar: AppBar(
title: StreamBuilder<String>(
stream: _controller.title,
builder: (context, snapshot) {
return Text(
snapshot.hasData ? snapshot.data! : 'WebView (Windows) Example');
},
)),
body: Center(
child: compositeView(),
),
);
}
Future<WebviewPermissionDecision> _onPermissionRequested(
String url, WebviewPermissionKind kind, bool isUserInitiated) async {
final decision = await showDialog<WebviewPermissionDecision>(
context: navigatorKey.currentContext!,
builder: (BuildContext context) => AlertDialog(
title: const Text('WebView permission requested'),
content: Text('WebView has requested permission \'$kind\''),
actions: <Widget>[
TextButton(
onPressed: () =>
Navigator.pop(context, WebviewPermissionDecision.deny),
child: const Text('Deny'),
),
TextButton(
onPressed: () =>
Navigator.pop(context, WebviewPermissionDecision.allow),
child: const Text('Allow'),
),
],
),
);
return decision ?? WebviewPermissionDecision.none;
}
}
js 方法调用
/// xxxx html 标签字符串
await _webViewController.executeScript('${"javascript:setEditor('${xxxxx}')"}');
///举个调用方法的例子
await _webViewController.executeScript('${"javascript:setFont('${xxxxx}')"}');
///获取 html 编辑内容
var msgChannel = teacherPlan?.getMsgChannel();
var result = await msgChannel?.invokeMethod(
'executeScript', '${"javascript:getEditor()"}');
var map = json.decode(result);
str = map['Content'];
webview_windows0.1.6 一些问题
- executeScript 没有返回值
- webview2 executeScript PCWSTR 没有上层调
/// Executes the given [script].
Future<void> executeScript(String script) async {
if (_isDisposed) {
return;
}
assert(value.isInitialized);
return _methodChannel.invokeMethod('executeScript', script);
}
void Webview::ExecuteScript(const std::string& script,
ScriptExecutedCallback callback) {
if (IsValid()) {
if (SUCCEEDED(webview_->ExecuteScript(
get_utf16(script, CP_UTF8).c_str(),
Callback<ICoreWebView2ExecuteScriptCompletedHandler>(
[callback](HRESULT result, PCWSTR _) {
callback(SUCCEEDED(result));
return S_OK;
})
.Get()))) {
return;
}
}
callback(false);
}
webview.cc
void Webview::ExecuteScript(const std::string& script,
ScriptExecutedCallback callback) {
if (IsValid()) {
webview_->ExecuteScript(
get_utf16(script, CP_UTF8).c_str(),
Callback<ICoreWebView2ExecuteScriptCompletedHandler>(
[callback](HRESULT error, PCWSTR result) {
const std::string st = wide_to_utf8(std::wstring(result));
// std::cout << st << std::endl;
callback(SUCCEEDED(error), st);
return S_OK;
})
.Get());
return;
}
callback(false,"");
}
webview_bridge.cc
// executeScript: string
if (method_name.compare(kMethodExecuteScript) == 0) {
if (const auto script = std::get_if<std::string>(method_call.arguments())) {
std::shared_ptr<flutter::MethodResult<flutter::EncodableValue>>
shared_result = std::move(result);
webview_->ExecuteScript(*script, [shared_result](bool success, std::string json) {
if (success) {
/*std::cout<<json<<std::endl;*/
shared_result->Success(json);
} else {
shared_result->Error(kScriptFailed, "Executing script failed.");
}
});
return;
}
return result->Error(kErrorInvalidArgs);
}