گرادیان‌های مش

گرادیان‌های مش با استفاده از یک شبکه دوبعدی از تکه‌ها، انتقال رنگ‌های پیچیده و چند جهته ایجاد می‌کنند. برخلاف گرادیان‌های خطی یا شعاعی، گرادیان‌های مش به آرامی رنگ‌ها را در یک شبکه درون‌یابی می‌کنند. از گرادیان‌های مش برای ایجاد عناصر زیبایی‌شناختی روان و ارگانیک در رابط کاربری خود استفاده کنید.

یک مثال گرادیان مش با نمایش نقاط گرادیان مش فعلی آن.
شکل 1. یک مثال گرادیان مش به همراه نمایش نقاط گرادیان مش فعلی آن.

مفاهیم کلیدی

برای ساخت یک گرادیان مش، ابعاد شبکه، رئوس و انتقال رنگ بین نقاط را تعریف کنید:

  • ابعاد شبکه: شبکه در امتداد محورهای عمودی و افقی به تکه‌هایی تقسیم می‌شود. یک شبکه از rows و columns شامل (ردیف‌ها+۱)×(ستون‌ها+۱) رأس است. به عنوان مثال، یک شبکه ۱×۱ از ۴ رأس تشکیل شده است که یک تکه را تشکیل می‌دهند.
  • مختصات نرمال‌شده: تمام موقعیت‌های رأس از یک سیستم مختصات نرمال‌شده استفاده می‌کنند که در آن (0f, 0f) نشان دهنده بالا-چپ و (1f, 1f) نشان دهنده پایین-راست مرزهای ترسیم است.
  • نقاط کنترل بزیه (مماس‌ها): هر رأس شامل حداکثر چهار نقطه کنترل بزیه اختیاری است. این مماس‌ها انحنای لبه بین رأس‌های همسایه را مشخص می‌کنند. اگر Offset.Unspecified استفاده کنید، Compose مماس‌ها را استنباط می‌کند تا انتقال‌های نرم در سراسر تکه‌ها تضمین شود. هر سلول شبکه که توسط ۴ رأس به همراه نقاط کنترل آنها تشکیل شده است، یک تکه بزیه ایجاد می‌کند.
  • درون‌یابی رنگ: این چارچوب، رنگ‌های بین رئوس اصلی را محاسبه می‌کند. برای درون‌یابی Catmull-Rom برای تغییر رنگ‌های نرم‌تر، hasBicubicColor را روی true و برای درون‌یابی دوخطی روی 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)
)

گرادیان مش پایه با ۴ رنگ تعریف شده در هر گوشه
شکل ۲. یک گرادیان مش پایه با چهار رنگ، که هر گوشه روی یکی از این چهار رنگ تنظیم شده است.

از نقاط کنترل خاص 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)
)

گرادیان مش با نقطه منحنی بالا سمت چپ.
شکل ۳. رأس بالا سمت چپ را با یک نقطه کنترل bezier منحنی کنید.

ایجاد شبکه‌های پیشرفته

این مثال یک شبکه ۳ در ۳ را نشان می‌دهد، به این معنی که ۱۶ نقطه وجود دارد که باید مشخص شوند و نقاط میانی با انحراف‌های مختلف تنظیم شده‌اند:

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)
        // ...
)

گرادیان مش با نقاط کنترل بزیه و رنگ‌های موج، و نقاط مش نشان داده شده در بالای آن.
شکل ۴. گرادیان مش با نقاط کنترل بزیه و رنگ‌های موج، و نقاط مش نشان داده شده در بالای آن.

متحرک سازی یک گرادیان مش

از آنجا که پارامتر 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)
)

شکل ۵. گرادیان مش متحرک با نقاطی برای نمایش انیمیشن.