מעברי צבע ברשת

מעברי צבעים ברשת יוצרים מעברי צבעים מורכבים ורב-כיווניים באמצעות רשת דו-ממדית של טלאים. בניגוד למעברי צבע לינאריים או רדיאליים, מעברי צבע ברשת מבצעים אינטרפולציה חלקה של צבעים ברשת דו-ממדית. כשיוצרים רקעים זורמים, אורגניים או עם כמה נקודות בממשק המשתמש, כדאי להשתמש במעברי צבע ברשת במקום לערום כמה מברשות radialGradient או linearGradient.

דוגמה למעבר צבעים ברשת עם תצוגה של נקודות מעבר הצבעים הנוכחיות ברשת.
איור 1. דוגמה למעבר צבעים ברשת עם תצוגה של נקודות מעבר הצבעים הנוכחיות ברשת.

מושגים מרכזיים

כדי ליצור מעבר צבעים ברשת, מגדירים את מידות הרשת, את הקודקודים ואת מעברי הצבעים בין הנקודות:

  • מידות הרשת: הרשת מחולקת לחלקים לאורך הצירים האנכיים והאופקיים. רשת של rows ו-columns מכילה (שורות+1)×(עמודות+1) קודקודים. לדוגמה, רשת בגודל 1×1 מורכבת מ-4 קודקודים שיוצרים טלאי אחד.
  • קואורדינטות מנורמלות: כל המיקומים של הקודקודים משתמשים במערכת קואורדינטות מנורמלת, שבה (0f, 0f) מייצג את הפינה השמאלית העליונה ו-(1f, 1f) מייצג את הפינה הימנית התחתונה של גבולות השרטוט.
  • נקודות בקרה של עקומת בזייה (משיקים): כל קודקוד מכיל עד ארבע נקודות בקרה אופציונליות של עקומת בזייה. המשיקים האלה מציינים את העקמומיות של הקצה בין קודקודים שכנים. אם משתמשים ב-Offset.Unspecified, Compose מסיק את המשיקים כדי להבטיח מעברים חלקים בין הטלאים. כל תא ברשת שנוצר על ידי 4 קודקודים יחד עם נקודות הבקרה שלהם יוצר טלאי בזייה.
  • אינטרפולציית צבעים: המסגרת מחשבת את הצבעים בין הקודקודים הראשיים. מגדירים את hasBicubicColor לערך true כדי להשתמש באינטרפולציה של Catmull-Rom למעברי צבע חלקים יותר, או לערך false כדי להשתמש באינטרפולציה בילינארית.

ציור באמצעות MeshGradientPainter

ב-Jetpack Compose, משתמשים ב-MeshGradientPainter כדי ליצור שיפוע רשת. ‫MeshGradientPainter מצייר על הקנבס.

יצירת מעבר צבעים פשוט ברשת

כדי ליצור מעבר צבעים סטטי בסיסי, מאתחלים MeshGradientPainter על ידי ציון המימדים שלו ושימוש בפונקציה setVertex בתוך בלוק ההגדרות כדי למקם את נקודות הפינה ולהקצות להן צבעים.

val rows = 1
val columns = 1

val gradientPainter = remember {
    MeshGradientPainter(rows, columns) {
        // Parameters: row, column, position, color
        setVertex(0, 0, Offset(0f, 0f), Color.Red)     // Top-Left
        setVertex(0, 1, Offset(1f, 0f), Color.Blue)    // Top-Right
        setVertex(1, 0, Offset(0f, 1f), Color.Green)   // Bottom-Left
        setVertex(1, 1, Offset(1f, 1f), Color.Yellow)  // Bottom-Right
    }
}

Box(
    modifier = modifier
        .aspectRatio(16/9f)
        .fillMaxWidth()
        .paint(gradientPainter)
)

מעבר צבעים בסיסי ברשת עם 4 צבעים שמוגדרים בכל פינה
איור 2. מעבר צבעים בסיסי ברשת עם ארבעה צבעים, כשכל פינה מוגדרת לאחד מארבעת הצבעים.

שימוש בנקודות בקרה ספציפיות של Bezier

כברירת מחדל, מחולל הרשת מטפל בחישובים מורכבים כדי לשמור על מעברים חלקים ברשת. עם זאת, אתם יכולים להתאים אישית באופן מפורש את המשיקים בכל קודקוד בודד אם אתם רוצים לדחוף, למשוך או לצבוט בחדות קטעי צבע מסוימים באופן סלקטיבי.

ההיסטים של אמצעי הבקרה נמדדים ביחס למיקום של קודקוד המארח.

val customTangentPainter = remember {
    MeshGradientPainter(rows = 1, columns = 1) {
        // Tweak the top-left vertex to curve outwards to the right and bottom
        setVertex(
            row = 0,
            column = 0,
            position = Offset(0f, 0f),
            color = Color.Magenta,
            rightControlPoint = Offset(0.4f, 0.1f),
            bottomControlPoint = Offset(0.1f, 0.4f)
        )

        // Other points can remain unspecified to use default inferred fallback tangents
        setVertex(0, 1, Offset(1f, 0f), Color.Cyan)
        setVertex(1, 0, Offset(0f, 1f), Color.Blue)
        setVertex(1, 1, Offset(1f, 1f), Color.Black)
    }
}
Box(
    modifier = modifier
        .aspectRatio(16/9f)
        .fillMaxWidth()
        .paint(customTangentPainter)
)

מעבר צבעים ברשת עם נקודה מעוקלת בפינה השמאלית העליונה.
איור 3. מעקמים את הקודקוד השמאלי העליון באמצעות נקודת בקרה של עקומת בזייה.

יצירת רשתות מתקדמות

בדוגמה הזו מוצגת רשת של 3 על 3, כלומר יש 16 נקודות שצריך לציין, כשהנקודות האמצעיות מוגדרות עם היסטים שונים:

val points = remember {
    listOf(
        Offset(0.0f, 0.0f), Offset(0.3f, 0.0f), Offset(0.7f, 0.0f), Offset(1.0f, 0.0f),
        Offset(0.0f, 0.3f), Offset(0.2f, 0.4f), Offset(0.7f, 0.2f), Offset(1.0f, 0.3f),
        Offset(0.0f, 0.7f), Offset(0.3f, 0.8f), Offset(0.7f, 0.6f), Offset(1.0f, 0.7f),
        Offset(0.0f, 1.0f), Offset(0.3f, 1.0f), Offset(0.7f, 1.0f), Offset(1.0f, 1.0f)
    )
}

val gradientPainter = remember {
    MeshGradientPainter(rows = 3, columns = 3) {
        // Row 0
        setVertex(0, 0, points[0], yellow)
        setVertex(0, 1, points[1], orange)
        setVertex(0, 2, points[2], yellow)
        setVertex(0, 3, points[3], purple)

        // Row 1
        setVertex(1, 0, points[4], pink)
        setVertex(1, 1, points[5], yellow)
        setVertex(1, 2, points[6], pink)
        setVertex(1, 3, points[7], purple)

        // Row 2
        setVertex(2, 0, points[8], indigo)
        setVertex(2, 1, points[9], pink)
        setVertex(2, 2, points[10], purple)
        setVertex(2, 3, points[11], indigo)

        // Row 3
        setVertex(3, 0, points[12], purple)
        setVertex(3, 1, points[13], indigo)
        setVertex(3, 2, points[14], pink)
        setVertex(3, 3, points[15], yellow)
    }
}

Box(
    modifier = modifier.padding(32.dp)
        .aspectRatio(16 / 9f)
        .fillMaxWidth()
        .paint(gradientPainter)
        // ...
)

מעבר צבעים ברשת עם נקודות בקרה של עקומת בזייה וצבעי גל, ונקודות הרשת מאוירות מעליו.
איור 4. מעבר צבעים ברשת עם נקודות בקרה של עקומת בזייה וצבעי גלים, ונקודות הרשת מאוירות מעליו.

הנפשה של מעבר צבעים ברשת

מכיוון שהפרמטר lambda block של MeshGradientPainter מופעל בתוך DrawScope, הוא יכול לקרוא מצב שניתן לשינוי ולצפות בו. אפשר להוסיף אנימציה למיקומים או לצבעים לאורך זמן בלי להקצות מחדש הצללות או מפות סיביות.

val infiniteTransition = rememberInfiniteTransition(label = "meshMovement")
val animatedOffset by infiniteTransition.animateFloat(
    initialValue = -0.1f,
    targetValue = 0.1f,
    animationSpec = infiniteRepeatable(
        animation = tween(2500, easing = LinearEasing),
        repeatMode = RepeatMode.Reverse
    ),
    label = "offset"
)

val coral = Color(255, 90, 90)
val peach = Color(255, 139, 90)
val amber = Color(255, 169, 90)
val sunshine = Color(255, 212, 90)
val indigo = Color(0xFF5856D6)
val pink = Color(0xFFFF2D55)


val gradientPainter = remember {
    MeshGradientPainter(rows = 3, columns = 3) {
        // Row 0
        setVertex(0, 0, Offset(0.0f, 0.0f), indigo)
        setVertex(0, 1, Offset(0.3f, 0.0f), peach)
        setVertex(0, 2, Offset(0.7f, 0.0f), amber)
        setVertex(0, 3, Offset(1.0f, 0.0f), sunshine)
        // Row 1
        setVertex(1, 0, Offset(0.0f, 0.3f), pink)
        setVertex(1, 1, Offset(0.2f, 0.4f) + Offset(animatedOffset, animatedOffset), coral)
        setVertex(1, 2, Offset(0.7f, 0.2f) + Offset(animatedOffset, animatedOffset), peach)
        setVertex(1, 3, Offset(1.0f, 0.3f), indigo)

        // Row 2
        setVertex(2, 0, Offset(0.0f, 0.7f), coral)
        setVertex(2, 1, Offset(0.3f, 0.8f) + Offset(animatedOffset, 0f), pink)
        setVertex(2, 2, Offset(0.7f, 0.6f) + Offset(animatedOffset, 0f), sunshine)
        setVertex(2, 3, Offset(1.0f, 0.7f), amber)

        // Row 3
        setVertex(3, 0, Offset(0.0f, 1.0f), sunshine)
        setVertex(3, 1, Offset(0.3f, 1.0f), amber)
        setVertex(3, 2, Offset(0.7f, 1.0f), pink)
        setVertex(3, 3, Offset(1.0f, 1.0f), indigo)
    }
}


Box(
    modifier = modifier.padding(32.dp)
        .safeContentPadding()
        .aspectRatio(16 / 9f)
        .fillMaxWidth()
        .paint(gradientPainter)
)

איור 5. מעבר צבעים מונפש עם נקודות שמציגות את האנימציה.