Flutter 扫码
2021-05-25 本文已影响0人
李小轰
项目要求使用flutter实现扫码能力,不想自己封装插件能力,于是我们找到了一些使用率较高三方库,如下:
- flutter_qr_reader 1.0.3(只支持二维码,不支持条形码,支持本地图片) 1.0.5(要求flutter2.0以上,支持空安全)
- qr_code_scanner 0.3.5 (不支持文件【相册】)
- flutter_barcode_scanner (需要google服务)
根据项目情况,我们选择使用 qr_code_scanner 。
首先,pubspec.yaml 中添加:
qr_code_scanner: ^0.3.5
下一步,封装我们的UI层,创建 scan_code_page.dart 文件:
import 'dart:async';
import 'package:flutter/material.dart';
import 'package:qr_code_scanner/qr_code_scanner.dart';
class ScanCodePage extends StatefulWidget {
@override
_ScanCodePageState createState() => _ScanCodePageState();
}
class _ScanCodePageState extends State<ScanCodePage>
with TickerProviderStateMixin {
AnimationController _animationController;
bool openFlashlight;
Timer _timer;
QRViewController controller;
final GlobalKey qrKey = GlobalKey(debugLabel: 'QR');
StreamSubscription subscription;
@override
void initState() {
super.initState();
openFlashlight = false;
_initAnimation();
}
@override
void dispose() {
controller?.dispose();
_clearAnimation();
super.dispose();
}
void _upState() {
setState(() {});
}
void _clearAnimation() {
_timer?.cancel();
if (_animationController != null) {
_animationController?.dispose();
_animationController = null;
}
}
void _initAnimation() {
setState(() {
_animationController = AnimationController(
vsync: this, duration: Duration(milliseconds: 1000));
});
_animationController
..addListener(_upState)
..addStatusListener((state) {
if (state == AnimationStatus.completed) {
_timer = Timer(Duration(seconds: 1), () {
_animationController?.reverse(from: 1.0);
});
} else if (state == AnimationStatus.dismissed) {
_timer = Timer(Duration(seconds: 1), () {
_animationController?.forward(from: 0.0);
});
}
});
_animationController.forward(from: 0.0);
}
Future setFlashlight() async {
await controller?.toggleFlash();
openFlashlight = await controller?.getFlashStatus();
_upState();
}
@override
Widget build(BuildContext context) {
return Scaffold(
body: Material(
color: Colors.black,
child: LayoutBuilder(builder: (context, constraints) {
final qrScanSize = constraints.maxWidth * 0.75;
return Stack(
children: <Widget>[
QRView(
key: qrKey,
onQRViewCreated: _onQRViewCreated,
),
Column(
crossAxisAlignment: CrossAxisAlignment.center,
children: [
AppBar(
leading: BackButton(),
elevation: 0,
backgroundColor: Colors.transparent,
),
SizedBox(
height: 100,
),
Container(
padding: EdgeInsets.all(20),
child: SizedBox(
width: qrScanSize,
height: qrScanSize,
child: Stack(
children: [
CustomPaint(
painter: QrScanBoxPainter(
boxLineColor: Colors.lightGreenAccent,
animationValue: _animationController?.value ?? 0,
isForward: _animationController?.status ==
AnimationStatus.forward,
),
child: Container(),
),
Positioned(
bottom: 10,
width: qrScanSize,
child: Align(
alignment: Alignment.bottomCenter,
child: GestureDetector(
behavior: HitTestBehavior.translucent,
onTap: setFlashlight,
child: Image(
image: AssetImage(openFlashlight
? 'assets/tool_flashlight_close.png'
: 'assets/tool_flashlight_open.png'),
width: 35,
height: 35,
color: Colors.white,
),
),
),
),
],
),
),
),
Text(
"请将二维码/条形码置于方框中",
style: TextStyle(color: Colors.white),
),
],
)
],
);
}),
),
);
}
void _onQRViewCreated(QRViewController controller) {
setState(() {
this.controller = controller;
});
if (subscription == null) {
subscription = controller.scannedDataStream.listen((scanData) {
//保证只接收一次数据
subscription.cancel();
Navigator.of(context).pop(scanData.code);
});
}
}
}
class QrScanBoxPainter extends CustomPainter {
final double animationValue;
final bool isForward;
final Color boxLineColor;
QrScanBoxPainter(
{@required this.animationValue,
@required this.isForward,
this.boxLineColor})
: assert(animationValue != null),
assert(isForward != null);
@override
void paint(Canvas canvas, Size size) {
final borderRadius = BorderRadius.all(Radius.circular(12)).toRRect(
Rect.fromLTWH(0, 0, size.width, size.height),
);
canvas.drawRRect(
borderRadius,
Paint()
..color = Colors.white54
..style = PaintingStyle.stroke
..strokeWidth = 1,
);
final borderPaint = Paint()
..color = Colors.greenAccent
..style = PaintingStyle.stroke
..strokeWidth = 2;
final path = new Path();
// leftTop
path.moveTo(0, 50);
path.lineTo(0, 12);
path.quadraticBezierTo(0, 0, 12, 0);
path.lineTo(50, 0);
// rightTop
path.moveTo(size.width - 50, 0);
path.lineTo(size.width - 12, 0);
path.quadraticBezierTo(size.width, 0, size.width, 12);
path.lineTo(size.width, 50);
// rightBottom
path.moveTo(size.width, size.height - 50);
path.lineTo(size.width, size.height - 12);
path.quadraticBezierTo(
size.width, size.height, size.width - 12, size.height);
path.lineTo(size.width - 50, size.height);
// leftBottom
path.moveTo(50, size.height);
path.lineTo(12, size.height);
path.quadraticBezierTo(0, size.height, 0, size.height - 12);
path.lineTo(0, size.height - 50);
canvas.drawPath(path, borderPaint);
canvas.clipRRect(
BorderRadius.all(Radius.circular(12)).toRRect(Offset.zero & size));
// 绘制横向网格
final linePaint = Paint();
final lineSize = size.height * 0.45;
final leftPress = (size.height + lineSize) * animationValue - lineSize;
linePaint.style = PaintingStyle.stroke;
linePaint.shader = LinearGradient(
colors: [Colors.transparent, boxLineColor],
begin: isForward ? Alignment.topCenter : Alignment(0.0, 2.0),
end: isForward ? Alignment(0.0, 0.5) : Alignment.topCenter,
).createShader(Rect.fromLTWH(0, leftPress, size.width, lineSize));
for (int i = 0; i < size.height / 5; i++) {
canvas.drawLine(
Offset(
i * 5.0,
leftPress,
),
Offset(i * 5.0, leftPress + lineSize),
linePaint,
);
}
for (int i = 0; i < lineSize / 5; i++) {
canvas.drawLine(
Offset(0, leftPress + i * 5.0),
Offset(
size.width,
leftPress + i * 5.0,
),
linePaint,
);
}
}
@override
bool shouldRepaint(QrScanBoxPainter oldDelegate) =>
animationValue != oldDelegate.animationValue;
@override
bool shouldRebuildSemantics(QrScanBoxPainter oldDelegate) =>
animationValue != oldDelegate.animationValue;
}
以上代码copy可直接使用,UI显示如下:
image.png使用方法:
//打开扫一扫
void _openScan() {
Navigator.of(context).push(MaterialPageRoute(builder: (context) {
return ScanCodePage();
})).then((value) {
print(value); //拿到扫描结果
});
}