Rounded corners on inside in Jetpack Compose - android-jetpack-compose

Is there an options to round the corners on inside like in the picture? I have tried with CutCornerShape but it cuts them straight.

thy something like this
#Composable
private fun DrawTicketPathWithArc() {
Canvas(modifier = canvasModifier) {
val canvasWidth = size.width
val canvasHeight = size.height
// Black background
val ticketBackgroundWidth = canvasWidth * .8f
val horizontalSpace = (canvasWidth - ticketBackgroundWidth) / 2
val ticketBackgroundHeight = canvasHeight * .8f
val verticalSpace = (canvasHeight - ticketBackgroundHeight) / 2
// Get ticket path for background
val path1 = ticketPath(
topLeft = Offset(horizontalSpace, verticalSpace),
size = Size(ticketBackgroundWidth, ticketBackgroundHeight),
cornerRadius = 20.dp.toPx()
)
drawPath(path1, color = Color.Black)
// Dashed path in foreground
val ticketForegroundWidth = ticketBackgroundWidth * .95f
val horizontalSpace2 = (canvasWidth - ticketForegroundWidth) / 2
val ticketForegroundHeight = ticketBackgroundHeight * .9f
val verticalSpace2 = (canvasHeight - ticketForegroundHeight) / 2
// Get ticket path for background
val path2 = ticketPath(
topLeft = Offset(horizontalSpace2, verticalSpace2),
size = Size(ticketForegroundWidth, ticketForegroundHeight),
cornerRadius = 20.dp.toPx()
)
drawPath(
path2,
color = Color.Red,
style = Stroke(
width = 2.dp.toPx(),
pathEffect = PathEffect.dashPathEffect(
floatArrayOf(20f, 20f)
)
)
)
}
}
check this page

I found a solution which looks like that
#Composable
private fun DrawDashLine() {
val pathEffect = PathEffect.dashPathEffect(floatArrayOf(12f, 10f), 0f)
Canvas(modifier = Modifier.fillMaxWidth()) {
drawLine(
color = Color.Black,
strokeWidth = 3f,
start = Offset(0f, 0f),
end = Offset(size.width, 0f),
pathEffect = pathEffect
)
drawCircle(
color = Color.Black,
center = Offset(x = 1f, y = 2f),
radius = 20f
)
drawCircle(
color = Color.Black,
center = Offset(x = size.width, y = 2f),
radius = 20f
)
}
}
The background must be black, and the front is white and the round circles just come above the white.

Related

Is there a way to hide the content overflow the canvas size?

I set a size for the canvas and used drawCircle. Since the circle is larger than the canvas, the circle will appear to be out of bounds.
class MainActivity : ComponentActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContent {
MyApplicationTheme {
// A surface container using the 'background' color from the theme
Box(
contentAlignment = Alignment.Center,
modifier = Modifier
.fillMaxSize()
.background(Color.Black)
) {
Canvas(modifier = Modifier.size(300.dp).background(Color.Blue)){
drawCircle(
color = Color.White,
center = Offset(x = size.width / 2f, y = size.height / 2f),
radius = (size.minDimension / 2f)+50
)
}
}
}
}
}
}
enter image description here
I expect the part of overflow will hide,just like this:
enter image description here
what should I do?
Modifie.clipToBounds() clips any drawing that is outside of Composable
/**
* Clip the content to the bounds of a layer defined at this modifier.
*/
#Stable
fun Modifier.clipToBounds() = graphicsLayer(clip = true)
which is Modifier.graphicsLayer{clip = true} under the hood that you can use as
Canvas(modifier = Modifier
.clipToBounds()
.size(300.dp).background(Color.Blue)){
drawCircle(
color = Color.White,
center = Offset(x = size.width / 2f, y = size.height / 2f),
radius = (size.minDimension / 2f)+50
)
}
You can use something like this:
// A surface container using the 'background' color from the theme
Box(
contentAlignment = Alignment.Center,
modifier = Modifier
.fillMaxSize()
.background(Color.Black)
) {
Canvas(
modifier = Modifier
.size(300.dp)
.background(Color.Blue)
){
clipRect {
drawCircle(
color = Color.White,
center = Offset(x = size.width / 2f, y = size.height / 2f),
radius = (size.minDimension / 2f)+50
)
}
}
}

How to correctly drag the view around the circle?

I want the green dot to follow the touch point in a circular path, but it doesn't seem to be doing it right.
It seems like there is an unwanted offset somewhere but I can't find it on my own for quite some time.
Here is my code:
#Preview
#Composable
fun Test() {
val touchPoint = remember { mutableStateOf(Offset.Zero) }
Scaffold {
Column() {
Box(Modifier.height(100.dp).fillMaxWidth().background(Color.Blue))
Layout(
modifier = Modifier.aspectRatio(1f).fillMaxSize(),
content = {
Box(
Modifier
.size(48.dp)
.clip(CircleShape)
.background(Color.Green)
.pointerInput(Unit) {
detectDragGestures(
onDrag = { change, dragAmount ->
change.consumeAllChanges()
touchPoint.value += dragAmount
}
)
}
)
}
) { measurables, constraints ->
val dot = measurables.first().measure(constraints.copy(minHeight = 0, minWidth = 0))
val width = constraints.maxWidth
val height = constraints.maxHeight
val centerX = width / 2
val centerY = height / 2
val lengthFromCenter = width / 2 - dot.width / 2
val touchX = touchPoint.value.x
val touchY = touchPoint.value.y
layout(width, height) {
// I planned to achieve the desired behaviour with the following steps:
// 1. Convert cartesian coordinates to polar ones
val r = sqrt(touchX.pow(2) + touchY.pow(2))
val angle = atan2(touchY.toDouble(), touchX.toDouble())
// 2. Use fixed polar radius
val rFixed = lengthFromCenter
// 3. Convert it back to cartesian coordinates
val x = rFixed * cos(angle)
val y = rFixed * sin(angle)
// 4. Layout on screen
dot.place(
x = (x + centerX - dot.width / 2).roundToInt(),
y = (y + centerY - dot.height / 2).roundToInt()
)
}
}
Box(Modifier.fillMaxSize().background(Color.Blue))
}
}
}
I'm definitely missing something but don't know what exactly. What am I doing wrong?
touchPoint.value += dragAmount
Is in pixel values, and you're updating the position of the dot with pixel values, where it requires dp values. If you update that with
private fun Float.pxToDp(context: Context): Dp = // Float or Int, depends on the value you have, or Double
(this / context.resources.displayMetrics.density).dp
The amount with which it will be moved, will be smaller and reflect the dragging made by the user
You can easily achieve this by using some math:
#Composable
fun CircularView(
content: #Composable () -> Unit
) {
var middle by remember {
mutableStateOf(Offset.Zero)
}
var size by remember {
mutableStateOf(0.dp)
}
var dragAngle by remember {
mutableStateOf(0f)
}
Canvas(modifier = Modifier.size(size)) {
drawCircle(
color = Color.Red,
center = middle,
style = Stroke(1.dp.toPx())
)
}
Layout(
content = content,
modifier = Modifier.pointerInput(true) {
detectDragGestures(
onDrag = { change, _ ->
change.consumeAllChanges()
val positionOfDrag = change.position
val previousPosition = change.previousPosition
dragAngle += atan2(
positionOfDrag.x - middle.x,
positionOfDrag.y - middle.y
) - atan2(
previousPosition.x - middle.x,
previousPosition.y - middle.y
)
}
)
}
) { measurables, constraints ->
val placeables = measurables.map { it.measure(constraints) }
val layoutWidth = constraints.maxWidth
val layoutHeight = constraints.maxHeight
layout(layoutWidth, layoutHeight) {
val childCount = placeables.size
if (childCount == 0) return#layout
val middleX = layoutWidth / 2f
val middleY = layoutHeight / 2f
middle = Offset(middleX, middleY)
val angleBetween = 2 * PI / childCount
val radius =
min(
layoutWidth - (placeables.maxByOrNull { it.width }?.width ?: 0),
layoutHeight - (placeables.maxByOrNull { it.height }?.height ?: 0)
) / 2
size = (radius * 2).toDp()
placeables.forEachIndexed { index, placeable ->
val angle = index * angleBetween - PI / 2 - dragAngle
val x = middleX + (radius) * cos(angle) - placeable.width / 2f
val y = middleY + (radius) * sin(angle) - placeable.height / 2f
placeable.placeRelative(x = x.toInt(), y = y.toInt())
}
}
}
}
On the calling side:
CircularView {
repeat(10) {
Box(
modifier = Modifier
.background(
Color(
red = random.nextInt(255),
green = random.nextInt(255),
blue = random.nextInt(255)
), shape = CircleShape
)
.size(50.dp),
contentAlignment = Alignment.Center
) {
Text(text = it.toString(), fontSize = 12.sp, color = Color.White)
}
}
}

Dynamically adjust the weighting of columns in a row based on string length

I have a layout with two texts, one on left side and one on right side. If both texts are long, left one should occupy 60%, and right one 40% of the width. But if right text is shorter than 40%, the left one should take all the available space.
Here are the examples:
and:
So I would like to write something like this:
Row {
Text(text = left, modifier = modifier.padding(8.dp).weight(<min 0.6f>))
Text(text = right, modifier = modifier.padding(8.dp).weight(<max 0.4f>))
}
Is there any way to achieve this?
Finally, I figured it out. Here is the modifier:
fun Modifier.maxWidth(
fraction: Float = 1f,
) = layout { measurable, constraints ->
val maxWidth = (constraints.maxWidth * fraction).roundToInt()
val width = measurable.maxIntrinsicWidth(constraints.maxHeight).coerceAtMost(maxWidth)
val placeable = measurable.measure(Constraints(constraints.minWidth, width, constraints.minHeight, constraints.maxHeight))
layout(width, placeable.height) {
placeable.placeRelative(0, 0)
}
}
So we can use it like this:
Row {
Text(text = left, modifier = modifier.padding(8.dp).weight(1f)) // left text uses all the available space
Text(text = right, modifier = modifier.padding(8.dp).maxWidth(fraction = 0.4f)) // right text can be 40% of the parent or less
}
Measure the length of each string and calculate their weights. Adjust the weights based on the maximum allowed for the right column:
class MainActivity : ComponentActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
startActivity(intent)
val paint = android.graphics.Paint()
// First row
val text1 = "This long text should take up maximum 60% of the space with wrapping"
val text2 = "This is not very long"
val width1 = paint.measureText(text1)
val width2 = paint.measureText(text2)
var w2 = width2 / (width1 + width2)
if (w2 > 0.4f) {
w2 = 0.4f
}
val w1 = 1f - w2
// Second row
val text3 = "This text should take up more than 60% of the space"
val text4 = "Short"
val width3 = paint.measureText(text3)
val width4 = paint.measureText(text4)
var w4 = width4 / (width3 + width4)
if (w4 > 0.4f) {
w4 = 0.4f
}
val w3 = 1f - w4
setContent {
Column(modifier = Modifier.fillMaxSize()) {
Row(
modifier = Modifier
.fillMaxWidth()
.wrapContentHeight()
.padding(bottom = 20.dp)
) {
Text(text1, modifier = Modifier.weight(w1))
Text(text2, modifier = Modifier.weight(w2), color = Color.Red)
}
Row(
modifier = Modifier
.fillMaxWidth()
.wrapContentHeight()
.padding(bottom = 20.dp)
) {
Text(text3, modifier = Modifier.weight(w3))
Text(text4, modifier = Modifier.weight(w4), color = Color.Red)
}
}
}
}
}

Konvajs filter fill transparent areas within image

Is there a filter in konva that can fill closed transparent areas of the image with the white color?
This is the original image
This is a target image
I'm currently using konva custom filter for border. https://konvajs.github.io/docs/filters/Custom_Filter.html
var canvas, tempCanvas
function initBorderCanvas () {
canvas = document.createElement('canvas')
tempCanvas = document.createElement('canvas')
}
// make all pixells opaque 100% (except pixels that 100% transparent)
function removeTransparency (canvas) {
var ctx = canvas.getContext('2d')
var imageData = ctx.getImageData(0, 0, canvas.width, canvas.height)
var nPixels = imageData.data.length
for (var i = 3; i < nPixels; i += 4) {
if (imageData.data[i] > 0) {
imageData.data[i] = 255
}
}
ctx.clearRect(0, 0, canvas.width, canvas.height)
ctx.putImageData(imageData, 0, 0)
return canvas
}
function Border (imageData) {
var nPixels = imageData.data.length
var ratio = this._cache.canvas.scene.pixelRatio
var size = (this.getAttr('borderSize') || 0) * ratio
if (size === 0) {
return imageData
}
// - first set correct dimensions for canvases
canvas.width = imageData.width
canvas.height = imageData.height
tempCanvas.width = imageData.width
tempCanvas.height = imageData.height
// - the draw original shape into temp canvas
tempCanvas.getContext('2d').putImageData(imageData, 0, 0)
// - then we need to remove alpha chanel, because it will affect shadow (transparent shapes has smaller shadow)
removeTransparency(tempCanvas)
var ctx = canvas.getContext('2d')
var color = this.getAttr('borderColor') || 'black'
// 3. we will use shadow as border
// so we just need apply shadow on the original image
ctx.save()
ctx.shadowColor = color
ctx.shadowBlur = size
ctx.drawImage(tempCanvas, 0, 0)
ctx.restore()
// - Then we will dive in into image data of [original image + shadow]
// and remove transparency from shadow
var tempImageData = ctx.getImageData(0, 0, canvas.width, canvas.height)
var SMOOTH_MIN_THRESHOLD = 0
var SMOOTH_MAX_THRESHOLD = 0
let val, hasValue
var offset = 3
for (var i = 3; i < nPixels; i += 4) {
// skip opaque pixels
if (imageData.data[i] === 255) {
continue
}
val = tempImageData.data[i]
hasValue = val !== 0
if (!hasValue) {
continue
}
if (val > SMOOTH_MAX_THRESHOLD) {
val = 255
} else if (val < SMOOTH_MIN_THRESHOLD) {
val = 0
} else {
val =
((val - SMOOTH_MIN_THRESHOLD) /
(SMOOTH_MAX_THRESHOLD - SMOOTH_MIN_THRESHOLD)) *
255
}
tempImageData.data[i] = val
}
// draw resulted image (original + shadow without opacity) into canvas
ctx.putImageData(tempImageData, 0, 0)
// then fill whole image with color (after that shadow is colored)
ctx.save()
ctx.globalCompositeOperation = 'source-in'
ctx.fillStyle = color
ctx.fillRect(0, 0, canvas.width, canvas.height)
ctx.restore()
// then we need to copy colored shadow into original imageData
var newImageData = ctx.getImageData(0, 0, canvas.width, canvas.height)
var indexesToProcess = []
for (var i = 3; i < nPixels; i += 4) {
var hasTransparentOnTop =
imageData.data[i - imageData.width * 4 * offset] === 0
var hasTransparentOnTopRight =
imageData.data[i - (imageData.width * 4 + 4) * offset] === 0
var hasTransparentOnTopLeft =
imageData.data[i - (imageData.width * 4 - 4) * offset] === 0
var hasTransparentOnRight = imageData.data[i + 4 * offset] === 0
var hasTransparentOnLeft = imageData.data[i - 4 * offset] === 0
var hasTransparentOnBottom =
imageData.data[i + imageData.width * 4 * offset] === 0
var hasTransparentOnBottomRight =
imageData.data[i + (imageData.width * 4 + 4) * offset] === 0
var hasTransparentOnBottomLeft =
imageData.data[i + (imageData.width * 4 - 4) * offset] === 0
var hasTransparentAround =
hasTransparentOnTop ||
hasTransparentOnRight ||
hasTransparentOnLeft ||
hasTransparentOnBottom ||
hasTransparentOnTopRight ||
hasTransparentOnTopLeft ||
hasTransparentOnBottomRight ||
hasTransparentOnBottomLeft
// if pixel presented in original image - skip it
// because we need to change only shadow area
if (
imageData.data[i] === 255 ||
(imageData.data[i] && !hasTransparentAround)
) {
continue
}
if (!newImageData.data[i]) {
// skip transparent pixels
continue
}
indexesToProcess.push(i)
}
for (var index = 0; index < indexesToProcess.length; index += 1) {
var i = indexesToProcess[index]
var alpha = imageData.data[i] / 255
imageData.data[i] = newImageData.data[i]
imageData.data[i - 1] =
newImageData.data[i - 1] * (1 - alpha) + imageData.data[i - 1] * alpha
imageData.data[i - 2] =
newImageData.data[i - 2] * (1 - alpha) + imageData.data[i - 2] * alpha
imageData.data[i - 3] =
newImageData.data[i - 3] * (1 - alpha) + imageData.data[i - 3] * alpha
}
}
export { initBorderCanvas, Border }
It does a good job on drawing a border around the image.
Konva custom filter applied
Is there a way to fill the circles as on target image?
Here is the fiddle: http://jsfiddle.net/ecsy6hb4/34/
In the end I ended up using https://github.com/sakri/MarchingSquaresJS. This is a algorithm to find a outline points. Once I got these I draw closed polygon and painted it inside. That solved my problem.
Outline points example
These red points are the outline points returned by the marching squares algorithm.
fiddle: jsfiddle.net/ecsy6hb4/42/

How do I programmatically create irregular buttons in Xamarin?

I am trying to programmatically create buttons for a interactive element that is part of my app. The interface looks like a doughnut chart, and if the user touches one of the regions in the doughnut chart, it will highlight that region and all of the others. I am not so concerned right now with the colors, I am trying to get the seven buttons to appear. Here is a code sample of how I am trying to create the buttons:
var radius = (Frame.Width - 38) / 2.0f;
var innerRadius = radius - 32;
var diameter = radius * 2;
energyCircleView = new UIView();
energyCircleView.BackgroundColor = UIColor.White;
energyCircleView.Frame = new RectangleF(19, confirmEntryButton.Frame.Top - 16 - (diameter), diameter, diameter);
energyCircleView.Layer.CornerRadius = radius;
energyCircleView.Layer.MasksToBounds = true;
var center = new PointF(energyCircleView.Frame.X + radius, energyCircleView.Frame.Y + radius);
var ninety = MathHelper.TwoPi * (90.0f / 360.0f);
var seventh = MathHelper.TwoPi * (1.0f / 7.0f) * 360.0f;
for (int i = 0; i < 7; i++)
{
var startAngle = ninety + (i * seventh);
var endAngle = ninety + ((i + 1) * seventh);
var shapePath = new CGPath();
shapePath.AddArc(center.X, center.Y, radius, startAngle, endAngle, false);
shapePath.AddArc(center.X, center.Y, innerRadius, endAngle, startAngle, true);
var shapeLayer = new CAShapeLayer();
shapeLayer.Path = shapePath;
var button = new UIButton();
button.Frame = new RectangleF(0, 0, diameter, diameter);
button.BackgroundColor = MyColors[i];
button.Layer.Mask = shapeLayer;
button.Layer.MasksToBounds = true;
energyButtons.Add(button);
}
foreach (UIButton button in energyButtons)
{
energyCircleView.AddSubview(button);
}
You are adding buttons as a subview of the view. But you do not show the view itself.
Add the following code to show energyCircleView:
this.View.AddSubview(energyCircleView);

Resources