I'm using canvas to draw a circle and border. The circle fill color and border color are different. I'm also drawing a text in the center of the circle. But the problem I'm facing is that the edges on all four sides of the circle seems to be cut off.
This is the code I'm using for drawing the circle, border and text.
override fun onDraw(canvas: Canvas) {
super.onDraw(canvas)
try {
/** Draw Circle **/
drawPaint.color = ContextCompat.getColor(context, R.color.white_static)
drawPaint.style = Paint.Style.FILL
canvas.drawCircle(size, size, size, drawPaint)
/** Draw Border **/
borderPaint.color = ContextCompat.getColor(context, R.color.color_primary)
borderPaint.style = Paint.Style.STROKE
borderPaint.strokeJoin = Paint.Join.ROUND
borderPaint.strokeWidth = 2f
canvas.drawCircle(size, size, size, borderPaint)
/** Draw Text **/
textPaint.textAlign = Paint.Align.CENTER
textPaint.color = ContextCompat.getColor(context, R.color.color_primary)
val xPos = width / 2
val yPos = (height / 2 - (textPaint.descent() + textPaint.ascent()) / 2).toInt()
/** Add Text **/
canvas.drawText(text!!, xPos.toFloat(), yPos.toFloat(), textPaint)
} catch (e: Exception) {
println("CircularTextView => Exception caught => $e")
}
}
Related
I want to design an adventure path for educational levels in Jetpack Compose Android, where each level is connected to the next and previous level by a curved line.
Each level is a circular view for example an image that can be clicked.
You can use the Canvas to draw circles and lines.
Something like:
Canvas(modifier = Modifier.fillMaxSize(), onDraw = {
//draw a circle
drawCircle(color = Red,
center = Offset(120f,120f),
radius = 100f)
//draw a simple line.In your case you can use a Bezier curves
val strokePath = Path().apply {
moveTo (220f, 120f)
lineTo (size.width - 220f, 400f)
}
drawPath(
path = strokePath,
color = Blue,
style = Stroke(
width = 3.dp.toPx(),
cap = StrokeCap.Round
)
)
//draw the 2nd circle
drawCircle(color = Color.Red,
center = Offset(size.width - 120f,400f),
radius = 100f)
})
To enable clickable areas you can define a list of Rect for each circle and then apply the pointerInput modifier to the Canvas.
Something like:
val circles = ArrayList<Rect>()
Canvas(modifier = Modifier
.fillMaxSize()
.pointerInput(Unit) {
detectTapGestures(
onTap = { tapOffset ->
circles.add(Rect(top = 70f, left = 70f, bottom = 170f, right = 170f))
circles.add(
Rect(
top = 350f,
left = size.width - 170f,
bottom = 450f,
right = size.width - 70f
)
)
for (rect in circles) {
if (rect.contains(tapOffset)) {
//Handle the click here and do something
//....
}
}
}
)
},
onDraw = { /* .... */ })
I'm drawing a custom progress bar, the idea is to have a vertical "line" of circles with space between them like this:
As you progress, the circles change color and size, and the text ("60" in the image I shared)is displayed alongside the top level circle. With the current text size it seems centered enough, but if I change the text size it is noticeable that is bottom aligned, like this:
Here is the code:
Box(
modifier
.aspectRatio(1f)
.drawWithCache {
onDrawBehind {
val height = size.height
val space = height / 10
for (i in 1..numberOfCircles) {
val onBrush =
if (i <= progressLevel) progressBrush(progressLevel) else Color.Gray.toBrush()
val circleSize =
if (i == progressLevel) circleRadius.value * 2.5F else if (i == progressLevel - 1) circleRadius.value * 1.5F else circleRadius.value
drawProgressCircle(
onBrush,
sizeArray[i - 1].value,
size.height - (space * i)
)
if (i == state.level) {
drawText(
onBrush,
circleSize,
size.height - (space * i),
(progressLevel * 10).toString()
)
}
}
}
},
contentAlignment = Alignment.Center
)
The draw circles function:
fun DrawScope.drawProgressCircle(
brush: Brush,
radius: Float,
place: Float
) {
drawCircle(
brush = brush,
radius = radius,
center = Offset(x = size.width/2, y = place),
)
}
The draw text function:
fun DrawScope.drawText(
brush: Brush,
radius: Float,
place: Float,
text: String
) {
drawContext.canvas.nativeCanvas.apply {
drawText(
text,
size.width/2.2F,
place + radius,
Paint().apply {
textSize = 20.sp.toPx()
color = Color.White.toArgb()
textAlign = Paint.Align.RIGHT
}
)
}
}
How can I keep the text vertically aligned at the last circle center with any text size?
I'm developing a function to draw a rounded button in my android application. The problem is that the final results is rectangular instead of something rounded on the corners. What am I missing?
fun draw(canvas: Canvas?) {
canvas?.let {
if (strokeColor != -1) {
shaderBorderPaint.color = strokeColor
shaderBorderPaint.shader = null
} else {
shaderBorderPaint.shader = shaderFactory.resize(it.width, it.height)
}
val cornerRadius = 8f
val halfStrokeSize: Float = shaderBorderPaint.strokeWidth / 2
val rect = RectF(halfStrokeSize, it.height - halfStrokeSize, it.width - halfStrokeSize, halfStrokeSize)
it.drawRoundRect(rect, cornerRadius, cornerRadius, shaderBorderPaint)
}
}
Try this instead.
val corners = floatArrayOf(
80f, 80f, // Top left radius in px
80f, 80f, // Top right radius in px
0f, 0f, // Bottom right radius in px
0f, 0f // Bottom left radius in px
)
val path = Path()
path.addRoundRect(rect, corners, Path.Direction.CW)
canvas.drawPath(path, mPaint)
or this,
public static void makeRoundCorner(int bgcolor,int radius,View v,int strokeWidth,int strokeColor)
{
GradientDrawable gdDefault = new GradientDrawable();
gdDefault.setColor(bgcolor);
gdDefault.setCornerRadius(radius);
gdDefault.setStroke(strokeWidth, strokeColor);
v.setBackgroundDrawable(gdDefault);
}
because val cornerRadius = 8f. try to set cornerRadius = heightRect / 2
When I use BlendMode.DstOut, it's supposed to show transparent source shape but it's black.
Supposed result:
Real result:
#Composable
fun BlendMode() {
Box(modifier = Modifier.fillMaxSize().background(White100))
Canvas(modifier = Modifier.size(300.dp)) {
val radius = size.width / 3
drawCircle(
color = Color.Red,
radius = radius,
center = Offset(radius, radius)
)
drawRect(
color = Color.Blue,
topLeft = Offset(radius, radius),
size = Size(radius * 2, radius * 2),
blendMode = BlendMode.DstOut
)
}
}
The blendMode will not allow you to achieve the desired result. You can do this by using clipRect:
clipRect(
top = radius,
left = radius,
right = radius * 3,
bottom = radius * 3,
clipOp = ClipOp.Difference
) {
drawCircle(
color = Color.Red,
radius = radius,
center = Offset(radius, radius)
)
}
I need to draw a circle and fill it according to a percentage value as image shows.
]2
I have tried to draw it using canvas:
#SuppressLint("DrawAllocation")
override fun onDraw(canvas: Canvas?) {
super.onDraw(canvas)
rectF = RectF(0f + margin, 0f + margin, width.toFloat() - margin, height.toFloat() - margin)
val scaledValues = scale()
var sliceStartPoint = 0F
for (i in scaledValues.indices) {
slicePaint.color = ContextCompat.getColor(context, sliceColors[i])
canvas!!.drawArc(rectF!!, sliceStartPoint, scaledValues[i], true, slicePaint)
canvas.drawArc(rectF!!, sliceStartPoint, scaledValues[i], true, centerPaint)
sliceStartPoint += scaledValues[i]
}
}
The problem is that I got all lines centered to radio (as a Pie) instead of slices.
I tried to set usecenter:false but it was not successful result.
Any idea how to draw an arc without set the center point as vertice?
I think you just need rotate the canvas & a little math.
private val slicePaint = Paint()
private val centerPaint = Paint().apply {
color = 0x33333333
}
private val sliceColors = arrayListOf<Int>(Color.GRAY, Color.RED, Color.BLACK)
private var rectF: RectF? = null
private val margin = 0f
private var rotDeg = -120f
override fun onDraw(canvas: Canvas) {
super.onDraw(canvas)
rectF = RectF(0f + margin, 0f + margin, width.toFloat() - margin, height.toFloat() - margin)
canvas.save()
canvas.rotate(rotDeg, canvas.width /2.0f, canvas.height/2.0f)
// val scaledValues = scale()
// var sliceStartPoint = 0F
for (i in percent.indices) {
slicePaint.color = sliceColors[i] // ContextCompat.getColor(context, sliceColors[i])
var d = percent2degree(percent[i])
canvas!!.drawArc(rectF!!, d, -2 * d, false, slicePaint)
// canvas.drawArc(rectF!!, sliceStartPoint, scaledValues[i], true, centerPaint)
// sliceStartPoint += scaledValues[i]
}
canvas.restore()
}
private fun percent2degree(p:Float): Float {
return (acos((0.5 - p)/ 0.5) / Math.PI * 180).toFloat()
}
var percent = arrayOf(1.0f, 0.9f, 0.55f) // 0.5 + 0.3 + 0.2 = 100%