בנוסף לרכיב הקומפוזבילי Canvas, ב-Compose יש כמה Modifiers גרפיים שימושיים שעוזרים לצייר תוכן בהתאמה אישית. העיבודים האלה שימושיים כי אפשר להחיל אותם על כל רכיב קומפוזבילי.
משני ציור
כל פקודות השרטוט מתבצעות באמצעות משתנה שינוי של שרטוט ב-Compose. יש שלושה משתני ציור עיקריים ב-Compose:
המשתנה הבסיסי לשרטוט הוא drawWithContent, שבו אפשר להחליט על סדר השרטוט של ה-Composable ועל פקודות השרטוט שמונפקות בתוך המשתנה. drawBehind הוא wrapper נוח מסביב ל-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, תראו שזה רק wrapper נוח סביב 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, מחוץ ל-modifier. עם זאת, זה לא תמיד אפשרי כי לא תמיד יש לכם גישה ליצירה המוזיקלית. אם האובייקטים משמשים רק לציור, יכול להיות שיהיה יעיל יותר להשתמש ב-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: החלת טרנספורמציות על רכיבים קומפוזביליים
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), הטרנספורמציות מתחילות מהפינה השמאלית העליונה של ה-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 } )
חיתוך וצורה
הצורה מציינת את קווי המתאר שהתוכן נחתך לפיהם כש-clip = true. בדוגמה הזו, הגדרנו שתי תיבות עם שני קליפים שונים – אחת באמצעות המשתנה 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)) ) }
התוכן של התיבה הראשונה (הטקסט "Hello Compose") נחתך לצורת העיגול:
אם תפעילו translationY על העיגול הוורוד העליון, תראו שהגבולות של הפונקציה הקומפוזבילית עדיין זהים, אבל העיגול מצויר מתחת לעיגול התחתון (ומחוץ לגבולות שלו).
כדי לחתוך את ה-composable לאזור שבו הוא משורטט, אפשר להוסיף עוד Modifier.clip(RectangleShape) בתחילת שרשרת ה-modifier. התוכן נשאר בתוך הגבולות המקוריים.
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 או גלילה מעבר לקצה תמיד מעבדת את התוכן למאגר נתונים זמני מחוץ למסך, בלי קשר לערך שמוגדר במדיניות 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 רק על התוכן של ה-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. הוא לא ייצור מאגר נתונים זמני מחוץ למסך לאלפא קטן מ-1.0f, אלא אם מוגדר RenderEffect, כך שהוא יכול להיות יעיל יותר לעיבוד אלפא. עם זאת, יכול להיות שהתוצאות יהיו שונות לגבי תוכן חופף. בתרחישי שימוש שבהם ידוע מראש שהתוכן לא חופף, אפשר להשתמש ב-CompositingStrategy.Auto עם ערכי אלפא קטנים מ-1 כדי לשפר את הביצועים.
בדוגמה הבאה מוצגות אסטרטגיות שונות של קומפוזיציה – החלת ערכי אלפא שונים על חלקים שונים של רכיבי ה-Composable, והחלת אסטרטגיית 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)
הבלטות של שכבות
כשפונקציה קומפוזבילית משורטטת במאגר נתונים זמני (buffer) מחוץ למסך (לדוגמה, כש-alpha קטן מ-1.0f, מוגדר RenderEffect או כשמשתמשים ב-CompositingStrategy.Offscreen), הטקסטורה מחוץ למסך מוגדרת כברירת מחדל לגבולות הפריסה שנמדדו של הפונקציה הקומפוזבילית. כל ציור או צללית בתוך השכבה שחורגים מגבולות הפריסה של ה-composable (כמו זוהר חיצוני שצויר באמצעות Modifier.dropShadow) נחתכים באופן משתמע בגבול של מאגר הנתונים הזמני מחוץ למסך.
במקום להוסיף Modifier.padding() (שמשנה את מדידת הפריסה של רכיבי ממשק משתמש סמוכים), מעבירים את LayerOutsets לפרמטר outsets של Modifier.graphicsLayer. הפעולה הזו מרחיבה את הגבולות החזותיים של המאגר מחוץ למסך בכל צד, בלי לשנות את גודל הפריסה שנמדד של הרכיב הניתן להגדרה. חשוב לדעת שהפונקציה LayerOutsets לא מרחיבה את צורת החיתוך או את גבולות הצל של השכבה, שנשארים מבוססים על הגודל המקורי של ה-composable (לכן צריך להשאיר את 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 (למעלה, חתוכה בגבולות הפריסה המלבנית של ה-composable) לעומת עם LayerOutsets(24.dp) (למטה, לא חתוכה)כתיבת התוכן של פונקציה קומפוזבילית למפת סיביות (bitmap)
תרחיש שימוש נפוץ הוא ליצור Bitmap מקומפוזבילי. כדי להעתיק את התוכן של הפונקציה הקומפוזבילית שלך ל-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 ובשרטוט בהתאמה אישית זמינות במקורות המידע הבאים:
מומלץ בשבילכם
- הערה: טקסט הקישור מוצג כש-JavaScript מושבת
- גרפיקה ב-Compose
- התאמה אישית של תמונה {:#customize-image}
- Kotlin ל-Jetpack Compose