import 'dart:async'; import 'dart:math'; import 'dart:ui'; import 'package:buffer/buffer.dart'; import 'package:flutter/material.dart'; import 'package:flutter_reactive_ble/flutter_reactive_ble.dart'; import 'package:nrf/app_subscription_state.dart'; class Test extends StatefulWidget { final DiscoveredDevice device; final QualifiedCharacteristic characteristicNotify; const Test({Key? key, required this.device, required this.characteristicNotify}) : super(key: key); @override State createState() => _State(); } class _State extends State with SubscriptionState { final LABEL = ["left acc x", "acc y", "acc z", "gyro x", "gyro y", "gyro z", "front mag x", "front mag y", "front mag z", "back mag x", "back mag y", "back mag z", "right acc x", "r acc y", "r acc z", "r gyro x", "r gyro y", "r gyro z", "r front mag x", "r front mag y", "r front mag z", "r back mag x", "r back mag y", "r back mag z", "d1", "d2", "d3", "d4", "d5", "d6", "d7", "d8"]; final List FIX = []; final List COLORS = []; final List> data = []; final flutterReactiveBle = FlutterReactiveBle(); Timer? _timer; @override void initState() { super.initState(); FIX.addAll(List.filled(LABEL.length, 1)); COLORS.addAll(List.generate(LABEL.length, (index) => Color.fromRGBO(Random().nextInt(256), Random().nextInt(256), Random().nextInt(256), 1))); addSubscription(flutterReactiveBle.subscribeToCharacteristic(widget.characteristicNotify).listen((event) { ByteDataReader reader = ByteDataReader(); reader.add(event); int flagAA = reader.readInt8(); int len = reader.readInt8(); reader.readInt8(); int cmd = reader.readInt8(); if(cmd != 4){ return; } List array = []; while (reader.remainingLength > 1) { array.add(reader.readInt16()); } if(data.length > 500) { data.removeAt(0); } data.add(array); })); _timer = Timer.periodic(const Duration(milliseconds: 16), (timer) { setState(() { }); }); } @override void dispose() { _timer?.cancel(); super.dispose(); } @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar( title: Text("${widget.device.name}"), actions: [ IconButton(icon: Icon(Icons.select_all), onPressed: () { setState(() { for(var i = 0; i < FIX.length;i++){ FIX[i] = 1; } }); },),IconButton(icon: Icon(Icons.clear_all), onPressed: () { setState(() { for(var i = 0; i < FIX.length;i++){ FIX[i] = 0; } }); },), IconButton(icon: Text("左"), onPressed: () { setState(() { for(var i = 0; i < FIX.length;i++){ FIX[i] = 0; } for(var i = 0; i < 12;i++){ FIX[i] = 1; } }); },), IconButton(icon: Text("右"), onPressed: () { setState(() { for(var i = 0; i < FIX.length;i++){ FIX[i] = 0; } for(var i = 12; i < FIX.length;i++){ FIX[i] = 1; } }); },), ], ), body: Row( children: [ Expanded( child: Padding( padding: const EdgeInsets.all(8.0), child: CustomPaint( painter: _Painter(data, FIX, COLORS), child: const SizedBox( width: double.infinity, height: double.infinity, ), ), ), ), const SizedBox( width: 20, ), Container( width: 150, height: double.infinity, child: SingleChildScrollView( child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ for (var i = 0; i < LABEL.length; i++) InkWell( onTap: () { setState(() { FIX[i] = FIX[i] == 1 ? 0 : 1; }); }, child: Row( mainAxisSize: MainAxisSize.min, children: [ Checkbox( value: FIX[i] == 1, onChanged: (bool? value) {}, ), Text(LABEL[i], style: TextStyle(color: COLORS[i]),) ], ), ), ], ), ), ), ], ), ); } } class _Painter extends CustomPainter { final _line = Paint() ..color = const Color(0xffdcdcdc) ..strokeWidth = 1; final _line1 = Paint() ..color = const Color(0xffdcdcdc) ..strokeWidth = 1; final List> data; final List fixed; final List colors; _Painter(this.data, this.fixed, this.colors); @override void paint(Canvas canvas, Size size) { List> items = data; int length = items.length; if(length <= 0)return; final center = Offset(size.width / 2, size.height / 2); final centerX = center.dx; final centerY = center.dy; const double paddingLeft = 60.0; canvas.drawLine(Offset(0, centerY), Offset(size.width, centerY), _line); canvas.drawLine(const Offset(paddingLeft, 0), Offset(paddingLeft, size.width), _line); double width = (size.width - paddingLeft) / length; int maxValue = items.map((e) { int d = 0; for(var i = 0 ; i < e.length; i++){ d = max(d, fixed[i] == 0 ? 0 : e[i]); } return d; }).reduce(max); int minValue = items.map((e) { int d = 0; for(var i = 0 ; i < e.length; i++){ d = min(d, fixed[i] == 0 ? 0 : e[i]); } return d; }).reduce(min); int split = 4; int absMax = max(maxValue, minValue.abs()); double height = size.height / 2; double yAxisSplitHeight = height / split; for(var i = 1 ; i <= split; i++){ canvas.drawLine(Offset(paddingLeft, centerY - i * yAxisSplitHeight), Offset(paddingLeft + 10, centerY - i * yAxisSplitHeight), _line); var text = TextPainter( text: TextSpan( style: const TextStyle(fontSize: 12.0, color: Color(0xffdcdcdc)), text: "${absMax / split * i}", ), textAlign: TextAlign.left, textDirection: TextDirection.ltr, ) ..layout(minWidth: 100); text.paint(canvas, Offset(0, centerY - i * yAxisSplitHeight - text.height / 2)); canvas.drawLine(Offset(paddingLeft, centerY + i * yAxisSplitHeight), Offset(paddingLeft + 10, centerY + i * yAxisSplitHeight), _line); text = TextPainter( text: TextSpan( style: const TextStyle(fontSize: 12.0, color: Color(0xffdcdcdc)), text: "-${absMax / split * i}", ), textAlign: TextAlign.left, textDirection: TextDirection.ltr, ) ..layout(minWidth: 100); text.paint(canvas, Offset(0, centerY + i * yAxisSplitHeight - text.height / 2)); } int maxColumn = items.first.length; for(var j = 0; j < fixed.length; j ++){ if(fixed[j] == 0) { continue; } if(j >= maxColumn) { break; } Path path = Path(); for(var i = 0; i < length; i++){ var item = items[i]; int value = item[j]; double dx = i*width + paddingLeft; double dy = value > 0 ? (1 - value / absMax) * height :(value.abs() / absMax) * height + centerY; if(i == 0){ path.moveTo(dx, dy); }else{ path.lineTo(dx, dy); } } final paint = Paint() ..color = colors[j] ..strokeWidth = 1 ..style = PaintingStyle.stroke; canvas.drawPath(path, paint); } } @override bool shouldRepaint(covariant CustomPainter oldDelegate) { return true; } }