How to make chat bubble shape use path ?
expect :
class ChatBubbleShapeBorder extends ShapeBorder {
final double arrowWidth;
final double arrowHeight;
final double arrowArc;
final double radius;
ChatBubbleShapeBorder({
this.radius = 12.0,
this.arrowWidth = 16.0,
this.arrowHeight = 8.0,
this.arrowArc = 0.5,
}) : assert(arrowArc <= 1.0 && arrowArc >= 0.0);
#override
EdgeInsetsGeometry get dimensions => EdgeInsets.only(bottom: arrowHeight);
#override
Path getInnerPath(Rect rect, {TextDirection? textDirection}) =>
null ?? Path();
#override
Path getOuterPath(Rect rect, {TextDirection? textDirection}) {
rect = Rect.fromPoints(
rect.topLeft, rect.bottomRight - Offset(0, arrowHeight));
double x = arrowWidth, y = arrowHeight, r = 1 - arrowArc;
return Path()
..addRRect(RRect.fromRectAndRadius(rect, Radius.circular(radius)))
..moveTo(rect.topRight.dx - 30, rect.topRight.dy)
..relativeLineTo(-x / 2 * r, -y * r)
..relativeQuadraticBezierTo(
-x / 2 * (1 - r), -y * (1 - r), -x * (1 - r), 0)
..relativeLineTo(-x / 2 * r, y * r);
}
#override
void paint(Canvas canvas, Rect rect, {TextDirection? textDirection}) {}
#override
ShapeBorder scale(double t) => this;
}
and after tries here is my shot
actual:
i try move arrow to left and right for chat bubble.
could you help me to move arrow?
update :
i try to change rect point and move position of arrow. look good, But, i get problem with arrow
rect =
Rect.fromPoints(rect.topLeft + Offset(arrowWidth, 0), rect.bottomRight - Offset(arrowWidth, 0));
double x = arrowWidth, y = arrowHeight, r = 1 - arrowArc;
return Path()
..addRRect(RRect.fromRectAndRadius(rect, Radius.circular(radius)))
..moveTo(rect.topRight.dx + arrowWidth, rect.topRight.dy + 30)
..relativeLineTo(-x / 2 * r, -y * r)
..relativeQuadraticBezierTo(
-x / 2 * (1 - r), -y * (1 - r), -x * (1 - r), 0)
..relativeLineTo(-x / 2 * r, y * r);
shot :
Related
I want to make circle container with wavy border. For that, I used flutter_custom_clippers 2.0.0 package's StarClipper with some changes. But, I can't make it same as I want. Can anyone help?
If any other way is available then please let me know. Thank you in advance :)
Here is my code:
import 'dart:math' as math;
import 'package:flutter/widgets.dart';
/// Clip widget in star shape
class StarClipper extends CustomClipper<Path> {
StarClipper(this.numberOfPoints);
/// The number of points of the star
final int numberOfPoints;
late int counter = 0;
#override
Path getClip(Size size) {
double width = size.width;
double halfWidth = width / 2;
double bigRadius = halfWidth;
double radius = halfWidth / 1.11;
double degreesPerStep = _degToRad(360 / numberOfPoints) as double;
double halfDegreesPerStep = degreesPerStep / 2;
var path = Path();
double max = 2 * math.pi;
path.moveTo(width, halfWidth);
debugPrint('halfWidth: $halfWidth');
debugPrint('bigRadius: $bigRadius');
debugPrint('degreesPerStep: $degreesPerStep');
debugPrint('max: $max');
path.moveTo(
halfWidth + radius * math.cos(0 + halfDegreesPerStep),
halfWidth + radius * math.sin(0 + halfDegreesPerStep),
);
for (double step = 0; step < max + 1; step += degreesPerStep) {
debugPrint('math.cos(step): ${math.cos(step)}');
debugPrint('math.sin(step): ${math.sin(step)}');
debugPrint(
'math.cos(step + halfDegreesPerStep): ${math.cos(step + halfDegreesPerStep)}');
debugPrint(
'math.sin(step + halfDegreesPerStep): ${math.sin(step + halfDegreesPerStep)}');
debugPrint('------step-------: $step');
debugPrint('x 1: ${halfWidth + bigRadius * math.cos(step)}');
debugPrint('y 1: ${halfWidth + bigRadius * math.sin(step)}');
debugPrint(
'x 2: ${halfWidth + radius * math.cos(step + halfDegreesPerStep)}');
debugPrint(
'y 2: ${halfWidth + radius * math.sin(step + halfDegreesPerStep)}');
/*path.quadraticBezierTo(
halfWidth + bigRadius * math.cos(step),
halfWidth + bigRadius * math.sin(step),
halfWidth + radius * math.cos(step + halfDegreesPerStep),
halfWidth + radius * math.sin(step + halfDegreesPerStep),
);*/
if (counter % 2 == 0) {
path.quadraticBezierTo(
halfWidth + bigRadius * math.cos(step),
halfWidth + bigRadius * math.sin(step),
halfWidth + radius * math.cos(step + halfDegreesPerStep),
halfWidth + radius * math.sin(step + halfDegreesPerStep),
);
} else {
path.quadraticBezierTo(
halfWidth + radius * math.cos(step),
halfWidth + radius * math.sin(step),
halfWidth + bigRadius * math.cos(step + halfDegreesPerStep),
halfWidth + bigRadius * math.sin(step + halfDegreesPerStep),
);
}
counter++;
}
path.close();
return path;
}
num _degToRad(num deg) => deg * (math.pi / 180.0);
#override
bool shouldReclip(CustomClipper<Path> oldClipper) {
StarClipper oldie = oldClipper as StarClipper;
return numberOfPoints != oldie.numberOfPoints;
}
}
Output:
I want like below
I achieved it using the same StarClipper file of flutter_custom_clippers 2.0.0 package's by applying some minor changes in above code.
double outerCurveRadius = width / 2.08;
double innerCurveRadius = width / 2.42;
for (double step = 0; step < max + 1; step += degreesPerStep) {
if (counter % 2 == 0) {
path.quadraticBezierTo(
halfWidth + outerCurveRadius * math.cos(step),
halfWidth + outerCurveRadius * math.sin(step),
halfWidth + radius * math.cos(step + halfDegreesPerStep),
halfWidth + radius * math.sin(step + halfDegreesPerStep),
);
} else {
path.quadraticBezierTo(
halfWidth + innerCurveRadius * math.cos(step),
halfWidth + innerCurveRadius * math.sin(step),
halfWidth + radius * math.cos(step + halfDegreesPerStep),
halfWidth + radius * math.sin(step + halfDegreesPerStep),
);
}
counter++;
}
Output:
Check this package flutter_custom_clippers. I think it's helpful.
I am trying to implement custom curved shape like below left image.
Below is the code what I have achieved so far by using quadraticBezierTo:
class CustomNotchedShape extends NotchedShape {
final BuildContext context;
const CustomNotchedShape(this.context);
#override
Path getOuterPath(Rect host, Rect? guest) {
const radius = 110.0;
const lx = 40.0;
const ly = 20;
const bx = 10.0;
const by = 50.0;
var x = (MediaQuery.of(context).size.width - radius) / 2 - lx;
return Path()
..moveTo(host.left, host.top)
..lineTo(x, host.top)
..quadraticBezierTo(x + bx, host.top, x += lx, host.top - ly)
..quadraticBezierTo(
x + radius / 2, host.top - by, x += radius, host.top - ly)
..quadraticBezierTo((x += lx) - bx, host.top, x, host.top)
..lineTo(host.right, host.top)
..lineTo(host.right, host.bottom)
..lineTo(host.left, host.bottom);
}
}
I am trying to implement such animation using AnimatedPositioned widget, where it will move around center point clockwise or counterclockwise by a certain degree on event. Basically like planet around the sun.
I am using this function to calculate movement:
double x = 0;
double y = 0;
double radius;
void updatePosition({
#required double centerX,
#required double centerY,
}) {
radius = min(centerX, centerY);
x = centerX + radius * cos(angle * -pi / 90);
y = centerX + radius * sin(angle * -pi / 90);
angle += 5;
}
But i cannot wrap my head around how to adjust initial points in relation to the center of container with given constrains.
using Cairo ;
using Gtk ;
using GLib ;
public class ClockWidget : DrawingArea {
private Time time ;
private int minute_offset ;
private bool dragging ;
public signal void time_changed (int hour, int minute) ;
public ClockWidget () {
add_events (Gdk.EventMask.BUTTON_PRESS_MASK
| Gdk.EventMask.BUTTON_RELEASE_MASK
| Gdk.EventMask.POINTER_MOTION_MASK);
update () ;
Timeout.add (1000, update) ;
set_size_request (100, 100) ;
}
public override bool draw (Cairo.Context cr) {
int y = get_allocated_height () / 2 ;
int x = get_allocated_width () / 2 ;
var radius = double.min (get_allocated_width () / 2,
get_allocated_height () / 2) - 5 ;
// clock back
cr.arc (x, y, radius, 0, 2 * 3.14) ;
cr.set_source_rgb (1, 1, 1) ;
cr.fill_preserve () ;
cr.set_source_rgb (0, 0, 0) ;
cr.stroke () ;
// clock ticks
for (int i = 0; i < 12; i++) {
int inset ;
cr.save () ;
if (i % 3 == 0) {
inset = (int) (0.2 * radius) ;
} else {
inset = (int) (0.1 * radius) ;
cr.set_line_width (0.5 * cr.get_line_width ()) ;
}
cr.move_to (x + (radius - inset) * Math.cos (i * Math.PI / 6),
y + (radius - inset) * Math.sin (i * Math.PI / 6));
cr.line_to (x + radius * Math.cos (i * Math.PI / 6),
y + radius * Math.sin (i * Math.PI / 6));
cr.stroke ();
cr.restore ();
}
// clock hands
var hours = this.time.hour ;
var minutes = this.time.minute + this.minute_offset ;
var seconds = this.time.second ;
/* hour hand: the hour hand is rotated 30 degrees (pi/6r) per hour + 1/2 a degree (pi/360r) per minute */
cr.save () ;
cr.set_line_width (2.5 * cr.get_line_width ()) ;
cr.move_to (x, y) ;
cr.line_to (x + radius / 2 * Math.sin (Math.PI / 6 * hours
+ Math.PI / 360 * minutes),
y + radius / 2 * -Math.cos (Math.PI / 6 * hours
+ Math.PI / 360 * minutes));
cr.stroke ();
cr.restore ();
// minute hand:
// the minute hand is rotated 6 degrees (pi/30 r) per minute
cr.move_to (x, y);
cr.line_to (x + radius * 0.75 * Math.sin (Math.PI / 30 * minutes),
y + radius * 0.75 * -Math.cos (Math.PI / 30 * minutes));
cr.stroke ();
// seconds hand:
// operates identically to the minute hand
cr.save ();
cr.set_source_rgb (1, 0, 0); // red
cr.move_to (x, y);
cr.line_to (x + radius * 0.7 * Math.sin (Math.PI / 30 * seconds),
y + radius * 0.7 * -Math.cos (Math.PI / 30 * seconds));
cr.stroke ();
cr.restore ();
return false ;
}
public override bool button_press_event (Gdk.EventButton event) {
var minutes = this.time.minute + this.minute_offset;
// From
// http://mathworld.wolfram.com/Point-LineDistance2-Dimensional.html
var px = event.x - get_allocated_width () / 2;
var py = get_allocated_height () / 2 - event.y;
var lx = Math.sin (Math.PI / 30 * minutes);
var ly = Math.cos (Math.PI / 30 * minutes);
var u = lx * px + ly * py;
// on opposite side of origin
if (u < 0) {
return false;
}
var d2 = Math.pow (px - u * lx, 2) + Math.pow (py - u * ly, 2);
if (d2 < 25) { // 5 pixels away from the line
this.dragging = true;
print ("got minute hand\n");
}
return false;
}
public override bool button_release_event (Gdk.EventButton event) {
if (this.dragging) {
this.dragging = false;
emit_time_changed_signal ((int) event.x, (int) event.y);
}
return false;
}
public override bool motion_notify_event (Gdk.EventMotion event) {
if (this.dragging) {
emit_time_changed_signal ((int) event.x, (int) event.y);
}
return false;
}
private void emit_time_changed_signal (int x, int y) {
// decode the minute hand
// normalise the coordinates around the origin
x -= get_allocated_width () / 2;
y -= get_allocated_height () / 2;
// phi is a bearing from north clockwise, use the same geometry as
// we did to position the minute hand originally
var phi = Math.atan2 (x, -y);
if (phi < 0) {
phi += Math.PI * 2;
}
var hour = this.time.hour;
var minute = (int) (phi * 30 / Math.PI);
// update the offset
this.minute_offset = minute - this.time.minute;
redraw_canvas ();
time_changed (hour, minute);
}
private bool update () {
// update the time
this.time = Time.local (time_t ());
redraw_canvas ();
return true; // keep running this event
}
private void redraw_canvas () {
var window = get_window ();
if (null == window) {
return;
}
var region = window.get_clip_region ();
// redraw the cairo canvas completely by exposing it
window.invalidate_region (region, true);
window.process_updates (true);
}
static int main (string[] args) {
Gtk.init (ref args);
var window = new Window ();
var clock = new ClockWidget ();
window.add (clock);
window.destroy.connect (Gtk.main_quit);
window.show_all ();
Gtk.main ();
return 0;
}
}
How do I solve it??
This is the error displayed:
/usr/bin/ld: /tmp/ccSTGo5z.o: undefined reference to symbol 'pow##GLIBC_2.2.5'
//lib/x86_64-linux-gnu/libm.so.6: error adding symbols: DSO missing from command line
collect2: error: ld returned 1 exit status
error: cc exited with status 256
Compilation failed: 1 error(s), 1 warning(s)
You need to link against libm which is the library which provides the pow() function, which you’re using.
Typically this is achieved by passing -lm in your linker flags. I can’t give a more concrete answer because you haven’t said what build system you’re using.
valac -X -lm --pkg gtk+-3.0 clock_widget.vala
I had to compile it with this code.
I am trying to make a custom curved app bar, look like the example below
and after many tries here is my shot
source code :
class CustomShapeBorder extends ContinuousRectangleBorder {
#override
Path getOuterPath(Rect rect, {TextDirection textDirection}) {
final double innerCircleRadius = 150.0;
Path path = Path();
path.lineTo(0, rect.height);
path.cubicTo(rect.width / 1.5 - 40, rect.height + innerCircleRadius - 40, rect.width / 1.5 + 40,
rect.height + innerCircleRadius - 40, rect.width / 1.5 + 75, rect.height + 50);
path.quadraticBezierTo(
rect.width / 1.5 + (innerCircleRadius / 2) + 30, rect.height + 35, rect.width, rect.height);
path.lineTo(rect.width, 0.0);
path.close();
return path;
}
}
is there an easy way to make it for example SVG.
I could come up with this result:
Here is the Path:
double x = 150, y = 45, r = 0.5;
Path path = Path()
..addRRect(RRect.fromRectAndCorners(rect))
..moveTo(rect.bottomRight.dx - 30, rect.bottomCenter.dy)
..relativeQuadraticBezierTo(((-x / 2)+(x/6)) * (1 - r), 0, -x / 2 * r, y * r)
..relativeQuadraticBezierTo(-x / 6 * r, y * (1 - r), -x / 2 * (1 - r), y * (1 - r))
..relativeQuadraticBezierTo(((-x / 2)+(x/6)) * (1 - r), 0, -x / 2 * (1 - r), -y * (1 - r))
..relativeQuadraticBezierTo(-x/6*r , -y * r, -x / 2 * r, -y * r);
And here is a good article to see the basics to make shapes: Paths in Flutter: A Visual Guide