علاوه بر Canvas composable، Compose چندین Modifiers گرافیکی مفید دارد که به ترسیم محتوای سفارشی کمک میکنند. این اصلاحکنندهها مفید هستند زیرا میتوانند روی هر ترکیبکنندهای اعمال شوند.
اصلاحکنندههای ترسیم
تمام دستورات ترسیم با یک اصلاحکننده ترسیم در Compose انجام میشوند. سه اصلاحکننده ترسیم اصلی در Compose وجود دارد:
 اصلاحکننده پایه برای ترسیم، drawWithContent است که در آن میتوانید ترتیب ترسیم Composable خود و دستورات ترسیم صادر شده درون اصلاحکننده را تعیین کنید. drawBehind یک پوشش مناسب در اطراف drawWithContent است که ترتیب ترسیم را در پشت محتوای composable تنظیم میکند. drawWithCache یا onDrawBehind یا onDrawWithContent درون آن فراخوانی میکند - و مکانیزمی برای ذخیرهسازی اشیاء ایجاد شده در آنها فراهم میکند.
 Modifier.drawWithContent : انتخاب ترتیب ترسیم
 Modifier.drawWithContent به شما امکان میدهد عملیات DrawScope قبل یا بعد از محتوای composable اجرا کنید. حتماً drawContent فراخوانی کنید تا محتوای واقعی composable رندر شود. با این modifier، میتوانید ترتیب عملیات را تعیین کنید، اگر میخواهید محتوای شما قبل یا بعد از عملیات طراحی سفارشی شما ترسیم شود.
برای مثال، اگر میخواهید یک گرادیان شعاعی روی محتوای خود ایجاد کنید تا جلوهای شبیه به چراغ قوه در رابط کاربری ایجاد شود، میتوانید موارد زیر را انجام دهید:
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.drawWithCache : رسم و ذخیره سازی اشیاء رسم شده
 Modifier.drawWithCache اشیاء ایجاد شده در داخل خود را در حافظه پنهان (cache) نگه میدارد. اشیاء تا زمانی که اندازه ناحیه ترسیم یکسان باشد، یا هر شیء خوانده شده در حالت خوانده شده تغییر نکرده باشد، در حافظه پنهان (cache) باقی میمانند. این اصلاحکننده برای بهبود عملکرد فراخوانیهای ترسیم مفید است زیرا از نیاز به تخصیص مجدد اشیاء (مانند: 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()) ) } } )

اصلاحکنندههای گرافیکی
 Modifier.graphicsLayer : اعمال تبدیلات به composables 
 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 } )
ترجمه
 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() } )
چرخش
 rotationX برای چرخش افقی، rotationY برای چرخش عمودی و rotationZ برای چرخش روی محور Z (چرخش استاندارد) تنظیم کنید. این مقدار بر حسب درجه (0-360) مشخص میشود. 
Image( painter = painterResource(id = R.drawable.sunset), contentDescription = "Sunset", modifier = Modifier .graphicsLayer { this.rotationX = 90f this.rotationY = 275f this.rotationZ = 180f } )
مبدا
 میتوان یک 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 } )
گیره و شکل
 شکل، طرح کلی را مشخص میکند که محتوا هنگام clip = true به آن میچسبد. در این مثال، ما دو کادر را طوری تنظیم میکنیم که دو کلیپ مختلف داشته باشند - یکی با استفاده از متغیر clip graphicsLayer و دیگری با استفاده از wrapper مناسب 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 روی دایره صورتی بالا اعمال کنید، میبینید که مرزهای Composable هنوز یکسان هستند، اما دایره زیر دایره پایینی (و خارج از مرزهای آن) رسم میشود. 

 برای اینکه عنصر ترکیبپذیر (composable) به ناحیهای که در آن ترسیم شده است، متصل شود، میتوانید یک 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)) ) }

آلفا
 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 تعیین میشود. اگر آلفا کمتر از ۱.۰f باشد یا RenderEffect تنظیم شده باشد، لایه را در یک بافر خارج از صفحه رندر میکند. هر زمان که آلفا کمتر از ۱f باشد، یک لایه ترکیب به طور خودکار ایجاد میشود تا محتویات را رندر کند و سپس این بافر خارج از صفحه را با آلفای مربوطه به مقصد ترسیم کند. تنظیم RenderEffect یا overscroll همیشه محتوا را صرف نظر از تنظیم CompositingStrategy در یک بافر خارج از صفحه رندر میکند.
خارج از صفحه
 محتویات فایل composable همیشه قبل از رندر شدن در مقصد، به یک بافت خارج از صفحه یا بیتمپ تبدیل (rasterize) میشوند. این قابلیت برای اعمال عملیات BlendMode برای ماسک کردن محتوا و برای بهبود عملکرد هنگام رندر کردن مجموعههای پیچیده از دستورالعملهای ترسیم مفید است.
 یک مثال از استفاده از CompositingStrategy.Offscreen با BlendModes است. با نگاهی به مثال زیر، فرض کنید میخواهید بخشهایی از یک Image قابل ترکیب را با صدور دستور draw که از 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 فقط روی محتویات این composable اعمال میکند). سپس آن را روی آنچه از قبل روی صفحه نمایش داده شده است، رندر میکند و بر محتوای ترسیم شده تأثیری نمیگذارد. 

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

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

 شایان ذکر است که هنگام استفاده از 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())) } } }

 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) ) } val Purple = Color(0xFF7E57C2) val Yellow = Color(0xFFFFCA28) val Red = Color(0xFFEF5350)

محتویات یک composable را در یک bitmap بنویسید
 یک مورد استفاده رایج، ایجاد یک Bitmap از یک composable است. برای کپی کردن محتویات 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 و طراحی سفارشی، منابع زیر را بررسی کنید:
برای شما توصیه میشود
- توجه: متن لینک زمانی نمایش داده میشود که جاوا اسکریپت غیرفعال باشد.
- گرافیک در نوشتن
- سفارشیسازی یک تصویر {:#customize-image}
- کاتلین برای جتپک کامپوز
