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

تصف 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: حجم أداة التظليل مقسومًا على 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))

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

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

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

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

فرشاة RuntimeShader في AGSL

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

لإنشاء فرشاة تظليل، عليك أولاً تحديد التظليل كسلسلة تظليل 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.

مراجع إضافية

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