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

علاوه‌بر ترکیب‌پذیر Canvas،‏ Compose چندین گرافیک مفید دارد Modifiers که به ترسیم محتوای سفارشی کمک می‌کند. این اصلاح‌کننده‌ها مفید هستند زیرا می‌توان آن‌ها را برای هر ترکیب‌پذیری اعمال کرد.

اصلاح‌کننده‌های طراحی

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

اصلاح‌کننده پایه برای ترسیم drawWithContent است که در آن می‌توانید ترتیب ترسیم Composable و فرمان‌های ترسیم صادرشده در اصلاح‌کننده را تعیین کنید. ‫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.drawWithContent که در بالای Composable برای ایجاد تجربه واسط کاربر از نوع چراغ‌قوه استفاده شده است.

‫Modifier.drawBehind: Drawing behind a composable

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.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
شکل ۳: ذخیره کردن شیء Brush در حافظه نهان با drawWithCache

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

‫Modifier.graphicsLayer: اعمال تبدیل‌ها به composables

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

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

تبدیل‌ها

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

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

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

مقیاس - افزایش اندازه

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 اعمال‌شده روی Image composable
ترجمه

translationX و translationY را می‌توان با graphicsLayer تغییر داد، translationX composable را به چپ یا راست حرکت می‌دهد. 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 اعمال‌شده روی Image با 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
        }
)

شکل ۶: rotationX،‏ rotationY، و rotationZ تنظیم‌شده روی Image توسط Modifier.graphicsLayer
مبدأ

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

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

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

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

کلیپ روی Box composable اعمال شد
شکل ۸: Clip اعمال‌شده روی Box composable

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

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

برای برش دادن ترکیب‌پذیر به منطقه‌ای که در آن ترسیم شده است، می‌توانید 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 تنظیم شده باشد، لایه را در میان‌گیر خارج از صفحه رندر می‌کند. هرگاه آلفا کمتر از 1f باشد، یک لایه ترکیب‌بندی به‌طور خودکار ایجاد می‌شود تا محتوا را رندر کند و سپس این میان‌گیر خارج از صفحه را با آلفای مربوطه به مقصد بکشد. تنظیم RenderEffect یا پیمایش بیش‌ازحد همیشه محتوا را در میان‌گیر خارج از صفحه صرف‌نظر از CompositingStrategy تنظیم‌شده ارائه می‌کند.

خارج از صفحه

محتوای composable همیشه قبل‌از رندر شدن در مقصد، به بافت یا نقشه بیتی خارج‌از صفحه شطرنجی می‌شود. این فرایند برای اعمال کردن عملیات 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 با کل برنامه تعامل برقرار می‌کرد. این کار همه پیکسل‌ها را پاک می‌کرد تا برنامه یا کاغذدیواری زیرین را نشان دهد، مانند این مثال:

CompositingStrategy تنظیم نشده است و از 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 - حالت offscreen به منطقه برش می‌دهد، اما حالت auto این کار را نمی‌کند
شکل ۱۵: CompositingStrategy.Auto درمقابل CompositingStrategy.Offscreen - offscreen به منطقه برش می‌دهد، درحالی‌که auto این کار را نمی‌کند
ModulateAlpha

این استراتژی ترکیب‌بندی آلفا را برای هریک از دستورالعمل‌های طراحی ضبط‌شده در graphicsLayer تعدیل می‌کند. اگر RenderEffect تنظیم نشده باشد، میان‌گیر خارج از صفحه برای آلفای کمتر از ۱٫۰f ایجاد نمی‌کند، بنابراین می‌تواند برای پردازش آلفا کارآمدتر باشد. اما ممکن است برای محتوای هم‌پوشان نتایج متفاوتی ارائه دهد. برای موارد استفاده‌ای که ازقبل مشخص است محتوا هم‌پوشانی ندارد، این روش می‌تواند عملکرد بهتری نسبت‌به 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 مجموعه آلفا را برای هر فرمان ترسیم جداگانه اعمال می‌کند

بیرون‌زدگی‌های لایه

وقتی یک composable در میان‌گیر خارج از صفحه کشیده می‌شود (برای مثال، وقتی alpha کمتر از 1.0f باشد، RenderEffect تنظیم شده باشد، یا از CompositingStrategy.Offscreen استفاده شود)، بافت خارج از صفحه به‌طور پیش‌فرض به مرزهای چیدمان اندازه‌گیری‌شده composable تبدیل می‌شود. هرگونه طراحی یا سایه درون لایه که فراتر از مرزهای چیدمان ترکیب‌پذیر گسترش یابد (مانند درخشش بیرونی که با Modifier.dropShadow رسم شده است) به‌طور ضمنی در مرز میان‌گیر خارج از صفحه بریده می‌شود.

به‌جای افزودن Modifier.padding() (که اندازه‌گیری چیدمان عناصر میانای کاربر مجاور را تغییر می‌دهد)، LayerOutsets را به پارامتر outsets از Modifier.graphicsLayer ارسال کنید. این کار مرزهای دیداری میان‌گیر خارج از صفحه را در هر طرف گسترش می‌دهد، بدون اینکه اندازه چیدمان اندازه‌گیری‌شده ترکیب‌پذیر را تغییر دهد. توجه داشته باشید که LayerOutsets شکل برش لایه یا حدود سایه را گسترش نمی‌دهد، که براساس اندازه اندازه‌گیری‌شده اصلی ترکیب‌پذیر باقی می‌ماند (بنابراین clip = false را روی graphicsLayer نگه دارید تا از برش به حدود اصلی جلوگیری شود):

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

مقایسه درخشش بیرونی لایه خارج از صفحه بدون LayerOutsets (بالا، برش‌خورده به مرزهای چیدمان مستطیلی) در مقابل با LayerOutsets(24.dp) (پایین، برش‌نخورده)
شکل ۱۷: درخشش بیرونی لایه خارج‌از صفحه بدون LayerOutsets (بالا، برش‌خورده در مرزهای چیدمان مستطیلی ترکیب‌پذیر) درمقابل با LayerOutsets(24.dp) (پایین، برش‌نخورده)

نوشتن محتوای یک composable در یک bitmap

یک مورد استفاده رایج، ایجاد Bitmap از یک composable است. برای کپی کردن محتوای ترکیب‌پذیرتان در 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 و طراحی سفارشی، منابع زیر را بررسی کنید: