- Jetpack Compose 【一】入門:擁抱現(xiàn)代 Android UI 開發(fā)
- Jetpack Compose 【二】狀態(tài)管理詳解
- Jetpack Compose 【三】附帶效應(yīng)、協(xié)程與異步
- Jetpack Compose 【四】動(dòng)畫
- Jetpack Compose【五】 高級(jí)布局與繪制技巧
- Jetpack Compose【六】終極:聲明式 UI 如何重塑開發(fā)者的思維
前言
在 Jetpack Compose 中,靈活的 UI 構(gòu)建能力允許開發(fā)者以直觀、聲明式的方式創(chuàng)建界面。不僅如此,Compose 還提供了多個(gè)強(qiáng)大的 API 以支持自定義繪制和布局。本文將通過幾個(gè)示例,展示如何在 Compose 中實(shí)現(xiàn)常見的自定義繪制與布局需求。
一、 使用 Canvas 自定義繪制
Canvas 是 Compose 中提供的低層次繪圖 API,類似于傳統(tǒng)的 onDraw() 方法。通過 drawRect()、drawCircle()、drawPath() 等方法,你可以繪制各種圖形,滿足自定義需求。
示例:繪制一個(gè)自定義圓形進(jìn)度條
@Composable
fun CoolProgressBar(
progress: Float, // 進(jìn)度 0f - 1f
modifier: Modifier = Modifier,
strokeWidth: Float = 20f, // 進(jìn)度條寬度
startColor: Color = Color(0xFFFF5722), // 漸變起始色
endColor: Color = Color(0xFF2196F3) // 漸變結(jié)束色
) {
Canvas(modifier = modifier) {
val size = size.minDimension
val radius = size / 2f
val center = Offset(size / 2f, size / 2f)
// 繪制背景圓環(huán)
drawCircle(
color = Color.LightGray.copy(alpha = 0.3f),
radius = radius - strokeWidth / 2,
style = Stroke(width = strokeWidth, cap = StrokeCap.Round)
)
// 計(jì)算漸變顏色
val brush = Brush.linearGradient(
colors = listOf(startColor, endColor),
start = Offset(0f, 0f),
end = Offset(size, size)
)
// 計(jì)算進(jìn)度角度
val sweepAngle = progress * 360f
// 繪制進(jìn)度條
drawArc(
brush = brush,
startAngle = -90f,
sweepAngle = sweepAngle,
useCenter = false,
style = Stroke(width = strokeWidth, cap = StrokeCap.Round)
)
}
}
@Composable
fun CoolProgressBarDemo() {
var progress by remember { mutableFloatStateOf(0.3f) }
// 進(jìn)度平滑動(dòng)畫
val animatedProgress by animateFloatAsState(
targetValue = progress,
animationSpec = tween(durationMillis = 1000, easing = FastOutSlowInEasing), label = ""
)
Column(
modifier = Modifier.fillMaxSize(),
verticalArrangement = Arrangement.Center,
horizontalAlignment = Alignment.CenterHorizontally
) {
CoolProgressBar(
progress = animatedProgress,
modifier = Modifier
.size(150.dp)
.clip(CircleShape)
)
Spacer(modifier = Modifier.height(20.dp))
Button(onClick = { progress = Random.nextFloat().coerceIn(0.1f, 1f)}) {
Text("隨機(jī)進(jìn)度")
通過 Canvas,我們可以繪制一個(gè)帶有背景圓環(huán)和動(dòng)態(tài)進(jìn)度的圓形進(jìn)度條。drawArc() 讓進(jìn)度條根據(jù)傳入的 progress 繪制。
二、 使用 Layout 自定義布局
Compose 中,Row、Column 和 Box 可能無法滿足所有布局需求。此時(shí),可以使用 Layout API 創(chuàng)建復(fù)雜的自定義布局。Layout 像是傳統(tǒng)View中onMeasure()+onLayout()的結(jié)合。
示例:實(shí)現(xiàn)一個(gè)流式布局
@Composable
fun FlowLayout(
modifier: Modifier = Modifier,
maxWidth: Dp = 300.dp, // 限制整體寬度
content: @Composable () -> Unit
) {
Layout(
content = content,
modifier = modifier
) { measurables, constraints ->
// 將 maxWidth 轉(zhuǎn)換為 Px,并與父布局寬度取最小值,確保不超出
val layoutMaxWidth = minOf(maxWidth.roundToPx(), constraints.maxWidth)
// 測(cè)量所有子項(xiàng)
val placeables = measurables.map { it.measure(constraints.copy(maxWidth = layoutMaxWidth)) }
var currentX = 0
var currentY = 0
var maxHeightInRow = 0
layout(layoutMaxWidth, constraints.maxHeight) {
placeables.forEach { placeable ->
// 超出 maxWidth 時(shí)換行
if (currentX + placeable.width > layoutMaxWidth) {
currentX = 0
currentY += maxHeightInRow
maxHeightInRow = 0
}
placeable.placeRelative(currentX, currentY)
currentX += placeable.width
maxHeightInRow = maxOf(maxHeightInRow, placeable.height)
}
}
}
}
@Composable
fun TestFlowLayout() {
FlowLayout(maxWidth = 320.dp) {
FlowText("MyOwnColumn")
FlowText("places items")
FlowText("vertically.")
FlowText("We've done it by hand!")
FlowText("final")
}
}
@Composable
fun FlowText(text: String) {
Text(
text = text,
modifier = Modifier
.padding(8.dp)
.border(1.dp, Color.Gray)
.padding(8.dp)
)
}
通過 Layout API,可以控制每個(gè)元素的尺寸和位置。這里的布局會(huì)將每個(gè)標(biāo)簽垂直排列,形成一個(gè)簡(jiǎn)單的標(biāo)簽列表。
三、 使用 Modifier.drawWithContent 定制繪制
如果你需要在現(xiàn)有組件上添加額外的繪制效果(如邊框或漸變效果),可以使用 Modifier.drawWithContent。
示例:為文本添加下劃線
@Composable
fun UnderlinedText(text: String) {
Text(
text = text,
modifier = Modifier.drawWithContent {
drawContent() // 先繪制內(nèi)容
drawLine(
color = Color.Red,
start = Offset(0f, size.height),
end = Offset(size.width, size.height),
strokeWidth = 2f
)
}
)
}
@Preview
@Composable
fun UnderlinedTextPreview() {
UnderlinedText("帶下劃線的文本")
}
drawWithContent 用來在繪制文本的基礎(chǔ)上,為其添加一條紅色的下劃線。
四、 使用 AndroidView 復(fù)用傳統(tǒng) View
通過 AndroidView,你可以將傳統(tǒng)的 View 嵌入 Compose 布局中,復(fù)用 XML 布局中已有的自定義視圖。
示例:在 Compose 中嵌入 傳統(tǒng)View
@Composable
fun WebViewComponent(url: String) {
AndroidView(
modifier = Modifier.fillMaxSize(),
factory = { context ->
//WebView、SurfaceView、其它自定義View
}
)
}
五、總結(jié)
| 自定義方式 | 適用場(chǎng)景 | 核心 API |
|---|---|---|
| Canvas | 自定義圖形、控件 |
drawCircle()、drawPath()
|
| Layout | 自定義布局 |
Layout、MeasureScope
|
| drawWithContent | 疊加效果 |
drawLine()、drawRect()
|
| AndroidView | 復(fù)用傳統(tǒng) View | AndroidView |
Compose 提供了靈活的自定義擴(kuò)展能力,可以滿足大多數(shù) UI 設(shè)計(jì)需求。根據(jù)實(shí)際情況選擇合適的自定義方式,能夠幫助你輕松實(shí)現(xiàn)多種復(fù)雜的 UI 效果。