Jetpack Compose
ใน Jetpack Compose โดยทั่วไประบบจะจัดการสถานะ UI โดยใช้ remember
และ rememberSaveable
แม้ว่า rememberSaveable
จะเสนอการคงสถานะโดยอัตโนมัติเมื่อมีการทําการเปลี่ยนแปลงการกําหนดค่า แต่ความสามารถในตัวจะจํากัดอยู่ที่ประเภทข้อมูลพื้นฐานและออบเจ็กต์ที่ใช้ Parcelable
หรือ Serializable
สําหรับออบเจ็กต์ที่กําหนดเอง เช่น Brush
ซึ่งอาจประกอบด้วยโครงสร้างและพร็อพเพอร์ตี้ที่ฝังอยู่อย่างซับซ้อน คุณต้องใช้กลไกการแปลงเป็นอนุกรมและถอดรหัสอย่างชัดแจ้ง ในกรณีนี้ ตัวบันทึกสถานะที่กําหนดเองจะมีประโยชน์ การกําหนด Saver
ที่กําหนดเองสําหรับออบเจ็กต์ Brush
ตามที่แสดงในตัวอย่างที่มี brushStateSaver
โดยใช้คลาส Converters
ตัวอย่าง จะช่วยให้คุณรักษาแอตทริบิวต์ที่สําคัญของแปรงได้แม้ว่าจะมีการเปลี่ยนแปลงการกําหนดค่า
fun brushStateSaver(converters: Converters): Saver<MutableState<Brush>, String> = Saver(
save = { state ->
converters.brushToString(state.value)
},
restore = { jsonString ->
val brush = converters.stringToBrush(jsonString)
mutableStateOf(brush)
}
)
จากนั้นคุณสามารถใช้ Saver
ที่กําหนดเองเพื่อเก็บสถานะแปรงที่เลือกของผู้ใช้ ดังนี้
val converters = Converters()
val currentBrush = rememberSaveable(saver = brushStateSaver(converters)) { mutableStateOf(defaultBrush) }
พื้นที่เก็บข้อมูลถาวร
หากต้องการเปิดใช้ฟีเจอร์ต่างๆ เช่น การบันทึก โหลดเอกสาร และการทํางานร่วมกันแบบเรียลไทม์ที่เป็นไปได้ ให้จัดเก็บการเขียนและข้อมูลที่เชื่อมโยงในรูปแบบที่เป็นอนุกรม สำหรับ Ink API คุณจะต้องดำเนินการจัดรูปแบบและถอดรูปแบบด้วยตนเอง
หากต้องการกู้คืนการวาดเส้นอย่างถูกต้อง ให้บันทึกBrush
และ [StrokeInputBatch
] ของเส้นนั้น
Brush
: มีฟิลด์ตัวเลข (ขนาด, epsilon), สี และBrushFamily
StrokeInputBatch
: โดยพื้นฐานแล้วคือรายการจุดอินพุตที่มีช่องตัวเลข
การจัดรูปแบบพื้นฐาน
กำหนดโครงสร้างออบเจ็กต์การจัดลำดับที่แสดงภาพออบเจ็กต์คลังโหมดหมึก
เข้ารหัสข้อมูลที่แปลงเป็นอนุกรมโดยใช้เฟรมเวิร์กที่ต้องการ เช่น Gson, Moshi, Protobuf และอื่นๆ และใช้การบีบอัดเพื่อเพิ่มประสิทธิภาพ
data class SerializedStroke(
val inputs: SerializedStrokeInputBatch,
val brush: SerializedBrush
)
data class SerializedBrush(
val size: Float,
val color: Long,
val epsilon: Float,
val stockBrush: SerializedStockBrush
)
enum class SerializedStockBrush {
MARKER_V1,
PRESSURE_PEN_V1,
HIGHLIGHTER_V1
}
data class SerializedStrokeInputBatch(
val toolType: SerializedToolType,
val strokeUnitLengthCm: Float,
val inputs: List<SerializedStrokeInput>
)
data class SerializedStrokeInput(
val x: Float,
val y: Float,
val timeMillis: Float,
val pressure: Float,
val tiltRadians: Float,
val orientationRadians: Float,
val strokeUnitLengthCm: Float
)
enum class SerializedToolType {
STYLUS,
TOUCH,
MOUSE,
UNKNOWN
}
class Converters {
private val gson: Gson = GsonBuilder().create()
companion object {
private val stockBrushToEnumValues =
mapOf(
StockBrushes.markerV1 to SerializedStockBrush.MARKER_V1,
StockBrushes.pressurePenV1 to SerializedStockBrush.PRESSURE_PEN_V1,
StockBrushes.highlighterV1 to SerializedStockBrush.HIGHLIGHTER_V1,
)
private val enumToStockBrush =
stockBrushToEnumValues.entries.associate { (key, value) -> value to key }
}
private fun serializeBrush(brush: Brush): SerializedBrush {
return SerializedBrush(
size = brush.size,
color = brush.colorLong,
epsilon = brush.epsilon,
stockBrush = stockBrushToEnumValues[brush.family] ?: SerializedStockBrush.MARKER_V1,
)
}
private fun serializeStrokeInputBatch(inputs: StrokeInputBatch): SerializedStrokeInputBatch {
val serializedInputs = mutableListOf<SerializedStrokeInput>()
val scratchInput = StrokeInput()
for (i in 0 until inputs.size) {
inputs.populate(i, scratchInput)
serializedInputs.add(
SerializedStrokeInput(
x = scratchInput.x,
y = scratchInput.y,
timeMillis = scratchInput.elapsedTimeMillis.toFloat(),
pressure = scratchInput.pressure,
tiltRadians = scratchInput.tiltRadians,
orientationRadians = scratchInput.orientationRadians,
strokeUnitLengthCm = scratchInput.strokeUnitLengthCm,
)
)
}
val toolType =
when (inputs.getToolType()) {
InputToolType.STYLUS -> SerializedToolType.STYLUS
InputToolType.TOUCH -> SerializedToolType.TOUCH
InputToolType.MOUSE -> SerializedToolType.MOUSE
else -> SerializedToolType.UNKNOWN
}
return SerializedStrokeInputBatch(
toolType = toolType,
strokeUnitLengthCm = inputs.getStrokeUnitLengthCm(),
inputs = serializedInputs,
)
}
private fun deserializeStroke(serializedStroke: SerializedStroke): Stroke? {
val inputs = deserializeStrokeInputBatch(serializedStroke.inputs) ?: return null
val brush = deserializeBrush(serializedStroke.brush) ?: return null
return Stroke(brush = brush, inputs = inputs)
}
private fun deserializeBrush(serializedBrush: SerializedBrush): Brush {
val stockBrushFamily = enumToStockBrush[serializedBrush.stockBrush] ?: StockBrushes.markerV1
return Brush.createWithColorLong(
family = stockBrushFamily,
colorLong = serializedBrush.color,
size = serializedBrush.size,
epsilon = serializedBrush.epsilon,
)
}
private fun deserializeStrokeInputBatch(
serializedBatch: SerializedStrokeInputBatch
): StrokeInputBatch {
val toolType =
when (serializedBatch.toolType) {
SerializedToolType.STYLUS -> InputToolType.STYLUS
SerializedToolType.TOUCH -> InputToolType.TOUCH
SerializedToolType.MOUSE -> InputToolType.MOUSE
else -> InputToolType.UNKNOWN
}
val batch = MutableStrokeInputBatch()
serializedBatch.inputs.forEach { input ->
batch.addOrThrow(
type = toolType,
x = input.x,
y = input.y,
elapsedTimeMillis = input.timeMillis.toLong(),
pressure = input.pressure,
tiltRadians = input.tiltRadians,
orientationRadians = input.orientationRadians,
)
}
return batch
}
fun serializeStrokeToEntity(stroke: Stroke): StrokeEntity {
val serializedBrush = serializeBrush(stroke.brush)
val serializedInputs = serializeStrokeInputBatch(stroke.inputs)
return StrokeEntity(
brushSize = serializedBrush.size,
brushColor = serializedBrush.color,
brushEpsilon = serializedBrush.epsilon,
stockBrush = serializedBrush.stockBrush,
strokeInputs = gson.toJson(serializedInputs),
)
}
fun deserializeEntityToStroke(entity: StrokeEntity): Stroke {
val serializedBrush =
SerializedBrush(
size = entity.brushSize,
color = entity.brushColor,
epsilon = entity.brushEpsilon,
stockBrush = entity.stockBrush,
)
val serializedInputs =
gson.fromJson(entity.strokeInputs, SerializedStrokeInputBatch::class.java)
val brush = deserializeBrush(serializedBrush)
val inputs = deserializeStrokeInputBatch(serializedInputs)
return Stroke(brush = brush, inputs = inputs)
}
fun brushToString(brush: Brush): String {
val serializedBrush = serializeBrush(brush)
return gson.toJson(serializedBrush)
}
fun stringToBrush(jsonString: String): Brush {
val serializedBrush = gson.fromJson(jsonString, SerializedBrush::class.java)
return deserializeBrush(serializedBrush)
}
}