筆刷:漸層和著色器

Compose 中的 Brush 說明瞭如何在螢幕上繪圖:判斷在繪圖區域 (即圓形、正方形、路徑) 所繪製的顏色。提供了幾個內建的繪圖專用筆刷,如 LinearGradientRadialGradient 或純色 SolidColor 筆刷。

您可以使用筆刷搭配 Modifier.background()TextStyleDrawScope 繪製呼叫,對繪製的內容套用繪製風格。

舉例來說,您可以在 DrawScope 中套用水平漸層筆刷來繪製圓形:

val brush = Brush.horizontalGradient(listOf(Color.Red, Color.Blue))
Canvas(
    modifier = Modifier.size(200.dp),
    onDraw = {
        drawCircle(brush)
    }
)
以水平漸層繪製的圓形
圖 1:以水平漸層繪製圓形

漸層筆刷

提供許多內建的漸層筆刷,可用於實現不同的漸層效果。這些筆刷可讓您指定想要在其中建立漸層的顏色清單。

可用的漸層筆刷清單及其相應輸出:

漸層筆刷類型 輸出
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 配對中定義的補償值分散,如黃色比紅色和藍色少。

設定不同顏色停止點的筆刷
圖 2:設定不同顏色停止點的筆刷

使用 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 重複
TileMode.Mirror:邊緣從最後一種顏色鏡像到第一種顏色。 TileMode 鏡像
TileMode.Clamp:邊緣被限制為最終顏色。然後對其餘區域繪製最接近的顏色。 圖塊模式限制
TileMode.Decal:僅在邊界大小內呈現。TileMode.Decal 善用透明黑色,對原始邊界外的內容進行取樣,而 TileMode.Clamp 則是運用邊緣色彩進行取樣。 圖塊模式貼紙

TileMode 的運作方式類似於其他定向漸層,在重複方向出現色差。

變更筆刷大小

如果您知道畫刷所繪製的區域大小,可以如以上 TileMode 章節所述設定圖塊 endX。如果採用 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)
)

著色器大小除以 4
圖 3:著色器大小除以 4

此外,您還可以變更任何其他漸層的筆刷大小,例如放射漸層。如果未指定大小和中心點,漸層會佔滿 DrawScope 的完整邊界,且放射漸層的中心會預設為 DrawScope 邊界的中心。這樣會使放射漸層的中心以較小維度 (寬度或高度) 顯示:

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

沒有大小變更的放射漸層組合
圖 4:沒有大小變更的放射漸層組合

在變更放射漸層時,如果將半徑大小設定為最大維度,可以看到其放射漸層有更好的效果:

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

視乎區域大小,放射漸層的放射半徑更大
圖 5:視乎區域大小,放射漸層的放射半徑更大

值得注意的是,傳遞至建立著色器的實際大小取決於叫用的位置。根據預設,如果大小與上次建立 Brush 時不同,或者建立著色器時使用的狀態物件已變更,則 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))

筆刷適用於多種繪製類型:背景、文字和畫布。這會輸出下列內容:

ImageShader 筆刷以不同方式使用
圖 6:使用 ImageShader 筆刷繪製背景、繪製文字和繪製圓形

請注意,現在還可以使用 ImageBitmap 轉譯文字,為文字繪製像素。

進階範例:自訂筆刷

AGSL RuntimeShader 筆刷

AGSL 提供部分 GLSL 著色器功能。可使用 AGSL 來編寫著色器,並在 Compose 中與筆刷搭配使用。

如要建立著色器筆刷,首先將著色器定義為 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()

上方的著色器使用兩種輸入顏色,計算繪製區域左下方 (vec2(0, 1)) 的距離,並根據距離在兩種顏色之間執行 mix。這會產生漸層效果。

然後,建立著色器筆刷,並設定 resolution 的樣式 - 繪製區域的大小,以及您要用作自訂輸入的 colorcolor2漸層:

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

執行完畢後,您可以看到下列顯示的內容:

在 Compose 中執行自訂 AGSL 著色器
圖 7:在 Compose 中自訂 AGSL 著色器執行

值得注意的是,除了漸層之外,著色器還可以做更多的事,因為所有這些採用的是數學運算。如要進一步瞭解 AGSL,請參閱 AGSL 說明文件

其他資源

如需更多在 Compose 中使用筆刷的範例,請參閱下列資源: