اصلاح‌کننده‌های گرافیک

علاوه‌بر Canvas قابل ترکیب، «نوشتن» چندین گرافیک مفید دارد Modifiers که به طراحی محتوای سفارشی کمک می‌کند. این اصلاح‌کننده‌ها مفید هستند زیرا می‌توانند روی هر عنصر ترکیبی اعمال شوند.

اصلاح‌گرهای طراحی

همه فرمان‌های طراحی با اصلاح‌کننده طراحی در «نوشتن» انجام می‌شود. سه اصلاح‌گر اصلی برای طراحی در «نوشتن» وجود دارد:

اصلاح‌گر پایه برای طراحی drawWithContent است، که در آن می‌توانید ترتیب طراحی «ترکیبی» خود و دستورات طراحی صادرشده در داخل اصلاح‌گر را تعیین کنید. ‫drawBehind یک بسته‌بندی مناسب برای drawWithContent است که ترتیب طراحی آن روی پشت محتوای عنصر ترکیبی تنظیم شده است. drawWithCacheیا onDrawBehind یا onDrawWithContent را در داخل خود فرا می‌خواند - و سازوکاری برای ذخیره کردن اشیای ایجادشده در آن‌ها ارائه می‌دهد.

‫Modifier.drawWithContent: انتخاب ترتیب قرعه‌کشی

Modifier.drawWithContent به شما امکان می‌دهد عملیات DrawScope را قبل یا بعداز محتوای ترکیبی اجرا کنید. حتماً drawContent را فراخوانی کنید تا محتوای واقعی ترکیبی را ارائه کند. با این اصلاح‌گر، می‌توانید ترتیب عملیات را تعیین کنید، اینکه می‌خواهید محتوایتان قبل‌از عملیات رسم سفارشی‌تان رسم شود یا بعداز آن.

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

var pointerOffset by remember {
    mutableStateOf(Offset(0f, 0f))
}
Column(
    modifier = Modifier
        .fillMaxSize()
        .pointerInput("dragging") {
            detectDragGestures { change, dragAmount ->
                pointerOffset += dragAmount
            }
        }
        .onSizeChanged {
            pointerOffset = Offset(it.width / 2f, it.height / 2f)
        }
        .drawWithContent {
            drawContent()
            // draws a fully black area with a small keyhole at pointerOffset that’ll show part of the UI.
            drawRect(
                Brush.radialGradient(
                    listOf(Color.Transparent, Color.Black),
                    center = pointerOffset,
                    radius = 100.dp.toPx(),
                )
            )
        }
) {
    // Your composables here
}

شکل ۱: از «اصلاح‌گر.کشیدن با محتوا» در بالای «ترکیب‌شدنی» برای ایجاد تجربه کاربری نوع چراغ‌قوه استفاده شده است.

‫Modifier.drawBehind: طراحی در پشت عنصر ترکیبی

‫Modifier.drawBehind به شما امکان می‌دهد DrawScope عملیات را در پس محتوای ترکیبی که روی صفحه کشیده می‌شود انجام دهید. اگر به پیاده‌سازی Canvas نگاهی بیندازید، ممکن است متوجه شوید که این فقط یک بسته‌بندی راحت برای Modifier.drawBehind است.

برای کشیدن مستطیل گردشده در پشت Text:

Text(
    "Hello Compose!",
    modifier = Modifier
        .drawBehind {
            drawRoundRect(
                Color(0xFFBBAAEE),
                cornerRadius = CornerRadius(10.dp.toPx())
            )
        }
        .padding(4.dp)
)

که نتیجه زیر را تولید می‌کند:

نوشتار و پس‌زمینه‌ای که بااستفاده از «اصلاح‌گر.رسم‌پشت» کشیده شده است
شکل ۲: نوشتار و پس‌زمینه‌ای که بااستفاده از Modifier.drawBehind کشیده شده است

‫Modifier.drawWithCache: رسم و ذخیره کردن اشیای رسم

Modifier.drawWithCache اشیایی را که در داخل آن ایجاد شده‌اند در حافظه نهان نگه می‌دارد. تا زمانی که اندازه ناحیه طراحی یکسان باشد، یا هریک از وضعیت‌های خوانده‌شده تغییر نکرده باشد، اشیا در حافظه نهان ذخیره می‌شوند. این اصلاح‌گر برای بهبود عملکرد فراخوانی‌های طراحی مفید است زیرا از نیاز به تخصیص مجدد اشیایی (مثل: Brush, Shader, Path و غیره) که در طراحی ایجاد می‌شوند جلوگیری می‌کند.

یا می‌توانید اشیا را بااستفاده از remember، خارج از اصلاح‌کننده، ذخیره کنید. بااین‌حال، این کار همیشه ممکن نیست زیرا همیشه به قطعه موسیقی دسترسی ندارید. اگر از اشیا فقط برای رسم کردن استفاده می‌شود، استفاده از drawWithCache می‌تواند عملکرد بهتری داشته باشد.

برای مثال، اگر Brush را برای رسم گرادیان در پشت Text ایجاد کنید، بااستفاده از drawWithCache، هدف Brush را تا زمانی‌که اندازه ناحیه رسم تغییر کند ذخیره می‌کند:

Text(
    "Hello Compose!",
    modifier = Modifier
        .drawWithCache {
            val brush = Brush.linearGradient(
                listOf(
                    Color(0xFF9E82F0),
                    Color(0xFF42A5F5)
                )
            )
            onDrawBehind {
                drawRoundRect(
                    brush,
                    cornerRadius = CornerRadius(10.dp.toPx())
                )
            }
        }
)

ذخیره کردن شیء «قلم‌مو» با drawWithCache
شکل ۳: ذخیره کردن شیء «قلم‌مو» در حافظه نهان با drawWithCache

اصلاح‌کننده‌های گرافیک

‫Modifier.graphicsLayer: اعمال تبدیل‌ها روی عناصر ترکیبی

Modifier.graphicsLayer یک اصلاح‌کننده است که باعث می‌شود محتوای عنصر ترکیبی در لایه طراحی کشیده شود. لایه چند عملکرد مختلف ارائه می‌دهد، ازجمله:

  • جداسازی دستورالعمل‌های طراحی آن (مشابه RenderNode). دستورالعمل‌های طراحی که به‌عنوان بخشی از لایه ضبط شده‌اند می‌توانند به‌طور کارآمد توسط خط لوله رندرینگ بدون اجرای مجدد کد برنامه مجدداً صادر شوند.
  • تبدیل‌هایی که روی همه دستورالعمل‌های طراحی موجود در یک لایه اعمال می‌شود.
  • شطرنجی‌سازی برای قابلیت‌های ترکیب‌بندی. وقتی لایه‌ای شطرنجی می‌شود، دستورالعمل‌های طراحی آن اجرا می‌شود و برونداد در بافر خارج از صفحه ضبط می‌شود. ترکیب کردن چنین بافری برای قاب‌های بعدی سریع‌تر از اجرای دستورالعمل‌های جداگانه است، اما وقتی تبدیل‌هایی مثل مقیاس‌بندی یا چرخش اعمال می‌شود، این بافر به‌عنوان یک بیت‌مپ عمل می‌کند.

تبدیل‌ها

‫Modifier.graphicsLayer برای دستورالعمل‌های طراحی خود ایزوله ارائه می‌دهد؛ برای مثال، بااستفاده از Modifier.graphicsLayer می‌توان تبدیل‌های مختلفی را اعمال کرد. این‌ها را می‌توان بدون نیاز به اجرای مجدد لامبدای طراحی، پویانمایی یا اصلاح کرد.

‫Modifier.graphicsLayer اندازه یا جای چیدمان شما را تغییر نمی‌دهد، زیرا فقط بر مرحله طراحی تأثیر می‌گذارد. این یعنی عنصر ترکیبی شما اگر در خارج از محدوده چیدمان خود رسم شود، ممکن است با عناصر دیگر هم‌پوشانی داشته باشد.

تبدیل‌های زیر را می‌توان با این اصلاح‌کننده اعمال کرد:

ترازو - افزایش اندازه

scaleX و scaleY محتوا را به‌ترتیب در جهت افقی یا عمودی بزرگ یا کوچک می‌کند. مقدار 1.0f نشان‌دهنده عدم تغییر در مقیاس است، مقدار 0.5f به معنای نصف ابعاد است.

Image(
    painter = painterResource(id = R.drawable.sunset),
    contentDescription = "Sunset",
    modifier = Modifier
        .graphicsLayer {
            this.scaleX = 1.2f
            this.scaleY = 0.8f
        }
)

شکل ۴: scaleX و scaleY اعمال‌شده روی عنصر ترکیبی «تصویر»
ترجمه

translationX و translationY را می‌توان با graphicsLayer تغییر داد، translationX عنصر ترکیبی را به چپ یا راست می‌برد. translationY عنصر ترکیبی را به‌بالا یا پایین می‌برد.

Image(
    painter = painterResource(id = R.drawable.sunset),
    contentDescription = "Sunset",
    modifier = Modifier
        .graphicsLayer {
            this.translationX = 100.dp.toPx()
            this.translationY = 10.dp.toPx()
        }
)

شکل ۵: translationX و translationY اعمال‌شده روی «تصویر» با Modifier.graphicsLayer
چرخش

rotationX را برای چرخش افقی، rotationY را برای چرخش عمودی، و rotationZ را برای چرخش در محور Z (چرخش استاندارد) تنظیم کنید. این مقدار برحسب درجه (۰ تا ۳۶۰) مشخص می‌شود.

Image(
    painter = painterResource(id = R.drawable.sunset),
    contentDescription = "Sunset",
    modifier = Modifier
        .graphicsLayer {
            this.rotationX = 90f
            this.rotationY = 275f
            this.rotationZ = 180f
        }
)

شکل ۶: چرخش X، چرخش Y، و چرخش Z روی «تصویر» با «اصلاح‌گر.لایه گرافیکی» تنظیم شده است
مبدأ

می‌توانید transformOrigin را مشخص کنید. سپس به‌عنوان نقطه‌ای که تبدیل‌ها از آنجا انجام می‌شود استفاده می‌شود. تاکنون همه مثال‌ها از TransformOrigin.Center استفاده کرده‌اند که در (0.5f, 0.5f) قرار دارد. اگر مبدأ را در (0f, 0f) مشخص کنید، تبدیل‌ها از گوشه بالا سمت راست عنصر ترکیب‌پذیر شروع می‌شود.

اگر مبدأ را با تبدیل rotationZ تغییر دهید، می‌توانید ببینید که عنصر حول گوشه بالا سمت چپ عنصر ترکیب‌شدنی می‌چرخد:

Image(
    painter = painterResource(id = R.drawable.sunset),
    contentDescription = "Sunset",
    modifier = Modifier
        .graphicsLayer {
            this.transformOrigin = TransformOrigin(0f, 0f)
            this.rotationX = 90f
            this.rotationY = 275f
            this.rotationZ = 180f
        }
)

شکل ۷: چرخش با تنظیم TransformOrigin روی 0f، 0f اعمال شد

برش و شکل

شکل مشخص می‌کند که محتوا هنگام clip = true به کدام خطوط کلی چسبانده شود. در این مثال، دو چارگوش را تنظیم می‌کنیم تا دو کلیپ متفاوت داشته باشند - یکی بااستفاده از متغیر کلیپ graphicsLayer و دیگری بااستفاده از بسته‌بندی مناسب Modifier.clip.

Column(modifier = Modifier.padding(16.dp)) {
    Box(
        modifier = Modifier
            .size(200.dp)
            .graphicsLayer {
                clip = true
                shape = CircleShape
            }
            .background(Color(0xFFF06292))
    ) {
        Text(
            "Hello Compose",
            style = TextStyle(color = Color.Black, fontSize = 46.sp),
            modifier = Modifier.align(Alignment.Center)
        )
    }
    Box(
        modifier = Modifier
            .size(200.dp)
            .clip(CircleShape)
            .background(Color(0xFF4DB6AC))
    )
}

محتوای چارگوش اول (نوشتار «سلام نوشتن») به شکل دایره برش داده می‌شود:

کلیپ روی عنصر ترکیبی «چارگوش» اعمال شد
شکل ۸: کلیپ اعمال‌شده روی عنصر ترکیبی «جعبه»

اگر سپس translationY را به دایره صورتی بالا اعمال کنید، می‌بینید که حدود «ترکیب‌شونده» همچنان یکسان است، اما دایره در زیر دایره پایین (و خارج از حدود آن) رسم می‌شود.

کلیپ با ترجمه Y و حاشیه قرمز برای سرمطلب اعمال شد
شکل ۹: کلیپ اعمال‌شده با ترجمه Y، و حاشیه قرمز برای طرح کلی

برای چسباندن عنصر ترکیبی به ناحیه‌ای که در آن رسم شده است، می‌توانید Modifier.clip(RectangleShape) دیگری را در ابتدای زنجیره اصلاح‌گر اضافه کنید. محتوا سپس در محدوده اصلی باقی می‌ماند.

Column(modifier = Modifier.padding(16.dp)) {
    Box(
        modifier = Modifier
            .clip(RectangleShape)
            .size(200.dp)
            .border(2.dp, Color.Black)
            .graphicsLayer {
                clip = true
                shape = CircleShape
                translationY = 50.dp.toPx()
            }
            .background(Color(0xFFF06292))
    ) {
        Text(
            "Hello Compose",
            style = TextStyle(color = Color.Black, fontSize = 46.sp),
            modifier = Modifier.align(Alignment.Center)
        )
    }

    Box(
        modifier = Modifier
            .size(200.dp)
            .clip(RoundedCornerShape(500.dp))
            .background(Color(0xFF4DB6AC))
    )
}

کلیپ روی تبدیل graphicsLayer اعمال شد
شکل ۱۰: کلیپ اعمال‌شده در بالای تبدیل graphicsLayer

آلفا

از Modifier.graphicsLayer می‌توان برای تنظیم alpha (کدری) برای کل لایه استفاده کرد. ‫1.0f کاملاً مات است و 0.0f نامرئی است.

Image(
    painter = painterResource(id = R.drawable.sunset),
    contentDescription = "clock",
    modifier = Modifier
        .graphicsLayer {
            this.alpha = 0.5f
        }
)

تصویر با آلفا اعمال‌شده
شکل ۱۱: تصویر با آلفا اعمال‌شده

راهبرد ترکیب

کار با آلفا و شفافیت همیشه به سادگی تغییر یک مقدار آلفا نیست. علاوه‌بر تغییر آلفا، گزینه تنظیم CompositingStrategy روی graphicsLayer نیز وجود دارد. CompositingStrategy تعیین می‌کند که محتوای عنصر ترکیبی چگونه با محتوای دیگری که قبلاً روی صفحه کشیده شده است ترکیب شود (کنار هم قرار گیرد).

استراتژی‌های مختلف عبارت‌اند از:

خودکار (پیش‌فرض)

استراتژی ترکیب‌بندی توسط بقیه graphicsLayer پارامترها تعیین می‌شود. اگر آلفا کمتر از ‫1.0f باشد یا RenderEffect تنظیم شده باشد، لایه را در میان‌گیری خارج از صفحه پرداز می‌کند. هرگاه آلفا کمتر از ۱f باشد، لایه ترکیبی به‌طور خودکار برای پرداز محتوا ایجاد می‌شود و سپس این بافر خارج از صفحه با آلفای مربوطه در مقصد رسم می‌شود. تنظیم RenderEffect یا پیمایش اضافی همیشه محتوا را در بافری خارج از صفحه صرف‌نظر از CompositingStrategy تنظیم‌شده پرداز می‌کند.

خارج از صفحه

محتوای عنصر ترکیبی همیشه قبل‌از پرداز کردن در مقصد، به بافت یا بیت‌مپ خارج از صفحه شطرنجی می‌شود. این ویژگی برای اعمال عملیات BlendMode روی محتوای پوششی و برای عملکرد هنگام پرداز کردن مجموعه‌های پیچیده دستورالعمل‌های طراحی مفید است.

نمونه‌ای از استفاده از CompositingStrategy.Offscreen در BlendModes است. در مثال زیر، فرض کنید می‌خواهید بخش‌هایی از یک Image ترکیب‌شدنی را با صدور فرمان طراحی که از BlendMode.Clear استفاده می‌کند بردارید. اگر compositingStrategy را روی CompositingStrategy.Offscreen تنظیم نکنید، BlendMode با همه محتوای پس‌زمینه تعامل می‌کند.

Image(
    painter = painterResource(id = R.drawable.dog),
    contentDescription = "Dog",
    contentScale = ContentScale.Crop,
    modifier = Modifier
        .size(120.dp)
        .aspectRatio(1f)
        .background(
            Brush.linearGradient(
                listOf(
                    Color(0xFFC5E1A5),
                    Color(0xFF80DEEA)
                )
            )
        )
        .padding(8.dp)
        .graphicsLayer {
            compositingStrategy = CompositingStrategy.Offscreen
        }
        .drawWithCache {
            val path = Path()
            path.addOval(
                Rect(
                    topLeft = Offset.Zero,
                    bottomRight = Offset(size.width, size.height)
                )
            )
            onDrawWithContent {
                clipPath(path) {
                    // this draws the actual image - if you don't call drawContent, it wont
                    // render anything
                    this@onDrawWithContent.drawContent()
                }
                val dotSize = size.width / 8f
                // Clip a white border for the content
                drawCircle(
                    Color.Black,
                    radius = dotSize,
                    center = Offset(
                        x = size.width - dotSize,
                        y = size.height - dotSize
                    ),
                    blendMode = BlendMode.Clear
                )
                // draw the red circle indication
                drawCircle(
                    Color(0xFFEF5350), radius = dotSize * 0.8f,
                    center = Offset(
                        x = size.width - dotSize,
                        y = size.height - dotSize
                    )
                )
            }
        }
)

با تنظیم CompositingStrategy روی Offscreen، بافت خارج از صفحه ایجاد می‌شود تا فرمان‌ها را اجرا کند (BlendMode فقط روی محتوای این عنصر ترکیبی اعمال می‌شود). سپس آن را روی آنچه ازقبل در صفحه پردازش شده است پردازش می‌کند و بر محتوای ازقبل رسم‌شده تأثیر نمی‌گذارد.

‫Modifier.drawWithContent در «تصویر» که نشانگر دایره‌ای را با BlendMode.Clear در داخل برنامه نشان می‌دهد
شکل ۱۲: Modifier.drawWithContent در «تصویر» که نشانگر دایره‌ای را با BlendMode.Clear و CompositingStrategy.Offscreen در داخل برنامه نشان می‌دهد

اگر از CompositingStrategy.Offscreen استفاده نکرده باشید، نتایج اعمال BlendMode.Clear همه پیکسل‌های مقصد را پاک می‌کند، صرف‌نظر از آنچه قبلاً تنظیم شده است - با این کار بافر پردازنده پنجره (سیاه) نمایان می‌شود. بسیاری از BlendModes که شامل آلفا هستند بدون بافر خارج از صفحه آن‌طور که انتظار می‌رود کار نخواهند کرد. به حلقه سیاه دور نشانگر دایره قرمز توجه کنید:

‫Modifier.drawWithContent در «تصویر» که نشانگر دایره‌ای را نشان می‌دهد، با BlendMode.Clear و بدون تنظیم CompositingStrategy
شکل ۱۳: Modifier.drawWithContent در «تصویر» که نشانگر دایره‌ای را نشان می‌دهد، با BlendMode.Clear و بدون تنظیم CompositingStrategy

برای درک بیشتر این موضوع: اگر برنامه دارای پس‌زمینه پنجره نیمه‌شفاف باشد و از CompositingStrategy.Offscreen استفاده نکنید، BlendMode با کل برنامه تعامل خواهد داشت. همه پیکسل‌ها را پاک می‌کند تا برنامه یا کاغذدیواری زیر را نشان دهد، مانند این مثال:

«استراتژی ترکیب‌بندی» تنظیم نشده است و از BlendMode.Clear با برنامه‌ای که پس‌زمینه پنجره شفاف دارد استفاده می‌شود. کاغذدیواری صورتی ازطریق ناحیه اطراف دایره وضعیت قرمز نشان داده می‌شود.
شکل ۱۴: بدون تنظیم CompositingStrategy و استفاده از BlendMode.Clear با برنامه‌ای که پس‌زمینه پنجره نیمه‌شفاف دارد. توجه کنید که کاغذدیواری صورتی ازطریق ناحیه اطراف دایره وضعیت قرمز نشان داده می‌شود.

شایان ذکر است که هنگام استفاده از CompositingStrategy.Offscreen، بافت خارج از صفحه‌ای به اندازه ناحیه طراحی ایجاد می‌شود و در صفحه رندر می‌شود. هر فرمان طراحی که با این استراتژی انجام شود، به‌طور پیش‌فرض به این منطقه چیده می‌شود. تکه کد زیر تفاوت‌ها را هنگام تغییر به استفاده از بافت‌های خارج از صفحه نشان می‌دهد:

@Composable
fun CompositingStrategyExamples() {
    Column(
        modifier = Modifier
            .fillMaxSize()
            .wrapContentSize(Alignment.Center)
    ) {
        // Does not clip content even with a graphics layer usage here. By default, graphicsLayer
        // does not allocate + rasterize content into a separate layer but instead is used
        // for isolation. That is draw invalidations made outside of this graphicsLayer will not
        // re-record the drawing instructions in this composable as they have not changed
        Canvas(
            modifier = Modifier
                .graphicsLayer()
                .size(100.dp) // Note size of 100 dp here
                .border(2.dp, color = Color.Blue)
        ) {
            // ... and drawing a size of 200 dp here outside the bounds
            drawRect(color = Color.Magenta, size = Size(200.dp.toPx(), 200.dp.toPx()))
        }

        Spacer(modifier = Modifier.size(300.dp))

        /* Clips content as alpha usage here creates an offscreen buffer to rasterize content
        into first then draws to the original destination */
        Canvas(
            modifier = Modifier
                // force to an offscreen buffer
                .graphicsLayer(compositingStrategy = CompositingStrategy.Offscreen)
                .size(100.dp) // Note size of 100 dp here
                .border(2.dp, color = Color.Blue)
        ) {
            /* ... and drawing a size of 200 dp. However, because of the CompositingStrategy.Offscreen usage above, the
            content gets clipped */
            drawRect(color = Color.Red, size = Size(200.dp.toPx(), 200.dp.toPx()))
        }
    }
}

CompositingStrategy.Auto دربرابر CompositingStrategy.Offscreen - کلیپ‌های خارج از صفحه به منطقه، جایی که خودکار این کار را نمی‌کند
شکل ۱۵: CompositingStrategy.Auto دربرابر CompositingStrategy.Offscreen - کلیپ‌های خارج از صفحه به ناحیه، جایی که خودکار کار نمی‌کند
ModulateAlpha

این استراتژی ترکیب مقدار آلفا را برای هریک از دستورالعمل‌های طراحی ضبط‌شده در graphicsLayer تغییر می‌دهد. برای آلفا کمتر از ۱٫۰f، بافر خارج از صفحه ایجاد نمی‌کند، مگر اینکه RenderEffect تنظیم شده باشد، بنابراین می‌تواند برای پرداز آلفا کارآمدتر باشد. بااین‌حال، ممکن است نتایج متفاوتی برای محتوای هم‌پوشان ارائه دهد. برای موارد استفاده‌ای که ازقبل مشخص است که محتوا هم‌پوشانی ندارد، این می‌تواند عملکرد بهتری نسبت‌به CompositingStrategy.Auto با مقادیر آلفای کمتر از ۱ ارائه دهد.

مثال زیر استراتژی‌های ترکیب‌بندی مختلف را نشان می‌دهد - اعمال کردن آلفاهای مختلف روی بخش‌های مختلف عناصر ترکیب‌شدنی، و اعمال کردن استراتژی Modulate:

@Preview
@Composable
fun CompositingStrategy_ModulateAlpha() {
    Column(
        modifier = Modifier
            .fillMaxSize()
            .padding(32.dp)
    ) {
        // Base drawing, no alpha applied
        Canvas(
            modifier = Modifier.size(200.dp)
        ) {
            drawSquares()
        }

        Spacer(modifier = Modifier.size(36.dp))

        // Alpha 0.5f applied to whole composable
        Canvas(
            modifier = Modifier
                .size(200.dp)
                .graphicsLayer {
                    alpha = 0.5f
                }
        ) {
            drawSquares()
        }
        Spacer(modifier = Modifier.size(36.dp))

        // 0.75f alpha applied to each draw call when using ModulateAlpha
        Canvas(
            modifier = Modifier
                .size(200.dp)
                .graphicsLayer {
                    compositingStrategy = CompositingStrategy.ModulateAlpha
                    alpha = 0.75f
                }
        ) {
            drawSquares()
        }
    }
}

private fun DrawScope.drawSquares() {

    val size = Size(100.dp.toPx(), 100.dp.toPx())
    drawRect(color = Red, size = size)
    drawRect(
        color = Purple, size = size,
        topLeft = Offset(size.width / 4f, size.height / 4f)
    )
    drawRect(
        color = Yellow, size = size,
        topLeft = Offset(size.width / 4f * 2f, size.height / 4f * 2f)
    )
}

@Preview
@Composable
fun ModifierGraphicsLayerOutsets() {
    Column(
        modifier = Modifier
            .background(Color(0xFF0A0E14))
            .padding(48.dp),
        verticalArrangement = Arrangement.spacedBy(48.dp),
        horizontalAlignment = Alignment.CenterHorizontally
    ) {
        val pillShape = RoundedCornerShape(percent = 50)

        // Without outsets: alpha = 0.5f creates an offscreen buffer that clips the 24.dp glow
        Box(
            modifier = Modifier
                .size(
                    width = 140.dp,
                    height = 56.dp
                )
                .graphicsLayer(
                    alpha = 0.5f,
                    clip = false
                )
                .dropShadow(
                    shape = pillShape,
                    shadow = Shadow(radius = 24.dp, color = Color.Cyan)
                )
                .background(color = Color(0xFF141824), shape = pillShape),
            contentAlignment = Alignment.Center
        ) {
            Text(
                text = "✨ Glow",
                color = Color.White,
                fontSize = 18.sp
            )
        }

        Box(
            modifier = Modifier
                .size(
                    width = 140.dp,
                    height = 56.dp
                )
                .graphicsLayer(
                    alpha = 0.5f,
                    outsets = LayerOutsets(24.dp),
                    clip = false
                )
                .dropShadow(
                    shape = pillShape,
                    shadow = Shadow(radius = 24.dp, color = Color.Cyan)
                )
                .background(color = Color(0xFF141824), shape = pillShape),
            contentAlignment = Alignment.Center
        ) {
            Text(
                text = "✨ Glow",
                color = Color.White,
                fontSize = 18.sp
            )
        }
    }
}


val Purple = Color(0xFF7E57C2)
val Yellow = Color(0xFFFFCA28)
val Red = Color(0xFFEF5350)

‫ModulateAlpha آلفا را روی هر فرمان طراحی جداگانه اعمال می‌کند
شکل ۱۶: ModulateAlpha مقدار آلفا را به هریک از دستورات رسم اعمال می‌کند

نوشتن محتوای عنصر ترکیبی در بیت‌مپ

یک مورد استفاده رایج ایجاد Bitmap از یک عنصر ترکیبی است. برای کپی کردن محتوای عناصر ترکیبی در Bitmap، بااستفاده از rememberGraphicsLayer()، GraphicsLayer ایجاد کنید.

فرمان‌های طراحی را بااستفاده از drawWithContent() و graphicsLayer.record{} به لایه جدید هدایت کنید. سپس لایه را در بوم نمایان بااستفاده از drawLayer بکشید:

val coroutineScope = rememberCoroutineScope()
val graphicsLayer = rememberGraphicsLayer()
Box(
    modifier = Modifier
        .drawWithContent {
            // call record to capture the content in the graphics layer
            graphicsLayer.record {
                // draw the contents of the composable into the graphics layer
                this@drawWithContent.drawContent()
            }
            // draw the graphics layer on the visible canvas
            drawLayer(graphicsLayer)
        }
        .clickable {
            coroutineScope.launch {
                val bitmap = graphicsLayer.toImageBitmap()
                // do something with the newly acquired bitmap
            }
        }
        .background(Color.White)
) {
    Text("Hello Android", fontSize = 26.sp)
}

می‌توانید بیت‌مپ را در دیسک ذخیره کنید و آن را هم‌رسانی کنید. برای جزئیات بیشتر، گزیده نمونه کامل را ببینید. قبل‌از تلاش برای ذخیره کردن در دیسک، حتماً اجازه‌های روی دستگاه را بررسی کنید.

اصلاح‌گر طراحی سفارشی

برای ایجاد اصلاح‌گر سفارشی خودتان، میانای DrawModifier را پیاده‌سازی کنید. این کار به شما امکان می‌دهد به ContentDrawScope دسترسی داشته باشید که همان چیزی است که هنگام استفاده از Modifier.drawWithContent() نمایان می‌شود. سپس می‌توانید عملیات طراحی مشترک را به اصلاح‌کننده‌های طراحی سفارشی استخراج کنید تا کد را پاک‌سازی کنید و پوشش‌های مناسبی ارائه دهید؛ برای مثال، Modifier.background() یک DrawModifier مناسب است.

برای مثال، اگر می‌خواهید Modifier را پیاده‌سازی کنید که محتوا را به‌صورت عمودی برمی‌گرداند، می‌توانید آن را به‌صورت زیر ایجاد کنید:

class FlippedModifier : DrawModifier {
    override fun ContentDrawScope.draw() {
        scale(1f, -1f) {
            this@draw.drawContent()
        }
    }
}

fun Modifier.flipped() = this.then(FlippedModifier())

سپس از این تغییردهنده معکوس‌شده که روی Text اعمال شده است استفاده کنید:

Text(
    "Hello Compose!",
    modifier = Modifier
        .flipped()
)

«اصلاح‌گر وارونه سفارشی» در «نوشتار»
شکل ۱۷: اصلاح‌گر سفارشی معکوس روی نوشتار

منابع بیشتر

برای مثال‌های بیشتر درباره استفاده از graphicsLayer و طراحی سفارشی، منابع زیر را بررسی کنید: