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

تصف العلامة 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 ناتج
TileMode.Repeated: يتم تكرار الحافة من اللون الأخير إلى اللون الأول. TileMode متكرر
TileMode.Mirror: يتم عكس الحافة من اللون الأخير إلى اللون الأول. مرآة TileMode
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 باستخدام طرق مختلفة
الشكل 6: استخدام فرشاة ImageShader لرسم خلفية ورسم نص ورسم دائرة

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

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

فرشاة AGSL RuntimeShader

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

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

مراجع إضافية

للحصول على مزيد من الأمثلة حول استخدام Brush في Compose، اطلع على الموارد التالية: