I would like to draw the ticket shape in the picture using Path in Jetpack Compose
Path().apply
Help is appreciated.
class TicketShape(private val cornerRadius: Float) : Shape {
override fun createOutline(
size: Size,
layoutDirection: LayoutDirection,
density: Density
): Outline {
return Outline.Generic(
// Draw your custom path here
path = drawTicketPath(size = size, cornerRadius = cornerRadius)
)
}
}
fun drawTicketPath(size: Size, cornerRadius: Float): Path {
return Path().apply {
reset()
// Top left arc
arcTo(
rect = Rect(
left = -cornerRadius,
top = -cornerRadius,
right = cornerRadius,
bottom = cornerRadius
),
startAngleDegrees = 90.0f,
sweepAngleDegrees = -90.0f,
forceMoveTo = false
)
lineTo(x = size.width - cornerRadius, y = 0f)
// Top right arc
arcTo(
rect = Rect(
left = size.width - cornerRadius,
top = -cornerRadius,
right = size.width + cornerRadius,
bottom = cornerRadius
),
startAngleDegrees = 180.0f,
sweepAngleDegrees = -90.0f,
forceMoveTo = false
)
lineTo(x = size.width, y = size.height - cornerRadius)
// Bottom right arc
arcTo(
rect = Rect(
left = size.width - cornerRadius,
top = size.height - cornerRadius,
right = size.width + cornerRadius,
bottom = size.height + cornerRadius
),
startAngleDegrees = 270.0f,
sweepAngleDegrees = -90.0f,
forceMoveTo = false
)
lineTo(x = cornerRadius, y = size.height)
// Bottom left arc
arcTo(
rect = Rect(
left = -cornerRadius,
top = size.height - cornerRadius,
right = cornerRadius,
bottom = size.height + cornerRadius
),
startAngleDegrees = 0.0f,
sweepAngleDegrees = -90.0f,
forceMoveTo = false
)
lineTo(x = 0f, y = cornerRadius)
close()
}
}
Now use it with any Composable,
MyComp(
Modifier.clip(
TicketShape( 24.dp.toPx() )
)
)
Source: https://juliensalvi.medium.com/custom-shape-with-jetpack-compose-1cb48a991d42
We're basically inheriting from a Shape object, which is something the clip modifiers and other shape parameters accept to render the desired shape. Other examples are RectangleShape, CircleShape, RoundedCornerShape, etc, which are pre-built for compose. You need a distinct shape and so, you require to create your own Shape object. You didn't exactly need to create the class separately if you don't wish to use it again and again. In that case, an object at the place of usage should have sufficed, but it appears that it is not just a decoration so you might wanna create the class itself to not have to create the same objects at multiple places.
Related
I am trying to make a rectangle and an arc attached to rectangle's bottom. I have used the size provided by the drawScope to lay the drawings on screen but I am unable to get why there is an unnecessary gap between the two drawings even if the specified topLeft parameter of the arc is equal to the height of the rectangle drawn.
Canvas(
modifier = Modifier.fillMaxWidth()
.height(200.dp)
){
drawRect(
color = Color(0xFFEF3125),
topLeft = Offset(0f, 0f),
size = Size(this.size.width, this.size.height.times(0.75f))
)
drawArc(
color = Color(0xFFEF3125),
startAngle = 0f,
sweepAngle = 180f,
useCenter = false,
topLeft = Offset(
0f, this.size.height.times(0.75f)
)
)
}
There is a gap because when you draw an Arc you use a rectangle as refefrence but you draw arc to half of the this rectangle so you need to offset up as much as the half of the height of the rectangle that you draw arc into
#Composable
private fun ArcSample() {
Canvas(
modifier = Modifier.fillMaxWidth()
.height(200.dp)
){
drawRect(
color = Color(0xFFEF3125),
size = Size(this.size.width, this.size.height.times(0.75f)),
style = Stroke(4.dp.toPx())
)
drawArc(
color = Color(0xFFEF3125),
startAngle = 0f,
sweepAngle = 180f,
useCenter = false,
size = Size(size.width, size.height.times(.25f)),
topLeft = Offset(
0f, this.size.height.times(0.625f)
),
style = Stroke(4.dp.toPx())
)
}
}
I used Stroke style to show bounds, it's for demonstration.
#Composable
private fun ArcSample2() {
Canvas(
modifier = Modifier.fillMaxWidth()
.height(200.dp)
){
drawRect(
color = Color(0xFFEF3125),
size = Size(this.size.width, this.size.height.times(0.75f))
)
val sizeCoefficient = 0.25f
drawArc(
color = Color(0xFFEF3125),
startAngle = 0f,
sweepAngle = 360f,
useCenter = false,
size = Size(size.width, size.height.times(sizeCoefficient)),
topLeft = Offset(
0f, this.size.height.times(0.75f-sizeCoefficient/2f)
)
)
}
}
I have created measure demo which allow to put multiple points and show distance between them. which works fine
I want to show preview that what so far has been drawn in real world to the UIView using UIBezierPath . Just like http://armeasure.com/
I have tried many things to achieve this but I couldn't find any right way to do it.
if self.linkList.count == 1 {
bezierPath.removeAllPoints()
bezierPath.move(to: CGPoint(x: 10,y: 10))
} else {
guard self.linkList.count > 1 ,let object2 = self.linkList.lastNode, let object1 = self.linkList.lastNode?.previous else {return}
let value = self.getMeasurementXandYBetween(vector1: object1.node.mainNode.position, and: object2.node.mainNode.position)
print(value)
let x = Double((object1.node.mainNode.position.x + value ) * 377.9527559055 )
let y = Double((object1.node.mainNode.position.y + value) * 377.9527559055)
let pointCoordinates = CGPoint(x: x , y: y)
print("x : Y ",x,y)
bezierPath.addLine(to: pointCoordinates)
}
shapeLayer.removeFromSuperlayer()
shapeLayer.path = bezierPath.cgPath
shapeLayer.lineWidth = 0.5
shapeLayer.fillColor = UIColor.clear.cgColor
shapeLayer.strokeColor = UIColor.red.cgColor
shapeLayer.position = CGPoint(x: 0, y: 0)
self.viewToDraw.layer.addSublayer(shapeLayer)
func getMeasurementXandYBetween(vector1:SCNVector3, and vector2:SCNVector3) -> Float {
return sqrtf((vector1.x - vector2.x) * (vector1.x - vector2.x) + (vector1.y - vector2.y) * (vector1.y - vector2.y))
}
The logic I used (which is not working is) Location of previous node + distance I got from getMeasurementXandYBetween multiply by 377.
Please suggest a hint or any other solution
You can get the coordinates of any point in screen-space by using the projectPoint() on your SCNSceneRenderer.
This will give you a vector with 3 elements, build your CGPoint using the first two and build your shape from those points.
I am pretty new to iOS development and I am trying to display a 10x10 grid inside a UIView respecting its bounds and I would like that the circles would be calculated based on the available width/height of the device.
What I tried so far without luck:
func setUpPoints() {
let matrixSize = 10
let diameter = (min(painelView.frame.size.width, painelView.frame.size.height) / CGFloat(matrixSize + 1)).rounded(.down)
let radius = diameter / 2
for i in 0...matrixSize {
for j in 0...matrixSize {
let x = CGFloat(i) * diameter + radius
let y = CGFloat(j) * diameter + radius
let frame = CGRect(x: x, y: y, width: diameter, height: diameter)
let circle = Circle(frame: frame)
circle.tag = j * matrixSize + i + 1
painelView.addSubview(circle)
}
}
}
My goal is to distribute the circles inside the gray rectangle proportionally so it will look like the Android pattern lock screen:
Can someone please give me some pointers?
Thanks.
If I understand what you are trying to do, then the following line:
let radius = (painelView.frame.size.width + painelView.frame.size.height) / CGFloat(matrixSize * 2)
should be:
let radius = (min(painelView.frame.size.width, painelView.frame.size.height) / CGFloat(matrixSize + 1)).rounded(.down)
The above change will allow the "square" of circles fit within whichever is smaller - the view's width or height, allowing for a gap around the "square" equal to half the diameter of each circle.
You also need to change both loops to start with 0.
for i in 0..<matrixSize {
for j in 0..<matrixSize {
BTW - your radius variable is really the diameter. And gap is really the radius.
The following code provides a border around the square of circles and it includes some space between the circles. Adjust as needed.
func setUpPoints() {
let matrixSize = 10
let borderRatio = CGFloat(0.5) // half a circle diameter - change as desired
let gapRatio = CGFloat(0.25) // quarter circle diameter - change as desired
let squareSize = min(painelView.frame.size.width, painelView.frame.size.height)
let diameter = (squareSize / (CGFloat(matrixSize) + 2 * borderRatio + CGFloat(matrixSize - 1) * gapRatio)).rounded(.down)
let centerToCenter = (diameter + diameter * gapRatio).rounded(.down)
let borderSize = (diameter * borderRatio).rounded()
for i in 0..<matrixSize {
for j in 0..<matrixSize {
let x = CGFloat(i) * centerToCenter + borderSize
let y = CGFloat(j) * centerToCenter + borderSize
let frame = CGRect(x: x, y: y, width: diameter, height: diameter)
let circle = Circle(frame: frame)
circle.tag = j * matrixSize + i + 1
painelView.addSubview(circle)
}
}
}
I am using on device image recognition from Catchoom CraftAR and working with the example available on Github https://github.com/Catchoom/craftar-example-ios-on-device-image-recognition.
The image recognition works, I would like to use the matchBoundingBox to draw some squares on all the 4 corners. Somehow the calculations I am doing are not working, I have based them on this article:
http://support.catchoom.com/customer/portal/articles/1886553-obtain-the-bounding-boxes-of-the-results-of-image-recognition
The square views are added to the scanning overlay and this is how I am calculating the points where to add the 4 views:
CraftARSearchResult *bestResult = [results objectAtIndex:0];
BoundingBox *box = bestResult.matchBoundingBox;
float w = self._preview.frame.size.width;
float h = self._preview.frame.size.height;
CGPoint tr = CGPointMake(w * box.topRightX , h * box.topRightY);
CGPoint tl = CGPointMake(w * box.topLeftX, h * box.topLeftY);
CGPoint br = CGPointMake(w * box.bottomRightX, h * box.bottomRightY);
CGPoint bl = CGPointMake(w * box.bottomLeftX, h * box.bottomLeftY);
The x position looks like it is pretty close, but the y position is completely off and looks like mirrored.
I am testing on iOS 10 iPhone 6s
Am I missing something?
The issue was that I was using the preview frame to make the translation to the points in screen. But the points that come through with bounding box are not relative to the preview view, they are relative to the VideoFrame (as the support people of catchoom.com pointed out). The VideoFrame size is set by the capturePreset which only accepts two values AVCaptureSessionPreset1280x720 and AVCaptureSessionPreset640x480. The default one is AVCaptureSessionPreset1280x720
So in my case I had to make the calculations with size 1280x720 and then make the conversion from those coordinates to the coordinates in my preview view size.
So it ended up looking like this:
let box = bestResult.matchBoundingBox
let wVideoFrame:CGFloat = 1080.0;
let hVideoFrame:CGFloat = 720.0;
let wRelativePreview = wVideoFrame/CGFloat(preview.frame.size.height)
let hRelativePreview = wVideoFrame/CGFloat(preview.frame.size.width)
var tl = CGPoint(x: wVideoFrame * CGFloat(box.topLeftX),y: hVideoFrame * CGFloat(box.topLeftY));
var tr = CGPoint(x: wVideoFrame * CGFloat(box.topRightX) ,y: hVideoFrame * CGFloat(box.topRightY));
var br = CGPoint(x: wVideoFrame * CGFloat(box.bottomRightX),y: hVideoFrame * CGFloat(box.bottomRightY));
var bl = CGPoint(x: wVideoFrame * CGFloat(box.bottomLeftX),y: hVideoFrame * CGFloat(box.bottomLeftY));
tl = CGPoint(x: tl.x/wRelativePreview, y: tl.y/hRelativePreview)
tr = CGPoint(x: tr.x/wRelativePreview, y: tr.y/hRelativePreview)
br = CGPoint(x: br.x/wRelativePreview, y: br.y/hRelativePreview)
bl = CGPoint(x: bl.x/wRelativePreview, y: bl.y/hRelativePreview)
// 4 square visualize top-left, top.right, bottom-left and bottom-right points
var fr = vTL.frame;
fr.origin = tl;
vTL.frame = fr;
fr.origin = tr;
vTR.frame = fr;
fr.origin = br;
vBR.frame = fr;
fr.origin = bl;
vBL.frame = fr;
Now the points looked quite ok on screen, but they looked some how rotated. So I rotated the view 90 degrees:
// overlay is the container of the 3 squares to visualize the points in screen
overlay.transform = CGAffineTransform(rotationAngle: CGFloat(M_PI/2.0))
Note this is not the official response from support from catchoom, this might not be 100% correct, but it worked for me quite well.
I have a UIView with rounded corners.
How can I get the size of the inner rectangle?
If you would like to be able to get something like the green rectangle here. You can use this method, where you pass the red rectangle as parameter:
func innerRectangle(view:UIView) -> CGRect {
let radius = view.layer.cornerRadius * (1 - M_SQRT1_2)
let origin = CGPoint(x: view.frame.origin.x + radius, y: view.frame.origin.y + radius)
let size = CGSize(width: view.frame.width-radius*2, height: view.frame.height-radius*2)
return CGRect(origin: origin, size: size)
}
The accepted answer is a great estimate for small values of cornerRadius, but it deviates noticeably from the outer rect's boundary as the value increases. Since the OP requested that the inner rect must touch the outer rect, it would be better to replace the first line inside the function with
let radius = view.layer.cornerRadius * (1 - (1 / pow(2, 0.5)))