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

يصف 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 Repeated
TileMode.Mirror: يتم عكس الحافة من اللون الأخير إلى اللون الأول. TileMode Mirror
TileMode.Clamp: يتم تثبيت الحافة على اللون النهائي. بعد ذلك، سيتم طلاء بقية المنطقة باللون الأقرب. Tile Mode 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)
)

حجم Shader مقسومًا على 4
الشكل 3: حجم Shader مقسومًا على 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 Brush لرسم خلفية ورسم نص ورسم دائرة

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

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

فرشاة AGSL RuntimeShader

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

لإنشاء فرشاة 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 بين اللونَين استنادًا إلى المسافة. يؤدي ذلك إلى إنشاء تأثير تدرّج.

بعد ذلك، أنشئ Shader Brush، واضبط المتغيرات الموحّدة 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.

مراجع إضافية

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