تدرّجات الشبكة المتداخلة

تُنشئ التدرّجات الشبكية انتقالات ألوان معقدة ومتعددة الاتجاهات باستخدام شبكة ثنائية الأبعاد من الرقع. على عكس التدرّجات الخطية أو الشعاعية، تُجري التدرّجات الشبكية استيفاءً سلسًا للألوان على مستوى الشبكة. استخدِم التدرّجات الشبكية لإنشاء عناصر جمالية سلسة وعضوية في واجهة المستخدم.

مثال على تدرّج شبكي مع عرض لنقاط التدرّج الشبكي الحالية
الشكل 1. مثال على تدرّج شبكي مع عرض لنقاط التدرّج الشبكي الحالية

المفاهيم الرئيسية

لإنشاء تدرّج شبكي، حدِّد أبعاد الشبكة والرؤوس وانتقالات الألوان بين النقاط:

  • أبعاد الشبكة: يتم تقسيم الشبكة إلى رقع على طول المحورَين العمودي والأفقي. تحتوي شبكة من rows و columns على (صفوف+1)×(أعمدة+1) من الرؤوس. على سبيل المثال، تتكوّن الشبكة 1×1 من 4 رؤوس تشكّل رقعة واحدة.
  • الإحداثيات العادية: تستخدم جميع مواضع الرؤوس نظام إحداثيات عاديًا، حيث يمثّل (0f, 0f) أعلى يسار حدود الرسم، ويمثّل (1f, 1f) أسفل يسارها.
  • نقاط تحكّم Bezier (المماسات): يحتوي كل رأس على ما يصل إلى أربع نقاط تحكّم اختيارية في Bezier. تحدّد هذه المماسات انحناء الحافة بين الرؤوس المجاورة. في حال استخدام Offset.Unspecified، يستنتج Compose المماسات لضمان الانتقالات السلسة بين الرقع. تُنشئ كل خلية شبكة رقعة Bezier تتكوّن من 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. انحنِ رأس أعلى اليسار باستخدام نقطة تحكّم Bezier.

إنشاء شبكات متقدّمة

يعرض هذا المثال شبكة 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. تدرّج شبكي مع نقاط تحكّم Bezier وألوان متموجة، مع توضيح نقاط الشبكة فوقه

تحريك تدرّج شبكي

بما أنّ مَعلمة 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 تدرّج شبكي متحرّك مع نقاط لعرض الحركة