While drawing with usual drawText the text is positioning to the center of canvas. But my requirment is to place multiline text, so I've to use StaticLayout, but StaticLayout is not getting placed like drawText
Here's what I've tried so far.
class TestView : View {
private lateinit var staticLayout: StaticLayout
private lateinit var ststicTextPaint: TextPaint
private lateinit var textPaint: TextPaint
private val helloworld = "Hello world!"
constructor(context: Context, attrs: AttributeSet) : super(context, attrs) {
}
constructor(context: Context, attrs: AttributeSet, defStyleAttr: Int) : super(
context,
attrs,
defStyleAttr
) {
}
#TargetApi(Build.VERSION_CODES.LOLLIPOP)
constructor(context: Context, attrs: AttributeSet, defStyleAttr: Int, defStyleRes: Int) : super(
context,
attrs,
defStyleAttr,
defStyleRes
) {
}
override fun onSizeChanged(w: Int, h: Int, oldw: Int, oldh: Int) {
super.onSizeChanged(w, h, oldw, oldh)
setUp(w, h)
}
private fun setUp(w: Int, h: Int) {
textPaint = TextPaint(Paint.ANTI_ALIAS_FLAG)
textPaint.color = Color.BLUE
textPaint.textSize = 40f
textPaint.textAlign = Paint.Align.CENTER
ststicTextPaint = TextPaint(Paint.ANTI_ALIAS_FLAG)
ststicTextPaint.color = Color.GREEN
ststicTextPaint.textSize = 40f
// textPaint.textAlign = Paint.Align.CENTER
staticLayout = StaticLayout(
helloworld,
ststicTextPaint,
w,
Layout.Alignment.ALIGN_CENTER,
0f,
0f,
false
)
}
override fun onDraw(canvas: Canvas?) {
super.onDraw(canvas)
//Just drawn a rect with cross-hair to know the relative position
val rect = Rect(0, 0, width, height)
val paint = Paint(Paint.ANTI_ALIAS_FLAG)
paint.style = Paint.Style.STROKE
paint.color = Color.RED
paint.strokeWidth = 5f
canvas?.drawRect(rect, paint)
canvas?.drawLine((width/2).toFloat(), 0F, (width/2).toFloat(), height.toFloat(),paint)
canvas?.drawLine(0F, (height/2).toFloat(),width.toFloat(), (height/2).toFloat(),paint)
canvas?.drawText(helloworld, (width / 2).toFloat(), (height / 2).toFloat(), textPaint)
// canvas?.drawText(helloworld, (width / 2).toFloat(), (height / 2).toFloat(), textPaint)
staticLayout.draw(canvas, (width / 2).toFloat(), (height / 2).toFloat())
}
fun StaticLayout.draw(canvas: Canvas?, x: Float, y: Float) {
canvas?.withTranslation(x, y) {
draw(canvas)
}
}
}
Here's what I'm getting with both
The blue one is done using normal drawText and the Green one is using StaticLayout
I figured out the issue guys. It was because i was ignoring the width of the StaticLayout text's width while placing it to the centre.
Here I'm giving the StaticLayout the entire width of the View.
staticLayout = StaticLayout(
"Hello world is helloworld",
ststicTextPaint,
w,
Layout.Alignment.ALIGN_CENTER,
1f,
0f,
false
)
While drawing the view
staticLayout.draw(canvas, ((width / 2)-staticLayout.width/2).toFloat(), (height / 2).toFloat())
I substracted the half of the width of the `StaticLayout width so that it'll place in the exact centre.
Related
I am struggling with a custom view, I want it to have this effect.
But the problem is that the RadialGradient its very soft.
This is my class:
class CustomView #JvmOverloads constructor(
context: Context,
attrs: AttributeSet? = null,
defStyleAttr: Int = 0
) : View(context, attrs, defStyleAttr) {
private var radius = 0f
private var centerX = 0f
private var centerY = 0f
private val mainPaint: Paint = Paint(Paint.ANTI_ALIAS_FLAG).apply {
color = Color.RED
}
private val gradientPaint: Paint = Paint(Paint.ANTI_ALIAS_FLAG)
override fun onSizeChanged(w: Int, h: Int, oldw: Int, oldh: Int) {
radius = w.coerceAtMost(h) * 0.50f
if (radius < 0) return
centerX = w * 0.5f
centerY = h * 0.5f
gradientPaint.shader = RadialGradient(
centerX,
centerY,
radius,
Color.BLACK,
Color.TRANSPARENT,
Shader.TileMode.REPEAT
)
}
override fun onDraw(canvas: Canvas) {
canvas.drawCircle(centerX, centerY, radius, mainPaint)
canvas.drawCircle(centerX, centerY, radius, gradientPaint)
}
}
The only way I achieved this effect is by drawing a lot of circles (like 10 or more) in the canvas, but this way doesn't feel like the right approach and since I will be doing more stuff in this view, I don't want to have performance issues.
Hope you can help me to find a right way to achieve this. Thank you.
What is the best way to create a bar chart like this?
You can use libraries for this or make by yourself.
For example this is my round rectangle:
class CustomRectangle : View {
private var paint = Paint()
constructor(context: Context) : super(context) {
init(context)
}
constructor(context: Context, attrs: AttributeSet?) : super(context, attrs) {
init(context, attrs)
}
constructor(context: Context, attrs: AttributeSet?, defStyleAttr: Int) : super(
context,
attrs,
defStyleAttr
) {
init(context, attrs)
}
private fun init(context: Context, attrs: AttributeSet? = null) {
val ta = context.obtainStyledAttributes(attrs, R.styleable.CustomRectangle)
val color = ta.getColor(
R.styleable.CustomRectangle_rectangleColor,
ContextCompat.getColor(context, R.color.black_5)
)
color.let {
this.rectangleColor = it
}
changeColor(color)
ta.recycle()
}
var rectangleColor: Int = R.color.black_5
set(value) {
field = value
changeColor(value)
}
private fun changeColor(color: Int) {
paint.color = color
invalidate()
}
override fun onDraw(canvas: Canvas?) {
super.onDraw(canvas)
val path = getPath(0.0f, 0.0F, width.toFloat(), height.toFloat(), 20f, 20f, false)
if (path != null) {
canvas?.drawPath(path, paint)
}
}
private fun getPath(
left: Float,
top: Float,
right: Float,
bottom: Float,
rx: Float,
ry: Float,
conformToOriginalPost: Boolean
): Path? {
var rx = rx
var ry = ry
val path = Path()
if (rx < 0) rx = 0f
if (ry < 0) ry = 0f
val width = right - left
val height = bottom - top
if (rx > width / 2) rx = width / 2
if (ry > height / 2) ry = height / 2
val widthMinusCorners = width - 2 * rx
val heightMinusCorners = height - 2 * ry
path.moveTo(right, top + ry)
path.rQuadTo(0f, -ry, -rx, -ry) // top-right corner
path.rLineTo(-widthMinusCorners, 0f)
path.rQuadTo(-rx, 0f, -rx, ry) // top-left corner
path.rLineTo(0f, heightMinusCorners)
if (conformToOriginalPost) {
path.rLineTo(0f, ry)
path.rLineTo(width, 0f)
path.rLineTo(0f, -ry)
} else {
path.rQuadTo(0f, ry, rx, ry) // bottom-left corner
path.rLineTo(widthMinusCorners, 0f)
path.rQuadTo(rx, 0f, rx, -ry) // bottom-right corner
}
path.rLineTo(0f, -heightMinusCorners)
path.close() // Given close, last lineto can be removed.
return path
}
}
And than I use it like:
<customView.CustomRectangle
android:id="#+id/custom_rectangle"
android:layout_width="16dp"
android:layout_margin="16dp"
android:layout_gravity="center"
android:layout_height="200dp"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent"
/>
If this is not all you need, please tell me more requirements so that I can help you
I try to create convex path for outline provider. I need a rectangular with rounded top left and top right corners.
There are one method which able to do this, but question: what wrong with my convex path?. It draws on paint like a convex, and geometrically should be fine.
(yes, I know about public void addRoundRect(float left, float top, float right, float bottom, #NonNull float[] radii, #NonNull Direction dir))
There is implementation of my ConvexView
val Int.toPx: Int
get() = (this * Resources.getSystem().displayMetrics.density).toInt()
class ConvexView #JvmOverloads constructor(
context: Context, attrs: AttributeSet? = null, defStyleAttr: Int = 0
) : View(context, attrs, defStyleAttr) {
private val paint = Paint().apply {
isAntiAlias = true
color = Color.RED
strokeWidth = 5f
style = Paint.Style.STROKE
}
private val convexPath = Path()
val r = 24.toPx.toFloat()
val d = r * 2
override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec)
with (convexPath) {
reset()
arcTo(0f, 0f, d, d, -90f, -90f, true)
lineTo(0f, measuredHeight.toFloat())
lineTo(measuredWidth.toFloat(), measuredHeight.toFloat())
lineTo(measuredWidth.toFloat(), r)
arcTo(measuredWidth.toFloat() - d, 0f,
measuredWidth.toFloat(), d,
0f, -90f, true)
lineTo(r, 0f)
}
Log.d("CONVEX", "Path is convex = ${convexPath.isConvex}")
}
override fun onDraw(canvas: Canvas?) {
canvas?.save()
canvas?.drawPath(convexPath, paint)
canvas?.restore()
}
}
And result:
But path still not convex
In the second arcTo() call, you need to specify the last parameter, forceMoveTo=false. arcTo(.... , true) terminates the path once. So it's drawing two separate paths that can never be convex.
I have been trying to create a custom view similar to that shown below in which the white 'wave' view contains an extended 'inverse rounded corner' in the top right and a rounded corner in the bottom left.
I had attempted to achieve this using the Material Shape themeing but this doesn't seem to support the 'inverse' rounded corner.
To achieve this, I have been using a View and custom drawing within its Canvas, but have not been able to get it working, as I am unsure how to achieve the inverse rounded corner effect.
Any help or guidance would be greatly appreciated
class TestView #JvmOverloads constructor(
context: Context, attrs: AttributeSet? = null, defStyleAttr: Int = 0
) : View(context, attrs, defStyleAttr) {
private var mPath = Path()
override fun onDraw(canvas: Canvas?) {
super.onDraw(canvas)
val h = height.toFloat()
val h2 = height.toFloat() / 2f
val w = width.toFloat()
val w2 = width.toFloat() / 2f
mPath.reset()
mPath.addArc(w2, 0f, w, h2, 0f, 90f)
mPath.addArc(0f, h2, w2, h, 180f, 90f)
mPath.lineTo(w, h2)
mPath.lineTo(w, h)
mPath.lineTo(0f, h)
mPath.close()
mPath.fillType = Path.FillType.WINDING
canvas?.clipPath(mPath)
canvas?.drawColor(Color.DKGRAY)
}
}
I solved this by using Bezier curves and adapting the answer found in this SO question
The code I used to achieve this was as follows:
class SweepView #JvmOverloads constructor(
context: Context, attrs: AttributeSet? = null, defStyleAttr: Int = 0
) : View(context, attrs, defStyleAttr) {
private val paint = Paint().apply {
color = Color.RED
isAntiAlias = true
style = Paint.Style.FILL
}
private val path = Path()
override fun onDraw(canvas: Canvas?) {
val h = height.toFloat()
val halfH = h / 2f
val w = width.toFloat()
val delta = width / 3f
path.reset()
path.moveTo(0f, h)
path.cubicTo(0f, halfH, 0f, halfH, delta, halfH)
path.lineTo(w - delta, halfH)
path.cubicTo(w, halfH, w, halfH, w, 0f)
path.lineTo(w, h)
path.close()
canvas?.drawPath(path, paint)
}
}
I am implementing custom ViewGroup which has overlapping ImageViews.
I load images with GlideApp. Problem is that some images are cropped a little (visible on the left side) and I cannot prevent this. The drawables which get cropped are Vector drawables and their mDrawableWidth & mDrawableHeight are larger than the height of the ViewGroup. I thought that the images would be scaled to fit the view dimensions (mScaleType = FIT_CENTER).
class OverlappingImageViewGroup : ViewGroup {
constructor(context: Context) : this(context, null)
constructor(context: Context, attrs: AttributeSet?) : this(context, attrs, 0)
constructor(context: Context, attrs: AttributeSet?, defStyleAttr: Int) : super(context, attrs, defStyleAttr) {}
override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) {
val widthSize: Int = getDefaultSize(0, widthMeasureSpec)
val heightSize: Int = getDefaultSize(0, heightMeasureSpec)
val majorDimension = Math.min(widthSize, heightSize)
//Measure all child views
val childSpec = MeasureSpec.makeMeasureSpec(majorDimension, MeasureSpec.EXACTLY)
measureChildren(childSpec, childSpec)
//MUST call this to save our own dimensions
setMeasuredDimension(majorDimension, majorDimension)
}
override fun onLayout(changed: Boolean,
leftRelativeToParent: Int,
topRelativeToParent: Int,
rightRelativeToParent: Int,
bottomRelativeToParent: Int) {
val percentOverlap = 0.8f
if (childCount > 0) {
for (i in 0 until childCount) {
val child = getChildAt(i)
val childWidth = child.measuredWidth
val topBound = 0 /* there is just one row */
if (i == 0) {
child.layout(0, topBound, childWidth, childWidth)
} else {
val leftBound = i * percentOverlap * childWidth
child.layout(
leftBound.toInt(),
topBound,
leftBound.toInt() + childWidth,
topBound + childWidth)
}
}
}
}
}
ImageViews are created programmatically:
val context = holder.rootView.context
val imageView: ImageView = ImageView(context)
GlideApp.with(context)
.load(imageUrl)
.placeholder(userPlaceholder)
.fallback(userPlaceholder)
.error(userPlaceholder)
.transform(CircleWithBorderTransform())
.into(imageView)
holder.membersIcons.addView(imageView)
Glide transformation:
override fun transform(pool: BitmapPool, source: Bitmap, outWidth: Int, outHeight: Int): Bitmap {
val width = source.width
val height = source.height
val canvasBitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888)
val shader = BitmapShader(source, Shader.TileMode.CLAMP, Shader.TileMode.CLAMP)
val paint = Paint()
paint.isAntiAlias = true
paint.shader = shader
val canvas = Canvas(canvasBitmap)
val radius = if (width > height) height.toFloat() / 2f else width.toFloat() / 2f
canvas.drawCircle((width / 2).toFloat(), (height / 2).toFloat(), radius, paint)
//border around the image
paint.shader = null
paint.style = Paint.Style.STROKE
paint.strokeWidth = 2f
paint.color = Color.WHITE
canvas.drawCircle((width / 2).toFloat(), (height / 2).toFloat(), radius, paint)
return canvasBitmap
}