علاوه بر 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