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

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

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

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

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

اگر اندازه ناحیه طراحی خود را نمی‌دانید (برای مثال، اگر Brush به «نوشتار» اختصاص داده شده است)، می‌توانید 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 که به روش‌های مختلف استفاده شده است
شکل ۶: استفاده از «قلم‌موی سایه‌زن تصویر» برای کشیدن پس‌زمینه، کشیدن «نوشتار»، و کشیدن «دایره»

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

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

قلم‌موی AGSL RuntimeShader

AGSL زیرمجموعه‌ای از قابلیت‌های سایه‌زن GLSL را ارائه می‌دهد. سایه‌زن‌ها را می‌توان به‌زبان AGSL نوشت و با «قلم‌مو» در «ترکیب» استفاده کرد.

برای ایجاد «قلم موی سایه‌زن»، ابتدا «سایه‌زن» را به‌عنوان رشته سایه‌زن 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 سفارشی» درحال اجرا در «ترکیب»
شکل ۷: سایه‌زن AGSL سفارشی درحال اجرا در Compose

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

منابع بیشتر

برای نمونه‌های بیشتر از استفاده از «قلم‌مو» در «نوشتن»، منابع زیر را بررسی کنید: