قلم‌مو: گرادیان‌ها و سایه‌زن‌ها

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)
    }
)
دایره‌ای که با «شیب رنگ افقی» کشیده شده است
شکل ۱: دایره‌ای که با «شیب رنگ افقی» کشیده شده است

قلم‌موهای گرادیان

قلم‌موهای گرادیان داخلی زیادی وجود دارد که می‌توان از آن‌ها برای دستیابی به جلوه‌های گرادیان مختلف استفاده کرد. این قلم‌موها به شما امکان می‌دهند فهرست رنگ‌هایی را که می‌خواهید از آن‌ها گرادیان ایجاد کنید مشخص کنید.

فهرستی از قلم‌موهای گرادیان دردسترس و خروجی مربوط به آن‌ها:

نوع قلم‌موی گرادیان خروجی
Brush.horizontalGradient(colorList) گرادیان افقی
Brush.linearGradient(colorList) گرادیان خطی
Brush.verticalGradient(colorList) گرادیان عمودی
Brush.sweepGradient(colorList)
توجه: برای داشتن گذار روان بین رنگ‌ها، رنگ آخر را روی رنگ شروع تنظیم کنید.
گرادیان جاروبی
Brush.radialGradient(colorList) گرادیان شعاعی

تغییر توزیع رنگ‌ها با colorStops

برای سفارشی‌سازی نحوه نمایش رنگ‌ها در گرادیان، می‌توانید مقدار colorStops را برای هرکدام تغییر دهید. colorStops باید به‌صورت کسر، بین ۰ و ۱ مشخص شود. مقادیر بزرگ‌تر از ۱ باعث می‌شود آن رنگ‌ها به‌عنوان بخشی از گرادیان رندر نشوند.

می‌توانید توقف‌های رنگ را برای داشتن مقادیر مختلف پیکربندی کنید، مثلاً کمتر یا بیشتر از یک رنگ:

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 تعریف شده است پراکنده می‌شوند، زرد کمتر از قرمز و آبی.

قلم‌مو با توقف‌های رنگی مختلف پیکربندی شده است
شکل ۲: قلم‌مو با توقف‌های رنگی مختلف پیکربندی شده است

تکرار الگو با TileMode

هر قلم‌موی گرادیان گزینه‌ای برای تنظیم TileMode روی آن دارد. اگر شروع و پایانی برای گرادیان تنظیم نکرده باشید، ممکن است متوجه TileMode نشوید، زیرا به‌طور پیش‌فرض کل ناحیه را پر می‌کند. TileMode فقط درصورتی گرادیان را کاشی‌کاری می‌کند که اندازه ناحیه بزرگ‌تر از اندازه «قلم‌مو» باشد.

کد زیر الگوی گرادیان را ۴ بار تکرار می‌کند، زیرا 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: لبه به رنگ نهایی محدود شده است. سپس نزدیک‌ترین رنگ را برای بقیه منطقه رنگ‌آمیزی می‌کند. گیره حالت کاشی
TileMode.Decal: فقط تا اندازه مرزها پردازش شود. TileMode.Decal از سیاه شفاف برای نمونه‌برداری از محتوای خارج از مرزهای اصلی استفاده می‌کند، درحالی‌که TileMode.Clamp از رنگ لبه نمونه‌برداری می‌کند. برگردان حالت کاشی

TileMode برای گرادیان‌های جهتی دیگر نیز به روش مشابهی کار می‌کند، با این تفاوت که تکرار در جهت دیگری رخ می‌دهد.

تغییر اندازه قلم‌مو

اگر اندازه ناحیه‌ای را که قلم‌مو در آن کشیده می‌شود می‌دانید، می‌توانید کاشی endX را همان‌طور که در بخش TileMode در بالا دیدیم تنظیم کنید. اگر در DrawScope هستید، می‌توانید از ویژگی size آن برای دریافت اندازه منطقه استفاده کنید.

اگر اندازه ناحیه طراحی را نمی‌دانید (برای مثال اگر Brush به Text اختصاص داده شده است)، می‌توانید Shader را گسترش دهید و از اندازه ناحیه طراحی در تابع createShader استفاده کنید.

در این مثال، اندازه را بر ۴ تقسیم کنید تا الگو ۴ بار تکرار شود:

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)
)

اندازه سایه‌زن تقسیم بر ۴
شکل ۳: اندازه سایه‌زن تقسیم بر ۴

همچنین می‌توانید اندازه قلم‌مو را برای هر گرادیان دیگری، مثل گرادیان‌های شعاعی، تغییر دهید. اگر اندازه و مرکز را مشخص نکنید، گرادیان کل مرزهای DrawScope را اشغال خواهد کرد و مرکز گرادیان شعاعی به‌طور پیش‌فرض در مرکز مرزهای DrawScope قرار می‌گیرد. این امر باعث می‌شود مرکز شیب شعاعی به‌صورت مرکز بعد کوچک‌تر (عرض یا ارتفاع) نشان داده شود:

Box(
    modifier = Modifier
        .fillMaxSize()
        .background(
            Brush.radialGradient(
                listOf(Color(0xFF2be4dc), Color(0xFF243484))
            )
        )
)

«شیب شعاعی» بدون تغییر اندازه تنظیم شد
شکل ۴: مجموعه «گرادیان شعاعی» بدون تغییر اندازه

وقتی گرادیان شعاعی تغییر می‌کند تا اندازه شعاع روی حداکثر بُعد تنظیم شود، می‌بینید که جلوه گرادیان شعاعی بهتری ایجاد می‌کند:

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)
)

شعاع بزرگ‌تر در گرادیان شعاعی، براساس اندازه ناحیه
شکل ۵: شعاع بزرگ‌تر در مدرج شعاعی، براساس اندازه ناحیه

شایان ذکر است که اندازه واقعی که به ایجاد سایه‌زن منتقل می‌شود از جایی که فراخوانی می‌شود تعیین می‌شود. به‌طور پیش‌فرض، اگر اندازه با آخرین ایجاد Brush متفاوت باشد، یا اگر شیء وضعیتی که در ایجاد سایه‌زن استفاده شده است تغییر کرده باشد، Brush Shader خود را به‌طور داخلی بازتخصیص خواهد داد.

کد زیر سایه‌زن را سه بار مختلف با اندازه‌های مختلف ایجاد می‌کند، زیرا اندازه ناحیه طراحی تغییر می‌کند:

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 که به روش‌های مختلف استفاده شده است
شکل ۶: استفاده از قلم‌موی ImageShader برای ترسیم پس‌زمینه، ترسیم نوشتار، و ترسیم دایره

توجه داشته باشید که اکنون نوشتار بااستفاده از ImageBitmap نیز برای رنگ‌آمیزی پیکسل‌های نوشتار پرداز می‌شود.

نمونه پیشرفته: قلم‌موی سفارشی

قلم‌موی AGSL RuntimeShader

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 بین دو رنگ انجام می‌دهد. این کار جلوه گرادیان ایجاد می‌کند.

سپس، 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
شکل ۷: سایه‌زن AGSL سفارشی درحال اجرا در Compose

شایان ذکر است که با سایه‌زن‌ها می‌توانید کارهای بسیار بیشتری نسبت‌به گرادیان‌ها انجام دهید، زیرا همه محاسبات مبتنی بر ریاضی است. برای کسب اطلاعات بیشتر درباره AGSL، به اسناد AGSL مراجعه کنید.

منابع بیشتر

برای نمونه‌های بیشتر از استفاده از Brush در Compose، منابع زیر را بررسی کنید: