像素艺术创作者:从 Room 加载保存的像素数据导致奇怪 Canvas bug/glitch

Pixel art creator: loading saved pixel data from Room causes strange Canvas bug/glitch

注意:我的问题很具体,如果标题不够清楚,我深表歉意。

我正在使用 Canvas 创建像素艺术编辑器应用程序,像素艺术数据保存到 Room 数据库中。

这是 canvas 代码:

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++
                }
            }
        })
    }
}

这是一个 10 x 10 canvas 的示例:

像素数据作为 Json 字符串保存到 Room 数据库中,每当我们想要访问此数据时,我们都会将 Json 字符串转换回 List<Pixel>,等等等等:

道:

@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(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
    }
}

型号:

@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
}

现在,假设我们有两个类似的项目,具有两个不同的 spanCount 值:

一旦我们点击第一项,就会发生以下情况:

出于某种原因,它将网格大小设置为等于第二项的网格大小,我真的很想了解为什么会这样。我已经尝试了几个小时来修复这个奇怪的故障,但没有成功。

但是,由于某种原因,当我们转到第二个项目时它会正确呈现:

如果我们创建一个 new 80 x 80 canvas 然后返回到 second 创建它将呈现为所以:

我假设它将 spanCount 设置为数据库中最新项目的那个,但我不确定为什么会这样。

我怀疑它与获取 List<Pixel> 并将其绘制在屏幕上的代码有关:

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++
                }
            }
        })
    }

虽然我不完全确定错误的来源是哪里,因为看起来我做的一切都是对的。老实说,试图解决这个问题简直是他妈的脑残 lol

如果能解决这个烦人的故障,我将不胜感激,这样我就可以完成我的像素艺术编辑器应用程序了。

此错误已通过在用户点击后退按钮后调用 Fragment 的 Canvas 属性 上的 invalidate() 修复。我花了几天时间才解决这个问题,所以我在这里发布了一个答案,以防有人遇到类似的错误。

fun CanvasActivity.extendedOnBackPressed() {
    canvasFragmentInstance.myCanvasViewInstance.invalidate()

    startActivity(Intent(context, MainActivity::class.java))
}