Airprint MFPS(多功能打印机)是与Apple Airprint技术兼容的打印机。 ESCL(可扩展的扫描仪控制语言)协议是一种无人驾驶扫描协议,是Airprint技术的一部分。它促进了Airprint MFP上的无线扫描功能。用户可以在同一网络上发现Airprint MFP,并使用其移动设备启动扫描操作。本文着重于构建一个使用Web视图和本机代码从Airprint MFPS扫描文档的混合动力Android应用程序。
支持Airprint的MFP列表
- 佳能Imageclass MF743CDW
- HP办公室250
- HP办公室3830
- 佳能Pixma IX6820
- HP OfficeJet Pro 9025E
- 佳能Pixma TR8620 li>
- pixma tr150
先决条件
- 与Android设备同一网络中的空气打击设备
- 从Google Play安装Dynamsoft Service。
免费的在线文档扫描演示
DynamSoft Service应用程序是一种Android背景服务,可在Web浏览器和无线扫描仪之间提供通信通道。
您可以单击“ Online Demo
”按钮以在移动Web浏览器中启动免费的online document scanning demo。该演示还与 Windows , linux 和 macOS 系统兼容,通过安装适当的DynamSoft服务。对于基本的文档扫描需求,在线演示就足够了。但是,如果您需要具有高级功能的更强大的文档扫描解决方案,请考虑使用Dynamic Web TWAIN SDK进行编码,该Dynamic Web TWAIN SDK提供30-day free trial。
为什么要创建混合应用程序而不是Web浏览器?
在Web浏览器中使用在线演示时,保存和共享文档可能会带来不便。混合应用程序提供了更好的用户体验。通过将演示加载到Web视图中,混合应用程序提供了Web技术和本机功能的无缝集成。通过使用本机代码,该应用程序可以将扫描的图像直接存储到本地存储中,从而为用户提供更高效,更灵活的处理选项。 Web和本地功能的这种组合确保了更光滑,更易于用户友好的文档扫描体验。
用颤音构建Android文档扫描应用程序
在以下各节中,我们将向您展示如何使用Flutter WebView加载在线文档扫描演示以及如何组合JavaScript和Dart代码以处理扫描的图像。
依赖的扑面包
入门之前,您需要将以下软件包添加到pubspec.yaml
文件:
- webview_flutter-一个提供WebView窗口小部件的颤音插件。
- permission_handler-一个弹奏插件,用于在Android和iOS上要求权限。
- webview_flutter_android-一个在Android上提供WebView小部件的颤音插件。
- url_launcher-用于在移动平台上启动URL的颤音插件。
- android_intent_plus-用于启动Android意图的颤音插件。
- path_provider-一个用于在文件系统上查找常用位置的颤音插件。
- share_plus-一个用于通过平台共享UI共享内容的扑面插件。
Android许可
该应用程序需要两个权限:
<uses-permission android:name="android.permission.CAMERA"/>
<uses-permission android:name="android.permission.QUERY_ALL_PACKAGES" />
-
android.permission.CAMERA
-访问网络视图中的相机所需的必需。 -
android.permission.QUERY_ALL_PACKAGES
-启动外部Dynamsoft服务应用所需
Flutter Web视图:加载URL,并处理导航和权限请求
根据 WebView_flutter 软件包的示例代码,可以将在线文档扫描演示加载到Android Web视图中:
_controller = WebViewController()
..setJavaScriptMode(JavaScriptMode.unrestricted)
..setBackgroundColor(const Color(0x00000000))
..setNavigationDelegate(
NavigationDelegate(
onProgress: (int progress) {},
onPageStarted: (String url) {},
onPageFinished: (String url) {},
onWebResourceError: (WebResourceError error) {},
onNavigationRequest: (NavigationRequest request) {
return NavigationDecision.navigate;
},
),
)
..loadRequest(Uri.parse('https://demo3.dynamsoft.com/web-twain/mobile-online-camera-scanner/'));
如果在线演示无法连接到背景DynamSoft服务,它将提示您安装它:
使用Android Web浏览器时,只需单击Open Service
按钮即可安装或启动DynamSoft Service应用程序。但是,在Web视图中操作时,您可能会遇到404页。要解决此问题,我们需要在onNavigationRequest
回调中处理该请求。 DynamSoft服务活动可以由其package
和componentName
启动。如果设备未安装DynamSoft Service应用程序,我们可以通过打开Google Play商店采取适当的操作。
Future<void> launchURL(String url) async {
await launchUrlString(url);
}
Future<void> launchIntent() async {
if (Platform.isAndroid) {
try {
AndroidIntent intent = const AndroidIntent(
componentName: 'com.dynamsoft.mobilescan.MainActivity',
package: 'com.dynamsoft.mobilescan',
);
await intent.launch();
} catch (e) {
// If the app is not installed, open the Google Play store to install the app.
launchURL(
'https://play.google.com/store/apps/details?id=com.dynamsoft.mobilescan');
}
}
}
onNavigationRequest: (NavigationRequest request) {
if (request.url.startsWith('intent://')) {
launchIntent();
return NavigationDecision.prevent;
} else if (request.url.endsWith('.apk')) {
launchURL(
'https://play.google.com/store/apps/details?id=com.dynamsoft.mobilescan');
return NavigationDecision.prevent;
}
return NavigationDecision.navigate;
},
您可能会遇到的另一个问题是Android Web视图中的相机访问。在线演示使您可以捕获扫描仪和相机的文档。默认情况下,在Android Web视图中禁用了相机访问。您必须在本机代码中授予摄像机权限。
解决方法是处理onPermissionRequest
回调中的WebViewPermissionRequest
。我们可以如下更改WebViewController
创建代码:
Future<void> requestCameraPermission() async {
final status = await Permission.camera.request();
if (status == PermissionStatus.granted) {
} else if (status == PermissionStatus.denied) {
} else if (status == PermissionStatus.permanentlyDenied) {
}
}
@override
void initState() {
super.initState();
late final PlatformWebViewControllerCreationParams params;
params = const PlatformWebViewControllerCreationParams();
_controller = WebViewController.fromPlatformCreationParams(
params,
onPermissionRequest: (WebViewPermissionRequest request) {
request.grant();
},
)
..setJavaScriptMode(JavaScriptMode.unrestricted)
...
requestCameraPermission();
}
现在,演示应在扑动网络视图中按预期工作。下一步是通过添加一些飞镖代码来优化用户体验。
扑面选项卡栏
与原始在线演示的UI相比,您可能已经观察到我们已经通过隐藏了HTML5中实现的About
和Contact Us
选项卡进行了一些更改。
在应用程序的底部添加了一个用于切换Web视图和其他本机视图的flutter选项卡栏。
如何将HTML元素隐藏在JavaScript中?
-
在Web视图中启用
Debugging
选项。
if (_controller.platform is AndroidWebViewController) { AndroidWebViewController.enableDebugging(true); }
-
在边缘或Chrome中打开
edge://inspect/#devices
或chrome://inspect/#devices
以检查HTML元素。 -
在控制台中,执行以下JavaScript代码以隐藏
About
和Contact Us
选项卡:
let parentElement = document.getElementsByClassName('dcs-main-footer')[0]; let tags = parentElement .getElementsByTagName('div'); tags[0].remove(); tags[6].remove();
此外,隐藏结果面板,该面板由本地历史记录页面代替:
document.getElementsByClassName('dcs-main-content')[0].style.display = 'none';
-
使用
onProgress
回调执行JavaScript代码:
NavigationDelegate( onProgress: (int progress) { if (progress == 100) { String jscode = ''' let parentElement = document.getElementsByClassName('dcs-main-footer')[0]; let tags = parentElement .getElementsByTagName('div'); tags[0].remove(); tags[6].remove(); document.getElementsByClassName('dcs-main-content')[0].style.display = 'none'; '''; _controller .runJavaScript(jscode) .then((value) => {setState(() {})}); } }, )
如何在颤音中创建一个标签栏?
class _MyAppPageState extends State<MyAppPage>
with SingleTickerProviderStateMixin {
late TabController _tabController;
@override
void initState() {
super.initState();
_tabController = TabController(vsync: this, length: 3);
...
}
@override
Widget build(BuildContext context) {
return Scaffold(
body: TabBarView(
controller: _tabController,
children: [
HomeView(title: 'Web TWAIN Demo', controller: _controller),
const HistoryView(title: 'History'),
const AboutView(title: 'About the SDK'),
],
),
bottomNavigationBar: TabBar(
labelColor: Colors.blue,
controller: _tabController,
tabs: const [
Tab(icon: Icon(Icons.home), text: 'Home'),
Tab(icon: Icon(Icons.history_sharp), text: 'History'),
Tab(icon: Icon(Icons.info), text: 'About'),
],
),
);
}
}
将base64图像从Web视图保存到本地存储
作为在Web视图中获取文档,可以将图像数据检索为base64字符串。
DWObject.ConvertToBase64([DWObject.CurrentImageIndexInBuffer], 1, (result, indices, type) =>{})
我们使用addJavaScriptChannel
方法在Web视图和本机代码之间添加了通信通道。
_controller = WebViewController.fromPlatformCreationParams(
params,
onPermissionRequest: (WebViewPermissionRequest request) {
request.grant();
},
)
..addJavaScriptChannel(
'ImageData',
onMessageReceived: (JavaScriptMessage message) {
saveImage(message.message).then((value) {
showToast(value);
});
},
)
然后,JavaScript代码可以致电postMessage
将消息发送到DART代码。当JavaScript映像缓冲区没有空时,我们创建一个按钮来触发图像保存操作。接收到消息时调用onMessageReceived
回调。
IconButton(
icon: const Icon(Icons.save),
onPressed: () async {
widget.controller.runJavaScript(
'DWObject.ConvertToBase64([DWObject.CurrentImageIndexInBuffer], 1, (result, indices, type) =>{ImageData.postMessage(result._content)})');
},
),
如何将base64字符串保存到flutter中的本地文件?
String getImageName() {
// Get the current date and time.
DateTime now = DateTime.now();
// Format the date and time to create a timestamp.
String timestamp =
'${now.year}${now.month}${now.day}_${now.hour}${now.minute}${now.second}';
// Create the image file name with the timestamp.
String imageName = 'image_$timestamp.jpg';
return imageName;
}
Future<String> saveImage(String base64String) async {
Uint8List bytes = base64Decode(base64String);
// Get the app directory
final directory = await getApplicationDocumentsDirectory();
// Create the file path
String imageName = getImageName();
final filePath = '${directory.path}/$imageName';
// Write the bytes to the file
await File(filePath).writeAsBytes(bytes);
return filePath;
}
使用特定于平台的代码实现了showToast()
方法:
-
在
MainActivity.kt
中添加以下Kotlin代码:
override fun configureFlutterEngine(flutterEngine: FlutterEngine) { super.configureFlutterEngine(flutterEngine) MethodChannel(flutterEngine.dartExecutor.binaryMessenger, "AndroidNative") .setMethodCallHandler { call, result -> when (call.method) { "showToast" -> { val message = call.argument<String>("message") showToast(message!!) result.success(null) } else -> { result.notImplemented() } } } } private fun showToast(message: String) { Toast.makeText(this, message, Toast.LENGTH_SHORT).show() }
-
调用DART代码中的
showToast()
方法:
void showToast(String message) { const platform = MethodChannel('AndroidNative'); platform.invokeMethod('showToast', {'message': message}); }
查看并共享扫描的图像
将扫描的图像保存到本地存储后,我们可以获取文件列表如下:
Future<List<String>> getImages() async {
// Get the app directory
final directory = await getApplicationDocumentsDirectory();
// Get the list of files in the app directory
List<FileSystemEntity> files = directory.listSync();
// Get the file paths
List<String> filePaths = [];
for (FileSystemEntity file in files) {
if (file.path.endsWith('.jpg')) {
filePaths.add(file.path);
}
}
return filePaths;
}
要查看图像,我们创建了一个新的无状态小部件,并将文件路径传递为参数:
class DocumentView extends StatelessWidget {
final String filePath;
const DocumentView({super.key, required this.filePath});
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text('Document Viewer'),
),
body: Center(child: Image.file(File(filePath))),
);
}
}
可以与Share.shareXFiles()
方法共享图像文件:
IconButton(
icon: const Icon(Icons.share),
onPressed: () async {
if (selectedValue == -1) {
return;
}
await Share.shareXFiles([XFile(_results[selectedValue])],
text: 'Check out this image!');
},
),