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

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

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

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

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

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

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

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

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

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. מעגלים את הקודקוד הימני העליון באמצעות נקודת בקרה של בזייה.

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

בדוגמה הזו מוצגת רשת בגודל 3x3, כלומר יש 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. מעבר צבעים ברשת עם נקודות בקרה של עקומת בזייה וצבעי גלים, ונקודות הרשת מאוירות מעל.

יצירת אנימציה של הדרגת רשת

מכיוון שהפרמטר block lambda של 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. הנפשה של מעבר צבעים ברשת עם נקודות שמציגות את האנימציה.