Jetpack Compose
في Jetpack Compose، عادةً ما تتم إدارة حالة واجهة المستخدم باستخدام
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
: يتضمّن قيمًا رقمية الحقول (الحجم، إبسيلون)، واللون،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)
}
}