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

تصف العلامة Brush في Compose طريقة رسم عنصر على الشاشة: فهي تحدد الألوان التي يتم رسمها في منطقة الرسم (أي دائرة أو مربع أو مسار). يمكن استخدام بعض الفُرش المدمَجة في الرسم، مثل 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))
)

تتوزع الألوان حسب الإزاحة المتوفّرة على النحو المحدّد في زوج colorStop، ويكون اللون الأصفر أقل من اللون الأحمر والأزرق.

تم ضبط الفرشاة على ألوان مختلفة.
الشكل 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 مجموعة فرعية من إمكانات GLSL Shader. يمكن كتابة التظليلات بلغة AGSL واستخدامها مع الفرشاة في Compose.

لإنشاء فرشاة 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 Shader مخصص في Compose
الشكل 7: أداة تظليل AGSL المخصّصة قيد التشغيل في Compose

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

مصادر إضافية

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