Brush ב-Compose מתאר איך משהו משורטט במסך: הוא קובע את הצבעים שמשורטטים באזור השרטוט (למשל עיגול, ריבוע, נתיב). יש כמה מברשות מובנות ששימושיות לציור, כמו LinearGradient, RadialGradient או מברשת SolidColor פשוטה.
אפשר להשתמש במברשות עם בקשות להזזת פריט גרפי (draw calls) מסוג Modifier.background(), TextStyle או DrawScope כדי להחיל את סגנון הציור על התוכן שמצויר.
לדוגמה, אפשר להחיל מברשת עם מעבר צבעים אופקי על ציור של עיגול ב-DrawScope:
val brush = Brush.horizontalGradient(listOf(Color.Red, Color.Blue)) Canvas( modifier = Modifier.size(200.dp), onDraw = { drawCircle(brush) } )
מברשות הדרגתיות
יש הרבה מברשות מעבר מובנות שאפשר להשתמש בהן כדי להשיג אפקטים שונים של מעבר. המברשות האלה מאפשרות לציין את רשימת הצבעים שמהם רוצים ליצור את ההדרגתיות.
רשימה של מברשות מעבר זמינות והפלט המתאים שלהן:
| סוג מברשת הדרגתית | פלט |
|---|---|
Brush.horizontalGradient(colorList) |
|
Brush.linearGradient(colorList) |
|
Brush.verticalGradient(colorList) |
|
Brush.sweepGradient(colorList)
הערה: כדי ליצור מעבר חלק בין הצבעים, צריך להגדיר את הצבע האחרון כצבע ההתחלה. |
|
Brush.radialGradient(colorList) |
|
שינוי התפלגות הצבעים באמצעות colorStops
כדי להתאים אישית את האופן שבו הצבעים מופיעים במעבר הצבעים, אפשר לשנות את הערך של colorStops לכל אחד מהם. צריך לציין את colorStops כשבר, בין 0 ל-1. אם תזינו ערכים גדולים מ-1, הצבעים האלה לא יוצגו כחלק מהמעבר.
אפשר להגדיר את נקודות הצבע כך שיהיו כמויות שונות, למשל פחות או יותר מצבע אחד:
val colorStops = arrayOf( 0.0f to Color.Yellow, 0.2f to Color.Red, 1f to Color.Blue ) Box( modifier = Modifier .requiredSize(200.dp) .background(Brush.horizontalGradient(colorStops = colorStops)) )
הצבעים מפוזרים בהיסט שצוין, כפי שמוגדר בצמד colorStop, פחות צהוב מאדום וכחול.
חזרה על תבנית עם TileMode
בכל מברשת הדרגתיות יש אפשרות להגדיר TileMode. יכול להיות שלא תבחינו ב-TileMode אם לא הגדרתם נקודת התחלה ונקודת סיום למעבר הצבעים, כי כברירת מחדל הוא ימלא את כל האזור. TileMode יחזור על המעבר רק אם גודל האזור גדול מגודל המברשת.
הקוד הבא יחזור על תבנית הגרדיאנט 4 פעמים, כי הערך של endX מוגדר ל-50.dp והגודל מוגדר ל-200.dp:
val listColors = listOf(Color.Yellow, Color.Red, Color.Blue) val tileSize = with(LocalDensity.current) { 50.dp.toPx() } Box( modifier = Modifier .requiredSize(200.dp) .background( Brush.horizontalGradient( listColors, endX = tileSize, tileMode = TileMode.Repeated ) ) )
בטבלה הבאה מפורט מה עושים מצבי ה-Tile השונים בדוגמה של HorizontalGradient שלמעלה:
| TileMode | פלט |
|---|---|
TileMode.Repeated: הקצה חוזר על עצמו מהצבע האחרון לצבע הראשון. |
|
TileMode.Mirror: הקצה משתקף מהצבע האחרון לצבע הראשון. |
|
TileMode.Clamp: הקצה מוגבל לצבע הסופי. הוא יצבע את שאר האזור בצבע הכי קרוב. |
|
TileMode.Decal: עיבוד רק עד לגודל הגבולות. TileMode.Decal משתמש בשחור שקוף כדי לדגום תוכן מחוץ לגבולות המקוריים, ואילו TileMode.Clamp דוגם את צבע הקצה. |
|
הפונקציה TileMode פועלת באופן דומה עבור מעברי הצבעים האחרים, וההבדל הוא הכיוון שבו מתרחשת החזרה.
שינוי גודל המברשת
אם אתם יודעים מה גודל האזור שבו המברשת תצויר, אתם יכולים להגדיר את האריח endX כמו שראינו למעלה בקטע TileMode. אם אתם נמצאים ב-DrawScope, אתם יכולים להשתמש במאפיין size כדי לקבל את גודל האזור.
אם אתם לא יודעים מה הגודל של אזור הציור (לדוגמה, אם Brush מוקצה לטקסט), אתם יכולים להרחיב את Shader ולהשתמש בגודל של אזור הציור בפונקציה createShader.
בדוגמה הזו, מחלקים את הגודל ב-4 כדי לחזור על התבנית 4 פעמים:
val listColors = listOf(Color.Yellow, Color.Red, Color.Blue) val customBrush = remember { object : ShaderBrush() { override fun createShader(size: Size): Shader { return LinearGradientShader( colors = listColors, from = Offset.Zero, to = Offset(size.width / 4f, 0f), tileMode = TileMode.Mirror ) } } } Box( modifier = Modifier .requiredSize(200.dp) .background(customBrush) )
אפשר גם לשנות את גודל המברשת של כל מעבר צבעים אחר, כמו מעבר צבעים רדיאלי. אם לא מציינים גודל ומרכז, ההדרגה תתפוס את כל הגבולות של DrawScope, והמרכז של ההדרגה הרדיאלית יהיה כברירת מחדל במרכז הגבולות של DrawScope. כתוצאה מכך, מרכז המעבר הרדיאלי מופיע כמרכז של המימד הקטן יותר (רוחב או גובה):
Box( modifier = Modifier .fillMaxSize() .background( Brush.radialGradient( listOf(Color(0xFF2be4dc), Color(0xFF243484)) ) ) )
כשמשנים את הגרדיאנט הרדיאלי כדי להגדיר את גודל הרדיוס לממד המקסימלי, אפשר לראות שהוא יוצר אפקט גרדיאנט רדיאלי טוב יותר:
val largeRadialGradient = object : ShaderBrush() { override fun createShader(size: Size): Shader { val biggerDimension = maxOf(size.height, size.width) return RadialGradientShader( colors = listOf(Color(0xFF2be4dc), Color(0xFF243484)), center = size.center, radius = biggerDimension / 2f, colorStops = listOf(0f, 0.95f) ) } } Box( modifier = Modifier .fillMaxSize() .background(largeRadialGradient) )
חשוב לציין שהגודל בפועל שמועבר ליצירת ה-shader נקבע לפי המקום שממנו הוא מופעל. כברירת מחדל, Brush יקצה מחדש את Shader באופן פנימי אם הגודל שונה מהיצירה האחרונה של Brush, או אם אובייקט מצב ששימש ליצירת ה-shader השתנה.
הקוד הבא יוצר את ה-shader שלוש פעמים שונות בגדלים שונים, כשאזור הציור משתנה:
val colorStops = arrayOf( 0.0f to Color.Yellow, 0.2f to Color.Red, 1f to Color.Blue ) val brush = Brush.horizontalGradient(colorStops = colorStops) Box( modifier = Modifier .requiredSize(200.dp) .drawBehind { drawRect(brush = brush) // will allocate a shader to occupy the 200 x 200 dp drawing area inset(10f) { /* Will allocate a shader to occupy the 180 x 180 dp drawing area as the inset scope reduces the drawing area by 10 pixels on the left, top, right, bottom sides */ drawRect(brush = brush) inset(5f) { /* will allocate a shader to occupy the 170 x 170 dp drawing area as the inset scope reduces the drawing area by 5 pixels on the left, top, right, bottom sides */ drawRect(brush = brush) } } } )
שימוש בתמונה בתור מברשת
כדי להשתמש ב-ImageBitmap בתור Brush, טוענים את התמונה בתור ImageBitmap ויוצרים מברשת ImageShader:
val imageBrush = ShaderBrush(ImageShader(ImageBitmap.imageResource(id = R.drawable.dog))) // Use ImageShader Brush with background Box( modifier = Modifier .requiredSize(200.dp) .background(imageBrush) ) // Use ImageShader Brush with TextStyle Text( text = "Hello Android!", style = TextStyle( brush = imageBrush, fontWeight = FontWeight.ExtraBold, fontSize = 36.sp ) ) // Use ImageShader Brush with DrawScope#drawCircle() Canvas(onDraw = { drawCircle(imageBrush) }, modifier = Modifier.size(200.dp))
המברשת מוחלת על כמה סוגים שונים של ציור: רקע, טקסט וקנבס. הפלט שיתקבל:
שימו לב שהטקסט מוצג עכשיו גם באמצעות ImageBitmap כדי לצבוע את הפיקסלים של הטקסט.
דוגמה מתקדמת: מברשת בהתאמה אישית
מברשת AGSL RuntimeShader
AGSL מציעה קבוצת משנה של יכולות הצללה של GLSL. אפשר לכתוב שיידרים ב-AGSL ולהשתמש בהם עם Brush ב-Compose.
כדי ליצור מברשת הצללה, קודם צריך להגדיר את ההצללה כמחרוזת הצללה של AGSL:
@Language("AGSL") val CUSTOM_SHADER = """ uniform float2 resolution; layout(color) uniform half4 color; layout(color) uniform half4 color2; half4 main(in float2 fragCoord) { float2 uv = fragCoord/resolution.xy; float mixValue = distance(uv, vec2(0, 1)); return mix(color, color2, mixValue); } """.trimIndent()
ההצללה שלמעלה מקבלת שני צבעי קלט, מחשבת את המרחק מהפינה השמאלית התחתונה (vec2(0, 1)) של אזור השרטוט ומבצעת mix בין שני הצבעים על סמך המרחק. ככה נוצר אפקט של מעבר צבעים.
אחר כך יוצרים את Shader Brush ומגדירים את המשתנים האחידים של resolution – הגודל של אזור הציור, ואת color ו-color2 שבהם רוצים להשתמש כקלט לגרדיאנט המותאם אישית:
val Coral = Color(0xFFF3A397) val LightYellow = Color(0xFFF8EE94) @RequiresApi(Build.VERSION_CODES.TIRAMISU) @Composable @Preview fun ShaderBrushExample() { Box( modifier = Modifier .drawWithCache { val shader = RuntimeShader(CUSTOM_SHADER) val shaderBrush = ShaderBrush(shader) shader.setFloatUniform("resolution", size.width, size.height) onDrawBehind { shader.setColorUniform( "color", android.graphics.Color.valueOf( LightYellow.red, LightYellow.green, LightYellow .blue, LightYellow.alpha ) ) shader.setColorUniform( "color2", android.graphics.Color.valueOf( Coral.red, Coral.green, Coral.blue, Coral.alpha ) ) drawRect(shaderBrush) } } .fillMaxWidth() .height(200.dp) ) }
כשמריצים את הקוד הזה, רואים את התוצאה הבאה על המסך:
חשוב לציין שאפשר לעשות הרבה יותר עם תוכניות הצללה מאשר רק שיפועים, כי הכול מבוסס על חישובים מתמטיים. מידע נוסף על AGSL זמין במסמכי התיעוד של AGSL.
מקורות מידע נוספים
דוגמאות נוספות לשימוש ב-Brush ב-Compose זמינות במקורות המידע הבאים:
- הנפשה של צביעת טקסט במברשת ב-Compose 🖌️
- גרפיקה ופריסות בהתאמה אישית ב-Compose – מפגש של מפתחי Android 2022
- <!--JetLagged Sample - RuntimeShader Brush-->
מומלץ בשבילכם
- הערה: טקסט הקישור מוצג כש-JavaScript מושבת
- משני גרפיקה
- גרפיקה ב-Compose
- עיצוב טקסט