我试图为Flutter应用制作指南针。
这个想法是将指南针的图像放在相机预览中。
结果似乎还可以:
首先,用户使用罗盘进行摄像机预览:
可以添加房间名称。
用户拍照时,会制作一个可以共享的屏幕截图:
即使看起来工作正常,Android Studio上也出现以下错误消息:
E/flutter (29454): [ERROR:flutter/lib/ui/ui_dart_state.cc(166)] Unhandled Exception: setState() called after dispose(): _CompassState#46249(lifecycle state: defunct, not mounted)
E/flutter (29454): This error happens if you call setState() on a State object for a widget that no longer appears in the widget tree (e.g., whose parent widget no longer includes the widget in its build). This error can occur when code calls setState() from a timer or an animation callback.
E/flutter (29454): The preferred solution is to cancel the timer or stop listening to the animation in the dispose() callback. Another solution is to check the "mounted" property of this object before calling setState() to ensure the object is still in the tree.
E/flutter (29454): This error might indicate a memory leak if setState() is being called because another object is retaining a reference to this State object after it has been removed from the tree. To avoid memory leaks, consider breaking the reference to this object during dispose().
E/flutter (29454): #0 State.setState.<anonymous closure> (package:flutter/src/widgets/framework.dart:1204:9)
E/flutter (29454): #1 State.setState (package:flutter/src/widgets/framework.dart:1239:6)
E/flutter (29454): #2 _CompassState._onData (package:franck_ehrhart/seller/compass.dart:28:29)
E/flutter (29454): #3 _rootRunUnary (dart:async/zone.dart:1198:47)
E/flutter (29454): #4 _CustomZone.runUnary (dart:async/zone.dart:1100:19)
E/flutter (29454): #5 _CustomZone.runUnaryGuarded (dart:async/zone.dart:1005:7)
E/flutter (29454): #6 _BufferingStreamSubscription._sendData (dart:async/stream_impl.dart:357:11)
E/flutter (29454): #7 _DelayedData.perform (dart:async/stream_impl.dart:611:14)
E/flutter (29454): #8 _StreamImplEvents.handleNext (dart:async/stream_impl.dart:730:11)
E/flutter (29454): #9 _PendingEvents.schedule.<anonymous closure> (dart:async/stream_impl.dart:687:7)
E/flutter (29454): #10 _rootRun (dart:async/zone.dart:1182:47)
E/flutter (29454): #11 _CustomZone.run (dart:async/zone.dart:1093:19)
E/flutter (29454): #12 _CustomZone.runGuarded (dart:async/zone.dart:997:7)
E/flutter (29454): #13 _CustomZone.bindCallbackGuarded.<anonymous closure> (dart:async/zone.dart:1037:23)
E/flutter (29454): #14 _rootRun (dart:async/zone.dart:1190:13)
E/flutter (29454): #15 _CustomZone.run (dart:async/zone.dart:1093:19)
E/flutter (29454): #16 _CustomZone.runGuarded (dart:async/zone.dart:997:7)
E/flutter (29454): #17 _CustomZone.bindCallbackGuarded.<anonymous closure> (dart:async/zone.dart:1037:23)
E/flutter (29454): #18 _microtaskLoop (dart:async/schedule_microtask.dart:41:21)
E/flutter (29454): #19 _startMicrotaskLoop (dart:async/schedule_microtask.dart:50:5)
E/flutter (29454):
这是指南针的源代码:
import 'dart:math';
import 'package:flutter/material.dart';
import 'package:flutter_compass/flutter_compass.dart';
class Compass extends StatefulWidget {
Compass({Key key}) : super(key: key);
@override
_CompassState createState() => _CompassState();
}
class _CompassState extends State<Compass> {
double _heading = 0;
String get _readout => _heading.toStringAsFixed(0) + '°';
@override
void initState() {
super.initState();
FlutterCompass.events.listen(_onData);
}
void _onData(double x) => setState(() { _heading = x; });
final TextStyle _style = TextStyle(
color: Colors.red,
fontSize: 32,
fontWeight: FontWeight.bold,
backgroundColor: Colors.white.withOpacity(0.5) ,
);
@override
Widget build(BuildContext context) {
return CustomPaint(
foregroundPainter: CompassPainter(angle: _heading),
child: Center(child: Text(_readout, style: _style))
);
}
}
class CompassPainter extends CustomPainter {
CompassPainter({ @required this.angle }) : super();
final double angle;
double get rotation => -2 * pi * (angle / 360);
Paint get _brush => new Paint()
..style = PaintingStyle.stroke
..strokeWidth = 4.0;
@override
void paint(Canvas canvas, Size size) {
Paint circle = _brush
..color = Colors.blue.shade800.withOpacity(0.6);
Paint needle = _brush
..color = Colors.lightBlueAccent;
double radius = min(size.width / 2.2, size.height / 2.2);
Offset center = Offset(size.width / 2, size.height / 2);
Offset start = Offset.lerp(Offset(center.dx, radius), center, .4);
Offset end = Offset.lerp(Offset(center.dx, radius), center, -2);
canvas.translate(center.dx, center.dy);
canvas.rotate(rotation);
canvas.translate(-center.dx, -center.dy);
canvas.drawLine(start, end, needle);
canvas.drawCircle(center, radius, circle);
}
@override
bool shouldRepaint(CustomPainter oldDelegate) => true;
}
import 'package:camera/camera.dart';
import 'package:flutter/cupertino.dart';
import 'package:path_provider/path_provider.dart';
import 'package:path/path.dart';
import'package:franck_ehrhart/camera/preview_compass.dart';
import 'dart:typed_data';
import 'dart:ui' as ui;
import 'package:flutter/rendering.dart';
import 'package:image_picker_saver/image_picker_saver.dart';
import 'package:flutter/services.dart';
class CompassTool extends StatefulWidget {
// Screenshot
CompassTool({Key key}) : super(key: key);
@override
_CompassToolState createState() => _CompassToolState();
}
class _CompassToolState extends State<CompassTool> {
// Screenshot
static GlobalKey screen = new GlobalKey();
// Camera
CameraController controller;
List cameras;
int selectedCameraIndex;
String imgPath;
String compassPath;
// Textinput name of room
TextEditingController nameController = TextEditingController();
String roomName = '';
@override
void initState() {
super.initState();
// Camera
availableCameras().then((availableCameras) {
cameras = availableCameras;
if (cameras.length > 0) {
setState(() {
selectedCameraIndex = 0;
});
_initCameraController(cameras[selectedCameraIndex]).then((void v) {});
} else {
print('No camera available');
}
}).catchError((err) {
print('Error :${err.code}Error message : ${err.message}');
});
}
Future _initCameraController(CameraDescription cameraDescription) async {
if (controller != null) {
await controller.dispose();
}
controller = CameraController(cameraDescription, ResolutionPreset.medium);
controller.addListener(() {
if (mounted) {
setState(() {});
}
if (controller.value.hasError) {
print('Camera error ${controller.value.errorDescription}');
}
});
try {
await controller.initialize();
} on CameraException catch (e) {
_showCameraException(e);
}
if (mounted) {
setState(() {});
}
}
@override
Widget build(BuildContext context) {
return Scaffold(
backgroundColor: Colors.blueAccent,
appBar: AppBar(
title: Text("ORIENTATION PIECES"),
centerTitle: true,
backgroundColor: Colors.blueAccent.shade700,
),
// body: Compass()
body:
Container(
child: SafeArea(
child: Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: <Widget>[
Expanded(
flex: 1,
child:
_cameraPreviewWidget(),
),
Align(
alignment: Alignment.bottomCenter,
child: Container(
height: 80,
width: double.infinity,
padding: EdgeInsets.all(15),
color: Colors.blueAccent,
child: Row(
mainAxisAlignment: MainAxisAlignment.start,
children: <Widget>[
_cameraToggleRowWidget(),
_cameraControlWidget(context),
Spacer()
],
),
),
),
Card(
margin: EdgeInsets.symmetric( horizontal: 10.0, vertical :5.0),
child: TextField(
controller: nameController,
decoration: InputDecoration(
hintText: 'Nom de la pièce',
border: OutlineInputBorder(),
labelText: 'Nom de la pièce',
),
onChanged: (text) {
setState(() {
roomName = text;
//you can access nameController in its scope to get
// the value of text entered as shown below
//fullName = nameController.text;
}
);
},
),
),
]
),
),
),
);
}
/// Display the control bar with buttons to take pictures
Widget _cameraControlWidget(context) {
return Expanded(
child: Align(
alignment: Alignment.center,
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
mainAxisSize: MainAxisSize.max,
children: <Widget>[
FloatingActionButton(
child: Icon(
Icons.camera,
color: Colors.black,
),
backgroundColor: Colors.white,
onPressed: () {
_onCapturePressed(context);
},
),
],
),
),
);
}
/// Display Camera preview.
Widget _cameraPreviewWidget() {
if (controller == null || !controller.value.isInitialized) {
return const Text(
'Loading',
style: TextStyle(
color: Colors.white,
fontSize: 20.0,
fontWeight: FontWeight.w900,
),
);
}
return
//screenshot
RepaintBoundary(
key: screen,
child: Stack(
alignment: FractionalOffset.center,
children: <Widget>[
new Positioned.fill(
child: new AspectRatio(
aspectRatio: controller.value.aspectRatio,
child: new CameraPreview(controller)),
),
new Positioned.fill(
child: Compass(
)
),
],
),
);
}
/// Display a row of toggle to select the camera (or a message if no camera is available).
Widget _cameraToggleRowWidget() {
if (cameras == null || cameras.isEmpty) {
return Spacer();
}
CameraDescription selectedCamera = cameras[selectedCameraIndex];
CameraLensDirection lensDirection = selectedCamera.lensDirection;
return Expanded(
child: Align(
alignment: Alignment.centerLeft,
child: FlatButton.icon(
onPressed: _onSwitchCamera,
icon: Icon(
_getCameraLensIcon(lensDirection),
color: Colors.white,
size: 24,
),
label: Text(
'${lensDirection.toString().substring(lensDirection.toString().indexOf('.') + 1).toUpperCase()}',
style: TextStyle(
color: Colors.white,
fontWeight: FontWeight.w500
),),
),
),
);
}
// Camera
IconData _getCameraLensIcon(CameraLensDirection direction) {
switch (direction) {
case CameraLensDirection.back:
return CupertinoIcons.switch_camera;
case CameraLensDirection.front:
return CupertinoIcons.switch_camera_solid;
case CameraLensDirection.external:
return Icons.camera;
default:
return Icons.device_unknown;
}
}
void _showCameraException(CameraException e) {
String errorText = 'Error:${e.code}\nError message : ${e.description}';
print(errorText);
}
void _onCapturePressed(context) async {
try {
// Compass
RenderRepaintBoundary boundary = screen.currentContext.findRenderObject();
ui.Image image = await boundary.toImage();
ByteData byteData = await image.toByteData(format: ui.ImageByteFormat.png);
final compassPath =
await ImagePickerSaver.saveFile(
fileData:byteData.buffer.asUint8List() );
// Camera
final path =
join((await getTemporaryDirectory()).path, '${DateTime.now()}.png');
await controller.takePicture(path);
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => PreviewCompass(
imgPath: path,
compassPath: compassPath,
roomName: roomName,
)
),
);
} catch (e) {
_showCameraException(e);
}
}
void _onSwitchCamera() {
selectedCameraIndex =
selectedCameraIndex < cameras.length - 1 ? selectedCameraIndex + 1 : 0;
CameraDescription selectedCamera = cameras[selectedCameraIndex];
_initCameraController(selectedCamera);
}
}
这是显示上一屏幕截图的页面的代码:
import 'dart:io';
import 'dart:typed_data';
import 'package:esys_flutter_share/esys_flutter_share.dart';
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
// packages screenshot
import 'package:flutter/rendering.dart';
import 'package:screenshot/screenshot.dart';
import 'package:path_provider/path_provider.dart';
import 'package:flutter_icons/flutter_icons.dart';
// Compass
class PreviewCompass extends StatefulWidget{
final String imgPath;
final String compassPath;
final String roomName;
const PreviewCompass({Key key, this.imgPath, this.compassPath, this.roomName}) : super(key: key);
//PreviewCompass({this.imgPath,this.roomName});
@override
_PreviewCompassState createState() => _PreviewCompassState();
}
class _PreviewCompassState extends State<PreviewCompass>{
// Screenshot
File _imageFile;
ScreenshotController screenshotController = ScreenshotController();
@override
Widget build(BuildContext context) {
return Screenshot(
controller: screenshotController,
child:
Scaffold(
backgroundColor: Colors.blueAccent,
appBar: AppBar(
automaticallyImplyLeading: true,
title: Text("ORIENTATION PIECE"),
centerTitle: true,
backgroundColor: Colors.blueAccent.shade700,
),
body: Container(
child: Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: <Widget>[
Expanded(
flex: 2,
child: Stack(
alignment: FractionalOffset.center,
children: <Widget>[
new Positioned.fill(child:
Image.file(File(widget.imgPath),fit: BoxFit.fill,),),
new Positioned.fill(child:
Image.file(File(widget.compassPath),fit: BoxFit.fill,),),
/*
Expanded(
// flex: 1,
child:
Image.file(File(widget.imgPath),fit: BoxFit.fill,),
),
Align(
// alignment: Alignment.bottomCenter,
child: Image.file(File(widget.compassPath),fit: BoxFit.fill,),
),*/
],
),
),
Card(
margin: EdgeInsets.symmetric( horizontal: 10.0, vertical :5.0),
child: ListTile (
leading: Icon(
FontAwesome.compass,
color: Colors.blue.shade900,
),
title: (
Text (widget?.roomName,
style: TextStyle(
fontSize: 15.0,
fontFamily: 'Source Sans Pro',
color: Colors.blue.shade900,
)
)
),
)
),
Align(
alignment: Alignment.bottomCenter,
child: Container(
width: double.infinity,
height: 60.0,
color: Colors.black,
child: Center(
child: IconButton(
icon: Icon(Icons.share,color: Colors.white,),
onPressed: () async {
_takeScreenshotandShare();
},
),
),
),
),
],
),
),
),
);
}
// Screenshot
_takeScreenshotandShare() async {
_imageFile = null;
screenshotController
.capture(delay: Duration(milliseconds: 10), pixelRatio: 2.0)
.then((File image) async {
setState(() {
_imageFile = image;
});
final directory = (await getApplicationDocumentsDirectory()).path;
Uint8List pngBytes = _imageFile.readAsBytesSync();
File imgFile = new File('$directory/screenshot.png');
imgFile.writeAsBytes(pngBytes);
print("File Saved to Gallery");
await Share.file('Screenshot', 'screenshot.png', pngBytes, 'image/png');
})
.catchError((onError) {
print(onError);
});
}
}
您能帮我解决这个错误吗?
非常感谢。
答案 0 :(得分:1)
您的错误来自这里:
FlutterCompass.events.listen(_onData);
您应将该流订阅存储在一个变量中,并在dispose方法中将其取消:
StreamSubscription<double> stream;
@override
void initState() {
super.initState();
stream = FlutterCompass.events.listen(_onData);
}
@override
void dispose() {
stream.cancel();
super.dispose();
}