I have created a custom view project based on the lessons I have learned in this and this codelab.
In my project, I tried to draw not just on a view but on a custom ImageView
. Therefore, I have created a custom ImageView
and did all the steps as in the mentioned official codelabs.
Here is my custom ImageView
class:
// Stroke width for the the paint.
private const val STROKE_WIDTH = 12f
class MyImageView @JvmOverloads constructor(
context: Context, attrs: AttributeSet? = null, defStyleAttr: Int = 0
) : AppCompatImageView(context, attrs, defStyleAttr) {
private var path = Path()
private val drawColor = ResourcesCompat.getColor(resources, R.color.colorPaint, null)
private lateinit var extraCanvas : Canvas
private lateinit var extraBitmap : Bitmap
private val touchTolerance = ViewConfiguration.get(context).scaledTouchSlop
private var currentX = 0f
private var currentY = 0f
private var motionTouchEventX = 0f
private var motionTouchEventY = 0f
// Set up the paint with which to draw.
private val paint = Paint().apply {
color = drawColor
// Smooths out edges of what is drawn without affecting shape.
isAntiAlias = true
// Dithering affects how colors with higher-precision than the device are down-sampled.
isDither = true
style = Paint.Style.STROKE // default: FILL
strokeJoin = Paint.Join.ROUND // default: MITER
strokeCap = Paint.Cap.ROUND // default: BUTT
strokeWidth = STROKE_WIDTH // default: Hairline-width (really thin)
}
init{
init()
}
private fun init(){
setOnTouchListener(OnTouchListener { _, event ->
event?.let {
motionTouchEventX = it.x
motionTouchEventY = it.y
when(it.action){
MotionEvent.ACTION_DOWN -> touchStart()
MotionEvent.ACTION_MOVE -> touchMove()
MotionEvent.ACTION_UP -> touchUp()
}
return@OnTouchListener true
}
false
})
}
override fun onDraw(canvas: Canvas?) {
canvas?.drawBitmap(extraBitmap, 0f, 0f, null)
}
override fun onSizeChanged(w: Int, h: Int, oldw: Int, oldh: Int) {
super.onSizeChanged(w, h, oldw, oldh)
if (::extraBitmap.isInitialized) extraBitmap.recycle()
extraBitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888)
extraCanvas = Canvas(extraBitmap)
}
private fun touchStart() {
path.reset()
path.moveTo(motionTouchEventX, motionTouchEventY)
currentX = motionTouchEventX
currentY = motionTouchEventY
}
private fun touchMove() {
val dx = Math.abs(motionTouchEventX - currentX)
val dy = Math.abs(motionTouchEventY - currentY)
if (dx >= touchTolerance || dy >= touchTolerance) {
// QuadTo() adds a quadratic bezier from the last point,
// approaching control point (x1,y1), and ending at (x2,y2).
path.quadTo(currentX, currentY, (motionTouchEventX + currentX) / 2, (motionTouchEventY + currentY) / 2)
currentX = motionTouchEventX
currentY = motionTouchEventY
// Draw the path in the extra bitmap to save it.
extraCanvas.drawPath(path, paint)
}
// Invalidate() is inside the touchMove() under ACTION_MOVE because there are many other
// types of motion events passed into this listener, and we don't want to invalidate the
// view for those.
invalidate()
}
private fun touchUp() {
// Reset the path so it doesn't get drawn again.
path.reset()
}
}
This is how my XML layout of the MainActivity
looks like
(in the codelab there was no one, they just used the custom view programmatically):
<layout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
xmlns:app="http://schemas.android.com/apk/res-auto">
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".MainActivity">
<com.celik.abdullah.drawingonimageview.MyImageView
android:id="@+id/myImageView"
android:layout_width="match_parent"
android:layout_height="match_parent" />
</androidx.constraintlayout.widget.ConstraintLayout>
</layout>
And here is my MainActivity.kt
class:
class MainActivity : AppCompatActivity() {
private lateinit var binding: ActivityMainBinding
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
binding = DataBindingUtil.setContentView(this, R.layout.activity_main)
binding.myImageView.setImageResource(R.drawable.ic_launcher_foreground)
}
}
My problem is: the image resource is not shown on which I want to draw.
The drawing part works like in the codelabs. But the R.drawable.ic_launcher_foreground
drawable which I wanted to use just for testing purposes is not shown on the screen. WHY ?
I hope somebody can help.
In the custom view, the canvas is only being used to draw the extraBitmap
which is being updated by extraCanvas
so MyImageView
is only handling the drawing of extraBitmap
.
setImageResource
belongs to ImageView
class which is internally handling the conversion of resource to drawable and drawing it on the canvas inside it's onDraw()
but onDraw
is being overridden by the custom view which is not handling the drawing of received bitmap so the solution is call super.onDraw
as:
override fun onDraw(canvas: Canvas?) {
super.onDraw(canvas)
// ^^^^^^^^^^^^^^^^ add this to execute imageview's onDraw for image handling
canvas?.drawBitmap(extraBitmap, 0f, 0f, null)
}
Alternately, you can override
setImageResource
and add the code to draw received resource via setImageResource
method on the canvas
inside onDraw
of MyImageView
.