Kuas: gradien dan shader

Brush di Compose menjelaskan cara menggambar sesuatu di layar: menentukan warna yang digambar di area gambar (misalnya lingkaran, persegi, jalur). Ada beberapa Kuas bawaan yang berguna untuk menggambar, seperti LinearGradient, RadialGradient, atau kuas SolidColor biasa.

Kuas dapat digunakan dengan panggilan gambar Modifier.background(), TextStyle, atau DrawScope untuk menerapkan gaya gambar ke konten yang digambar.

Misalnya, kuas gradien horizontal dapat diterapkan untuk menggambar lingkaran di DrawScope:

val brush = Brush.horizontalGradient(listOf(Color.Red, Color.Blue))
Canvas(
    modifier = Modifier.size(200.dp),
    onDraw = {
        drawCircle(brush)
    }
)

Lingkaran yang digambar dengan Gradien Horizontal
Gambar 1: Lingkaran yang digambar dengan Gradien Horizontal

Kuas gradien

Ada banyak kuas gradien bawaan yang dapat digunakan untuk mendapatkan efek gradien yang berbeda. Kuas ini memungkinkan Anda menentukan daftar warna yang ingin Anda buatkan gradiennya.

Daftar kuas gradien yang tersedia dan outputnya yang sesuai:

Jenis Kuas Gradien Output
Brush.horizontalGradient(colorList) Gradien Horizontal
Brush.linearGradient(colorList) Gradien Linear
Brush.verticalGradient(colorList) Gradien Vertikal
Brush.sweepGradient(colorList)
Catatan: Untuk mendapatkan transisi yang lancar antar-warna, tetapkan warna terakhir ke warna awal.
Gradien Sapu
Brush.radialGradient(colorList) Gradien Radial

Mengubah distribusi warna dengan colorStops

Untuk menyesuaikan tampilan warna dalam gradien, Anda dapat menyesuaikan nilai colorStops untuk setiap warna. colorStops harus ditentukan sebagai pecahan, antara 0 dan 1. Nilai yang lebih besar dari 1 akan menyebabkan warna tersebut tidak dirender sebagai bagian dari gradien.

Anda dapat mengonfigurasi perhentian warna agar memiliki jumlah yang berbeda, seperti lebih sedikit atau lebih banyak dari warna tertentu:

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

Warna tersebar pada offset yang diberikan seperti yang ditentukan dalam pasangan colorStop, agak lebih kuning daripada merah dan biru.

Kuas dikonfigurasi dengan berbagai perhentian warna
Gambar 2: Kuas dikonfigurasi dengan berbagai perhentian warna

Mengulang pola dengan TileMode

Setiap kuas gradien memiliki opsi untuk menyetel TileMode di atasnya. Anda mungkin tidak melihat TileMode jika belum menyetel awal dan akhir gradien, karena defaultnya adalah untuk mengisi seluruh area. TileMode hanya akan membuat kotak gradien jika ukuran area lebih besar dari ukuran Kuas.

Kode berikut akan mengulangi pola gradien 4 kali, karena endX disetel ke 50.dp dan ukurannya disetel ke 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
            )
        )
)

Berikut adalah tabel yang menjelaskan fungsi Mode Kotak yang berbeda untuk contoh HorizontalGradient di atas:

TileMode Output
TileMode.Repeated: Tepi diulang dari warna terakhir ke warna pertama. TileMode Repeated
TileMode.Mirror: Tepi dicerminkan dari warna terakhir ke pertama. TileMode Mirror
TileMode.Clamp: Tepi dibatasi ke warna akhir. Langkah ini akan menampilkan warna terdekat untuk wilayah lainnya. Tile Mode Clamp
TileMode.Decal: Hanya merender hingga ukuran batas. TileMode.Decal memanfaatkan warna hitam transparan untuk mengambil sampel konten di luar batas asli sedangkan TileMode.Clamp mengambil sampel warna tepi. Tile Mode Decal

TileMode berfungsi dengan cara yang sama untuk gradien arah lainnya, yang berbeda adalah arah pengulangannya.

Ubah Ukuran kuas

Jika mengetahui ukuran area tempat kuas akan digambar, Anda dapat menyetel kotak endX seperti yang telah kita lihat di atas, di bagian TileMode. Jika berada dalam DrawScope, Anda dapat menggunakan properti size untuk mendapatkan ukuran area tersebut.

Jika tidak tahu ukuran area gambar (misalnya, jika Brush ditetapkan ke Teks), Anda dapat memperluas Shader dan memanfaatkan ukuran area gambar di fungsi createShader.

Dalam contoh ini, bagi ukuran dengan 4 untuk mengulangi pola 4 kali:

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

Ukuran shader dibagi 4
Gambar 3: Ukuran shader dibagi 4

Anda juga dapat mengubah ukuran kuas gradien lainnya, seperti gradien radial. Jika Anda tidak menentukan ukuran dan pusat, gradien akan menempati batas penuh DrawScope, dan pusat gradien radial akan disetel default ke pusat batas DrawScope. Hal ini menyebabkan pusat gradien radial muncul sebagai pusat dimensi yang lebih kecil (lebar atau tinggi):

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

Setelan Gradien Radial tanpa perubahan ukuran
Gambar 4: Setelan Gradien Radial tanpa perubahan ukuran

Ketika gradien radial diubah untuk menetapkan ukuran radius ke dimensi maksimum, Anda dapat melihat bahwa gradien menghasilkan efek gradien radial yang lebih baik:

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

Radius yang lebih besar pada gradien radial, berdasarkan ukuran area
Gambar 5: Radius yang lebih besar pada gradien radial, berdasarkan ukuran area

Perlu diperhatikan bahwa ukuran sebenarnya yang diteruskan ke pembuatan shader ditentukan dari tempat pemanggilannya. Secara default, Brush akan mengalokasikan ulang Shader secara internal jika ukurannya berbeda dengan pembuatan Brush terakhir, atau jika objek status yang digunakan dalam pembuatan shader telah berubah.

Kode berikut membuat shader dengan tiga waktu yang berbeda dengan ukuran yang berbeda, karena ukuran area gambar berubah:

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

Menggunakan gambar sebagai kuas

Untuk menggunakan ImageBitmap sebagai Brush, muat gambar sebagai ImageBitmap, lalu buat kuas 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))

Kuas diterapkan ke beberapa jenis gambar: latar belakang, Teks, dan Kanvas. Tindakan ini menghasilkan hal berikut:

Kuas ImageShader digunakan dengan cara yang berbeda
Gambar 6: Menggunakan Kuas ImageShader untuk menggambar latar belakang, menggambar Teks, dan menggambar Lingkaran

Perhatikan bahwa teks kini juga dirender menggunakan ImageBitmap guna menggambar piksel untuk teks.

Contoh lanjutan: Kuas kustom

Kuas RuntimeShader AGSL

AGSL menawarkan subset kemampuan Shader GLSL. Shader dapat ditulis dalam AGSL dan digunakan dengan Kuas di Compose.

Untuk membuat kuas Shader, tentukan terlebih dahulu string 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 di atas mengambil dua warna input, menghitung jarak dari kiri bawah (vec2(0, 1)) area gambar dan melakukan mix di antara dua warna berdasarkan jaraknya. Tindakan ini akan menghasilkan efek gradien.

Kemudian, buat Kuas Shader, dan setel seragam untuk resolution - ukuran area gambar, serta color dan color2 yang ingin Anda gunakan sebagai input untuk gradien kustom:

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

Dengan menjalankan ini, Anda dapat melihat hal berikut dirender di layar:

Shader AGSL kustom yang berjalan di Compose
Gambar 7: Shader AGSL kustom yang berjalan di Compose

Perlu diperhatikan bahwa Anda dapat melakukan lebih banyak hal dengan shader, bukan hanya gradien, karena semuanya merupakan penghitungan berbasis matematika. Untuk informasi selengkapnya tentang AGSL, lihat dokumentasi AGSL.

Referensi tambahan

Untuk contoh selengkapnya tentang penggunaan Kuas di Compose, lihat referensi berikut: