الفرشاة: التدرجات والتظليل

يصف الرمز Brush في ميزة "الإنشاء" كيفية رسم عنصر على الشاشة: فهو يحدّد الألوان التي يتم رسمها في منطقة الرسم (أي دائرة أو مربّع أو مسار). هناك بعض الفرش المدمجة المفيدة للرسم، مثل LinearGradient أو RadialGradient أو فرشاة SolidColor عادية.

يمكن استخدام الفرش مع طلبات الرسم Modifier.background() أو TextStyle أو DrawScope لتطبيق نمط الرسم على المحتوى الذي يتم رسمه.

على سبيل المثال، يمكن تطبيق فرشاة تدرج أفقي لرسم دائرة في DrawScope:

val brush = Brush.horizontalGradient(listOf(Color.Red, Color.Blue))
Canvas(
    modifier = Modifier.size(200.dp),
    onDraw = {
        drawCircle(brush)
    }
)
دائرة مرسومة باستخدام مخطط متدرّج أفقي
الشكل 1: دائرة مرسومة باستخدام مخطط انتقال أفقي

فُرش متدرجة

هناك العديد من فُرش تدرّج الألوان المدمجة التي يمكن استخدامها لإنشاء تأثيرات تدرّج مختلفة. تتيح لك هذه الفرش تحديد قائمة الألوان التي تريد إنشاء تدرج منها.

قائمة بفرش التدرّج اللوني المتاحة والناتج المقابل لها:

نوع فرشاة التدرّج الإخراج
Brush.horizontalGradient(colorList) تدرج أفقي
Brush.linearGradient(colorList) تدرج خطي
Brush.verticalGradient(colorList) تدرج الألوان العمودي
Brush.sweepGradient(colorList)
ملاحظة: للحصول على انتقال سلس بين الألوان، عليك ضبط اللون الأخير على لون البداية.
تدرج الألوان
Brush.radialGradient(colorList) تدرج شعاعي

تغيير توزيع الألوان باستخدام colorStops

لتخصيص طريقة ظهور الألوان في التدرّج، يمكنك تعديل قيمة colorStops لكل لون. يجب تحديد colorStops ككسر، بين 0 و1. ستؤدي القيم الأكبر من 1 إلى عدم عرض هذه الألوان كجزء من التدرج اللوني.

يمكنك ضبط نقاط توقّف الألوان لتكون بكميات مختلفة، مثل استخدام لون واحد بكمية أقل أو بكمية أكبر:

val colorStops = arrayOf(
    0.0f to Color.Yellow,
    0.2f to Color.Red,
    1f to Color.Blue
)
Box(
    modifier = Modifier
        .requiredSize(200.dp)
        .background(Brush.horizontalGradient(colorStops = colorStops))
)

يتم توزيع الألوان عند القيمة المعروضة في الحقل offset كما هو محدّد في colorStop pair، ويكون اللون الأصفر أقل من الأحمر والأزرق.

فرشاة تم ضبطها باستخدام نقاط توقف ألوان مختلفة
الشكل 2: فرشاة تم ضبطها باستخدام نقاط توقف ألوان مختلفة

تكرار نقش مع TileMode

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

سيكرّر الرمز التالي نمط التدرّج اللوني 4 مرات، لأنّه تم ضبط endX على 50.dp وتم ضبط الحجم على 200.dp:

val listColors = listOf(Color.Yellow, Color.Red, Color.Blue)
val tileSize = with(LocalDensity.current) {
    50.dp.toPx()
}
Box(
    modifier = Modifier
        .requiredSize(200.dp)
        .background(
            Brush.horizontalGradient(
                listColors,
                endX = tileSize,
                tileMode = TileMode.Repeated
            )
        )
)

في ما يلي جدول يوضّح بالتفصيل ما تفعله أوضاع مربّعات التطبيقات المختلفة في مثال HorizontalGradient أعلاه:

وضع تجانب الإخراج
TileMode.Repeated: يتم تكرار الحافة من اللون الأخير إلى الأول. وضع TileMode متكرّر
TileMode.Mirror: يتم عكس اللون من آخر لون إلى أول لون الحافة. وضع المرآة في وضع "التصغير إلى مربّعات"
TileMode.Clamp: يتم تثبيت الحافة على اللون النهائي. بعد ذلك، سيتم تطبيق أقرب لون لبقية المنطقة. مشبك وضع التجانب
TileMode.Decal: لا يتم عرض سوى المحتوى الذي يشغل مساحة حدود العنصر. يستخدم TileMode.Decal اللون الأسود الشفاف لتحليل عيّنة من المحتوى خارج الحدود الأصلية، في حين يستخدِم TileMode.Clamp لون الحافة. ملصق وضع "تقسيم الشاشة حسب عدد المشاركين"

تعمل TileMode بالطريقة نفسها مع التدرجات اللونية الأخرى ذات الاتجاهات المختلفة، ويتمثل الاختلاف في الاتجاه الذي يحدث فيه التكرار.

تغيير حجم الفرشاة

إذا كنت تعرف حجم المنطقة التي سيتم رسم الفرشاة عليها، يمكنك ضبط مربّع endX كما رأينا أعلاه في قسم TileMode. إذا كنت في DrawScope، يمكنك استخدام السمة size للحصول على حجم المنطقة.

إذا كنت لا تعرف حجم منطقة الرسم (على سبيل المثال، إذا كانت Brush مخصّصة للنص)، يمكنك توسيع Shader واستخدام حجم مساحة الرسم في الدالة createShader.

في هذا المثال، اقسم الحجم على 4 لتكرار النمط 4 مرات:

val listColors = listOf(Color.Yellow, Color.Red, Color.Blue)
val customBrush = remember {
    object : ShaderBrush() {
        override fun createShader(size: Size): Shader {
            return LinearGradientShader(
                colors = listColors,
                from = Offset.Zero,
                to = Offset(size.width / 4f, 0f),
                tileMode = TileMode.Mirror
            )
        }
    }
}
Box(
    modifier = Modifier
        .requiredSize(200.dp)
        .background(customBrush)
)

حجم التظليل مقسومًا على 4
الشكل 3: حجم التظليل مقسومًا على 4

يمكنك أيضًا تغيير حجم الفرشاة لأي تدرج آخر، مثل التدرجات الشعاعية. في حال عدم تحديد حجم ومركز، سيشغل التدرّج حدود DrawScope الكاملة، وسيكون مركز التدرّج الشعاعي تلقائيًا في مركز حدود DrawScope. يؤدي ذلك إلى ظهور مركز التدرّج الشعاعي في منتصف السمة الأصغر (إما العرض أو الارتفاع):

Box(
    modifier = Modifier
        .fillMaxSize()
        .background(
            Brush.radialGradient(
                listOf(Color(0xFF2be4dc), Color(0xFF243484))
            )
        )
)

تم ضبط التدرج الشعاعي بدون تغييرات الحجم
الشكل 4: تم ضبط التدرج الشعاعي بدون تغييرات الحجم

عند تغيير التدرّج الشعاعي لضبط حجم نصف القطر على أقصى أبعاد، يمكنك ملاحظة أنّه ينتج تأثيرًا أفضل للتدرّج الشعاعي:

val largeRadialGradient = object : ShaderBrush() {
    override fun createShader(size: Size): Shader {
        val biggerDimension = maxOf(size.height, size.width)
        return RadialGradientShader(
            colors = listOf(Color(0xFF2be4dc), Color(0xFF243484)),
            center = size.center,
            radius = biggerDimension / 2f,
            colorStops = listOf(0f, 0.95f)
        )
    }
}

Box(
    modifier = Modifier
        .fillMaxSize()
        .background(largeRadialGradient)
)

نصف قطر أكبر على التدرج الشعاعي، بناءً على حجم المساحة
الشكل 5: نصف قطر أكبر في التدرج الشعاعي، بناءً على حجم المساحة

الجدير بالذكر أن الحجم الفعلي الذي يتم تمريره إلى إنشاء التظليل يتم تحديده من حيث تم استدعاؤه. بشكلٍ تلقائي، سيعيد Brush تخصيص Shader داخليًا إذا كان الحجم مختلفًا عن عملية الإنشاء الأخيرة لBrush، أو إذا تغيّر كائن الحالة المستخدَم في إنشاء برنامج التظليل.

تنشئ التعليمة البرمجية التالية مخطّط الألوان ثلاث مرات مختلفة بأحجام مختلفة، وذلك مع تغيُّر حجم مساحة الرسم:

val colorStops = arrayOf(
    0.0f to Color.Yellow,
    0.2f to Color.Red,
    1f to Color.Blue
)
val brush = Brush.horizontalGradient(colorStops = colorStops)
Box(
    modifier = Modifier
        .requiredSize(200.dp)
        .drawBehind {
            drawRect(brush = brush) // will allocate a shader to occupy the 200 x 200 dp drawing area
            inset(10f) {
      /* Will allocate a shader to occupy the 180 x 180 dp drawing area as the
       inset scope reduces the drawing  area by 10 pixels on the left, top, right,
      bottom sides */
                drawRect(brush = brush)
                inset(5f) {
        /* will allocate a shader to occupy the 170 x 170 dp drawing area as the
         inset scope reduces the  drawing area by 5 pixels on the left, top,
         right, bottom sides */
                    drawRect(brush = brush)
                }
            }
        }
)

استخدام صورة كفرشاة

لاستخدام ImageBitmap كBrush، حمِّل الصورة كImageBitmap، وأنشئ فرشاة ImageShader:

val imageBrush =
    ShaderBrush(ImageShader(ImageBitmap.imageResource(id = R.drawable.dog)))

// Use ImageShader Brush with background
Box(
    modifier = Modifier
        .requiredSize(200.dp)
        .background(imageBrush)
)

// Use ImageShader Brush with TextStyle
Text(
    text = "Hello Android!",
    style = TextStyle(
        brush = imageBrush,
        fontWeight = FontWeight.ExtraBold,
        fontSize = 36.sp
    )
)

// Use ImageShader Brush with DrawScope#drawCircle()
Canvas(onDraw = {
    drawCircle(imageBrush)
}, modifier = Modifier.size(200.dp))

يتم تطبيق الفرشاة على بعض أنواع الرسم المختلفة: الخلفية والنص واللوحة. ينتج عن ذلك ما يلي:

يتم استخدام فرشاة ImageShader Brush بطرق مختلفة
الشكل 6: استخدام فرشاة ImageShader لرسم خلفية ونص ودائرة

لاحظ أنّه يتم الآن أيضًا عرض النص باستخدام ImageBitmap لرسم وحدات البكسل للنص.

مثال متقدم: فرشاة مخصصة

فرشاة AGSL RuntimeShader

يوفّر AGSL مجموعة فرعية من إمكانات Shader في GLSL. يمكن كتابة مواد التشطيب باستخدام AGSL واستخدامها مع فرشاة في أداة "الإنشاء".

لإنشاء فرشاة Shader، حدِّد أولاً Shader كسلسلة Shader AGSL:

@Language("AGSL")
val CUSTOM_SHADER = """
    uniform float2 resolution;
    layout(color) uniform half4 color;
    layout(color) uniform half4 color2;

    half4 main(in float2 fragCoord) {
        float2 uv = fragCoord/resolution.xy;

        float mixValue = distance(uv, vec2(0, 1));
        return mix(color, color2, mixValue);
    }
""".trimIndent()

تأخذ أداة التظليل أعلاه لونين للإدخال، وتحسب المسافة من أسفل اليسار (vec2(0, 1)) من منطقة الرسم وتضيف mix بين اللونين بناءً على المسافة. يؤدي ذلك إلى إنشاء تأثير متدرّج.

بعد ذلك، أنشئ فرشاة التظليل، واضبط القيم الموحّدة لـ resolution، وهو حجم منطقة الرسم، وcolor وcolor2 اللذين تريد استخدامهما كمدخلات ل التدرّج المخصّص:

val Coral = Color(0xFFF3A397)
val LightYellow = Color(0xFFF8EE94)

@RequiresApi(Build.VERSION_CODES.TIRAMISU)
@Composable
@Preview
fun ShaderBrushExample() {
    Box(
        modifier = Modifier
            .drawWithCache {
                val shader = RuntimeShader(CUSTOM_SHADER)
                val shaderBrush = ShaderBrush(shader)
                shader.setFloatUniform("resolution", size.width, size.height)
                onDrawBehind {
                    shader.setColorUniform(
                        "color",
                        android.graphics.Color.valueOf(
                            LightYellow.red, LightYellow.green,
                            LightYellow
                                .blue,
                            LightYellow.alpha
                        )
                    )
                    shader.setColorUniform(
                        "color2",
                        android.graphics.Color.valueOf(
                            Coral.red,
                            Coral.green,
                            Coral.blue,
                            Coral.alpha
                        )
                    )
                    drawRect(shaderBrush)
                }
            }
            .fillMaxWidth()
            .height(200.dp)
    )
}

عند تشغيل هذا الإجراء، يمكنك الاطّلاع على ما يلي معروضًا على الشاشة:

تطبيق مخصّص لتأثيرات AGSL يتم تشغيله في أداة Compose
الشكل 7: برنامج تشويش مخصّص لـ AGSL يتم تشغيله في أداة "الإنشاء"

تجدر الإشارة إلى أنّه يمكنك إجراء الكثير من الإجراءات باستخدام مخطّطات التظليل أكثر من مجرد التدرجات، لأنّها جميعها عمليات حسابية مستندة إلى الرياضيات. لمزيد من المعلومات حول AGSL، يُرجى الاطّلاع على مستندات AGSL.

مصادر إضافية

لمزيد من الأمثلة على استخدام "الأداة المخصّصة للرسم" في ميزة "الإنشاء"، يمكنك الاطّلاع على المراجع التالية: