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

یک 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)
    }
)
دایره ای که با شیب افقی ترسیم شده است
شکل 1 : دایره ای که با شیب افقی ترسیم شده است

برس های گرادیان

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

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

نوع برس گرادیان خروجی
Brush.horizontalGradient(colorList) شیب افقی
Brush.linearGradient(colorList) گرادیان خطی
Brush.verticalGradient(colorList) گرادیان عمودی
Brush.sweepGradient(colorList)
توجه: برای انتقال صاف بین رنگ ها - آخرین رنگ را روی رنگ شروع قرار دهید.
Sweep Gradient
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
TileMode.Clamp : لبه به رنگ نهایی گیره می شود. سپس نزدیک ترین رنگ را برای بقیه منطقه رنگ می کند. گیره حالت کاشی
TileMode.Decal : فقط تا اندازه کران ها رندر کنید. TileMode.Decal از رنگ مشکی شفاف برای نمونه برداری از محتوای خارج از محدوده اصلی استفاده می کند در حالی که TileMode.Clamp از رنگ لبه نمونه برداری می کند. برگردان حالت کاشی

TileMode به روشی مشابه برای سایر گرادیان های جهت کار می کند، تفاوت در جهتی است که تکرار اتفاق می افتد.

تغییر اندازه برس

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

اگر اندازه منطقه طراحی خود را نمی دانید (به عنوان مثال اگر Brush به Text اختصاص داده شده است)، می توانید 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 متفاوت باشد، یا اگر یک شیء حالت استفاده شده در ایجاد سایه‌زن تغییر کرده باشد، 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 Brush به روش های مختلف استفاده می شود
شکل 6 : با استفاده از ImageShader Brush برای ترسیم پس زمینه، کشیدن متن و رسم یک دایره

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

مثال پیشرفته: برس سفارشی

براش RuntimeShader AGSL

AGSL زیر مجموعه ای از قابلیت های GLSL Shader را ارائه می دهد. Shader ها را می توان با AGSL نوشت و با Brush در Compose استفاده کرد.

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

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

منابع اضافی

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

{% کلمه به کلمه %} {% آخر کلمه %} {% کلمه به کلمه %} {% آخر کلمه %}