Open SaulSDS opened 1 year ago
The same issue here
I'm facing the same issue, with the allowDuplicates
param kinda worked but i dont think that is the best way to solve this problem
You can use this as a workaround, e.g. three different controllers, if it is possible in the implementation.
final MobileScannerController _cameraController1 = MobileScannerController();
final MobileScannerController _cameraController2 = MobileScannerController();
final MobileScannerController _cameraController3 = MobileScannerController();
int cameraOpenCount = 1;
cameraOpenCount == 1 ? MobileScanner(
controller: _cameraController1,
onDetect: (capture) {})
: cameraOpenCount == 2
? MobileScanner(
controller:
_cameraController2,
onDetect: (capture) {})
: MobileScanner(
controller:
_cameraController3,
onDetect: (capture) {}),
),
),
),
...
GestureDetector(
onTap: () {
if (cameraOpenCount == 1) {
_cameraController1.stop().then((_) =>
_cameraController1.dispose());
setState(() {
cameraOpenCount = 2;
});
} else {
_cameraController2.stop().then((_) =>
_cameraController2.dispose());
setState(() {
cameraOpenCount = 3;
});
}
This code allows 3 camera starts. Just add more if needed. Tested on reading QR-code and it works.
Same problem here when I stop and re-start the camera with the controller
Same problem here
same issue. Does any official maintainers give some solution?
when reopen and scan fails, always get Error:
W/System (13336): A resource failed to call release. I/BpBinder(13336): onLastStrongRef automatically unlinking death recipients:
It seems last MobileScannerController instance not destroyed successfully. May be that's the point why MobileScanner not work when reopen.
Here is my code
import 'dart:ui';
import 'package:flutter/material.dart';
import 'package:go_router/go_router.dart';
import 'package:medical_instrument_manage/screen_adaptive/int_extension.dart';
import 'package:mobile_scanner/mobile_scanner.dart';
import '../../constants.dart';
class ScanSign extends StatefulWidget {
const ScanSign({super.key});
@override
State<ScanSign> createState() => _ScanSignState();
}
class _ScanSignState extends State<ScanSign> with SingleTickerProviderStateMixin {
MobileScannerController cameraController = MobileScannerController(
detectionSpeed: DetectionSpeed.noDuplicates,
);
// ### 1. **AnimationController Statement**
late AnimationController _controller;
late Animation<double> _animation;
String showTitle = "扫码签名";
@override
void initState() {
// TODO: implement initState
super.initState();
// ### 2. **AnimationController Initialization**
// The AnimationController is initialized in the _ScannerAnimationState class. It controls the animation, including its duration and repeating behavior. The vsync parameter prevents off-screen animations from consuming unnecessary resources.
_controller = AnimationController(
duration: const Duration(seconds: 3),
vsync: this,
);
// ### 3. **Tween and Animation**
// A Tween defines the range between the starting and ending points of the animation. In this case, the animation moves the line from 0.0 (top) to 1.0 (bottom) relative to the container's height. The Tween is animated by the AnimationController .
_animation = Tween<double>(begin: 0.0, end: 1.0).animate(_controller)
..addListener(() {
// ### 8. **Rebuilding the Widget with setState**
// The addListener callback attached to the animation triggers a call to setState every time the animation value changes, prompting the widget to rebuild with the new line position.
setState(() {});
})
..addStatusListener((status) {
// ### 4. **Listening to Animation Status**
// The addStatusListener listens to the animation status. When the animation completes, it resets the AnimationController and starts the animation again, creating a loop. This is crucial for achieving the continuous scanning effect.
if (status == AnimationStatus.completed) {
_controller.reset();
_controller.forward();
}
});
// ### 6. **Starting the Animation**
// The animation is started by calling .forward() on the AnimationController from within the initState method. This begins the animation when the widget is inserted into the widget tree.
_controller.forward();
}
@override
void dispose() {
cameraController.dispose();
// ### 7. **Disposing of the Controller**
// To avoid memory leaks and ensure resources are released when the widget is destroyed, the AnimationController is disposed of in the dispose method.
_controller.dispose();
super.dispose();
}
@override
Widget build(BuildContext context) {
dynamic appBar = AppBar(
centerTitle: true,
title: Text(showTitle,
style: const TextStyle(
// fontSize: 32.rpx,
color: Color(0xFF333333),
fontWeight: FontWeight.bold)),
elevation: 0,
backgroundColor: Colors.white,
leading: IconButton(
// iconSize: 32.rpx,
onPressed: () {
Navigator.pop(context);
},
icon: const Icon(
// size: 32.rpx,
color: Color(0xFF000000),
Icons.arrow_back_ios),),
actions: [
IconButton(
color: Colors.white,
icon: ValueListenableBuilder(
valueListenable: cameraController.torchState,
builder: (context, state, child) {
switch (state) {
case TorchState.off:
return const Icon(Icons.flash_off, color: Colors.grey);
case TorchState.on:
return const Icon(Icons.flash_on, color: Colors.yellow);
}
},
),
// iconSize: 32.0,
onPressed: () => cameraController.toggleTorch(),
),
],
);
return Scaffold(
appBar: appBar,
body: LayoutBuilder(builder: (BuildContext context, BoxConstraints constraints) {
return Stack(
alignment: Alignment.center,
children: [
MobileScanner(
scanWindow: Rect.fromCenter(
center: Offset(constraints.maxWidth/2,constraints.maxHeight/2),
width: 650.rpx,
height: 650.rpx,
),
fit: BoxFit.contain,
controller: cameraController,
onDetect: (capture) {
final List<Barcode> barcodes = capture.barcodes;
debugPrint('皮蛋守护!');
for (final barcode in barcodes) {
debugPrint('皮蛋守护!Barcode found! ${barcode.rawValue}');
cameraController.stop();
cameraController.dispose();
Future.delayed(const Duration(seconds:1), () {
context.pushReplacement(Uri(path: "/sign-name", queryParameters: {
"code":barcode.rawValue,
}).toString());
});
}
},
),
ClipPath(
clipper: RectClipper(),
child: BackdropFilter(
filter: ImageFilter.blur(sigmaX: 2.5, sigmaY: 2.5),
child: CustomPaint(
painter: ScannerLinePainter(_animation.value),
child: Container(
decoration: BoxDecoration(
color: Colors.black.withOpacity(0.25),
),
),
),
/*child: Container(
decoration: BoxDecoration(
color: Colors.black.withOpacity(0.5),
),
),*/
),
),
]
);
},),
);
}
}
class RectClipper extends CustomClipper<Path> {
@override
Path getClip(Size size) {
var path = Path();
var rect = Rect.fromCenter(center: Offset(size.width / 2, size.height / 2), width: 650.rpx, height: 650.rpx);
path.addRect(Rect.fromLTRB(0, 0, size.width, size.height));
path.addRect(rect);
path.fillType = PathFillType.evenOdd;
return path;
}
@override
bool shouldReclip(CustomClipper<Path> oldClipper) => false;
}
// ### 5. **CustomPainter for Drawing**
// The ScannerLinePainter class, which extends CustomPainter , is used to draw the horizontal line on the canvas. It takes the current animation value ( position ) to determine the line's vertical position within the container.
class ScannerLinePainter extends CustomPainter {
final double position;
ScannerLinePainter(this.position);
@override
void paint(Canvas canvas, Size size) {
const int linesCount = 10; // Number of lines to create the splash effect
final paint = Paint()
..color = Colors.green.withOpacity(0.5) // Starting color of the lines
..strokeWidth = 2; // Thickness of the lines
for (int i = 0; i < linesCount; i++) {
// Calculate the opacity for each line based on its index
final opacity = (1 - (i / linesCount)).clamp(0.0, 1.0);
paint.color = paint.color.withOpacity(opacity);
// Calculate the vertical position for each line
final yOffset = size.height * position + (i * 5.0) - (linesCount * 2.5); // Adjust the multiplier for spacing
// Ensure the lines are drawn within the container
if (yOffset >= 0 && yOffset <= size.height) {
canvas.drawLine(
Offset(0, yOffset),
Offset(size.width, yOffset),
paint,
);
}
}
}
@override
bool shouldRepaint(covariant CustomPainter oldDelegate) => true;
}
Hi. I suggest that you create a new StatefulWidget that will manage the MobileScanner. This widget will handle the scanner initialization, barcode detection, and any other related tasks.
In your code you can use it for example like this: bool _showMobileScanner = true/false; _showMobileScanner ? MobileScannerWidget() : Container(),
This way MobileScanner gets initialized and disposed properly every time and there will be no errors.
I updated mobile scanner to 5.00. That works well.
The first time the scan screen is opened it works fine, but after i close it and open it again it stops detecting codes.
I'm trying disposing the controller and starting it on initstate
These are the logs when opening the scan screen for the second time.