How can I move a Path object drawn on the canvas? - android

I have a Custom View that draws a gray rectangle and a black line inside it:
class CustomViewDemo: View {
private val mRectangleGray = Path()
private val mLineBlack = Path()
private lateinit var mRectanglePaint: Paint
private lateinit var mBarPaint: Paint
private var marginLeft: Float = 0F
private var marginBottom: Float = 0F
private var marginRight: Float = 0F
private var marginTop: Float = 0F
private lateinit var mRectSize: RectF
private lateinit var mLineSize: RectF
constructor(context: Context?) : super(context) {
init(null)
}
constructor(context: Context?, list: MutableList<String>, timelineWidth: Int) : super(context) {
init(null)
}
constructor(context: Context?, attrs: AttributeSet?) : super(context, attrs) {
init(attrs)
}
constructor(context: Context?, attrs: AttributeSet?, defStyleAttr: Int) : super(
context,
attrs,
defStyleAttr
) {
init(attrs)
}
constructor(
context: Context?,
attrs: AttributeSet?,
defStyleAttr: Int,
defStyleRes: Int
) : super(context, attrs, defStyleAttr, defStyleRes) {
init(attrs)
}
private fun init(attrs: AttributeSet?) {
mRectanglePaint = Paint()
mRectanglePaint.isAntiAlias = true
mRectanglePaint.color = Color.parseColor("#dedede")
mBarPaint = Paint()
mBarPaint.style = Paint.Style.FILL
mBarPaint.color = Color.BLACK
}
#SuppressLint("DrawAllocation")
override fun onDraw(canvas: Canvas) {
drawRectangle(canvas)
drawVerticalLine(canvas)
}
private fun drawRectangle(canvas: Canvas) {
marginLeft = 50F
marginRight = width - 50F
marginTop = 300F
marginBottom = 450F
mRectSize = RectF(marginLeft, marginTop, marginRight, marginBottom)
mRectangleGray.addRect(mRectSize, Path.Direction.CW)
canvas.drawPath(mRectangleGray, mRectanglePaint)
}
private fun drawVerticalLine(canvas: Canvas) {
marginLeft = 50F
marginRight = width - 50F
marginTop = 300F
marginBottom = 450F
mLineSize = RectF(marginRight - 7, marginTop, marginRight, marginBottom)
mLineBlack.addRect(mLineSize, Path.Direction.CW)
canvas.drawPath(mLineBlack, mBarPaint)
}
}
Output:
I want to move black line to right and left by touching within the borders of the gray rectangle.
However, I couldn't do this with onTouchEvent. , Drag and move a circle drawn on canvas I tried to adapt it according to the answer to this question, but instead of lines, I started drawing different black dots. Maybe I did something wrong I don't know but I tried so many times.
How can I move a Path object drawn on the canvas to the right and left in the rectangle by touching it? Thank you.

This won't give you exactly what you need, but it should be enough to get you on the right track.
Add the following onTouchEvent handler to your custom view:
private var linePositionOffset = 0f
private var startX = width - 7
override fun onTouchEvent(event: MotionEvent): Boolean {
val eventAction = event.action
val x = event.x.toInt()
when (eventAction) {
MotionEvent.ACTION_DOWN -> {
startX = x
}
MotionEvent.ACTION_UP -> {}
MotionEvent.ACTION_MOVE -> {
linePositionOffset = (startX - x).toFloat()
}
}
// tell the View to redraw the Canvas
invalidate()
// tell the View that we handled the event
return true
}
And change onDraw() to this:
override fun onDraw(canvas: Canvas) {
drawRectangle(canvas)
canvas.withTranslation(-linePositionOffset, 0f) {
drawVerticalLine(canvas)
}
}

Related

BlurMaskFilter blur radius question on canvas with scale transformation

I have two views: View1 and View2. View1 draws a point with BlurMaskFilter on a canvas with a scale amount 3F. View2 first draws that point on a bitmap at 1x scale, and then the bitmap is drawn on the canvas, with 3x scale.
Now I expect the two views to draw the visually identical images, but it doesn't. Anyone can tell why it's like this?
View1:
class DemoView1 : View {
constructor(context: Context?) : this(context, null)
constructor(context: Context?, attrs: AttributeSet?) : super(context, attrs)
private var paint: Paint = Paint()
init {
paint.apply {
strokeWidth = 500F
style = Paint.Style.FILL
color = Color.RED
strokeJoin = Paint.Join.ROUND
strokeCap = Paint.Cap.ROUND
maskFilter = BlurMaskFilter(200F, BlurMaskFilter.Blur.NORMAL)
}
}
override fun onDraw(canvas: Canvas) {
canvas.scale(3F, 3F)
canvas.drawPoint(0F, 0F, paint)
}
}
View2:
class DemoView2 : View {
constructor(context: Context?) : this(context, null)
constructor(context: Context?, attrs: AttributeSet?) : super(context, attrs)
private var paint: Paint = Paint()
init {
paint.apply {
strokeWidth = 500F
style = Paint.Style.FILL
color = Color.RED
strokeJoin = Paint.Join.ROUND
strokeCap = Paint.Cap.ROUND
maskFilter = BlurMaskFilter(200F, BlurMaskFilter.Blur.NORMAL)
}
}
#SuppressLint("DrawAllocation")
override fun onDraw(canvas: Canvas) {
val bitmap = Bitmap.createBitmap(measuredWidth, measuredHeight, Bitmap.Config.ARGB_8888)
val bitmapCanvas = Canvas(bitmap)
bitmapCanvas.apply {
drawPoint(0F, 0F, paint)
}
canvas.scale(3F, 3F)
canvas.drawBitmap(bitmap, 0F, 0F, Paint())
}
}
Demo result:
The upper one is View1, and lower one is View2.

How to add padding in custom switch compat in android?

I am trying to implement a custom switch compat and I am referencing another stackoverflow answer: How to set width and track text in a Switch/SwitchCompat button and achieve this result? (Image and GIF attached)
My code is almost identical to this other than small updates.
class SwitchCompatEx : SwitchCompat {
companion object {
val TRACK_COLOR = 0xFFFFFFFF.toInt()
val TRACK_STROKE_WIDTH = 2f.dp2Px.toInt()
val TRACK_STROKE_COLOR = 0xFF00A1FF.toInt()
val TRACK_LABEL_COLOR = 0xFF00A1FF.toInt()
val TRACK_LABEL_SIZE = 14f.sp2Px
val THUMB_COLOR = 0xFF00A1FF.toInt()
val THUMB_LABEL_COLOR = 0xFFFFFFFF.toInt()
val THUMB_LABEL_SIZE = 14f.sp2Px
fun drawLabel(canvas: Canvas,
bounds: Rect,
paint: Paint,
text: CharSequence?) {
text ?: return
val tb = RectF();
tb.right = paint.measureText(text, 0, text.length)
tb.bottom = paint.descent() - paint.ascent()
tb.left += bounds.centerX() - tb.centerX()
tb.top += bounds.centerY() - tb.centerY() - paint.ascent()
canvas.drawText(text.toString(), tb.left, tb.top, paint)
}
private inline val Float.sp2Px
get() = TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_SP,
this,
Resources.getSystem().displayMetrics)
private inline val Float.dp2Px
get() = TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP,
this,
Resources.getSystem().displayMetrics)
}
private val trackLabelPaint = Paint().apply {
isAntiAlias = true
textSize = TRACK_LABEL_SIZE
color = TRACK_LABEL_COLOR
}
private val thumbLabelPaint = Paint().apply {
isAntiAlias = true
textSize = THUMB_LABEL_SIZE
color = THUMB_LABEL_COLOR
}
private val thumbLabel
get () = if (isChecked) textOn else textOff
constructor(context: Context?) : super(context)
constructor(context: Context?, attrs: AttributeSet?) : super(context, attrs)
constructor(context: Context?, attrs: AttributeSet?, defStyleAttr: Int) : super(context, attrs, defStyleAttr)
init {
background = null
trackDrawable = TrackDrawable()
thumbDrawable = ThumbDrawable()
}
override fun onSizeChanged(w: Int,
h: Int,
oldw: Int,
oldh: Int) {
super.onSizeChanged(w, h, oldw, oldh)
(trackDrawable as GradientDrawable).setSize(w, h)
(thumbDrawable as GradientDrawable).setSize(w / 2, h)
}
inner class TrackDrawable : GradientDrawable() {
private val textOffBounds = Rect()
private val textOnBounds = Rect()
init {
setColor(TRACK_COLOR)
setStroke(TRACK_STROKE_WIDTH, TRACK_STROKE_COLOR)
}
override fun onBoundsChange(r: Rect) {
super.onBoundsChange(r)
cornerRadius = r.height() / 2f
textOffBounds.set(r)
textOffBounds.right /= 2
textOnBounds.set(textOffBounds)
textOnBounds.offset(textOffBounds.right, 0)
}
override fun draw(canvas: Canvas) {
super.draw(canvas)
drawLabel(canvas, textOffBounds, trackLabelPaint, textOff)
drawLabel(canvas, textOnBounds, trackLabelPaint, textOn)
}
}
inner class ThumbDrawable : GradientDrawable() {
private val thumbLabelBounds = Rect()
init {
setColor(THUMB_COLOR)
}
override fun onBoundsChange(r: Rect) {
super.onBoundsChange(r)
cornerRadius = r.height() / 2f
thumbLabelBounds.set(r)
}
override fun draw(canvas: Canvas) {
super.draw(canvas)
drawLabel(canvas, thumbLabelBounds, thumbLabelPaint, thumbLabel)
}
}
}
I am trying to put padding between the thumb view and gray borders. How can I achieve that view?
This is what I want to do.
This is what I currently have.
I finally achieved the UI by changing
inner class ThumbDrawable : GradientDrawable() {
private val thumbLabelBounds = Rect()
init {
//Set color for selected items background
setColor(ContextCompat.getColor(context, R.color.colorPrimary))
}
override fun onBoundsChange(r: Rect) {
super.onBoundsChange(r)
setupPadding(r)
cornerRadius = r.height() / 2f
thumbLabelBounds.set(r)
}
fun setupPadding(r: Rect) {
val padding = 4f.dp2Px.toInt()
r.top += padding
r.right -= padding
r.bottom -= padding
r.left += padding
}
override fun draw(canvas: Canvas) {
super.draw(canvas)
drawLabel(canvas, thumbLabelBounds, thumbLabelPaint, thumbLabel)
invalidate()
requestLayout()
}
}
Basically, added coordinates +padding for each side.
Try to override onMeasure()method and call setMeasuredDimension() with the measured width, and adding the a new value to the vertical/horizontal padding.
override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) {
val paddingHorizontal = paddingLeft + paddingRight + 100
val paddingVertical = paddingTop + paddingBottom + 100
val height = resolveSizeAndState(paddingVertical, heightMeasureSpec, 0)
val width = resolveSizeAndState(paddingHorizontal, widthMeasureSpec, 0)
setMeasuredDimension(width, height)
}

Top left transparent triangle with canvas

I try to create a custom shape to accomplish this:
But this is my result:
I have created a custom view and I paint a rectangle border with a top left empty space (with a triangle shape). This is the custom class:
class MatchTriangleImageView : ImageView {
private var mCanvas: Canvas? = null
private var paint: Paint? = null
private var path: Path? = null
private var mWidth = 0
private var mHeight = 0
private val mPaintClear = Paint()
#JvmOverloads
constructor(
context: Context,
attrs: AttributeSet? = null,
defStyleAttr: Int = 0)
: super(context, attrs, defStyleAttr)
init {
mCanvas = Canvas()
paint = Paint()
paint?.isAntiAlias = true
paint?.alpha = 0
paint?.strokeJoin = Paint.Join.ROUND
paint?.strokeCap = Paint.Cap.ROUND
paint?.color = Color.TRANSPARENT
paint?.xfermode = PorterDuffXfermode(PorterDuff.Mode.CLEAR)
path = Path()
mPaintClear.color = Color.TRANSPARENT
mPaintClear.style = Paint.Style.FILL
}
override fun onSizeChanged(w: Int, h: Int, oldw: Int, oldh: Int) {
super.onSizeChanged(w, h, oldw, oldh)
mWidth = w
mHeight = h
}
override fun onDraw(canvas: Canvas?) {
super.onDraw(canvas)
canvas?.drawPaint(mPaintClear)
drawable?.let {
val triangleSize = 90f
path?.fillType = Path.FillType.INVERSE_EVEN_ODD
path?.moveTo(triangleSize, 0f)
path?.lineTo(mWidth.toFloat(), 0f)//top right
path?.lineTo(mWidth.toFloat(), mHeight.toFloat())//bottom right
path?.lineTo(0f, mHeight.toFloat())//bottom left
path?.lineTo(0f, triangleSize)//top left
path?.lineTo(triangleSize, 0f)
path?.close()
canvas?.drawPath(path, paint)
}
}
}
And this this the layout where I put the custom view:
<android.support.v7.widget.CardView xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="match_parent"
app:cardElevation="0dp"
app:cardCornerRadius="0dp"
android:background="#color/transparent">
<FrameLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="#color/transparent"
android:clipToPadding="false"
android:clipChildren="false">
<com...MatchTriangleImageView
android:id="#+id/matchCarViewImageWithTriangleIv"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:adjustViewBounds="true"
android:cropToPadding="true"
android:scaleType="centerCrop"/>
How can I solve this?
I found out the solution.
class MatchTriangleImageView : ImageView {
private var mPaint : Paint? = null
private var path: Path? = null
private var mWidth = 0
private var mHeight = 0
private var rect : Rect? = null
var triangleSize = 0f
#JvmOverloads
constructor(
context: Context,
attrs: AttributeSet? = null,
defStyleAttr: Int = 0)
: super(context, attrs, defStyleAttr)
init {
val triangleRes : BitmapDrawable? = ContextCompat.getDrawable(context, R.drawable.white_match_triangle) as BitmapDrawable
triangleSize = triangleRes?.bitmap?.height!!.toFloat()
setLayerType(View.LAYER_TYPE_SOFTWARE, null)
mPaint = Paint(Paint.ANTI_ALIAS_FLAG)
mPaint?.xfermode = PorterDuffXfermode(PorterDuff.Mode.CLEAR)
mPaint?.color = Color.TRANSPARENT
mPaint?.strokeWidth = 5f
mPaint?.strokeJoin = Paint.Join.ROUND
mPaint?.strokeCap = Paint.Cap.ROUND
path = Path()
}
override fun onSizeChanged(w: Int, h: Int, oldw: Int, oldh: Int) {
super.onSizeChanged(w, h, oldw, oldh)
mWidth = w
mHeight = h
rect = Rect(0,0,w,h)
path?.fillType = Path.FillType.INVERSE_EVEN_ODD
path?.reset()
path?.moveTo(triangleSize, 0f)
path?.lineTo(mWidth.toFloat(), 0f)//top right
path?.lineTo(mWidth.toFloat(), mHeight.toFloat())//bottom right
path?.lineTo(0f, mHeight.toFloat())//bottom left
path?.lineTo(0f, triangleSize)//top left
path?.lineTo(triangleSize, 0f)
path?.close()
}
override fun onDraw(canvas: Canvas) {
super.onDraw(canvas)
drawable?.let {
canvas.drawPath(path!!, mPaint!!)
}
}
}
I putted this custom view in a CardView component, but this component doesn't like canvas transparency. I changed it by FrameLayout, and with some litle changes, worked!!

Shadow effect on Bezier curve custom view

How do I add a shadow effect at the bottom of a curved custom view group? I am implementing a bezier curved path to add a bottom curve to a rectangle shape. I would like to add a bottom shadow parallel to the curved bottom edge, can anyone suggest ways to do this?
class CurveContainer : ConstraintLayout{
var mainPaint = Paint()
var mShadowPaint = Paint()
var mainPath = Path()
var xWidth = 0f
var xHeight = 0f
var biezerYValue = 50
constructor(context: Context?) : super(context){
init()
}
constructor(context: Context?, attrs: AttributeSet?) : super(context, attrs) {
init()
}
constructor(context: Context?, attrs: AttributeSet?, defStyleAttr: Int) : super(context, attrs, defStyleAttr){
init()
}
open fun setBiezerY(y : Int){
biezerYValue = y
invalidate()
}
private fun init() {
mainPaint.color = ContextCompat.getColor(context, R.color.showContainer)
mainPaint.style = Paint.Style.FILL
mainPaint.isAntiAlias = true
}
override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec)
xWidth = measuredWidth.toFloat()
xHeight = measuredHeight.toFloat()
invalidate()
}
override fun onDraw(canvas: Canvas?) {
super.onDraw(canvas)
mainPath.reset()
canvas!!.drawColor(Color.TRANSPARENT)
mainPath.moveTo(0f, 0f)
mainPath.lineTo(xWidth, 0f)
mainPath.lineTo(xWidth, xHeight - biezerYValue)
mainPath.quadTo(xWidth/2, xHeight + biezerYValue - 10, 0f, xHeight - biezerYValue)
mainPath.lineTo(0f, 0f)
canvas!!.drawPath(mainPath, mainPaint)
}
}
This is how it currently looks
What you can do is draw the same shape below the original one but with a BlurMaskFilter, I refactored your class a bit but the result seems promising
class CurveContainer #JvmOverloads constructor(context: Context, attrs: AttributeSet? = null, defStyleAttr: Int = 0)
: ConstraintLayout(context, attrs, defStyleAttr) {
private val mainPaint = Paint(Paint.ANTI_ALIAS_FLAG).apply {
color = 0xffff0000.toInt()
style = Paint.Style.FILL
}
private val shadowPaint = Paint(Paint.ANTI_ALIAS_FLAG.or(Paint.DITHER_FLAG)).apply {
color = 0xff000000.toInt()
style = Paint.Style.FILL
maskFilter = BlurMaskFilter(32f, BlurMaskFilter.Blur.OUTER)
}
private val mainPath = Path()
private val shadowPath = Path()
private var bezierEdgeY = 0f
private var bezierHandleY = 0f
private var edgePercentY = 0.25f
private var handlePercentY = 0.5f
init {
setWillNotDraw(false)
setLayerType(View.LAYER_TYPE_SOFTWARE, shadowPaint)
}
fun setBezierPositionPercent(edgePercentY: Float, handlePercentY: Float) {
this.edgePercentY = edgePercentY
this.handlePercentY = handlePercentY
computePath(width.toFloat(), height.toFloat())
}
private fun computePath(width: Float, height: Float) {
bezierEdgeY = height * 0.25f
bezierHandleY = height * 0.5f
val halfWidth = width / 2
shadowPath.reset()
shadowPath.moveTo(0f, 0f)
shadowPath.lineTo(width, 0f)
shadowPath.lineTo(width, bezierEdgeY)
shadowPath.quadTo(halfWidth, bezierHandleY, 0f, bezierEdgeY)
shadowPath.lineTo(0f, 0f)
mainPath.reset()
mainPath.moveTo(0f, 0f)
mainPath.lineTo(width, 0f)
mainPath.lineTo(width, bezierEdgeY)
mainPath.quadTo(halfWidth, bezierHandleY, 0f, bezierEdgeY)
mainPath.lineTo(0f, 0f)
invalidate()
}
override fun onSizeChanged(w: Int, h: Int, oldw: Int, oldh: Int) {
super.onSizeChanged(w, h, oldw, oldh)
computePath(w.toFloat(), h.toFloat())
}
override fun onDraw(canvas: Canvas) {
super.onDraw(canvas)
canvas.drawPath(shadowPath, shadowPaint)
canvas.drawPath(mainPath, mainPaint)
}
}

canvas.drawRect() bottom edge is thicker

I draw a rectangle, but the bottom edge looks thicker. How to fix this?
Box.kt
import android.content.Context
import android.graphics.Canvas
import android.graphics.Color
import android.graphics.Paint
import android.graphics.Rect
import android.util.AttributeSet
import android.view.View
class Box : View
{
var BoxBounds = Rect();
private var BoxPaint: Paint? = null
val BarHeight = 96;
constructor(context: Context?) : super(context)
{
init();
}
constructor(context: Context?, attrs: AttributeSet?) : super(context, attrs)
{
init();
}
private fun init()
{
BoxPaint = Paint(Paint.ANTI_ALIAS_FLAG);
BoxPaint?.color = Color.GRAY;
BoxPaint?.strokeWidth = 16f;
BoxPaint?.style = Paint.Style.STROKE;
}
override fun onDraw(canvas: Canvas?)
{
super.onDraw(canvas)
BoxPaint?.color = Color.GRAY;
BoxPaint?.style = Paint.Style.STROKE;
canvas?.drawRect(BoxBounds, BoxPaint)
}
override fun onSizeChanged(w: Int, h: Int, oldw: Int, oldh: Int)
{
super.onSizeChanged(w, h, oldw, oldh)
BoxBounds = Rect(0, 0, w, BarHeight);
}
}
Fragment
<com.loser.Box
android:layout_width="match_parent"
android:layout_height="100dp"
android:layout_margin="8dp"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintBottom_toBottomOf="parent"/>
As Damn Vegetables pointed out the problem was he was drawing outside the bounds of the view.
Here is my updated answer in which i have added an offset to counter that:
class Box : View {
private val rect = Rect()
private val paint = Paint(Paint.ANTI_ALIAS_FLAG)
private val density = resources.displayMetrics.density
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) {
// here you can do all your initialisation and retrieve custom styled attributes
val strokeWidth = 2f // this can be a style attribute we retrieve from xml if we get it from xml we don't need to use the density we will automaticly get the pixel size of the dimension and the system will make the conversion
val color = Color.GRAY // this can be a style attribute we retrieve from xml
paint.strokeWidth = strokeWidth * density
paint.color = color
paint.style = Paint.Style.STROKE
// the init function is already in kotlin so if you what use an init function i suggest you call it something else
}
override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec)
// this will get called everytime the size change so out dimensions will be right
// if our view does not have special requirements regarding size we just call super but at this point we can get the actual size of the view so we will set the size of our box to match the size of the view
val offset = paint.strokeWidth / 2f // offset used to keep the edges inside the visible rect
rect.top = paddingTop + offset
rect.left = paddingStart + offset
rect.right = measuredWidth - paddingEnd - offset
rect.bottom = measuredHeight - paddingBottom - offset
}
override fun onDraw(canvas: Canvas?) {
super.onDraw(canvas)
canvas?.drawRect(rect, paint)
}
}
To learn more about custom views checkout this article.

Categories

Resources