حفظ حالت و ذخیره سازی دائمی جنبه های غیر ضروری برنامه های جوهرسازی هستند، به خصوص در Compose. اشیاء داده های اصلی، مانند ویژگی های قلم مو و نقاطی که یک سکته مغزی را تشکیل می دهند، پیچیده هستند و به طور خودکار باقی نمی مانند. این به یک استراتژی عمدی برای ذخیره وضعیت در طول سناریوهایی مانند تغییرات پیکربندی و ذخیره دائمی نقشه های کاربر در پایگاه داده نیاز دارد.
状态保留
在 Jetpack Compose 中,界面状态通常使用 remember
和 rememberSaveable
进行管理。虽然 rememberSaveable
可在配置更改期间自动保留状态,但其内置功能仅限于实现 Parcelable
或 Serializable
的基本数据类型和对象。
对于包含复杂属性(例如 Brush
)的自定义对象,您需要定义显式序列化和反序列化机制。自定义状态保存器对此很有用。通过为 Brush
对象定义自定义 Saver
,您可以在配置更改发生时保留其基本属性,如以下 brushStateSaver
示例所示。
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) }
ذخیره سازی دائمی
برای فعال کردن ویژگیهایی مانند ذخیره سند، بارگیری و همکاری بالقوه بلادرنگ، Strokes و دادههای مرتبط را در قالب سریالی ذخیره کنید. برای Ink API، سریالسازی دستی و سریالزدایی ضروری است.
برای بازیابی دقیق یک Stroke، Brush
و StrokeInputBatch
آن را ذخیره کنید.
-
Brush
: شامل فیلدهای عددی (اندازه، اپسیلون)، رنگ، و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)
}
}