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

اگر سپس یک translationY روی دایره صورتی بالا اعمال کنید، می بینید که مرزهای 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 تعیین می شود. اگر آلفا کمتر از 1.0f باشد یا RenderEffect تنظیم شده باشد، لایه را به یک بافر خارج از صفحه نمایش می دهد. هر زمان که آلفا کمتر از 1f باشد، یک لایه ترکیبی به طور خودکار ایجاد می شود تا محتویات را رندر کند و سپس این بافر خارج از صفحه را با آلفای مربوطه به مقصد بکشد. تنظیم RenderEffect یا Overscroll همیشه محتوا را بدون توجه به مجموعه CompositingStrategy در یک بافر خارج از صفحه نمایش می دهد.
خارج از صفحه نمایش
محتویات composable همیشه قبل از رندر شدن به مقصد، به یک بافت خارج از صفحه یا bitmap تبدیل می شوند. این برای اعمال عملیات BlendMode برای پوشاندن محتوا و برای عملکرد هنگام ارائه مجموعه های پیچیده دستورالعمل های طراحی مفید است.
نمونه ای از استفاده از CompositingStrategy.Offscreen با BlendModes است. با نگاهی به مثال زیر، فرض کنید میخواهید با صدور فرمان ترسیم که از BlendMode.Clear استفاده میکند، بخشهایی از یک Image قابل ترکیب را حذف کنید. اگر 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 که شامل آلفا هستند، بدون بافر خارج از صفحه، آنطور که انتظار می رود کار نمی کنند. به حلقه سیاه دور نشانگر دایره قرمز توجه کنید:

برای درک بیشتر این موضوع: اگر برنامه یک پسزمینه پنجره شفاف داشت و از 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 ثبت شده اند، تعدیل می کند. تا زمانی که RenderEffect تنظیم نشده باشد، بافر خارج از صفحه برای آلفا زیر 1.0f ایجاد نمی کند، بنابراین می تواند برای رندر آلفا کارآمدتر باشد. با این حال، می تواند نتایج متفاوتی را برای محتوای همپوشانی ارائه دهد. برای موارد استفاده که از قبل مشخص شده است که محتوا همپوشانی ندارد، این می تواند عملکرد بهتری نسبت به CompositingStrategy.Auto با مقادیر آلفای کمتر از 1 ارائه دهد.
مثال دیگری از استراتژیهای ترکیببندی مختلف در زیر است - اعمال آلفاهای مختلف در بخشهای مختلف ترکیبپذیرها و اعمال استراتژی 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 از یک Composable است. برای کپی کردن محتویات composable خود در Bitmap ، یک GraphicsLayer با استفاده از rememberGraphicsLayer() ایجاد کنید.
دستورات ترسیم را با استفاده از 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 و طراحی سفارشی، منابع زیر را بررسی کنید:
برای شما توصیه می شود
- توجه: وقتی جاوا اسکریپت خاموش است، متن پیوند نمایش داده می شود
- گرافیک در Compose
- سفارشی کردن یک تصویر {:#customize-image}
- Kotlin برای Jetpack Compose