Durum koruma ve kalıcı depolama

Jetpack Compose

Jetpack Compose'da kullanıcı arayüzü durumu genellikle remember ve rememberSaveable kullanılarak yönetilir. rememberSaveable, yapılandırma değişikliklerinde otomatik durum koruması sunsa da yerleşik özellikleri, Parcelable veya Serializable uygulayan ilkel veri türleri ve nesnelerle sınırlıdır.

Karmaşık iç içe yerleştirilmiş yapıları ve özellikleri içerebilen Brush gibi özel nesneler için açık serileştirme ve seri bozma mekanizmaları gereklidir. Bu noktada özel durum kaydedicinin faydası ortaya çıkar. Brush nesnesi için özel bir Saver tanımlayarak (örnek Converters sınıfını kullanarak sağlanan örnekte gösterildiği gibi), yapılandırma değişiklikleri olduğunda bile fırçanın temel özelliklerinin korunmasını sağlayabilirsiniz.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)
   
}
)

Ardından, kullanıcının seçtiği fırça durumunu korumak için özel Saver öğesini aşağıdaki gibi kullanabilirsiniz:

val converters = Converters()
val currentBrush = rememberSaveable(saver = brushStateSaver(converters)) { mutableStateOf(defaultBrush) }

Kalıcı depolama alanı

Doküman kaydetme, yükleme ve gerçek zamanlı ortak çalışma gibi özellikleri etkinleştirmek için vuruşları ve ilişkili verileri serileştirilmiş biçimde depolayın. Mürekkep API'si için manuel serileştirme ve seri bozma gerekir.

Bir çizgiyi doğru şekilde geri yüklemek için Brush ve [StrokeInputBatch] değerlerini kaydedin.

  • Brush: Sayısal alanları (beden, epsilon), rengi ve BrushFamily öğesini içerir.
  • StrokeInputBatch: Temel olarak, sayısal alanlara sahip giriş noktalarının listesi.

Temel serileştirme

Mürekkep kitaplığı nesnelerini yansıtan bir serileştirme nesnesi yapısı tanımlayın.

Gson, Moshi, Protobuf gibi tercih ettiğiniz çerçeveyi kullanarak serileştirilmiş verileri kodlayın ve optimizasyon için sıkıştırma kullanın.

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)
   
}

}