筆刷:漸層和著色器

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 會 如果大小與上一個不同,則在內部重新分配其 Shader 建立 Brush,或建立著色器時使用的狀態物件 已變更。

下列程式碼會建立三種著色器,且不同的 此時,會隨著繪圖區域大小改變:

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 Brush 繪製背景、繪製文字和繪製圓形

請注意,系統現在也會使用 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 中使用筆刷的範例,請參閱下列資源: