Brush ב-Compose מתאר איך משהו מצויר על המסך: הוא קובע את הצבעים שמצוירים באזור הציור (כלומר, מעגל, ריבוע, נתיב). יש כמה מברשות מובנות ששימושיות לציור, כמו LinearGradient, RadialGradient או מברשת SolidColor רגילה.
אפשר להשתמש במברשות עם קריאות לציור מסוג 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
pair, עם פחות צהוב מאדום וכחול.
חזרה על תבנית עם 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 ) ) )
בטבלה הבאה מפורטות הפעולות של מצבי התצוגה השונים של האריחים בדוגמה 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 שלוש פעמים שונות עם גדלים שונים, כשהגודל של אזור הציור משתנה:
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 מציע קבוצת משנה של יכולות Shader של GLSL. אפשר לכתוב שיידרים ב-AGSL ולהשתמש בהם עם מברשת ב-Compose.
כדי ליצור מברשת Shader, קודם מגדירים את ה-Shader כמחרוזת Shader של 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()
ה-shader שלמעלה מקבל שני צבעי קלט, מחשב את המרחק מהפינה השמאלית התחתונה (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) ) }
אחרי שמריצים את הפקודה, רואים את הפלט הבא במסך:
חשוב לציין שאפשר לעשות הרבה יותר עם shaders מאשר רק מעברי צבע, כי הכול מבוסס על חישובים מתמטיים. מידע נוסף על AGSL זמין במסמכי התיעוד.
מקורות מידע נוספים
דוגמאות נוספות לשימוש במברשת ב-Compose זמינות במקורות המידע הבאים:
- הנפשת צביעת טקסט במברשת ב-Compose 🖌️
- Custom Graphics and Layouts in Compose - Android Dev Summit 2022
- JetLagged Sample - RuntimeShader Brush
מומלץ בשבילכם
- הערה: טקסט הקישור מוצג כש-JavaScript מושבת
- שינויים בגרפיקה
- גרפיקה ב-Compose
- עיצוב טקסט