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

یک 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 و size روی 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
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 بارگذاری کنید و یک Brush 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 Shader را ارائه می‌دهد. Shaderها را می‌توان در AGSL نوشت و با یک Brush در Compose استفاده کرد.

برای ایجاد یک براش Shader، ابتدا Shader را به عنوان رشته AGSL shader تعریف کنید:

@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، منابع زیر را بررسی کنید:

{% کلمه به کلمه %} {% فعل کمکی %} {% کلمه به کلمه %} {% فعل کمکی %}