Eliminate stroke commit flicker with pending path overlay

When the pen lifts, the in-progress path was cleared immediately but
the completed stroke didn't appear on the backing bitmap until the
async DB save completed — creating a visible flash where the stroke
vanished for several frames.

Fix: transfer the completed path to a "pending" overlay that stays
visible in the dynamic layer until addCompletedStroke() confirms the
stroke is on the backing bitmap. The stroke is now visible continuously
from pen-down through commit with no gap.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-03-24 22:16:53 -07:00
parent 73f35cd33d
commit bf15db8334

View File

@@ -58,6 +58,12 @@ class PadCanvasView(context: Context) : View(context) {
private var currentPaint: Paint? = null private var currentPaint: Paint? = null
private val currentPoints = mutableListOf<Float>() private val currentPoints = mutableListOf<Float>()
// --- Pending stroke: keeps the last completed stroke visible until
// the backing bitmap confirms it, preventing the flash where the
// stroke disappears between pen-up and DB save completion ---
private var pendingPath: Path? = null
private var pendingPaint: Paint? = null
// --- Line snap --- // --- Line snap ---
private var strokeOriginX = 0f private var strokeOriginX = 0f
private var strokeOriginY = 0f private var strokeOriginY = 0f
@@ -255,6 +261,9 @@ class PadCanvasView(context: Context) : View(context) {
} else { } else {
bitmapDirty = true bitmapDirty = true
} }
// Stroke is now on the backing bitmap — clear the pending overlay
pendingPath = null
pendingPaint = null
invalidate() invalidate()
} }
@@ -310,6 +319,13 @@ class PadCanvasView(context: Context) : View(context) {
// Dynamic elements // Dynamic elements
c.withMatrix(viewMatrix) { c.withMatrix(viewMatrix) {
// Pending stroke (visible until backing bitmap confirms it)
pendingPath?.let { path ->
pendingPaint?.let { paint ->
drawPath(path, paint)
}
}
// In-progress stroke
currentPath?.let { path -> currentPath?.let { path ->
currentPaint?.let { paint -> currentPaint?.let { paint ->
drawPath(path, paint) drawPath(path, paint)
@@ -581,6 +597,10 @@ class PadCanvasView(context: Context) : View(context) {
isSnappedToLine = false isSnappedToLine = false
if (currentPoints.size >= 4) { if (currentPoints.size >= 4) {
val points = currentPoints.toFloatArray() val points = currentPoints.toFloatArray()
// Keep the path visible as "pending" until the backing
// bitmap confirms the stroke (prevents flash on commit)
pendingPath = currentPath
pendingPaint = currentPaint
onStrokeCompleted?.invoke(canvasState.penWidthPt, Color.BLACK, points, Stroke.STYLE_PLAIN) onStrokeCompleted?.invoke(canvasState.penWidthPt, Color.BLACK, points, Stroke.STYLE_PLAIN)
} }
currentPath = null currentPath = null