为了实现文档保存、加载和潜在的实时协作等功能,请以序列化格式存储笔画和关联数据。对于 Ink API,需要手动进行序列化和反序列化。
如需准确恢复笔画,请保存其 Brush 和 StrokeInputBatch。
Brush:包括数值字段(大小、epsilon)、颜色和BrushFamily。StrokeInputBatch:具有数值字段的输入点列表。
基本序列化
定义一个与 Ink 库对象相对应的序列化对象结构。
使用您偏好的框架(例如 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) } }