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

يصف 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 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 مستخدَمة بطرق مختلفة
الشكل 6: استخدام ImageShader Brush لرسم خلفية ونص ودائرة

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

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

فرشاة AGSL RuntimeShader

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

بعد ذلك، أنشئ 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.

مراجع إضافية

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