Jetpack Compose【五】 高級(jí)布局與繪制技巧

前言

在 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 中,RowColumnBox 可能無法滿足所有布局需求。此時(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 效果。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容