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

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

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

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

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