I'm new to flame and flutter and have some questions about PositionComponent's anchor argument.
According to flame help doc: "So if you, for example, wanted to position a child 50 logical pixels above the center of the parent you would do this:"
Future<void> onLoad() async {
final parent = PositionComponent(
position: Vector2(100, 100),
size: Vector2(100, 100),
anchor: Anchor.center,
);
final child = PositionComponent(position: Vector2(0, -50));
await parent.add(child);
}
However, in my experiment, the child component appears at 50 pixels above the top-left corner of its parent.
Below is the snippet of the code I used, with PositionComponent replaced with RectangleComponent in order to visualize the effect of the anchor.
final parent = RectangleComponent(
position: Vector2(100, 100),
size: Vector2(100, 100),
anchor: Anchor.center,
)..setColor(Colors.blue);
add(parent);
final child = RectangleComponent(
position: Vector2(0, -50),
size: Vector2(10, 10),
)..setColor(Colors.yellow);
parent.add(child);
Using Flame 1.6.0 with Flutter 3.7.3 on Windows 11.
Appreciate it if anyone can help point out what I might have done incorrectly. TIA.
Related
I am trying to figure out how to recolour or 'tint' a Sprite using the Flame engine for Flutter. I've tried to add or override a Paint object as described here: How to Add transparency to image.
Using the code below I tried changing the Paint object using a different color than white. The alpha does change, but my Sprite image stays white. My source image is a white png.
void render(Canvas c) {
Sprite spriteImg = Sprite('someImg.png');
rect = Rect.fromLTWH(10, 10, 20, 20);
Paint redPaint = Paint()..color = Colors.red.withOpacity(0.5);
spriteImg.renderRect(c, rect, overridePaint: redPaint);
}
The Flame engine must have a way to tint Sprites that I'm not seeing or using wrong right?
You can use a ColorEffect for that:
yourSpriteComponent
..add(
ColorEffect(
Colors.red,
const Offset(
0.0,
0.5,
), // Means, applies from 0% to 50% of the color
EffectController(
duration: 1.5,
),
),
);
I'm using google_maps_flutter_web flutter's package and I only see onTap, onDrag and onDragged event available in Marker Widget. Are there anyway I could add a callback when I hovering my mouse over the Marker? Because the mouse cursor icon change when it's hovered.
enter image description here
There is no built in onHover or onMouseOver methods for Google map Markers on Flutter web and unfortunately, something like this is unlikely to be prioritized (a similar feature request to add an onLongPress callback to markers is currently p5).
But, with a little ingenuity it's possible to implement this ourselves.
1. Wrap your GoogleMap widget with the MouseRegion and LayoutBuilder widgets
The MouseRegion widget will allow us to track the location of the user's mouse.
The LayoutBuilder widget will give us the dimensions of the GoogleMap so we can eventually translate the position of the mouse to a latitude and longitude on the map.
LayoutBuilder(builder: (context, constraints) {
return MouseRegion(
child: GoogleMap(
minMaxZoomPreference: const MinMaxZoomPreference(10, 18)
mapType: MapType.normal,
initialCameraPosition: mapModel.initialMapLocation(),
onMapCreated: (controller) {
mapModel.onMapCreated(controller));
},
markers: markers
),
);
});
2. Create a variable to store the LatLng value of the mouse position
LatLng? hoverSpot;
void setHoverSpot(LatLng? val) {
hoverSpot = val;
notifyListeners();
}
3. Add an onHover callback to the MouseRegion widget
Whenever the user moves the mouse within the MouseRegion widget, the onHover callback will be called. This callback provides us with a PointerEvent object that holds the position data of the mouse.
This part is a little hairy but stay with me.
Our goal is to convert the local position of the mouse to a LatLng value we can plot on the map. The bulk of the work can be done by the getLatLng method of the GoogleMapController. All we have to do is give it a ScreenCoordinate.
MouseRegion(
onHover: (event) async {
LatLng? position = await mapModel.mapController?.getLatLng(ScreenCoordinate(
x: ((constraints.maxWidth) * (event.localPosition.dx / (constraints.maxWidth))).round(),
y: ((constraints.maxHeight) * (event.localPosition.dy / constraints.maxHeight)).round(),
));
mapModel.setHoverSpot(position);
},
child: GoogleMap(
...
),
);
4. Calculate the distance between the hoverSpot and each of your markers
The geolocator package has a distanceBetween method you can use to calculate the distance between 2 points.
Then based on the zoom level, you can determine if the distance is within an acceptable range to indicate the user is actually hovering over a given marker.
Map<int, int> zoomToDistance = {
10: 4600,
11: 2400,
12: 1250,
13: 770,
14: 410,
15: 180,
16: 80,
17: 30,
18: 15,
};
Map<int, double> zoomToMarkerHeight = {
10: .06,
11: .03,
12: .016,
13: .009,
14: .005,
15: .002,
16: .0008,
17: .0004,
18: .0002,
};
if (hoverSpot != null) {
double distance = Geolocator.distanceBetween(
hoverSpot!.latitude,
hoverSpot!.longitude,
markerLocation.latitude + zoomToMarkerHeight[currentZoomLevel]!,
markerLocation.longitude,
);
// debugPrint('distance: ' + distance.toString());
if (distance < zoomToDistance[currentZoomLevel]!) {
selected = true;
minDistance = distance;
showInfoWindow(
markerId: marker.id,
latLng: LatLng(location.latitude, location.longitude),
);
}
}
5. Show the info window over the marker
https://gfycat.com/whisperedadmiredivorybilledwoodpecker
Is there any way to set parent container's height based on height of what is drawn in the child canvas?
I am using a custom painter like this:
double width = MediaQuery.of(context).size.width;
return Container(
color: Colors.yellow,
height: 240,
width: width,
child: CustomPaint(
painter: ShapePainter(),
),
);
Then ShapePainter() draws different shapes (1 shape for each canvas in the list).
But as you can see, some shapes like 2nd rectangle, take twice the space they actually need.
I can calculate height of a shape inside ShapePainter() easily,
but I have no idea how to apply it to its parent's height. (Except calling an insta-setState() from child, but there should be a better way, because this may flicker for one frame)
Thanks for any help!
Flutter has several phases that it goes through when creating a frame, including building, layout, and painting. The size of widgets is determined during the layout phase. So you can't set the height based it what was painted. (Except, as you've said, by using something like setState to generate a new frame.) You'll have to determine the the size you want before any painting has happened. For example, you can give your CustomPainter a getter to provide the shape:
class ShapePainter extends CustomPainter {
ShapePainter({#required this.shape});
final Shape shape;
void paint(Canvas canvas, Size size) {
// the painting
}
Size get size {
// determine size based on shape
}
}
...
Widget build(BuildContext context) {
final shapePainter = ShapePainter();
return Container(
color: Colors.yellow,
height: shapePainter.size.height,
align: Alignment.center,
child: CustomPaint(
painter: shapePainter,
),
);
}
Introduction
I draw a custom Tab-bar using Flutter's CustomPaint Widget it looks like this:
In Addition, this is the code I used to draw the widget :
class TabBarPainter extends CustomPainter {
final Color paintColor = Colors.redAccent
#override
void paint(Canvas canvas, Size size) {
/// the thing that I called as a deleter circle is an imaginary circle that I used to delete a semicircle from my rectangular shape
var diameter = 80; /// X value is the diameter(R) of the deleter circle;
double topSpace = 2;
double startCurve = (size.width-diameter)/2; /// Start point of the curve
double endCurve = startCurve+diameter; // End Point of the curve
/// Some math about my cubic bezier curve
double xValueInset = diameter * 0.05;
double yValueOffset = (diameter/2) * 4.0 / 3.0;
Path path = Path();
Paint paint = Paint();
/// Points to make a semicircle approximation using Bezier Curve
var firstendPoint = new Offset(endCurve, topSpace);
var controlPoint1 = new Offset(startCurve+xValueInset,yValueOffset+topSpace);
var controlPoint2 = new Offset((diameter-xValueInset)+startCurve,yValueOffset+topSpace);
//! Start sketching Shape
path.moveTo(0.0,topSpace);
path.lineTo(startCurve, topSpace);
path.cubicTo(controlPoint1.dx, controlPoint1.dy,
controlPoint2.dx, controlPoint2.dy,
firstendPoint.dx, firstendPoint.dy);
path.lineTo(size.width, topSpace);
path.lineTo(size.width, size.height);
path.lineTo(0.0, size.height);
path.close();
//! End sketching Shape
paint.color = paintColor;
canvas.drawPath(path, paint);
}
#override
bool shouldRepaint(TabBarPainter oldDelegate) => oldDelegate != this;
}
Experimenting
I want to apply a shodow on my CustomDrawen Shape so I tried 2 methods:
First Method: I used a classical container and boxshadow to shadow it and it looked like this:
I used the following code :
Container(
decoration: BoxDecoration(
boxShadow: [
BoxShadow(
color : Colors.black.withOpacity(0.30),
blurRadius: 3,
ffset: new Offset(0, -3)
)
]
),
child: CustomPaint(
painter: TabBarPainter(),
),
Second Method : I drew a similar black colored(opacity(0.15)) shape
and it looked like this:
I added the following code to my CustomPainter in order to draw this shape:
final Color shadowColor = Colors.black.withOpacity(0.15);
Path shadowPath = Path();
Paint shadowPaint = Paint();
/// Points to make a semicircle approximation using Bezier Curve
var shadowfirstendPoint = new Offset(endCurve-topSpace, 0.0);
var shadowcontrolPoint1 = new Offset(startCurve+xValueInset+topSpace,yValueOffset);
var shadowcontrolPoint2 = new Offset((diameter-xValueInset)+startCurve-topSpace,yValueOffset);
//! Start sketching Shadow
shadowPath.lineTo(startCurve+topSpace, 0.0);
shadowPath.cubicTo(shadowcontrolPoint1.dx, shadowcontrolPoint1.dy,
shadowcontrolPoint2.dx, shadowcontrolPoint2.dy,
shadowfirstendPoint.dx, shadowfirstendPoint.dy);
shadowPath.lineTo(size.width, 0.0);
shadowPath.lineTo(size.width, size.height);
shadowPath.lineTo(0.0, size.height);
shadowPath.close();
//! End Sketching Shadow
shadowPaint.color = shadowColor;
canvas.drawPath(shadowPath, shadowPaint);
Problem
In the first method, blank area - at the top center of widget - was filled with shadow
In the second method, shadow was not realistic because even though it had low opacity, it did not had a blur and I could not find a method to make it blurred
Conclusion
I need another way to properly shadow my widget or add blur to my shadow-like custom drawn shape
Since flutter is doing this to every type of widget they created, it seems possible
I would be thankful to any kind of helper
Regards
An easier way I found was to offset the path
canvas.drawShadow(path.shift(Offset(0, -5)), Colors.black, 2.0, true);
As #pskink mentioned, there is a method of canvas called drawShadow
so changing my drawing method for my shadowPath to :
canvas.drawShadow(shadowPath, Colors.black, 2.0, true);
Will solve the problem and the output will be something like this :
Thanks for everyone !!
It's clear how to draw a shadow with an elevation property but what if I want to have the shadow centered for example?
Found a solution:
I can simply go into the source code for the BoxShadow widget and apply the path properties they used to my own paths.
Here's the source code.
Here's the code that I used to create a shadow for a custom path (rather than a circle or rectangle with a border radius) that allowed me to create a custom shadow rather than using the elevation preset.
canvas.drawPath(
Path()
..addRect(
Rect.fromPoints(Offset(-15, -15), Offset(size.width+15, size.height+15)))
..addOval(
Rect.fromPoints(Offset(0, 0), Offset(size.width, size.height)))
..fillType = PathFillType.evenOdd,
Paint()
..color= Colors.black.withAlpha(shadowAlpha)
..maskFilter = MaskFilter.blur(BlurStyle.normal, convertRadiusToSigma(3))
);
static double convertRadiusToSigma(double radius) {
return radius * 0.57735 + 0.5;
}
To draw shadow on CustomPaint you can use painter.
CustomPaint(
painter: BoxShadowPainter(),
child: ClipPath(
clipper: MyClipper(), //my CustomClipper
child: Container(), // my widgets inside
)));
check my answer here