Pinsel: Farbverläufe und Shader

Ein Brush in „Schreiben“ beschreibt, wie etwas auf dem Bildschirm gezeichnet wird: bestimmt die Farbe(n), die im Zeichenbereich gezeichnet werden (d.h. ein Kreis, Quadrat, Pfad). Es gibt einige integrierte Pinsel, die zum Zeichnen wie LinearGradient, RadialGradient oder eine einfache SolidColor-Pinsel

Pinsel können mit den Zeichenaufrufen Modifier.background(), TextStyle oder DrawScope verwendet werden, um den Malstil auf die gezeichneten Inhalte anzuwenden.

Mit einem horizontalen Farbverlauf-Pinsel können Sie zum Beispiel einen Kreis in DrawScope:

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

Farbverlaufspinsel

Es gibt viele integrierte Farbverlaufs-Pinsel, mit denen Sie verschiedene Farbverlaufseffekte erzielen können. Mit diesen Pinseln können Sie eine Liste der Farben festlegen, aus dem Sie einen Farbverlauf erstellen möchten.

Eine Liste der verfügbaren Farbverlaufspinsel und ihrer entsprechenden Ausgabe:

Art des Farbverlaufs-Pinsels Ausgabe
Brush.horizontalGradient(colorList) Horizontaler Verlauf
Brush.linearGradient(colorList) Linearer Farbverlauf
Brush.verticalGradient(colorList) Vertikaler Farbverlauf
Brush.sweepGradient(colorList)
Hinweis: Legen Sie für die letzte Farbe die Startfarbe fest, um einen gleichmäßigen Übergang zwischen den Farben zu erzielen.
Farbverlauf der Funktion "Erledigen"
Brush.radialGradient(colorList) Radialer Farbverlauf

Farbverteilung mit colorStops ändern

Um die Darstellung der Farben im Farbverlauf anzupassen, können Sie colorStops Wert pro Element. colorStops muss als Bruch angegeben werden, zwischen 0 und 1 liegen. Werte über 1 führen dazu, dass diese Farben nicht als Teil des Farbverlaufs gerendert werden.

Sie können die Farbstopps so konfigurieren, dass sie unterschiedlich groß sind, z. B. weniger oder mehrere Farben:

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 sind mit dem angegebenen Versatz verteilt, wie in den colorStop definiert. weniger Gelb als Rot und Blau.

Pinsel mit verschiedenen Farbstopps
Abbildung 2: Pinsel mit unterschiedlichen Farbstopps

Muster mit TileMode wiederholen

Für jeden Farbverlauf kann ein TileMode festgelegt werden. Sie dürfen nicht Wenn Sie kein Anfang und Ende für den Farbverlauf festgelegt haben, sehen Sie TileMode. füllt sie standardmäßig den gesamten Bereich aus. Mit TileMode wird nur der Farbverlauf in Kacheln umgewandelt. wenn der Bereich größer als der Pinsel ist.

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

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 sehen Sie, welche Funktion die verschiedenen Kachelmodi HorizontalGradient-Beispiel oben:

Kachelmodus Ausgabe
TileMode.Repeated: Der Rand wird von der letzten Farbe zur ersten wiederholt. Kachelmodus wiederholt
TileMode.Mirror: Der Rand wird von der letzten Farbe zur ersten gespiegelt. Kachelmodus-Spiegel
TileMode.Clamp: Der Rand wird an die endgültige Farbe gebunden. Dann wird die Farbe für den Rest der Region verwendet. Kachelmodus-Klemme
TileMode.Decal: Wird nur bis zur Größe der Grenzen gerendert. Bei TileMode.Decal wird transparentes Schwarz verwendet, um Inhalte außerhalb der ursprünglichen Grenzen zu erfassen, während bei TileMode.Clamp die Randfarbe verwendet wird. Aufkleber für den Kachelmodus

TileMode funktioniert ähnlich für die anderen Richtungsverläufe, den die Richtung der Wiederholung.

Pinselgröße ändern

Wenn Sie die Größe des Bereichs kennen, in dem Ihr Pinsel gezeichnet werden soll, Legen Sie die Kachel endX fest, wie wir oben im Abschnitt TileMode gesehen haben. In diesem Fall eine DrawScope haben, können Sie ihre size-Eigenschaft verwenden, um die Größe des Bereichs abzurufen.

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

In diesem Beispiel teilen Sie die Größe durch 4, 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 geteilt durch 4

Sie können auch die Pinselgröße jedes anderen Farbverlaufs ändern, z. B. den radialen Farbverlauf. Farbverläufe. Wenn Sie keine Größe und Mitte angeben, nimmt der Farbverlauf vollständige Grenzen von DrawScope und den Mittelpunkt der Standardeinstellungen für den radialen Farbverlauf bis zum Mittelpunkt der DrawScope-Grenzen. Daraus ergibt sich die Mittelpunkt, der als Mittelpunkt der kleineren Dimension (entweder Breite oder Höhe):

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

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

Wenn der radiale Farbverlauf geändert wird, um die Radiusgröße auf die maximale Dimension festzulegen, sehen Sie, dass es einen besseren radialen Farbverlaufseffekt erzeugt:

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 auf Grundlage des radialen Farbverlaufs, basierend auf der Flächengröße
Abbildung 5: Größerer Radius auf Basis des radialen Farbverlaufs, basierend auf der Flächengröße

Die tatsächliche Größe, die bei der Erstellung des Shader wird von dem Ort ermittelt, an dem er aufgerufen wird. Standardmäßig wird Shader von Brush intern neu zugewiesen, wenn sich die Größe von der letzten Erstellung von Brush unterscheidet oder sich ein Statusobjekt geändert hat, das bei der Erstellung des Shaders verwendet wurde.

Mit dem folgenden Code wird der Shader dreimal mit unterschiedlichen wenn 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)
                }
            }
        }
)

Bilder als Pinsel verwenden

Um eine ImageBitmap als Brush zu verwenden, laden Sie das Bild als ImageBitmap hoch. und erstelle einen ImageShader-Pinsel:

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 Pinsel wird auf verschiedene Zeichenarten angewendet: auf den Hintergrund, den Text und Canvas. Dadurch wird Folgendes ausgegeben:

Verschiedene Einsatzmöglichkeiten des ImageShader-Pinsels
Abbildung 6: Mit dem ImageShader-Pinsel einen Hintergrund zeichnen, Text zeichnen und einen Kreis zeichnen

Beachten Sie, dass der Text jetzt ebenfalls mit ImageBitmap gerendert wird, um das Pixel für den Text.

Beispiel für Fortgeschrittene: Benutzerdefinierter Pinsel

AGSL-Pinsel "RuntimeShader"

AGSL bietet einen Teil der GLSL-Shader-Funktionen an. Shader können in AGSL geschrieben und mit einem Pinsel in Compose verwendet.

Um einen Shader-Pinsel zu erstellen, definieren Sie zuerst den Shader als AGSL-Shaderstring:

@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 und berechnet den Abstand vom unteren Rand links (vec2(0, 1)) des Zeichenbereichs und führt ein mix zwischen den beiden Farben basierend auf der Entfernung. Dadurch entsteht ein Farbverlaufseffekt.

Erstelle dann den Shader-Pinsel und lege die Uniformen für resolution fest – die Größe des Zeichenbereichs und die color und color2, die Sie als Eingabe für Ihren benutzerdefinierten Farbverlauf:

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 Befehl ausführen, sehen Sie Folgendes auf dem Bildschirm:

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

Mit Shadern lassen sich neben Farbverläufen noch viel mehr. weil das alles mathematisch basierte Berechnungen sind. Weitere Informationen zu AGSL finden Sie in der AGSL-Dokumentation

Weitere Informationen

Weitere Beispiele zur Verwendung von Prush in Compose finden Sie in den folgenden Ressourcen: