Pinsel: Farbverläufe und Shader

Ein Brush in Compose beschreibt, wie etwas auf dem Bildschirm gezeichnet wird. Es bestimmt die Farbe(n), die im Zeichenbereich gezeichnet werden (z. B. ein Kreis, ein Quadrat oder ein Pfad). Es gibt einige integrierte Brushes, die zum Zeichnen nützlich sind, z. B. LinearGradient, RadialGradient oder ein einfacher SolidColor-Brush.

Brushes können mit Modifier.background(), TextStyle oder DrawScope-Zeichnungsaufrufen verwendet werden, um den Malstil auf den gezeichneten Inhalt anzuwenden.

Ein horizontaler Farbverlaufs-Brush kann beispielsweise zum Zeichnen eines Kreises in DrawScope verwendet werden:

val brush = Brush.horizontalGradient(listOf(Color.Red, Color.Blue))
Canvas(
    modifier = Modifier.size(200.dp),
    onDraw = {
        drawCircle(brush)
    }
)
Kreis mit horizontalem Farbverlauf
Abbildung 1: Kreis mit horizontalem Farbverlauf gezeichnet

Farbverlaufs-Brushes

Es gibt viele integrierte Farbverlaufs-Brushes, mit denen verschiedene Farbverlaufseffekte erzielt werden können. Mit diesen Brushes können Sie die Liste der Farben angeben, aus denen ein Farbverlauf erstellt werden soll.

Eine Liste der verfügbaren Farbverlaufs-Brushes und der entsprechenden Ausgabe:

Typ des Farbverlaufs-Brush Ausgabe
Brush.horizontalGradient(colorList) Horizontaler Farbverlauf
Brush.linearGradient(colorList) Linearer Farbverlauf
Brush.verticalGradient(colorList) Vertikaler Farbverlauf
Brush.sweepGradient(colorList)
Hinweis: Für einen reibungslosen Übergang zwischen den Farben legen Sie die letzte Farbe auf die Startfarbe fest.
Farbverlauf
Brush.radialGradient(colorList) Radialer Farbverlauf

Verteilung der Farben mit colorStops ändern

Sie können den Wert colorStops für jede Farbe anpassen, um zu ändern, wie die Farben im Farbverlauf angezeigt werden. colorStops sollte als Bruch zwischen 0 und 1 angegeben werden. Bei Werten über 1 werden diese Farben nicht als Teil des Farbverlaufs gerendert.

Sie können die Farb-Stops so konfigurieren, dass sie unterschiedliche Mengen einer Farbe enthalten, z. B. weniger oder mehr von einer Farbe:

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))
)

Die Farben werden mit dem angegebenen Offset verteilt, wie im colorStop-Paar definiert. Es gibt weniger Gelb als Rot und Blau.

Pinsel mit verschiedenen Farbstopps
Abbildung 2: Brush mit verschiedenen Farb-Stops konfiguriert

Muster mit TileMode wiederholen

Für jeden Farbverlaufs-Brush kann ein TileMode festgelegt werden. Wenn Sie keinen Start- und Endpunkt für den Farbverlauf festgelegt haben, wird TileMode möglicherweise nicht angezeigt, da standardmäßig der gesamte Bereich ausgefüllt wird. Mit TileMode wird der Farbverlauf nur gekachelt, wenn die Größe des Bereichs größer als die Größe des Brush ist.

Mit dem folgenden Code wird das Farbverlaufsmuster viermal wiederholt, da endX auf 50.dp und die Größe auf 200.dp festgelegt ist:

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
            )
        )
)

In der folgenden Tabelle wird beschrieben, was die verschiedenen Kachelmodi für das obige Beispiel HorizontalGradient bewirken:

TileMode Ausgabe
TileMode.Repeated: Die Kante wird von der letzten Farbe zur ersten wiederholt. TileMode.Repeated
TileMode.Mirror: Die Kante wird von der letzten Farbe zur ersten gespiegelt. Kachelmodus – Spiegeln
TileMode.Clamp: Die Kante wird auf die letzte Farbe begrenzt. Anschließend wird für den Rest der Region die nächstgelegene Farbe verwendet. Klemme für den Kachelmodus
TileMode.Decal: Nur bis zur Größe der Begrenzungen rendern. TileMode.Decal verwendet transparentes Schwarz, um Inhalte außerhalb der ursprünglichen Begrenzungen zu erfassen, während TileMode.Clamp die Kantenfarbe erfasst. Aufkleber für den Kachelmodus

TileMode funktioniert auf ähnliche Weise für die anderen gerichteten Farbverläufe. Der Unterschied besteht in der Richtung, in der die Wiederholung erfolgt.

Brush-Größe ändern

Wenn Sie die Größe des Bereichs kennen, in dem der Brush gezeichnet wird, können Sie endX für die Kachel festlegen, wie oben im Abschnitt TileMode beschrieben. Wenn Sie sich in einem DrawScope befinden, können Sie die Größe des Bereichs mit der Eigenschaft size abrufen.

Wenn Sie die Größe des Zeichenbereichs nicht kennen (z. B. wenn der Brush Text zugewiesen ist), können Sie Shader erweitern und die Größe von dem Zeichenbereich in der createShader Funktion verwenden.

In diesem Beispiel wird die Größe durch 4 geteilt, um das Muster viermal zu wiederholen:

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)
)

Shader-Größe geteilt durch 4
Abbildung 3: Shader-Größe durch 4 geteilt

Sie können auch die Brush-Größe anderer Farbverläufe ändern, z. B. radialer Farbverläufe. Wenn Sie keine Größe und kein Zentrum angeben, nimmt der Farbverlauf die gesamten Begrenzungen des DrawScope ein. Das Zentrum des radialen Farbverlaufs wird standardmäßig auf das Zentrum der DrawScope-Begrenzungen festgelegt. Dadurch wird das Zentrum des radialen Farbverlaufs als Zentrum der kleineren Dimension (Breite oder Höhe) angezeigt:

Box(
    modifier = Modifier
        .fillMaxSize()
        .background(
            Brush.radialGradient(
                listOf(Color(0xFF2be4dc), Color(0xFF243484))
            )
        )
)

Radialer Farbverlauf ohne Größenänderungen
Abbildung 4: Radialer Farbverlauf ohne Größenänderungen

Wenn Sie den radialen Farbverlauf so ändern, dass die Radiusgröße auf die maximale Dimension festgelegt wird, wird ein besserer radialer Farbverlaufseffekt erzielt:

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)
)

Größerer Radius für den radialen Farbverlauf basierend auf der Größe des Bereichs
Abbildung 5: Größerer Radius für radialen Farbverlauf basierend auf der Größe des Bereichs

Die tatsächliche Größe, die bei der Erstellung des Shaders übergeben wird, wird durch den Aufrufort bestimmt. Standardmäßig weist Brush den Shader intern neu zu, wenn sich die Größe von der letzten Erstellung des Brush unterscheidet oder wenn sich ein Statusobjekt geändert hat, das bei der Erstellung des Shaders verwendet wurde.

Mit dem folgenden Code wird der Shader dreimal mit unterschiedlichen Größen erstellt, da sich die Größe des Zeichenbereichs ändert:

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)
                }
            }
        }
)

Bild als Brush verwenden

Wenn Sie ein ImageBitmap als Brush verwenden möchten, laden Sie das Bild als ImageBitmap, und erstellen Sie einen ImageShader-Brush:

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))

Der Brush wird auf verschiedene Arten von Zeichnungen angewendet: einen Hintergrund, den Text und die Canvas. Dadurch wird Folgendes ausgegeben:

ImageShader-Pinsel auf unterschiedliche Weise verwendet
Abbildung 6: ImageShader-Brush zum Zeichnen eines Hintergrunds, von Text und eines Kreises verwenden

Der Text wird jetzt auch mit ImageBitmap gerendert, um die Pixel für den Text zu malen.

Erweitertes Beispiel: Benutzerdefinierter Brush

AGSL RuntimeShader-Brush

AGSL bietet eine Teilmenge der GLSL-Shader-Funktionen. Shader können in AGSL geschrieben und mit einem Brush in Compose verwendet werden.

Um einen Shader-Brush zu erstellen, definieren Sie zuerst den Shader als AGSL-Shader-String:

@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()

Der obige Shader verwendet zwei Eingabefarben, berechnet den Abstand von der unteren linken Ecke (vec2(0, 1)) des Zeichenbereichs und führt basierend auf dem Abstand eine mix-Funktion zwischen den beiden Farben aus. Dadurch entsteht ein Farbverlaufseffekt.

Erstellen Sie dann den Shader-Brush und legen Sie die Uniformen für resolution (die Größe des Zeichenbereichs) sowie die color und color2 fest, die als Eingabe für den benutzerdefinierten Farbverlauf verwendet werden sollen:

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)
    )
}

Wenn Sie diesen Code ausführen, wird Folgendes auf dem Bildschirm gerendert:

Benutzerdefinierter AGSL-Shader, der in Compose ausgeführt wird
Abbildung 7: Benutzerdefinierter AGSL-Shader in Compose

Mit Shadern können Sie viel mehr als nur Farbverläufe erstellen, da alle Berechnungen auf mathematischen Formeln basieren. Weitere Informationen zu AGSL finden Sie in der AGSL Dokumentation.

Zusätzliche Ressourcen

Weitere Beispiele für die Verwendung von Brush in Compose finden Sie in den folgenden Ressourcen: