You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何测量Composable尺寸?解决自定义按钮切换加载状态宽度收缩问题

实现方案

宽度收缩问题本质是两种状态下按钮内容的测量宽度不一致导致的,以下是两种可行的实现方案:

方案1:占位布局法(绝大多数场景推荐)

核心逻辑是让正常状态的文本始终参与布局测量,仅通过透明度控制可见性,加载指示器叠加在文本上层,天然保证按钮宽度永远匹配文本宽度,无需额外测量逻辑。
实现代码如下:

import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.height
import androidx.compose.material3.Button
import androidx.compose.material3.CircularProgressIndicator
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.alpha
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp

enum class State { NORMAL, LOADING }

@Composable
fun MyButton(onClick: () -> Unit, text: String, state: State) {
    Button(
        onClick = onClick,
        modifier = Modifier.height(60.dp),
        // 可选:加载状态下禁止按钮点击
        enabled = state == State.NORMAL
    ) {
        Box(contentAlignment = Alignment.Center) {
            // 文本始终参与布局,仅控制可见性
            Text(
                text = text,
                fontSize = 32.sp,
                modifier = Modifier.alpha(if (state == State.NORMAL) 1f else 0f)
            )
            if (state == State.LOADING) {
                CircularProgressIndicator(color = Color.Yellow)
            }
        }
    }
}

方案2:动态测量存储宽度法

如果按钮内容存在动态变化的复杂元素,需要真实测量正常状态下的布局宽度,可以通过监听尺寸变化存储正常宽度,加载状态下复用该宽度作为最小宽度约束。
实现代码如下:

import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.widthIn
import androidx.compose.material3.Button
import androidx.compose.material3.CircularProgressIndicator
import androidx.compose.material3.Text
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.platform.LocalDensity
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp

enum class State { NORMAL, LOADING }

@Composable
fun MyButton(onClick: () -> Unit, text: String, state: State) {
    var normalWidth by remember { mutableStateOf<Dp?>(null) }
    val density = LocalDensity.current

    Button(
        onClick = onClick,
        modifier = Modifier
            .height(60.dp)
            // 存在存储的正常宽度时,强制最小宽度不小于该值
            .let { modifier ->
                normalWidth?.let { width -> modifier.widthIn(min = width) } ?: modifier
            }
            .onSizeChanged { size ->
                // 仅在正常状态下更新存储的宽度,避免加载状态的窄宽度覆盖
                if (state == State.NORMAL) {
                    normalWidth = with(density) { size.width.toDp() }
                }
            },
        enabled = state == State.NORMAL
    ) {
        if (state == State.NORMAL) {
            Text(text, fontSize = 32.sp)
        } else {
            CircularProgressIndicator(color = Color.Yellow)
        }
    }
}

适用场景说明

  • 方案1实现成本最低,无状态切换闪烁,即使初始状态为LOADING也能保持正确宽度,适合绝大多数业务场景
  • 方案2适合按钮内容包含多种动态组合元素,无法简单用占位布局实现的场景,注意如果初始状态为LOADING,第一次切换到NORMAL前会出现宽度收缩问题

内容的提问来源于stack exchange,提问作者netimen

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.10.14 01:00:02