Issue
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.
Solution
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))
}
Answered By - Tom Joney
0 comments:
Post a Comment
Note: Only a member of this blog may post a comment.