Durumun korunması ve kalıcı depolama, mürekkep uygulamalarının önemli yönleridir. Bu, yapılandırma değişiklikleri gibi senaryolarda durumu kaydetmek ve kullanıcının çizimlerini kalıcı olarak bir veritabanına kaydetmek için kasıtlı bir strateji gerektirir.
Durumu koruma
Görünüme dayalı uygulamalarda kullanıcı arayüzü durumu, aşağıdakilerin bir kombinasyonu kullanılarak yönetilir:
ViewModelnesneleri- Kullanılarak kaydedilen örnek durumu:
- Etkinlik
onSaveInstanceState() - ViewModel SavedStateHandle
- Uygulama ve etkinlik geçişleri sırasında kullanıcı arayüzü durumunu kalıcı hale getirmek için yerel depolama alanı
- Etkinlik
Kullanıcı arayüzü durumlarını kaydetme başlıklı makaleyi inceleyin.
Kalıcı depolama
Belge kaydetme, yükleme ve olası gerçek zamanlı ortak çalışma gibi özellikleri etkinleştirmek için vuruşları ve ilişkili verileri seri hale getirilmiş bir biçimde saklarız. Ink API için manuel serileştirme ve seri durumdan çıkarma gereklidir.
Bir vuruşu doğru şekilde geri yüklemek için Brush ve StrokeInputBatch değerlerini kaydedin.
Brush: Sayısal alanlar (boyut, epsilon), renk veBrushFamilyiçerir.StrokeInputBatch: Sayısal alanlar içeren giriş noktalarının listesi.
Depolama modülü, en karmaşık bölüm olan StrokeInputBatch'ı kompakt bir şekilde serileştirmeyi kolaylaştırır.
Bir vuruşu kaydetmek için:
- Depolama modülünün kodlama işlevini kullanarak
StrokeInputBatchöğesini serileştirin. Elde edilen ikili verileri saklayın. - Fırça darbesinin temel özelliklerini ayrı ayrı kaydedin:
- Fırça ailesini temsil eden enum. Örnek seri hale getirilebilse de bu, sınırlı sayıda fırça ailesi kullanan uygulamalar için verimli değildir.
colorLongsizeepsilon
fun serializeStroke(stroke: Stroke): SerializedStroke {
val serializedBrush = serializeBrush(stroke.brush)
val encodedSerializedInputs = ByteArrayOutputStream().use
{
stroke.inputs.encode(it)
it.toByteArray()
}
return SerializedStroke(
inputs = encodedSerializedInputs,
brush = serializedBrush
)
}
Kontur nesnesi yüklemek için:
StrokeInputBatchiçin kaydedilen ikili verileri alın ve depolama modülünün decode() işlevini kullanarak seri durumdan çıkarın.- Kaydedilen
Brushözelliklerini alın ve fırçayı oluşturun. Yeniden oluşturulan fırçayı ve seri durumdan çıkarılan
StrokeInputBatchkullanarak son fırça darbesini oluşturun.fun deserializeStroke(serializedStroke: SerializedStroke): Stroke { val inputs = ByteArrayInputStream(serializedStroke.inputs).use { StrokeInputBatch.decode(it) } val brush = deserializeBrush(serializedStroke.brush) return Stroke(brush = brush, inputs = inputs) }
Yakınlaştırma, kaydırma ve döndürme işlemlerini yönetme
Uygulamanızda yakınlaştırma, kaydırma veya döndürme destekleniyorsa mevcut dönüşümü InProgressStrokes iletmeniz gerekir. Bu sayede yeni çizilen konturlar, mevcut konturlarınızın konumuna ve ölçeğine uygun hale gelir.
Bunu, Matrix parametresine pointerEventToWorldTransform ileterek yapabilirsiniz. Matris, bitmiş vuruşlar tuvalinize uyguladığınız dönüşümün tersini temsil etmelidir.
@Composable
fun ZoomableDrawingScreen(...) {
// 1. Manage your zoom/pan state (e.g., using detectTransformGestures).
var zoom by remember { mutableStateOf(1f) }
var pan by remember { mutableStateOf(Offset.Zero) }
// 2. Create the Matrix.
val pointerEventToWorldTransform = remember(zoom, pan) {
android.graphics.Matrix().apply {
// Apply the inverse of your rendering transforms
postTranslate(-pan.x, -pan.y)
postScale(1 / zoom, 1 / zoom)
}
}
Box(modifier = Modifier.fillMaxSize()) {
// ...Your finished strokes Canvas, with regular transform applied
// 3. Pass the matrix to InProgressStrokes.
InProgressStrokes(
modifier = Modifier.fillMaxSize(),
pointerEventToWorldTransform = pointerEventToWorldTransform,
defaultBrush = currentBrush,
nextBrush = onGetNextBrush,
onStrokesFinished = onStrokesFinished
)
}
}
Vuruşları dışa aktarma
Fırça darbesi sahnenizi statik bir resim dosyası olarak dışa aktarmanız gerekebilir. Bu, çizimi diğer uygulamalarla paylaşmak, küçük resimler oluşturmak veya içeriğin son, düzenlenemeyen bir sürümünü kaydetmek için kullanışlıdır.
Bir sahneyi dışa aktarmak için vuruşlarınızı doğrudan ekrana değil, ekran dışı bir bit eşleme olarak oluşturabilirsiniz. Görünür bir kullanıcı arayüzü bileşenine ihtiyaç duymadan tuval üzerinde çizimler kaydetmenize olanak tanıyan Android's Picture API öğesini kullanın.
Bu işlemde Picture örneği oluşturulur, beginRecording() çağrılarak Canvas alınır ve ardından mevcut CanvasStrokeRenderer kullanılarak her bir vuruş bu Canvas üzerine çizilir. Tüm çizim komutlarını kaydettikten sonra Picture simgesini kullanarak Bitmap oluşturabilir, ardından bunu sıkıştırıp dosyaya kaydedebilirsiniz.
fun exportDocumentAsImage() {
val picture = Picture()
val canvas = picture.beginRecording(bitmapWidth, bitmapHeight)
// The following is similar logic that you'd use in your custom View.onDraw or Compose Canvas.
for (item in myDocument) {
when (item) {
is Stroke -> {
canvasStrokeRenderer.draw(canvas, stroke, worldToScreenTransform)
}
// Draw your other types of items to the canvas.
}
}
// Create a Bitmap from the Picture and write it to a file.
val bitmap = Bitmap.createBitmap(picture)
val outstream = FileOutputStream(filename)
bitmap.compress(Bitmap.CompressFormat.PNG, 100, outstream)
}
Veri nesnesi ve dönüştürücü yardımcıları
Gerekli Ink API nesnelerini yansıtan bir serileştirme nesne yapısı tanımlayın.
StrokeInputBatch kodlamak ve kodunu çözmek için Ink API'nin depolama modülünü kullanın.
Veri aktarım nesneleri
@Parcelize
@Serializable
data class SerializedStroke(
val inputs: ByteArray,
val brush: SerializedBrush
) : Parcelable {
override fun equals(other: Any?): Boolean {
if (this === other) return true
if (other !is SerializedStroke) return false
if (!inputs.contentEquals(other.inputs)) return false
if (brush != other.brush) return false
return true
}
override fun hashCode(): Int {
var result = inputs.contentHashCode()
result = 31 * result + brush.hashCode()
return result
}
}
@Parcelize
@Serializable
data class SerializedBrush(
val size: Float,
val color: Long,
val epsilon: Float,
val stockBrush: SerializedStockBrush,
val clientBrushFamilyId: String? = null
) : Parcelable
enum class SerializedStockBrush {
Marker,
PressurePen,
Highlighter,
DashedLine,
}
Dönüşüm sağlayan kullanıcı sayısı
object Converters {
private val stockBrushToEnumValues = mapOf(
StockBrushes.marker() to SerializedStockBrush.Marker,
StockBrushes.pressurePen() to SerializedStockBrush.PressurePen,
StockBrushes.highlighter() to SerializedStockBrush.Highlighter,
StockBrushes.dashedLine() to SerializedStockBrush.DashedLine,
)
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,
)
}
fun serializeStroke(stroke: Stroke): SerializedStroke {
val serializedBrush = serializeBrush(stroke.brush)
val encodedSerializedInputs = ByteArrayOutputStream().use { outputStream ->
stroke.inputs.encode(outputStream)
outputStream.toByteArray()
}
return SerializedStroke(
inputs = encodedSerializedInputs,
brush = serializedBrush
)
}
private fun deserializeStroke(
serializedStroke: SerializedStroke,
): Stroke? {
val inputs = ByteArrayInputStream(serializedStroke.inputs).use { inputStream ->
StrokeInputBatch.decode(inputStream)
}
val brush = deserializeBrush(serializedStroke.brush, customBrushes)
return Stroke(brush = brush, inputs = inputs)
}
private fun deserializeBrush(
serializedBrush: SerializedBrush,
): Brush {
val stockBrushFamily = enumToStockBrush[serializedBrush.stockBrush]
val brushFamily = customBrush?.brushFamily ?: stockBrushFamily ?: StockBrushes.marker()
return Brush.createWithColorLong(
family = brushFamily,
colorLong = serializedBrush.color,
size = serializedBrush.size,
epsilon = serializedBrush.epsilon,
)
}
}