flutter_demo_gps/lib/widgets/compass.dart

152 lines
4.8 KiB
Dart

import 'dart:math';
import 'package:flutter/material.dart';
class Compass extends StatelessWidget {
static const double fallbackSize = 100.0;
final double degree;
final Color pointerColor;
final double pointerThickness;
final double relativeCircleSize;
final double relativePointerLength;
final double? height;
final double? width;
const Compass(
{this.degree = 0.0,
this.pointerColor = Colors.red,
this.pointerThickness = 0.2,
this.relativeCircleSize = 0.8,
this.relativePointerLength = 1.0,
this.width,
this.height,
Key? key})
: assert(pointerThickness > 0, "Pointer must be thicker than zero"),
assert(relativeCircleSize >= 0 && relativeCircleSize <= 1.0,
"Relative circle size must be between 0.0 and 1.0"),
assert(relativePointerLength >= 0 && relativePointerLength <= 1.0,
"Relative circle size must be between 0.0 and 1.0"),
super(key: key);
/// Determines widget's width and height
/// Both will be equal to the smallest length which is either
/// set as [width], [height] or as [constraints] maxWidth/maxHeight.
/// [fallbackSize] will be used if none of them is set.
double calcSideLength(
double? setWidth, double? setHeight, BoxConstraints constraints) {
List<double> sizes = [
setWidth ?? double.infinity,
setHeight ?? double.infinity,
constraints.maxWidth,
constraints.maxHeight,
];
var tmp = sizes.where((s) => s != double.infinity);
return tmp.isNotEmpty ? tmp.reduce(min) : fallbackSize;
}
@override
Widget build(BuildContext context) {
return LayoutBuilder(
builder: (context, constraints) {
var sideLength = calcSideLength(width, height, constraints);
return SizedBox(
height: sideLength,
width: sideLength,
child: CustomPaint(
painter: _Compass(
degree: degree,
pointerColor: pointerColor,
pointerThickness: pointerThickness,
relativeCircleSize: relativeCircleSize,
relativePointerLength: relativePointerLength),
),
);
},
);
}
/// Creates a 6x6 grid with Compass widgets.
/// The look of each widget differs from its predecessor
/// - Orientation: 0 - 350°
/// - Color: from green over brown to red
/// - Size of circle: 0-87,5% of widgets width/height
/// - Pointers thickness: 5-35% of widgets width/height
static Widget demo() {
return Container(
color: Colors.grey,
width: 300,
height: 300,
child: GridView.builder(
gridDelegate: const SliverGridDelegateWithMaxCrossAxisExtent(
maxCrossAxisExtent: 50,
childAspectRatio: 1,
crossAxisSpacing: 0,
mainAxisSpacing: 0),
itemCount: 36,
itemBuilder: (BuildContext ctxt, int index) => CustomPaint(
painter: _Compass(
degree: index * 10,
relativeCircleSize: (index * 0.025),
pointerThickness: index / 120 + 0.05,
pointerColor: Color.fromRGBO((index * 255 / 36).floor(),
255 - (index * 255 / 36).floor(), 20, 1),
),
),
),
);
}
}
class _Compass extends CustomPainter {
static const Offset origin = Offset(0, 0);
static final Paint black = Paint()..color = Colors.black;
static final Paint white = Paint()..color = Colors.white;
final double degree;
final Color pointerColor;
final double pointerThickness;
final double relativeCircleSize;
final double relativePointerLength;
_Compass({
this.degree = 0.0,
this.pointerColor = Colors.red,
this.pointerThickness = 0.2,
this.relativeCircleSize = 0.8,
this.relativePointerLength = 1.0,
});
@override
void paint(Canvas canvas, Size size) {
final Paint pointerPaint = Paint()..color = pointerColor;
double minSide = size.width < size.height ? size.width : size.height;
double circleRadius = minSide * relativeCircleSize / 2;
double pointerLength = minSide * relativePointerLength / 2;
double pointerRadius = minSide * pointerThickness / 2;
canvas.save();
canvas.translate(size.width / 2, size.height / 2);
canvas.drawCircle(origin, circleRadius, black);
canvas.drawCircle(origin, circleRadius * 0.9, white);
canvas.drawCircle(origin, pointerRadius, pointerPaint);
canvas.rotate(degree * pi / 180);
canvas.drawPath(makeTriangle(pointerRadius, pointerLength), pointerPaint);
canvas.restore();
}
Path makeTriangle(double halfBase, double height) {
var path = Path();
path.moveTo(-halfBase, 0);
path.lineTo(0, -height);
path.lineTo(halfBase, 0);
path.close();
return path;
}
@override
bool shouldRepaint(CustomPainter oldDelegate) => false;
}