如何测量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
相关产品推荐
相关产品推荐

