Nothing is being drawn, even tho function is called.
I tried:
1. Moving super.onDraw(canvas) to beginning and end of onDraw
2. Replacing it with draw(canvas)
3. Creating my view dynamically and statically (I mean, in XML)
4. Adding setWillNotDraw(false)
class GameView(context: Context) : View(context) {
init {
setWillNotDraw(false) // doesn't change anything
setBackgroundColor( Color.DKGRAY ) // this actually works, but onDraw is not, why?
}
private val ballPaint = Paint(ANTI_ALIAS_FLAG).apply {
color = 0xfefefe
style = Paint.Style.FILL
}
override fun onDraw(canvas: Canvas?) {
println("Test") // printed!
canvas!!.apply {
drawCircle((width/2).toFloat(), (height/2).toFloat(), 100.0f, ballPaint )
}
super.onDraw(canvas)
}
}
What am I doing wrong?
Thanks to pskink, that was the problem
color = 0xfffefefe.toInt()
Related
I have checked my code several times and don't know where is it going wrong. i am trying to build a drawing app and after doing all this code I am unable to get any output. Not able to draw on the canvas.
please help me get rid of this issue.
This is my DrawingView.kt
package com.example.drawingapp
import android.annotation.SuppressLint
import android.content.Context
import android.graphics.*
import android.util.AttributeSet
import android.view.MotionEvent
import android.view.View
import androidx.core.content.res.ResourcesCompat
class DrawingView(context : Context, attrs:AttributeSet): View(context, attrs){
private var myDrawPath: CustomPath? = null
private var myCanvasBitmap: Bitmap? = null
private var myDrawPaint: Paint? = null
private var myCanvasPaint: Paint? = null
private var myCanvas: Canvas?= null
private var myBrushSize :Float = 0.toFloat()
private var myColor = Color.BLACK
init{
drawingSetUp()
}
private fun drawingSetUp(){
myDrawPaint = Paint()
myDrawPath = CustomPath(myColor,myBrushSize)
myDrawPaint!!.color= myColor
myDrawPaint!!.style = Paint.Style.STROKE
myDrawPaint!!.strokeJoin =Paint.Join.ROUND
myDrawPaint!!.strokeCap =Paint.Cap.ROUND
myCanvasPaint= Paint(Paint.DITHER_FLAG)
myBrushSize=20.toFloat()
}
override fun onSizeChanged(w: Int, h: Int, oldw: Int, oldh: Int) {
super.onSizeChanged(w, h, oldw, oldh)
myCanvasBitmap= Bitmap.createBitmap(w, h, Bitmap.Config.ARGB_8888)
myCanvas = Canvas(myCanvasBitmap!!)
}
override fun onDraw(canvas: Canvas) {
super.onDraw(canvas)
myCanvas!!.drawBitmap(myCanvasBitmap!!, 0f,0f , myCanvasPaint)
if (!myDrawPath!!.isEmpty){
myDrawPaint!!.strokeWidth = myDrawPath!!.brushThickness
myDrawPaint!!.color= myDrawPath!!.color
myCanvas!!.drawPath(myDrawPath!!, myDrawPaint!!)
}
}
#SuppressLint("ClickableViewAccessibility")
override fun onTouchEvent(event: MotionEvent?): Boolean {
val touchX = event?.x
val touchY = event?.y
when (event?.action) {
MotionEvent.ACTION_DOWN -> {
myDrawPath!!.color = myColor
myDrawPath!!.brushThickness = myBrushSize
myDrawPath!!.reset()
if (touchX != null) {
if (touchY != null) {
myDrawPath!!.moveTo(touchX, touchX)
}
}
}
MotionEvent.ACTION_MOVE -> {
if (touchX != null) {
if (touchY != null) {
myDrawPath!!.lineTo(touchX, touchY)
}
}
}
MotionEvent.ACTION_UP->{
myDrawPath = CustomPath(myColor, myBrushSize)
}
else->return false
}
invalidate()
return true
}
internal inner class CustomPath(var color:Int, var brushThickness:Float): Path(){
}
}
This is my activity_main.xml file
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".MainActivity">
<com.example.drawingapp.DrawingView
android:id="#+id/drawing_view"
android:layout_width="0dp"
android:layout_height="0dp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintLeft_toLeftOf="parent"
/>
</androidx.constraintlayout.widget.ConstraintLayout>
And this is my MainActivity.kt file
package com.example.drawingapp
import androidx.appcompat.app.AppCompatActivity
import android.os.Bundle
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
}
}
You're drawing to the wrong Canvas. You need to draw to the one passed in to onDraw, that's the one linked to what your View will actually display.
When you do this:
myCanvasBitmap= Bitmap.createBitmap(w, h, Bitmap.Config.ARGB_8888)
myCanvas = Canvas(myCanvasBitmap!!)
you're creating your own, personal Bitmap, and creating your own personal Canvas to draw stuff on that bitmap. You can do that - but unless you draw that bitmap to the canvas onDraw gives you at some point, you'll never see it.
I'm assuming you wanted to do this:
override fun onDraw(canvas: Canvas) {
super.onDraw(canvas)
// draw your stored bitmap to the view's canvas
canvas.drawBitmap(myCanvasBitmap!!, 0f,0f , myCanvasPaint)
if (!myDrawPath!!.isEmpty){
myDrawPaint!!.strokeWidth = myDrawPath!!.brushThickness
myDrawPaint!!.color= myDrawPath!!.color
// draw this stored path to the view's canvas, on top of
// the stored bitmap you just drew
canvas.drawPath(myDrawPath!!, myDrawPaint!!)
}
}
i.e. drawing to canvas and not myCanvas. I don't know where you're actually drawing stuff to myCanvas though (to build up your stored image), you still need to do that so you have something to paint on the view's canvas. I'm assuming that happens elsewhere and the path you're drawing in onDraw is like a temporary overlay that isn't part of the stored image yet
Also just as a piece of advice, you need to avoid that !! stuff everywhere - that's a big sign you've made something nullable that shouldn't be nullable, and now you have to fight with the null-checking system because you're sure it can't be null but the system thinks it could be.
If you just move all your drawingSetup code into init, it'll see that you're assigning everything a value and you won't need to make them null. It's because you're assigning them through another function called from init that it can't be sure you're doing that - it's a limitation of the language unfortunately. Or you could just assign them directly:
private var myDrawPath = CustomPath(myColor,myBrushSize)
private lateinit var myCanvasBitmap: Bitmap
private var myDrawPaint = Paint().apply {
color= myColor
style = Paint.Style.STROKE
strokeJoin = Paint.Join.ROUND
strokeCap = Paint.Cap.ROUND
}
// etc
If you do want to keep that initialisation function (e.g. because you want to reset everything to normal) at least make your vars lateinit non-nullable types - if they're never going to be null when they're accessed, don't make them nullable! You just have to make sure lateinit stuff is assigned a value before something tries to read it - but the same is true for making something null and doing !! every time you access it, aka "trust me bro it's not null"
How can I create pie chart using onDraw override method in Kotlin on Android? I've tested multiple method Paint.apply parameters but didnt achieve pie chart. My code so far looks like this:
class ChartView(context: Context, attrs: AttributeSet) : View(context, attrs){
val paint = Paint().apply {
color = Color.GREEN
style = Paint.Style.FILL
}
val text = "My Component"
val bounds = Rect().also {
paint.getTextBounds(text, 0, text.length, it)
}
override fun onDraw(canvas: Canvas?) {
super.onDraw(canvas)
canvas?.drawCircle(344F, 890F, 160.0F,paint)
}
}
This resulted in regular green circle. Target effect would be:
https://ibb.co/Vw9Lzc0
I am working on a paint tool for android and have attempted to implement a 'fill with color' tool. Where a user is able to fill a particular area with color depending on what they have selected. I have found examples of Flood Fill for Java but cant see anything similar for Kotlin.
I have tried the following - https://developer.android.com/reference/kotlin/android/graphics/Path.FillType
But have not had too much luck since it instead uses the surrounding colour and not what colour the user has selected.
EDIT:
A snippet of the code I am working with:
class DrawingView(context: Context, attrs: AttributeSet) : View(context, attrs) {
private var mDrawPath: CustomPath? = null
private var mCanvasBitMap: Bitmap? = null
private var mDrawPaint: Paint? = null
private var mCanvasPaint: Paint? = null
private var mBrushSize: Float = 0.toFloat()
private var color = Color.BLACK
private var canvas: Canvas? = null
private val mPaths = ArrayList<CustomPath>()
private val mUndoPaths = ArrayList<CustomPath>()
private val mRedoPaths = ArrayList<CustomPath>()
private var counterUndo = 0
init {
setUpDrawing()
}
private fun setUpDrawing() {
mDrawPaint = Paint()
mDrawPath = CustomPath(color, mBrushSize)
mDrawPaint!!.color = color
mDrawPaint!!.style = Paint.Style.STROKE
mDrawPaint!!.strokeJoin = Paint.Join.ROUND
mDrawPaint!!.strokeCap = Paint.Cap.ROUND
mCanvasPaint = Paint(Paint.DITHER_FLAG)
}
//Change Canvas to Canvas? if fails
override fun onDraw(canvas: Canvas) {
super.onDraw(canvas)
canvas.drawBitmap(mCanvasBitMap!!, 0f,0f, mCanvasPaint)
for(path in mPaths){
mDrawPaint!!.strokeWidth = path.brushThickness
mDrawPaint!!.color = path.color
canvas.drawPath(path, mDrawPaint!!)
}
if(!mDrawPath!!.isEmpty) {
mDrawPaint!!.strokeWidth = mDrawPath!!.brushThickness
mDrawPaint!!.color = mDrawPath!!.color
canvas.drawPath(mDrawPath!!, mDrawPaint!!)
}
}
override fun onTouchEvent(event: MotionEvent?): Boolean {
val touchX = event?.x
val touchY = event?.y
when(event?.action){
MotionEvent.ACTION_DOWN -> {
mDrawPath!!.color = color
mDrawPath!!.brushThickness = mBrushSize
mDrawPath!!.reset()
mDrawPath!!.moveTo(touchX!!, touchY!!)
}
MotionEvent.ACTION_MOVE -> {
mDrawPath!!.lineTo(touchX!!, touchY!!)
}
MotionEvent.ACTION_UP -> {
mPaths.add(mDrawPath!!)
mDrawPath = CustomPath(color, mBrushSize)
}
else -> return false
}
invalidate()
return true
}
fun setFill(){
mDrawPaint!!.style = Paint.Style.FILL
}
}
This is kind of a complicated subject - if you want to draw a Path with a particular fill and colour, you need to create a Paint and use setColor (and setStyle to make it Paint.Style.FILL.
But Paths are for vector graphics, they're all defined lines and curves. Unless you're creating the shapes that the user can fill in as paths in the first place, you'll have trouble defining one to fit an arbitrary area.
I'm guessing you're actually using bitmaps, like a normal paint program, and you want to tap on a pixel and have that change colour, and also recursively change the colour of surrounding pixels if they meet a certain threshold. So you'll have to get the Canvas's Bitmap, and work out how to move through the pixels, changing them as you go (e.g. with Bitmap.setPixel())
There are a lot of algorithms for doing this, you'll just need to pick one and implement it. You probably don't want to use Paths to do it though!
I want to create a layout with animation curved lines like this link:
https://cdn.dribbble.com/users/1032798/screenshots/4981336/untitled-1.gif?vid=1
I would go with a custom view and draw my own path
class DrawView : View {
...
override fun onDraw(canvas: Canvas) {
super.onDraw(canvas)
path = Path()
paint.color = Color.RED
paint.strokeWidth = 3f
path.moveTo(34f, 259f)
path.cubicTo(68f, 151f, 286f, 350f, 336f, 252f)
canvas.drawPath(path!!, paint!!)
}
}
This ends up into
Now you have to make this parameterize, to make it animate-able.
To do so, it's quite easy to use a MotionLayout
Here is the code
Following up with my problem I describe here i'm trying to draw some shapes to a canvas that isn't associated with any view/layout (it's totally in memory & never drawn to the screen) and then use the bitmap I drew in my activity.
The problem:
It seems like my draw method never gets called. I have some log messages within it and I never see them print, and when I view the bitmap during runtime in Android Studio's debugger its always blank.
I've read various posts about having to call setWillNotDraw(false) to get the onDraw() method in a custom view to trigger, but because i'm never going to render this canvas to the screen my custom class extends Drawable() instead of View(context which doesn't include that method. This seems like a good choice since View includes lots of logic for user touches and other actions that my no-UI, background-generated drawable won't use.
That being said I still only saw blank bitmaps and no log messages from onDraw() when I extended View instead of Drawable and called setWillNowDraw(false) in the classes' constructor.
What's causing my canvas to always be blank?
class CustomImage : Drawable() {
var bitmap: Bitmap
private var barcodeText = Paint(ANTI_ALIAS_FLAG).apply {
color = Color.BLACK
textSize = 24f
}
private var circlePainter = Paint(ANTI_ALIAS_FLAG).apply {
color = Color.WHITE
}
init {
bitmap = Bitmap.createBitmap(LABEL_SIDE_LENGTH, LABEL_SIDE_LENGTH, Bitmap.Config.ARGB_8888)
}
override fun draw(canvas: Canvas) {
Timber.d("canvas: In draw")
canvas.setBitmap(bitmap)
canvas.apply {
drawColor(Color.TRANSPARENT, PorterDuff.Mode.CLEAR)
drawText("002098613", LABEL_SIDE_LENGTH - 50f, 50f, barcodeText)
drawCircle(200f, 200f, 100f, circlePainter)
}
Timber.d("canvas: $canvas")
}
override fun setAlpha(alpha: Int) {
}
override fun getOpacity(): Int {
return PixelFormat.OPAQUE
}
override fun setColorFilter(colorFilter: ColorFilter?) {
}
companion object {
const val LABEL_SIDE_LENGTH = 1160
}
}
class MyActivity: AppCompatActivity(){
private lateinit var customImgBitmap: Bitmap
override fun onCreate(savedInstanceState: Bundle?) {
customImgBitmap = createImgBitmap()
val test = POLabelGenerator
}
...
private fun createImgBitmap(): Bitmap {
return CustomImage.bitmap
}
}