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