My program now draws an oval at the touch of a finger and moves it along with the finger.
I want to add a multi-tap so that without releasing the first oval, pressing with the second finger will add the next one.
How my code works now:
Here I create 20 ovals:
var ovalsViews = Array(20) { OvalsView(this) }
In the function MotionEvent.ACTION_DOWN, MotionEvent.ACTION_POINTER_DOWN (here I put my finger on the screen) I add ovals from the ovals table to the layout (ConstraintLayout):
layout.addView(ovalsViews[ID])
In the MotionEvent.ACTION_MOVE function (here I move my finger on the screen) I change the coordinates of each oval that I hold with my finger:
ovalsViews[ID]._top = motionEvent.getY(ID)
ovalsViews[ID]._left = motionEvent.getX(ID)
And finally, in the function MotionEvent.ACTION_UP, MotionEvent.ACTION_POINTER_UP (here I release my finger from the screen) I want to remove the oval that I am holding with my finger:
layout.removeView(ovalsViews[ID])
Now my program is working strangely, when I click once everything works ok, but the next clicks do not add new ovals. Also, when I release the first finger, the oval disappears and in order to get an oval for the first time, now you will need to hit it twice, then three, and so on. What am I doing wrong?
Here is the whole code:
package com.example.lab10
import android.annotation.SuppressLint
import androidx.appcompat.app.AppCompatActivity
import android.os.Bundle
import android.content.Context
import android.graphics.Canvas
import android.graphics.Color
import android.graphics.Paint
import android.util.DisplayMetrics
import android.util.Log
import android.view.MotionEvent
import android.view.SurfaceHolder
import android.view.SurfaceView
import android.view.View
import android.view.WindowManager
import androidx.constraintlayout.widget.ConstraintLayout
class TaskSevenNEW : AppCompatActivity() {
#SuppressLint("ClickableViewAccessibility")
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_task_seven_new)
title = "Task#7 NEW"
val layout = findViewById<ConstraintLayout>(R.id.layout_7_NEW)
var ovalsViews = Array(20) { OvalsView(this) }
layout.setOnTouchListener(View.OnTouchListener { view, motionEvent ->
when (motionEvent.getActionMasked()) {
MotionEvent.ACTION_DOWN, MotionEvent.ACTION_POINTER_DOWN -> {
Log.d("touchInfo", "down")
val ID: Int = motionEvent.getPointerId(motionEvent.getActionIndex())
Log.d("touchInfo", "down ID: " + ID)
layout.addView(ovalsViews[ID])
}
MotionEvent.ACTION_MOVE -> {
Log.d("touchInfo", "move")
var idx = 0
while (idx < motionEvent.pointerCount) {
val ID: Int = motionEvent.getPointerId(idx) // pobranie unikalnego id dla każdego dotyku
idx++
Log.d("touchInfo", "move ID: " + ID)
ovalsViews[ID]._top = motionEvent.getY(ID)
ovalsViews[ID]._left = motionEvent.getX(ID)
}
}
MotionEvent.ACTION_UP, MotionEvent.ACTION_POINTER_UP -> {
Log.d("touchInfo", "up")
val ID: Int = motionEvent.getPointerId(motionEvent.getActionIndex())
Log.d("touchInfo", "up ID: " + ID)
layout.removeView(ovalsViews[ID])
}
else -> Log.d("touchInfo", "unhandled")
}
return#OnTouchListener true
})
}
private class OvalsView(context: Context) : SurfaceView(context), SurfaceHolder.Callback {
private val mSurfaceHolder: SurfaceHolder
private val mPainter = Paint()
private var mDrawingThread: Thread? = null
private val mDisplay = DisplayMetrics()
private var mDisplayWidth: Int
private var mDisplayHeight: Int
private var mRotation = 0f
private var running = true
var _top: Float = 0f
var _left: Float = 0f
init {
val wm = context.getSystemService(Context.WINDOW_SERVICE) as WindowManager
wm.defaultDisplay.getMetrics(mDisplay)
mDisplayWidth = mDisplay.widthPixels
mDisplayHeight = mDisplay.heightPixels
mPainter.isAntiAlias = true
mPainter.color = Color.RED
mSurfaceHolder = holder
mSurfaceHolder.addCallback(this)
}
private fun animateOvals(): Boolean {
mRotation += 1
return true
}
private fun drawWheel(canvas: Canvas) {
canvas.drawColor(Color.WHITE)
// canvas.rotate(mRotation, mDisplayWidth / 2f, mDisplayHeight / 2f)
//drawOval(float left, float top, float right, float bottom, #NonNull Paint paint)
// canvas.drawOval(mDisplayWidth/2f - 100f/2f - 100f, mDisplayHeight/2 + 100f/2f,
// mDisplayWidth/2f + 100f/2f + 100f, mDisplayHeight/2f - 100/2f, mPainter)
if (_top > 0f && _left > 0f)
canvas.drawOval(_left, _top, _left + 200f, _top + 350f, mPainter)
}
override fun onSizeChanged(w: Int, h: Int, oldw: Int, oldh: Int) {
mDisplayWidth = w
mDisplayHeight = h
super.onSizeChanged(w, h, oldw, oldh)
}
override fun surfaceChanged(holder: SurfaceHolder, format: Int, width: Int, height: Int) {
}
override fun surfaceDestroyed(holder: SurfaceHolder) {
if (mDrawingThread != null) {
mDrawingThread!!.interrupt()
running = false
mDrawingThread!!.join()
Log.e("qqq", "Thread done")
}
}
override fun surfaceCreated(holder: SurfaceHolder) {
mDrawingThread = Thread(Runnable {
var frameStartTime = System.nanoTime();
var frameTime: Long = 0
var canvas: Canvas? = null
while (!Thread.currentThread().isInterrupted && animateOvals() && running) {
canvas = mSurfaceHolder.lockCanvas()
if (canvas != null) {
drawWheel(canvas)
mSurfaceHolder.unlockCanvasAndPost(canvas)
}
frameTime = (System.nanoTime() - frameStartTime) / 1000000
if (frameTime < MAX_FRAME_TIME) // faster than the max fps - limit the FPS
{
try {
Thread.sleep(MAX_FRAME_TIME - frameTime)
} catch (e: InterruptedException) {
// ignore
}
}
}
})
mDrawingThread!!.start()
}
}
}
Related
My program draws an oval when we click on the screen and + changes the coordinates of the oval depending on the coordinates of the held finger. When we release the finger, the oval should disappear.
Now when I click on the screen I get my oval and + when I move my finger the oval is always next to it, but when I release my finger I get an error:
E/Surface: freeAllBuffers: 1 buffers were freed while being dequeued!
E/AndroidRuntime: FATAL EXCEPTION: Thread-2
Process: com.example.lab10, PID: 24198
java.lang.IllegalStateException: Surface has already been released.
at android.view.Surface.checkNotReleasedLocked(Surface.java:774)
at android.view.Surface.unlockCanvasAndPost(Surface.java:473)
at android.view.SurfaceView$1.unlockCanvasAndPost(SurfaceView.java:1629)
at com.example.lab10.TaskSix$OvalsView.surfaceCreated$lambda$0(TaskSix.kt:136)
at com.example.lab10.TaskSix$OvalsView.$r8$lambda$fxYnyciEdLkjDqbt7r8jlbaJ-60(Unknown Source:0)
at com.example.lab10.TaskSix$OvalsView$$ExternalSyntheticLambda0.run(Unknown Source:2)
at java.lang.Thread.run(Thread.java:1012)
package com.example.lab10
import android.annotation.SuppressLint
import androidx.appcompat.app.AppCompatActivity
import android.os.Bundle
import android.content.Context
import android.graphics.Canvas
import android.graphics.Color
import android.graphics.Paint
import android.util.DisplayMetrics
import android.util.Log
import android.view.MotionEvent
import android.view.SurfaceHolder
import android.view.SurfaceView
import android.view.View
import android.view.WindowManager
import androidx.constraintlayout.widget.ConstraintLayout
class TaskSix : AppCompatActivity() {
#SuppressLint("ClickableViewAccessibility")
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_task_six)
title = "Task#6"
val layout = findViewById<ConstraintLayout>(R.id.layout)
val ovalsView = OvalsView(this)
layout.setOnTouchListener(View.OnTouchListener { view, motionEvent ->
when (motionEvent.getActionMasked()) {
MotionEvent.ACTION_DOWN, MotionEvent.ACTION_POINTER_DOWN -> {
Log.d("qqq", "down")
val ID: Int = motionEvent.getPointerId(motionEvent.getActionIndex())
Log.d("qqq", "element with id: $ID")
layout.addView(ovalsView)
}
MotionEvent.ACTION_MOVE -> {
Log.d("qqq", "move")
var idx = 0
while (idx < motionEvent.pointerCount) {
val ID: Int = motionEvent.getPointerId(idx) // pobranie unikalnego id dla każdego dotyku
idx++
ovalsView._top = motionEvent.y
ovalsView._left = motionEvent.x
}
}
MotionEvent.ACTION_UP, MotionEvent.ACTION_POINTER_UP -> {
Log.d("qqq", "up")
layout.removeView(ovalsView)
}
else -> Log.d("qqq", "unhandled")
}
return#OnTouchListener true
})
}
private class OvalsView(context: Context) : SurfaceView(context), SurfaceHolder.Callback {
private val mSurfaceHolder: SurfaceHolder
private val mPainter = Paint()
private var mDrawingThread: Thread? = null
private val mDisplay = DisplayMetrics()
private var mDisplayWidth: Int
private var mDisplayHeight: Int
private var mRotation = 0f
var _top: Float = 0f
var _left: Float = 0f
init {
val wm = context.getSystemService(Context.WINDOW_SERVICE) as WindowManager
wm.defaultDisplay.getMetrics(mDisplay)
mDisplayWidth = mDisplay.widthPixels
mDisplayHeight = mDisplay.heightPixels
mPainter.isAntiAlias = true
mPainter.color = Color.RED
mSurfaceHolder = holder
mSurfaceHolder.addCallback(this)
}
private fun animateOvals(): Boolean {
mRotation += 1
return true
}
private fun drawWheel(canvas: Canvas) {
canvas.drawColor(Color.WHITE)
// canvas.rotate(mRotation, mDisplayWidth / 2f, mDisplayHeight / 2f)
//drawOval(float left, float top, float right, float bottom, #NonNull Paint paint)
// canvas.drawOval(mDisplayWidth/2f - 100f/2f - 100f, mDisplayHeight/2 + 100f/2f,
// mDisplayWidth/2f + 100f/2f + 100f, mDisplayHeight/2f - 100/2f, mPainter)
if (_top > 0f && _left > 0f)
canvas.drawOval(_left, _top, _left + 200f, _top + 350f, mPainter)
}
override fun onSizeChanged(w: Int, h: Int, oldw: Int, oldh: Int) {
mDisplayWidth = w
mDisplayHeight = h
super.onSizeChanged(w, h, oldw, oldh)
}
override fun surfaceChanged(holder: SurfaceHolder, format: Int, width: Int, height: Int) {
}
override fun surfaceDestroyed(holder: SurfaceHolder) {
if (mDrawingThread != null) {
mDrawingThread!!.interrupt()
}
}
override fun surfaceCreated(holder: SurfaceHolder) {
mDrawingThread = Thread(Runnable {
var MAX_FRAME_TIME = 1000/60
var frameStartTime = System.nanoTime();
var frameTime: Long = 0
var canvas: Canvas? = null
while (!Thread.currentThread().isInterrupted && animateOvals()) {
canvas = mSurfaceHolder.lockCanvas()
if (canvas != null) {
drawWheel(canvas)
mSurfaceHolder.unlockCanvasAndPost(canvas)
}
frameTime = (System.nanoTime() - frameStartTime) / 1000000
if (frameTime < MAX_FRAME_TIME) // faster than the max fps - limit the FPS
{
try {
Thread.sleep(MAX_FRAME_TIME - frameTime)
} catch (e: InterruptedException) {
// ignore
}
}
}
})
mDrawingThread!!.start()
}
}
}
I don't have time to dig into the Android source and find out exactly what's happening, but your crash is being generated by that Runnable interacting with the Surface's Canvas (creating the "Surface has been released" exception).
When you lift your finger, you do this:
MotionEvent.ACTION_UP, MotionEvent.ACTION_POINTER_UP -> {
layout.removeView(ovalsView)
}
And in the docs for removeView:
Note: do not invoke this method from View.draw(android.graphics.Canvas), View.onDraw(android.graphics.Canvas), dispatchDraw(android.graphics.Canvas) or any related method.
Which implies to me that calling removeView will cause things to happen to the View which mean they can no longer be drawn to, which is why you can't do it from inside one of the draw calls where you're actively drawing to their Canvas.
Since your drawing thread is constantly drawing to the Surface on a tick, you have a potential race condition where the Surface is released before the thread is interrupted. And you call interrupt() in surfaceDestroyed - but releasing happens before the Surface is actually destroyed, it just releases a reference and makes the Surface invalid.
So your drawing loop is probably still attempting to draw to that released, invalid Surface - there are lots of ways to get around this, but you might want to look at Surface#isValid (you can access a SurfaceView's underlying surface with getHolder()). And since you have multiple threads here, you might want to implement some kind of concurrent locking to avoid the UI thread invalidating the Surface with removeView while the drawing thread is running the Canvas-touching code, which could cause rare crashes
When I add ImageView using animation it works smoothly fine. But when I set up the player (astronaut) to be moved with interaction, the ImageView keeps trembling when not moving.
Problem
I have attached all the related codes in Kotlin below
MyGameObject class
open class MyGameObject(var x:Int, var y:Int, var dx:Int, var dy:Int, var image:Drawable) {
var width:Int = 300
var height:Int = 300
open fun move(canvas:Canvas)
{
x += dx
y += dy
if(x > (canvas.width - width) || x < 0)
dx = -dx
if(y > (canvas.height - height) || y < 0)
dy = -dy
image.setBounds(x, y, x+width, y+width)
image.draw(canvas)
}
}
Astronaut class (player)
import android.graphics.Canvas
import android.graphics.drawable.Drawable
class Astronaut(x:Int, y:Int, dx:Int, dy:Int, image: Drawable) : MyGameObject(x, y,dx, dy, image) {
var px:Int=0
var py:Int=0
override fun move(canvas: Canvas) {
if (px > x)
x += 5
else
x -= 5
if (py > y)
y += 5
else
y -= 5
image.setBounds(x, y, x + width, y + height)
image.draw(canvas)
}
}
MySurfaceView class
import android.content.Context
import android.graphics.Canvas
import android.graphics.Paint
import android.util.AttributeSet
import android.view.*
class MySurfaceView(context: Context?, attrs: AttributeSet?) : SurfaceView(context, attrs), Runnable {
var paint = Paint()
var isRunning = true
lateinit var myThread: Thread
lateinit var myHolder: SurfaceHolder
var myGameObjects = ArrayList<MyGameObject>()
val astronautImage = context!!.resources.getDrawable(R.drawable.astronaut, null)
val astronaut = Astronaut(100, 100, 0, 0, astronautImage)
init {
val asteroid = context!!.resources.getDrawable(R.drawable.asteroid, null)
myGameObjects.add(MyGameObject(100, 100, 10, 10, asteroid))
myGameObjects.add(astronaut)
myThread = Thread(this)
myThread.start()
myHolder = holder // controls access to canvas
}
override fun onTouchEvent(event: MotionEvent?): Boolean {
//return super.onTouchEvent(event)
astronaut.px = event!!.x.toInt()
astronaut.py = event!!.y.toInt()
return true
}
override fun run() {
while(isRunning)
{
if(!myHolder.surface.isValid)
{
continue
}
val canvas: Canvas = myHolder.lockCanvas() // prevent other threads using this section
canvas.drawRect(0f, 0f, canvas.width.toFloat(), canvas.height.toFloat(), paint)
for(gameObject in myGameObjects)
{
gameObject.move(canvas)
}
myHolder.unlockCanvasAndPost(canvas)
}
}
}
I have already tried to change values in astronaut class and MySurfaceView class but that did not work.
I think it is because you haven't set a fallback option in case astronaut is not moving. When px is equal to astronaut's x, astronaut goes into the else block because the px > x is false, then it moves 5 pixels to the left, then it goes back to initial position because now px is greater than x.
So, based by your logic, the condition should be:
x = x + when {
px > x -> 5
px == x -> 0
px < x -> -5
}
same with the y coordinate, you need a case when the astronaut is NOT moving... That's why the bitmap *trembles* in a diagonal path.
Hope this helps!
I have tried using ProgressBar or SeekBar but how to add left cut on one side and right side also
I've created a sample custom ConstraintLayout for you to achive what you want.it will behave as a regular constraintLayout but just with a custom background shape.
you have to change numbers and logic according to ui ofcourse, but it will cut your imageView inside it and you can also put progressbar and textViews to create your own ui
package com.customView
import android.content.Context
import android.graphics.Canvas
import android.graphics.Color
import android.graphics.Paint
import android.graphics.Path
import android.util.AttributeSet
import androidx.constraintlayout.widget.ConstraintLayout
/**
* Created by Payam Monsef
* At: 2022/Jun/27
*/
class CustomConstraintLayout #JvmOverloads constructor(
context: Context, attrs: AttributeSet? = null
) : ConstraintLayout(context, attrs) {
private var strokeWidth = 4F
private var strokeColor = Color.CYAN
private val cutPosition = CutPosition.TOP_LEFT
//like 0.2 of width will be reduced from cut position
private val reduceWidthMultiplier = 0.2
private var mPath: Path? = null
private var mPathBorder: Path? = null
private var mPaint: Paint? = null
init {
mPath = Path()
mPathBorder = Path()
mPaint = Paint()
mPaint?.style = Paint.Style.STROKE
mPaint?.color = strokeColor
mPaint?.strokeWidth = strokeWidth
setBackgroundColor(Color.TRANSPARENT)
}
override fun onSizeChanged(w: Int, h: Int, oldw: Int, oldh: Int) {
super.onSizeChanged(w, h, oldw, oldh)
val mW = width.toFloat()
val mH = height.toFloat()
mPathBorder?.apply {
reset()
if (cutPosition == CutPosition.BOTTOM_RIGHT) {
moveTo(0F, 0F)
lineTo(mW, 0F)
lineTo(mW - (reduceWidthMultiplier * mW).toFloat(), mH)
lineTo(0F, mH)
close()
} else {
moveTo((reduceWidthMultiplier * mW).toFloat(), 0F)
lineTo(mW, 0F)
lineTo(mW, mH)
lineTo(0F, mH)
close()
}
}
val offset = strokeWidth / 2
mPath?.apply {
reset()
if (cutPosition == CutPosition.BOTTOM_RIGHT) {
moveTo(offset, offset)
lineTo(mW - offset, offset)
lineTo(mW - (reduceWidthMultiplier * mW).toFloat(), mH - offset)
lineTo(offset, mH - offset)
close()
} else {
moveTo((reduceWidthMultiplier * mW).toFloat() , offset)
lineTo(mW - offset, offset)
lineTo(mW - offset, mH - offset)
lineTo(offset, mH - offset)
close()
}
}
}
override fun onDraw(canvas: Canvas) {
mPathBorder?.let { p ->
mPaint?.let { paint ->
canvas.drawPath(p, paint)
}
}
mPath?.let {
canvas.clipPath(it)
}
}
private enum class CutPosition {
TOP_LEFT, BOTTOM_RIGHT
}
}
Its better than you define above variables as custom attributes.you can read the Google documents to do that
Note: my question is very specific, apologies if the title isn't clear enough as to what the problem is.
I'm creating a pixel art editor application using Canvas, and the pixel art data is saved into a Room database.
Here's the canvas code:
package com.realtomjoney.pyxlmoose.customviews
import android.content.Context
import android.graphics.*
import android.util.Log
import android.view.MotionEvent
import android.view.View
import androidx.lifecycle.LifecycleOwner
import com.realtomjoney.pyxlmoose.activities.canvas.*
import com.realtomjoney.pyxlmoose.converters.JsonConverter
import com.realtomjoney.pyxlmoose.database.AppData
import com.realtomjoney.pyxlmoose.listeners.CanvasFragmentListener
import com.realtomjoney.pyxlmoose.models.Pixel
import kotlin.math.sqrt
class MyCanvasView(context: Context, val spanCount: Double) : View(context) {
lateinit var extraCanvas: Canvas
lateinit var extraBitmap: Bitmap
val rectangles = mutableMapOf<RectF, Paint?>()
private lateinit var caller: CanvasFragmentListener
private var thisWidth: Int = 0
private var scale: Double = 0.0
override fun onSizeChanged(w: Int, h: Int, oldw: Int, oldh: Int) {
super.onSizeChanged(w, h, oldw, oldh)
thisWidth = w
caller = context as CanvasFragmentListener
if (::extraBitmap.isInitialized) extraBitmap.recycle()
extraBitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888)
extraCanvas = Canvas(extraBitmap)
scale = (w / spanCount)
for (i in 0 until spanCount.toInt()) {
for (i_2 in 0 until spanCount.toInt()) {
val rect = RectF((i * scale).toFloat(), (i_2 * scale).toFloat(), (i * scale).toFloat() + scale.toFloat(), (i_2 * scale).toFloat() + scale.toFloat())
rectangles[rect] = null
extraCanvas.drawRect(rect, Paint().apply { style = Paint.Style.FILL; color = Color.WHITE })
}
}
}
private fun drawRectAt(x: Float, y: Float) {
for (rect in rectangles.keys) {
if (rect.contains(x, y)) {
caller.onPixelTapped(this, rect)
invalidate()
}
}
}
override fun dispatchTouchEvent(event: MotionEvent): Boolean {
when (event.actionMasked) {
MotionEvent.ACTION_MOVE -> drawRectAt(event.x, event.y)
MotionEvent.ACTION_DOWN -> drawRectAt(event.x, event.y)
}
return true
}
override fun onDraw(canvas: Canvas) {
super.onDraw(canvas)
canvas.drawBitmap(extraBitmap, 0f, 0f, null)
}
fun saveData(): List<Pixel> {
val data = mutableListOf<Pixel>()
for (pair in rectangles) {
data.add(Pixel(pair.value?.color))
}
return data
}
fun loadData(context: LifecycleOwner, index: Int) {
AppData.db.pixelArtCreationsDao().getAllPixelArtCreations().observe(context, {
currentPixelArtObj = it[index]
val localPixelData = JsonConverter.convertJsonStringToPixelList(currentPixelArtObj.pixelData)
var index = 0
for (i in 0 until sqrt(localPixelData.size.toDouble()).toInt()) {
for (i_2 in 0 until sqrt(localPixelData.size.toDouble()).toInt()) {
val rect = RectF((i * scale).toFloat(), (i_2 * scale).toFloat(), (i * scale).toFloat() + scale.toFloat(), (i_2 * scale).toFloat() + scale.toFloat())
rectangles[rect] = null
extraCanvas.drawRect(rect, Paint().apply { style = Paint.Style.FILL; isAntiAlias = false; color = localPixelData[index].pixelColor ?: Color.WHITE })
rectangles[rectangles.keys.toList()[index]] = Paint().apply { style = Paint.Style.FILL; isAntiAlias = false; color = localPixelData[index].pixelColor ?: Color.WHITE }
index++
}
}
})
}
}
Here's an example of how a 10 by 10 canvas may look like:
The pixel data is saved into a Room database as a Json String, and whenever we want to access this data we convert the Json String back to a List<Pixel>, et cetera:
Dao:
#Dao
interface PixelArtCreationsDao {
#Insert
suspend fun insertPixelArt(pixelArt: PixelArt)
#Query("SELECT * FROM PixelArt ")
fun getAllPixelArtCreations(): LiveData<List<PixelArt>>
#Query("DELETE FROM PixelArt WHERE objId=:pixelArtId")
fun deletePixelArtCreation(pixelArtId: Int)
#Query("UPDATE PixelArt SET item_bitmap=:bitmap WHERE objId=:id_t")
fun updatePixelArtCreationBitmap(bitmap: String, id_t: Int): Int
#Query("UPDATE PixelArt SET item_pixel_data=:pixelData WHERE objId=:id_t")
fun updatePixelArtCreationPixelData(pixelData: String, id_t: Int): Int
#Query("UPDATE PixelArt SET item_favourited=:favorited WHERE objId=:id_t")
fun updatePixelArtCreationFavorited(favorited: Boolean, id_t: Int): Int
}
PixelArt database:
#Database(entities = [PixelArt::class], version = 1)
abstract class PixelArtDatabase: RoomDatabase() {
abstract fun pixelArtCreationsDao(): PixelArtCreationsDao
companion object {
private var instance: PixelArtDatabase? = null
fun getDatabase(context: Context): PixelArtDatabase {
if (instance == null) {
synchronized(PixelArtDatabase::class) {
if (instance == null) instance = Room.databaseBuilder(context.applicationContext, PixelArtDatabase::class.java, AppData.dbFileName).allowMainThreadQueries().build()
}
}
return instance!!
}
}
}
AppData:
class AppData {
companion object {
var dbFileName = "pixel_art_db"
lateinit var db: PixelArtDatabase
}
}
Model:
#Entity
data class PixelArt(
#ColumnInfo(name = "item_bitmap") var bitmap: String,
#ColumnInfo(name = "item_title") var title: String,
#ColumnInfo(name = "item_pixel_data") var pixelData: String,
#ColumnInfo(name = "item_favourited") var favourited: Boolean,
#ColumnInfo(name = "item_date_created") var dateCreated: String = DateTimeFormatter.ofPattern("yyyy/MM/dd HH:mm:ss").format(LocalDateTime.now())) {
#PrimaryKey(autoGenerate = true) var objId = 0
}
Now, say we have two projects like so with two different spanCount values:
Once we click on the first item, the following occurs:
For some reason it's setting the grid size to be equal to that of the second item, and I'm really trying to understand why this is the case. I've tried for a couple of hours to fix this weird glitch and have had no luck doing so.
But, for some reason when we go to our second item it renders properly:
If we create a new 80 x 80 canvas and then go back to the second creation it will render like so:
I'm assuming that it's setting the spanCount to that of the latest item in the database, but I'm unsure why this is happening.
I suspect it has something to do with the code that takes the List<Pixel> and draws it onscreen:
fun loadData(context: LifecycleOwner, index: Int) {
AppData.db.pixelArtCreationsDao().getAllPixelArtCreations().observe(context, {
currentPixelArtObj = it[index]
val localPixelData = JsonConverter.convertJsonStringToPixelList(currentPixelArtObj.pixelData)
var index = 0
for (i in 0 until sqrt(localPixelData.size.toDouble()).toInt()) {
for (i_2 in 0 until sqrt(localPixelData.size.toDouble()).toInt()) {
val rect = RectF((i * scale).toFloat(), (i_2 * scale).toFloat(), (i * scale).toFloat() + scale.toFloat(), (i_2 * scale).toFloat() + scale.toFloat())
rectangles[rect] = null
extraCanvas.drawRect(rect, Paint().apply { style = Paint.Style.FILL; isAntiAlias = false; color = localPixelData[index].pixelColor ?: Color.WHITE })
rectangles[rectangles.keys.toList()[index]] = Paint().apply { style = Paint.Style.FILL; isAntiAlias = false; color = localPixelData[index].pixelColor ?: Color.WHITE }
index++
}
}
})
}
Although I'm not entirely sure where the source of the bug is coming from because it seems I'm doing everything right. It's honestly been a brainfuck trying to fix this lol
Any help would be appreciated to fix this annoying glitch so I can finish my pixel art editor app.
This bug was fixed by calling invalidate() on the Fragment's Canvas property after the user taps the back button. It took me a couple of days to get to fix this, so I'm posting an answer here in case someone has a similar bug.
fun CanvasActivity.extendedOnBackPressed() {
canvasFragmentInstance.myCanvasViewInstance.invalidate()
startActivity(Intent(context, MainActivity::class.java))
}
At this class i draw simple ground for Tic-Tae-Toe. It consists of intercepted lines and "X" in the center of the cell.
So when User touches the cell, then textColor in it should be changed.
I use invalidate(rect) to redraw concrete cell, but in this case every cell changes it's textColor.
According to Romain Guy words, the canvas with whole view Rect comes
for drawing. The DisplayList will find interceptions between drawing commands and your dirty Rect, and only those commands will be drawn. But seems, that it doesn't work so way.
Partial invalidation in custom Android view with hardware acceleration
And also i found strange code change between 4.4 - 5.0 Android. So you can see, that mCurrentDirty disappeared from code at all.
Android View.invalidate(Rect) different behavior between two devices
P.S for SA this logic works correctly, and only dirty Rect is changed.
package com.eugeneshapovalov.fizmigclient
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.MotionEvent
import android.view.View
import timber.log.Timber
class TicTacToeView : View, View.OnTouchListener {
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)
companion object {
const val CELL_SIZE_RATIO = 1 / 3f
const val LINE_SIZE = 2f
const val CELL_COUNT = 3
}
val linePaint = Paint()
val textPaint = Paint()
val dirtyCell = Rect()
var colorNumber: Int
init {
setOnTouchListener(this)
colorNumber = 0
linePaint.strokeWidth = resources.displayMetrics.density * LINE_SIZE
textPaint.textSize = 60f
}
private lateinit var cells: Array<Array<Rect>>
override fun onSizeChanged(w: Int, h: Int, oldw: Int, oldh: Int) {
super.onSizeChanged(w, h, oldw, oldh)
initCells()
}
private fun initCells() {
cells = Array(CELL_COUNT, { Array(CELL_COUNT, { Rect() }) })
val xCell = (width * CELL_SIZE_RATIO).toInt()
val yCell = (height * CELL_SIZE_RATIO).toInt()
for (i in 0 until CELL_COUNT) {
for (j in 0 until CELL_COUNT) {
cells[i][j].left = (x + j * xCell).toInt()
cells[i][j].top = (y + i * yCell).toInt()
cells[i][j].right = (x + (j + 1) * xCell).toInt()
cells[i][j].bottom = (y + (i + 1) * yCell).toInt()
}
}
}
override fun onDraw(canvas: Canvas) {
super.onDraw(canvas)
drawLines(canvas)
drawText(canvas)
}
private fun drawLines(canvas: Canvas) {
// Vertical lines
canvas.drawLine(x + width * CELL_SIZE_RATIO, y, x + width * CELL_SIZE_RATIO, y + height, linePaint)
canvas.drawLine(x + width * 2 * CELL_SIZE_RATIO, y, x + width * 2 * CELL_SIZE_RATIO, y + height, linePaint)
// Horizontal lines
canvas.drawLine(x, y + height * CELL_SIZE_RATIO, x + width, y + height * CELL_SIZE_RATIO, linePaint)
canvas.drawLine(x, y + height * 2 * CELL_SIZE_RATIO, x + width, y + height * 2 * CELL_SIZE_RATIO, linePaint)
}
private fun drawText(canvas: Canvas) {
textPaint.color = when (colorNumber % 5) {
0 -> Color.BLACK
1 -> Color.BLUE
2 -> Color.RED
3 -> Color.GRAY
4 -> Color.YELLOW
else -> Color.GREEN
}
for (i in 0 until CELL_COUNT) {
for (j in 0 until CELL_COUNT) {
val rect = cells[i][j]
canvas.drawText("X", rect.exactCenterX(), rect.exactCenterY(), textPaint)
}
}
}
override fun onTouch(v: View?, event: MotionEvent?): Boolean {
when (event?.action) {
MotionEvent.ACTION_UP -> {
for (i in 0 until CELL_COUNT) {
for (j in 0 until CELL_COUNT) {
val rect = cells[i][j]
if (rect.contains(event.x.toInt(), event.y.toInt())) {
colorNumber += (j + 7)
Timber.d("Rect: ${rect.flattenToString()}")
invalidate(rect)
}
}
}
}
}
return true
}
}