So I'm trying to create a draw app using Flutter following the "signature canvas" method. However, I'm having trouble being able to change the colour of the CustomPaint object without it already changing the colours for each line draw before the change as shown here:
As you can see, the colour change happens once the Page Widget's state is changed (either by clicking on the main FAB or if I were to draw on the canvas again). Here is my code below for my DrawPage:
class DrawPage extends StatefulWidget {
#override
DrawPageState createState() => new DrawPageState();
}
class DrawPageState extends State<DrawPage> with TickerProviderStateMixin {
AnimationController controller;
List<Offset> points = <Offset>[];
Color color = Colors.black;
StrokeCap strokeCap = StrokeCap.round;
double strokeWidth = 5.0;
#override
void initState() {
super.initState();
controller = new AnimationController(
vsync: this,
duration: const Duration(milliseconds: 500),
);
}
#override
Widget build(BuildContext context) {
return Scaffold(
body: Container(
child: GestureDetector(
onPanUpdate: (DragUpdateDetails details) {
setState(() {
RenderBox object = context.findRenderObject();
Offset localPosition =
object.globalToLocal(details.globalPosition);
points = new List.from(points);
points.add(localPosition);
});
},
onPanEnd: (DragEndDetails details) => points.add(null),
child: CustomPaint(
painter: Painter(
points: points,
color: color,
strokeCap: strokeCap,
strokeWidth: strokeWidth),
size: Size.infinite,
),
),
),
floatingActionButton:
Column(mainAxisSize: MainAxisSize.min, children: <Widget>[
Container(
height: 70.0,
width: 56.0,
alignment: FractionalOffset.topCenter,
child: ScaleTransition(
scale: CurvedAnimation(
parent: controller,
curve: Interval(0.0, 1.0 - 0 / 3 / 2.0, curve: Curves.easeOut),
),
child: FloatingActionButton(
mini: true,
child: Icon(Icons.clear),
onPressed: () {
points.clear();
},
),
),
),
Container(
height: 70.0,
width: 56.0,
alignment: FractionalOffset.topCenter,
child: ScaleTransition(
scale: CurvedAnimation(
parent: controller,
curve: Interval(0.0, 1.0 - 1 / 3 / 2.0, curve: Curves.easeOut),
),
child: FloatingActionButton(
mini: true,
child: Icon(Icons.lens),
onPressed: () {},
),
),
),
Container(
height: 70.0,
width: 56.0,
alignment: FractionalOffset.topCenter,
child: ScaleTransition(
scale: CurvedAnimation(
parent: controller,
curve:
Interval(0.0, 1.0 - 2 / 3 / 2.0, curve: Curves.easeOut),
),
child: FloatingActionButton(
mini: true,
child: Icon(Icons.color_lens),
onPressed: () async {
Color temp;
temp = await showDialog(
context: context,
builder: (context) => ColorDialog());
if (temp != null) {
setState(() {
color = temp;
});
}
}))),
FloatingActionButton(
child: AnimatedBuilder(
animation: controller,
builder: (BuildContext context, Widget child) {
return Transform(
transform: Matrix4.rotationZ(controller.value * 0.5 * math.pi),
alignment: FractionalOffset.center,
child: Icon(Icons.brush),
);
},
),
onPressed: () {
if (controller.isDismissed) {
controller.forward();
} else {
controller.reverse();
}
},
),
]),
);
}
}
What I've tried so far:
I've tried playing around with how the points are added to my List of Offsets as this list is recreated after each "draw" gesture such as just adding to the current List without recreating it but this breaks the "draw" gesture:
setState(() {
RenderBox object = context.findRenderObject();
Offset localPosition =
object.globalToLocal(details.globalPosition);
points = new List.from(points);
points.add(localPosition);
});
I've tried making a reference to the CustomPaint object or my Painter object outside of the build() scope and updating the color property that way but this also breaks the "draw" gesture.
Any help would be greatly appreciated!
Also, here's the code for my Painter class in case people wish to see it:
class Painter extends CustomPainter {
List<Offset> points;
Color color;
StrokeCap strokeCap;
double strokeWidth;
Painter({this.points, this.color, this.strokeCap, this.strokeWidth});
#override
void paint(Canvas canvas, Size size) {
Paint paint = new Paint();
paint.color = color;
paint.strokeCap = strokeCap;
paint.strokeWidth = strokeWidth;
for (int i = 0; i < points.length - 1; i++) {
if (points[i] != null && points[i + 1] != null) {
canvas.drawLine(points[i], points[i + 1], paint);
}
}
}
#override
bool shouldRepaint(Painter oldPainter) => oldPainter.points != points;
}
I think, for different colors you have to use different Paints. I've added small changes to your code, it works.
class DrawPageState extends State<DrawPage> with TickerProviderStateMixin {
...
List<Painter> painterList = [];
#override
Widget build(BuildContext context) {
...
child: CustomPaint(
painter: Painter(
points: points, color: color, strokeCap: strokeCap, strokeWidth: strokeWidth, painters: painterList),
size: Size.infinite,
),
...
onPressed: () async {
Color temp;
temp = await showDialog(
context: context,
builder: (context) => ColorDialog());
if (temp != null) {
setState(() {
painterList
.add(Painter(points: points.toList(), color: color, strokeCap: strokeCap, strokeWidth: strokeWidth));
points.clear();
strokeCap = StrokeCap.round;
strokeWidth = 5.0;
color = temp;
});
}
...
}
}
class Painter extends CustomPainter {
List<Offset> points;
Color color;
StrokeCap strokeCap;
double strokeWidth;
List<Painter> painters;
Painter({this.points, this.color, this.strokeCap, this.strokeWidth, this.painters = const []});
#override
void paint(Canvas canvas, Size size) {
for (Painter painter in painters) {
painter.paint(canvas, size);
}
Paint paint = new Paint()
..color = color
..strokeCap = strokeCap
..strokeWidth = strokeWidth;
for (int i = 0; i < points.length - 1; i++) {
if (points[i] != null && points[i + 1] != null) {
canvas.drawLine(points[i], points[i + 1], paint);
}
}
}
#override
bool shouldRepaint(Painter oldDelegate) => oldDelegate.points != points;
}
2020, I have a nice solution for this, because the actually chosen doesn't work for me and I see some redundant calls.
So, I start creating a small class:
class _GroupPoints {
Offset offset;
Color color;
_GroupPoints({this.offset, this.color});
}
next, i declare my CustomPainter like this:
class Signature extends CustomPainter {
List<_GroupPoints> points;
Color color;
Signature({
this.color,
this.points,
});
#override
void paint(Canvas canvas, Size size) {
Paint paint = new Paint()
// if you need this next params as dynamic, you can move it inside the for part
..strokeCap = StrokeCap.round
..strokeWidth = 5.0;
for (int i = 0; i < newPoints.length - 1; i++) {
paint.color = points[i].color;
if (points[i].offset != null && points[i + 1].offset != null) {
canvas.drawLine(points[i].offset, points[i + 1].offset, paint);
}
canvas.clipRect(Offset.zero & size);
}
}
#override
bool shouldRepaint(Signature oldDelegate) => true;
}
And on my widget:
...
class _MyPageState extends State<MyPage> {
...
List<_GroupPoints> points = [];
...
Container(
height: 500,
width: double.infinity,
child: GestureDetector(
onPanUpdate: (DragUpdateDetails details) {
setState(() {
points = new List.from(points)
..add(
new _GroupPoints(
offset: details.localPosition,
color: myDynamicColor,
),
);
});
},
onPanEnd: (DragEndDetails details) {
points.add(
_GroupPoints(
color: myDynamicColor,
offset: null),
);
},
child: CustomPaint(
painter: Signature(
newPoints: points,
color: myDynamicColor,
),
),
),
),
}
On this way we can use multiple draws of points with their respective color. Hope this can help anybody.
Related
I am developing an shape editor in flutter. For that, I need to add a feature of adding shapes dynamically to the screen. Each of them should be able to independently scale, position, and rotate. There should be an anchor point like in word or in Photoshop to indicate the active shapes and to scale and rotate them. The shape should be positioned with drag and drop.
I found this answer :
Rotate and resize container
But the answer only rotate the shape from the center, I want to scale the shape from the corners also.
The code :
class FooResizer extends StatefulWidget {
#override
_FooResizerState createState() => _FooResizerState();
}
class _FooResizerState extends State<FooResizer> with TickerProviderStateMixin, ChangeNotifier {
Sizer currentSizer;
double angle = 0.0;
AnimationController ctrl;
final Map<String, Sizer> sizers = {
'l': Sizer('l', Alignment.centerLeft, {'t': 0.5, 'b': 0.5, 'M': 0.5}),
't': Sizer('t', Alignment.topCenter, {'l': 0.5, 'r': 0.5, 'M': 0.5}),
'r': Sizer('r', Alignment.centerRight, {'t': 0.5, 'b': 0.5, 'R': 1.0, 'M': 0.5}),
'b': Sizer('b', Alignment.bottomCenter, {'l': 0.5, 'r': 0.5, 'R': 1.0, 'M': 0.5}),
'R': Sizer('R', Alignment.bottomRight, {}),
'M': Sizer('M', Alignment.center, {}),
};
#override
void initState() {
super.initState();
ctrl = AnimationController(vsync: this, duration: Duration(milliseconds: 300), value: 1.0);
}
#override
Widget build(BuildContext context) {
return ClipRect(
child: ColoredBox(
color: Colors.black12,
// CustomMultiChildLayoutPainter:
// https://gist.github.com/pskink/0f82724b41d9ebe89604782fbf62fe03#file-multi_layout_painter-dart-L447
child: CustomMultiChildLayoutPainter(
delegate: _FooResizerDelegate(sizers, this),
children: [
LayoutId(
id: 'body',
child: AnimatedBuilder(
animation: this,
builder: (ctx, child) {
return Transform.rotate(
angle: angle,
child: child,
);
},
child: Material(color: Colors.grey[300], elevation: 4, child: FlutterLogo()),
),
),
...sizers.values.map(_sizerBuilder),
],
),
),
);
}
Widget _sizerBuilder(Sizer sizer) {
final colors = {
'M': Colors.orange,
'R': Colors.teal,
};
return LayoutId(
id: sizer.id,
child: GestureDetector(
onPanStart: (details) => _panStart(sizer),
onPanUpdate: _panUpdate,
onPanEnd: _panEnd,
child: AnimatedBuilder(
animation: ctrl,
builder: (context, child) {
final color = colors[sizer.id] ?? Colors.green;
return Opacity(
opacity: currentSizer == sizer? 1.0 : Curves.ease.transform(ctrl.value),
child: Container(
decoration: ShapeDecoration(
shape: CircleBorder(side: BorderSide(width: lerpDouble(0.0, 2.0, ctrl.value), color: Colors.black38)),
color: currentSizer == sizer? Color.lerp(Colors.deepPurple, color, ctrl.value) : color,
shadows: [BoxShadow.lerp(BoxShadow(spreadRadius: 2, blurRadius: 4, offset: Offset(2, 2)), null, ctrl.value)],
),
),
);
}
),
),
);
}
_panStart(Sizer sizer) {
currentSizer = sizer;
ctrl.reverse();
}
_panUpdate(DragUpdateDetails details) {
assert(currentSizer != null);
if (currentSizer.id == 'M') {
// move
sizers.values.forEach((sizer) => sizer.center += details.delta);
} else
if (currentSizer.id == 'R') {
// rotate
final localCenter = sizers['M'].center;
final globalCenter = (context.findRenderObject() as RenderBox).localToGlobal(localCenter);
final angle0 = (details.globalPosition - details.delta - globalCenter).direction;
final angle1 = (details.globalPosition - globalCenter).direction;
final deltaAngle = angle1 - angle0;
sizers.values
.where((sizer) => sizer.id != 'M')
.forEach((sizer) {
final vector = sizer.center - localCenter;
sizer.center = localCenter + Offset.fromDirection(vector.direction + deltaAngle, vector.distance);
});
angle += deltaAngle;
} else {
// resize
final adjustedAngle = angle + currentSizer.angleAdjustment;
final rotatedDistance = details.delta.distance * math.cos(details.delta.direction - adjustedAngle);
final vector = Offset.fromDirection(adjustedAngle, rotatedDistance);
currentSizer.center += vector;
currentSizer.dependents.forEach((id, factor) => sizers[id].center += vector * factor);
}
notifyListeners();
}
_panEnd(DragEndDetails details) {
assert(currentSizer != null);
// currentSizer = null;
ctrl.forward();
}
}
class _FooResizerDelegate extends MultiChildLayoutPainterDelegate {
static const SIZE = 48.0;
final Map<String, Sizer> sizers;
_FooResizerDelegate(this.sizers, Listenable relayout) : super(relayout: relayout);
#override
void performLayout(Size size) {
sizers['M'].center ??= init(size);
for (var sizer in sizers.values) {
layoutChild(sizer.id, BoxConstraints.tight(Size(SIZE, SIZE)));
positionChild(sizer.id, sizer.center - Offset(SIZE / 2, SIZE / 2));
}
final w = (sizers['l'].center - sizers['r'].center).distance;
final h = (sizers['t'].center - sizers['b'].center).distance;
layoutChild('body', BoxConstraints.tight(Size(w, h)));
positionChild('body', sizers['M'].center - Offset(w / 2, h / 2));
}
Offset init(Size size) {
final rect = (Offset.zero & size).deflate(24);
print('init rect: $rect');
for (var sizer in sizers.values) {
sizer
..center = sizer.alignment.withinRect(rect)
..angleAdjustment = sizer.alignment.x == 0? math.pi / 2 : 0;
}
return sizers['M'].center;
}
#override
bool shouldRelayout(covariant MultiChildLayoutDelegate oldDelegate) => true;
#override
void foregroundPaint(Canvas canvas, Size size) {
}
#override
void paint(Canvas canvas, Size size) {
final w = (sizers['r'].center - sizers['l'].center).distance;
final h = (sizers['b'].center - sizers['t'].center).distance;
final rect = Rect.fromCenter(center: sizers['M'].center, width: w, height: h);
final angle = (sizers['r'].center - sizers['l'].center).direction;
final matrix = Matrix4.identity()
..translate(rect.center.dx, rect.center.dy)
..rotateZ(angle)
..translate(-rect.center.dx, -rect.center.dy);
final transformedRect = MatrixUtils.transformRect(matrix, rect);
final points = [
Offset(transformedRect.left, 0), Offset(transformedRect.left, size.height),
Offset(0, transformedRect.top), Offset(size.width, transformedRect.top),
Offset(transformedRect.right, 0), Offset(transformedRect.right, size.height),
Offset(0, transformedRect.bottom), Offset(size.width, transformedRect.bottom),
];
canvas.drawPoints(PointMode.lines, points, Paint());
}
}
class Sizer {
final String id;
final Alignment alignment;
final Map<String, double> dependents;
Offset center;
double angleAdjustment;
Sizer(this.id, this.alignment, this.dependents);
}
The result :
what I am trying to achieve :
There are other ways of handling this overlay, but AFAIK this should work, and it does sometimes, but not always on Android. On Windows it appeared to work perfectly. I have the latest Flutter (3.0.3) and the latest Dart (2.17.5) and Flutter doctor shows no problems. I have tested this on 2 physical devices (Android 6.0.1 - level 23, and Android 11 - level 30). I have also tested it on an emulator (Android 11 - level 30). The results on all three were very similar - the overlay doesn't always show. I also tested it on Windows and it worked perfectly for all attempts (30).
The code below is a cut-down version of the code to demonstrate the problem. The problem is that the overlay does not always display. After a reboot it will often work well for about 10-12 reloads of the program, and then it is inconsistent, sometimes showing the overlay and sometimes not. As I said, it worked perfectly on Windows.
import 'dart:io';
import 'package:flutter/services.dart';
import 'package:flutter/material.dart';
const D_OVERLAY_HEIGHT = 290.0;
const D_BTN_HEIGHT = 45.0;
void main() => runApp(OverlayTestApp());
class OverlayTestApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
home: OverlayTest(),
);
}
}
class OverlayTest extends StatefulWidget {
#override
_PageOverlayTest createState() => _PageOverlayTest();
}
//==== THE MAIN PAGE FOR THE APP ====//
class _PageOverlayTest extends State<OverlayTest> {
TextEditingController? _wController = TextEditingController();
ClassOverlay? _clOverlay;
#override
Widget build(BuildContext context) {
WidgetsFlutterBinding.ensureInitialized();
WidgetsBinding.instance
.addPostFrameCallback((_) => fnOnBuildComplete(context));
return Scaffold(
resizeToAvoidBottomInset: false,
appBar: AppBar(
leading: IconButton(
icon: Icon(Icons.arrow_back), onPressed: () => fnExit()),
title: Center(child: Text('Overlay Test'))),
body: WillPopScope(
onWillPop: () async => await _fnDispose(),
child: Column(
children: [
SizedBox(height: 50),
TextField(
controller: _wController,
decoration: InputDecoration(
border: OutlineInputBorder(
borderRadius: BorderRadius.circular(3),
),
),
style: TextStyle(fontSize: 16),
autofocus: true,
showCursor: true,
readOnly: true,
toolbarOptions: ToolbarOptions(
copy: false,
cut: false,
paste: false,
selectAll: false,
),
),
],
),
));
}
fnExit() {
dispose();
SystemNavigator.pop();
exit(0);
}
#override
void dispose() {
_fnDispose();
super.dispose();
}
Future<bool> _fnDispose() async {
if (_wController != null) {
_wController!.dispose();
_wController = null;
}
if (_clOverlay != null) {
_clOverlay!.fnDispose();
}
return true;
}
void fnOnBuildComplete(BuildContext context) async {
if (_clOverlay == null) {
double dScreenWidth = MediaQuery.of(context).size.width;
dScreenWidth = dScreenWidth < 510 ? dScreenWidth : 500;
double dScreenHeight = MediaQuery.of(context).size.height;
_clOverlay = ClassOverlay(dScreenWidth, dScreenHeight - 320);
}
_clOverlay!.fnInitContext(context, _wController!);
}
}
//==== CLASS FOR THE OVERLAY ====//
class ClassOverlay {
TextEditingController? _wCtlText;
OverlayEntry? _wOverlayEntry;
final double dScreenWidth;
final double dOverlayTop;
Material? _wOverlayWidget;
ClassOverlay(this.dScreenWidth, this.dOverlayTop) {
SystemChrome.setEnabledSystemUIMode(SystemUiMode.manual,
overlays: [SystemUiOverlay.bottom, SystemUiOverlay.top]);
_wOverlayWidget = fnCreateOverlayWidget(dScreenWidth: dScreenWidth);
}
//==== AFTER BUILD OF CALLER INITIALIZE WITH CONTEXT AND OVERLAYSTATE ====//
void fnInitContext(BuildContext context, TextEditingController wCtlText) {
try {
_wCtlText = wCtlText;
if (_wOverlayEntry != null) {
_wOverlayEntry!.remove();
_wOverlayEntry = null;
}
if (_wOverlayWidget == null) {
throw ('_wOverlayWidget is null');
}
_wOverlayEntry = OverlayEntry(builder: (context) {
return Positioned(left: 0, top: dOverlayTop, child: _wOverlayWidget!);
});
OverlayState? wOverlayState = Navigator.of(context).overlay;
if (wOverlayState == null) {
throw ('Failed to get OverlayState');
}
if (_wOverlayEntry == null) {
throw ('OverlayEntry is null');
}
wOverlayState.insert(_wOverlayEntry!);
if (!(wOverlayState.mounted)) {
wOverlayState.activate();
}
} catch (vError) {
_wCtlText!.text = '**** Fatal Error ${vError.toString()}';
}
}
void fnDispose() {
if (_wOverlayEntry != null) {
_wOverlayEntry!.remove();
_wOverlayEntry = null;
}
}
}
//==== CLASS - OVERLAY WIDGET ====//
Material fnCreateOverlayWidget({required double dScreenWidth}) {
final double dBtnWidth = (dScreenWidth - 60) / 10;
final double dLineSeparator = 10;
return Material(
child: Container(
height: (D_OVERLAY_HEIGHT),
width: dScreenWidth - 10,
padding: EdgeInsets.only(bottom: 10),
decoration: BoxDecoration(
color: Colors.white,
border: Border.all(color: Colors.grey.shade500, width: 2),
borderRadius: BorderRadius.circular(10)),
child: Column(
children: [
fnCreateRow('1234567890', dBtnWidth),
SizedBox(height: dLineSeparator),
fnCreateRow('qwertyuiop', dBtnWidth),
SizedBox(height: dLineSeparator),
fnCreateRow('asdfghjkl', dBtnWidth),
SizedBox(height: dLineSeparator),
fnCreateRow(',zxcvbnm.', dBtnWidth),
SizedBox(height: dLineSeparator),
fnCreateRow('1234567890', dBtnWidth)
],
),
));
}
//==== FUNCTION - CREATE A ROW OF TEXT BUTTONS ====//
Row fnCreateRow(String sRow, double dBtnWidth) {
List<Widget> wRow = [];
double dSpace = sRow.length == 10 ? 0 : (dBtnWidth * (10 - sRow.length)) / 2;
if (dSpace > 0) {
wRow.add(SizedBox(width: dSpace));
}
for (int iNdx = 0; iNdx < sRow.length; iNdx++) {
double dFontSize = sRow[iNdx] == '.' || sRow[iNdx] == ',' ? 30 : 20;
wRow.add(SizedBox(
height: D_BTN_HEIGHT,
width: dBtnWidth,
child: TextButton(
onPressed: () {},
child: Text(sRow[iNdx],
style: TextStyle(
color: Colors.black,
fontSize: dFontSize,
fontWeight: FontWeight.w400)),
)));
}
if (dSpace > 0) {
wRow.add(SizedBox(width: dSpace));
}
return Row(mainAxisAlignment: MainAxisAlignment.spaceEvenly, children: wRow);
}
It took a lot of time to find the reason for this problem. I was not aware of the fact that MediaQuery can return zero for the height or width because of a timing issue, and the application program must cater for that. It appears that this was working correctly, but the height and/or width was set to zero. There must be a lot of programs that don't check for that.
newbie in Flutter. How show markers in the circle in flutter_map. Which day already trying to solve this problem. I can't make the markers in the radius change, or just change color. That's what i have achieved:
Position _currentPosition;
static final List<LatLng> _points = [
LatLng(55.749122, 37.659750),
LatLng(55.770854, 37.626963),
LatLng(55.776937, 37.637949),
LatLng(55.739266, 37.637434),
];
static const _markerSize = 40.0;
List<Marker> _markers;
List<CircleMarker> circle;
#override
void initState() {
super.initState();
_markers = _points
.map(
(LatLng point) => Marker(
point: point,
width: _markerSize,
height: _markerSize,
builder: (_) => Icon(Icons.location_on, size: _markerSize),
),
).toList();
_getCurrentLocation();
}
_getCurrentLocation() {
geolocator
.getCurrentPosition(desiredAccuracy: LocationAccuracy.best)
.then((Position position) {
setState(() {
_currentPosition = position;
});
}).catchError((e) {
print(e);
});
}
#override
Widget build(BuildContext context) {
return FlutterMap(
options: new MapOptions(
minZoom: 16.0,
center: new LatLng(_currentPosition.latitude,_currentPosition.longitude)),
layers: [new TileLayerOptions(urlTemplate: 'https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png',
subdomains: ['a','b','c']),
new MarkerLayerOptions(
markers: [
new Marker(
width: 45.0,
height: 45.0,
point: new LatLng(_currentPosition.latitude,_currentPosition.longitude),
builder: (context)=>new Container(
child: IconButton(icon: Icon(Icons.accessibility), onPressed: () {print('Marker tapped!');}),
))]),
MarkerLayerOptions(markers: _markers),
CircleLayerOptions(circles: [ CircleMarker(
point: LatLng(_currentPosition.latitude,_currentPosition.longitude),
color: Colors.blue.withOpacity(0.7),
borderStrokeWidth: 2,
useRadiusInMeter: true,
radius: 2000 // 2000 meters | 2 km
)]),
],
);
}
my result - (this is image)
How to change the markers in the blue circle. For example change the marker icon?
Have you tried changing the builder of your marker, like:
_markers = _points
.map(
(LatLng point) => Marker(
point: point,
width: _markerSize,
height: _markerSize,
builder: (_) => Icon(
Icons.location_city,
size: _markerSize,
color: Colors.red,
),
),
).toList();
First, you need to find the points inside your circle. You can use something like this before mentioned here:
static double getDistanceFromGPSPointsInRoute(List<LatLng> gpsList) {
double totalDistance = 0.0;
for (var i = 0; i < gpsList.length; i++) {
var p = 0.017453292519943295;
var c = cos;
var a = 0.5 -
c((gpsList[i + 1].latitude - gpsList[i].latitude) * p) / 2 +
c(gpsList[i].latitude * p) *
c(gpsList[i + 1].latitude * p) *
(1 - c((gpsList[i + 1].longitude - gpsList[i].longitude) * p)) /
2;
double distance = 12742 * asin(sqrt(a));
totalDistance += distance;
print('Distance is ${12742 * asin(sqrt(a))}');
}
print('Total distance is $totalDistance');
return totalDistance;
}
Then you need to signature inside points with a boolean field.
static final List<NewModel> _points = []; // NewModel contains boolean field with LatLng
After that you can easily customize the icons of the inside points like this:
_markers = _points
.map(
(NewModel newModel) => Marker(
point: newModel.point,
width: _markerSize,
height: _markerSize,
builder: (_) =>
newModel.isInside
? Icon(Icons.location_city, size: _markerSize),
: Icon(Icons.location_on, size: _markerSize),
),
).toList();
1- you can use decoration in container:
new Marker(
width: 45.0,
height: 45.0,
point: new LatLng(_currentPosition.latitude,_currentPosition.longitude),
builder: (context)=> new Container(
decoration: BoxDecoration(
color: Colors.blue,
borderRadius: BorderRadius.circular(20),
),
child: IconButton(
icon: Icon(Icons.accessibility, color: Colors.white),
onPressed: () {print('Marker tapped!');},
),
)
2- or you can create a custom marker from a image :
import 'dart:ui' as ui;
import 'package:flutter/services.dart';
import 'package:google_maps_flutter/google_maps_flutter.dart';
...
Future<BitmapDescriptor> createMarkIcon(assetsPath, size) async {
ByteData data = await rootBundle.load(assetsPath);
ui.Codec codec = await ui.instantiateImageCodec(data.buffer.asUint8List(),
targetWidth: size);
ui.FrameInfo fi = await codec.getNextFrame();
return BitmapDescriptor.fromBytes(
(await fi.image.toByteData(format: ui.ImageByteFormat.png))
.buffer
.asUint8List(),
);
}
...
BitmapDescriptor _markIcon =
await createMarkIcon('assets/images/marker_icon.png', 60);
Marker _marker = Marker(
position: _latlng,
icon: _markIcon,
onTap: () {
// your code
});
I am trying to set any widget to screen at any position so if user open app in other devices the widget must should remain at that give position , for example if I set widget on 1/4 part of the screen then it should be the same in every device . I tested in simulator from Iphone 8 to Ipad 8th gen.
Below is my code I am using Positioned.fromRect and passing Offset(dx,dy). I also tried MediaQuery but all in vain . Please guide me even if you find my question stupid.
Reference app Story click or post maker app with pre built in templates.
This is simple constructor.
FlutterSimpleStickerImage(
this.image, {
Key key,
this.width,
this.height,
this.viewport,
this.minScale = 1.0,
this.maxScale = 2.0,
this.onTapRemove,
}) : super(key: key);
Widget image;
final double width;
final double height;
final Size viewport;
final double minScale;
final double maxScale;
A class for positioning widget in stack the Position.fromRect()
class _FlutterSimpleStickerImageState extends State<FlutterSimpleStickerImage> {
_FlutterSimpleStickerImageState();
double _scale = 1.0;
double _previousScale = 1.0;
Offset _previousOffset = Offset(0, 0);
Offset _startingFocalPoint = Offset(0, 0);
Offset _offset = Offset(0, 0);
double _rotation = 0.0;
double _previousRotation = 0.0;
bool _isSelected = false;
#override
void dispose() {
super.dispose();
_offset = Offset(0, 0);
_scale = 1.0;
}
#override
Widget build(BuildContext context) {
double sizewidth=183;
double sizeheight=0;
return Positioned.fromRect(
rect: Rect.fromPoints(Offset(sizewidth, sizeheight),Offset(sizewidth , sizeheight )),
child: Container(
child: Stack(
children: <Widget>[
Center(
child: Transform(
transform: Matrix4.diagonal3(Vector3(_scale, _scale, _scale)),
// ..setRotationZ(_rotation),
alignment: FractionalOffset.center,
child: GestureDetector(
onScaleStart: (ScaleStartDetails details) {
_startingFocalPoint = Offset(sizewidth,sizeheight);
_previousOffset = Offset(sizewidth,sizeheight);
_previousRotation = _rotation;
_previousScale = _scale;
// print(
// "begin - focal : ${details.focalPoint}, local : ${details.localFocalPoint}");
},
onScaleUpdate: (ScaleUpdateDetails details) {
_scale = min(
max(_previousScale * details.scale, widget.minScale),
widget.maxScale);
_rotation = details.rotation;
final Offset normalizedOffset =
(_startingFocalPoint - _previousOffset) /
_previousScale;
Offset __offset =
details.focalPoint - (normalizedOffset * _scale);
__offset = Offset(max(__offset.dx, -widget.width),
max(__offset.dy, -widget.height));
__offset = Offset(min(__offset.dx, widget.viewport.width),
min(__offset.dy, widget.viewport.height));
setState(() {
_offset = __offset;
// print("move - $_offset, scale : $_scale");
});
},
onTap: () {
setState(() {
_isSelected = !_isSelected;
});
},
onTapCancel: () {
setState(() {
_isSelected = false;
});
},
onDoubleTap: () {
setState(() {
_scale = 1.0;
});
},
onLongPress: (){
setState(() {
widget.image=Container(height: 100,width: 100,color: Color.fromRGBO(255, 150, 0, 1.0),);
});
},
child: Container(child: widget.image),
),
),
),
_isSelected
? Positioned(
top: 12,
right: 12,
width: 24,
height: 24,
child: Container(
child: IconButton(
padding: EdgeInsets.zero,
icon: Icon(Icons.remove_circle),
color: Color.fromRGBO(255, 150, 0, 1.0),
onPressed: () {
print('tapped remove sticker');
if (this.widget.onTapRemove != null) {
this.widget.onTapRemove(this.widget);
}
},
),
),
)
: Container(),
],
),
),
);
}
void hideRemoveButton() {
setState(() {
_isSelected = false;
});
}
}
I'm new to Dart/Flutter framework and I'm still exploring their possibilities.
I know in Android it's possible to take a picture and extract the main color value from it programmatically. (Android example)
I wonder, how would this be achieved in pure Dart? I would like it to be compatible with both iOS and Android operating system.
Here's a simple function which returns the dominant color given an ImageProvider. This shows the basic usage of Palette Generator without all the boilerplate.
import 'package:palette_generator/palette_generator.dart';
// Calculate dominant color from ImageProvider
Future<Color> getImagePalette (ImageProvider imageProvider) async {
final PaletteGenerator paletteGenerator = await PaletteGenerator
.fromImageProvider(imageProvider);
return paletteGenerator.dominantColor.color;
}
Then use FutureBuilder on the output to build a Widget.
I probably think you got a fix but for future searches to this question, I suggest you check Pallete Generator by the flutter team.
I will try and give a simple explanation of how the code works but for a detailed example head over to the plugin's GitHub repo.
The example below is going to take an image then select the dominant colors from it and then display the colors
First, we add the required imports
import 'package:palette_generator/palette_generator.dart';
After that let's create the main application class.
class MyApp extends StatelessWidget {
// This widget is the root of your application.
#override
Widget build(BuildContext context) {
return MaterialApp(
...
home: const HomePage(
title: 'Colors from image',
image: AssetImage('assets/images/artwork_default.png',),
imageSize: Size(256.0, 170.0),
...
),
);
}
}
In the image field above, place the image that you want to extract the dominant colors from, i used the image shown here.
Next, we create the HomePage class
#immutable
class HomePage extends StatefulWidget {
/// Creates the home page.
const HomePage({
Key key,
this.title,
this.image,
this.imageSize,
}) : super(key: key);
final String title; //App title
final ImageProvider image; //Image provider to load the colors from
final Size imageSize; //Image dimensions
#override
_HomePageState createState() {
return _HomePageState();
}
}
Lets create the _HomePageState too
class _HomePageState extends State<HomePage> {
Rect region;
PaletteGenerator paletteGenerator;
final GlobalKey imageKey = GlobalKey();
#override
void initState() {
super.initState();
region = Offset.zero & widget.imageSize;
_updatePaletteGenerator(region);
}
Future<void> _updatePaletteGenerator(Rect newRegion) async {
paletteGenerator = await PaletteGenerator.fromImageProvider(
widget.image,
size: widget.imageSize,
region: newRegion,
maximumColorCount: 20,
);
setState(() {});
}
#override
Widget build(BuildContext context) {
return Scaffold(
backgroundColor: _kBackgroundColor,
appBar: AppBar(
title: Text(widget.title),
),
body: Column(
mainAxisSize: MainAxisSize.max,
mainAxisAlignment: MainAxisAlignment.start,
crossAxisAlignment: CrossAxisAlignment.center,
children: <Widget>[
new AspectRatio(
aspectRatio: 15 / 15,
child: Image(
key: imageKey,
image: widget.image,
),
),
Expanded(child: Swatches(generator: paletteGenerator)),
],
),
);
}
}
The code above just lays out the image and the Swatches which is a class defined below. In initState, we first select a region which the colors will be derived from which in our case is the whole image.
After that we create a class Swatches which receives a PalleteGenerator and draws the swatches for it.
class Swatches extends StatelessWidget {
const Swatches({Key key, this.generator}) : super(key: key);
// The PaletteGenerator that contains all of the swatches that we're going
// to display.
final PaletteGenerator generator;
#override
Widget build(BuildContext context) {
final List<Widget> swatches = <Widget>[];
//The generator field can be null, if so, we return an empty container
if (generator == null || generator.colors.isEmpty) {
return Container();
}
//Loop through the colors in the PaletteGenerator and add them to the list of swatches above
for (Color color in generator.colors) {
swatches.add(PaletteSwatch(color: color));
}
return Column(
mainAxisAlignment: MainAxisAlignment.center,
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.center,
children: <Widget>[
//All the colors,
Wrap(
children: swatches,
),
//The colors with ranking
Container(height: 30.0),
PaletteSwatch(label: 'Dominant', color: generator.dominantColor?.color),
PaletteSwatch(
label: 'Light Vibrant', color: generator.lightVibrantColor?.color),
PaletteSwatch(label: 'Vibrant', color: generator.vibrantColor?.color),
PaletteSwatch(
label: 'Dark Vibrant', color: generator.darkVibrantColor?.color),
PaletteSwatch(
label: 'Light Muted', color: generator.lightMutedColor?.color),
PaletteSwatch(label: 'Muted', color: generator.mutedColor?.color),
PaletteSwatch(
label: 'Dark Muted', color: generator.darkMutedColor?.color),
],
);
}
}
After that lets create a PaletteSwatch class. A palette swatch is just a square of color with an optional label
#immutable
class PaletteSwatch extends StatelessWidget {
// Creates a PaletteSwatch.
//
// If the [color] argument is omitted, then the swatch will show a
// placeholder instead, to indicate that there is no color.
const PaletteSwatch({
Key key,
this.color,
this.label,
}) : super(key: key);
// The color of the swatch. May be null.
final Color color;
// The optional label to display next to the swatch.
final String label;
#override
Widget build(BuildContext context) {
// Compute the "distance" of the color swatch and the background color
// so that we can put a border around those color swatches that are too
// close to the background's saturation and lightness. We ignore hue for
// the comparison.
final HSLColor hslColor = HSLColor.fromColor(color ?? Colors.transparent);
final HSLColor backgroundAsHsl = HSLColor.fromColor(_kBackgroundColor);
final double colorDistance = math.sqrt(
math.pow(hslColor.saturation - backgroundAsHsl.saturation, 2.0) +
math.pow(hslColor.lightness - backgroundAsHsl.lightness, 2.0));
Widget swatch = Padding(
padding: const EdgeInsets.all(2.0),
child: color == null
? const Placeholder(
fallbackWidth: 34.0,
fallbackHeight: 20.0,
color: Color(0xff404040),
strokeWidth: 2.0,
)
: Container(
decoration: BoxDecoration(
color: color,
border: Border.all(
width: 1.0,
color: _kPlaceholderColor,
style: colorDistance < 0.2
? BorderStyle.solid
: BorderStyle.none,
)),
width: 34.0,
height: 20.0,
),
);
if (label != null) {
swatch = ConstrainedBox(
constraints: const BoxConstraints(maxWidth: 130.0, minWidth: 130.0),
child: Row(
mainAxisAlignment: MainAxisAlignment.start,
children: <Widget>[
swatch,
Container(width: 5.0),
Text(label),
],
),
);
}
return swatch;
}
}
Hope this helps, thank you.
//////////////////////////////
//
// 2019, roipeker.com
// screencast - demo simple image:
// https://youtu.be/EJyRH4_pY8I
//
// screencast - demo snapshot:
// https://youtu.be/-LxPcL7T61E
//
//////////////////////////////
import 'dart:async';
import 'dart:typed_data';
import 'dart:ui' as ui;
import 'dart:math';
import 'package:flutter/material.dart';
import 'package:flutter/rendering.dart';
import 'package:image/image.dart' as img;
import 'package:flutter/services.dart' show rootBundle;
void main() => runApp(const MaterialApp(home: MyApp()));
class MyApp extends StatefulWidget {
const MyApp({Key? key}) : super(key: key);
#override
State<StatefulWidget> createState() => _MyAppState();
}
class _MyAppState extends State<MyApp> {
String imagePath = 'assets/5.jpg';
GlobalKey imageKey = GlobalKey();
GlobalKey paintKey = GlobalKey();
// CHANGE THIS FLAG TO TEST BASIC IMAGE, AND SNAPSHOT.
bool useSnapshot = true;
// based on useSnapshot=true ? paintKey : imageKey ;
// this key is used in this example to keep the code shorter.
late GlobalKey currentKey;
final StreamController<Color> _stateController = StreamController<Color>();
//late img.Image photo ;
img.Image? photo;
#override
void initState() {
currentKey = useSnapshot ? paintKey : imageKey;
super.initState();
}
#override
Widget build(BuildContext context) {
final String title = useSnapshot ? "snapshot" : "basic";
return SafeArea(
child: Scaffold(
appBar: AppBar(title: Text("Color picker $title")),
body: StreamBuilder(
initialData: Colors.green[500],
stream: _stateController.stream,
builder: (buildContext, snapshot) {
Color selectedColor = snapshot.data as Color ?? Colors.green;
return Stack(
children: <Widget>[
RepaintBoundary(
key: paintKey,
child: GestureDetector(
onPanDown: (details) {
searchPixel(details.globalPosition);
},
onPanUpdate: (details) {
searchPixel(details.globalPosition);
},
child: Center(
child: Image.asset(
imagePath,
key: imageKey,
//color: Colors.red,
//colorBlendMode: BlendMode.hue,
//alignment: Alignment.bottomRight,
fit: BoxFit.contain,
//scale: .8,
),
),
),
),
Container(
margin: const EdgeInsets.all(70),
width: 50,
height: 50,
decoration: BoxDecoration(
shape: BoxShape.circle,
color: selectedColor!,
border: Border.all(width: 2.0, color: Colors.white),
boxShadow: [
const BoxShadow(
color: Colors.black12,
blurRadius: 4,
offset: Offset(0, 2))
]),
),
Positioned(
child: Text('${selectedColor}',
style: const TextStyle(
color: Colors.white,
backgroundColor: Colors.black54)),
left: 114,
top: 95,
),
],
);
}),
),
);
}
void searchPixel(Offset globalPosition) async {
if (photo == null) {
await (useSnapshot ? loadSnapshotBytes() : loadImageBundleBytes());
}
_calculatePixel(globalPosition);
}
void _calculatePixel(Offset globalPosition) {
RenderBox box = currentKey.currentContext!.findRenderObject() as RenderBox;
Offset localPosition = box.globalToLocal(globalPosition);
double px = localPosition.dx;
double py = localPosition.dy;
if (!useSnapshot) {
double widgetScale = box.size.width / photo!.width;
print(py);
px = (px / widgetScale);
py = (py / widgetScale);
}
int pixel32 = photo!.getPixelSafe(px.toInt(), py.toInt());
int hex = abgrToArgb(pixel32);
_stateController.add(Color(hex));
}
Future<void> loadImageBundleBytes() async {
ByteData imageBytes = await rootBundle.load(imagePath);
setImageBytes(imageBytes);
}
Future<void> loadSnapshotBytes() async {
RenderRepaintBoundary boxPaint =
paintKey.currentContext!.findRenderObject() as RenderRepaintBoundary;
//RenderObject? boxPaint = paintKey.currentContext.findRenderObject();
ui.Image capture = await boxPaint.toImage();
ByteData? imageBytes =
await capture.toByteData(format: ui.ImageByteFormat.png);
setImageBytes(imageBytes!);
capture.dispose();
}
void setImageBytes(ByteData imageBytes) {
List<int> values = imageBytes.buffer.asUint8List();
photo;
photo = img.decodeImage(values)!;
}
}
// image lib uses uses KML color format, convert #AABBGGRR to regular #AARRGGBB
int abgrToArgb(int argbColor) {
int r = (argbColor >> 16) & 0xFF;
int b = argbColor & 0xFF;
return (argbColor & 0xFF00FF00) | (b << 16) | r;
}