7、按钮与交互:Button、IconButton、TextButton、OutlinedButton 的使用、点击事件处理

按钮,是任何 UI 系统里最基础、也最关键的交互元素。在 Compose 里,Google 提供了几种开箱即用的按钮组件:ButtonIconButtonTextButtonOutlinedButton。说实话,刚开始接触时我也觉得「不就一个按钮嘛,能有多大区别?」—— 但实际项目做多了,你会发现每种按钮都有自己的「脾气」和适用场景。

核心要点:Compose 的按钮本质上是 Surface + Clickable 的封装。理解这一点,你就能灵活定制任何样式的按钮。

Compose 按钮家族 按钮交互体系 Button 填充背景,高强调 IconButton 纯图标,紧凑型 TextButton 无边框,低强调 OutlinedButton 有边框,中等强调 点击事件统一通过 onClick 参数处理

7.1 Button:最常用的填充按钮

Button 是默认的「主角按钮」。它带有背景色填充,视觉权重最高。我个人习惯在「提交」、「保存」、「确认」这类主要操作上使用它。

@Composable
fun PrimaryButtonExample() {
    Button(
        onClick = { /* 处理点击事件 */ },
        modifier = Modifier.fillMaxWidth().padding(horizontal = 16.dp),
        enabled = true,
        colors = ButtonDefaults.buttonColors(
            containerColor = MaterialTheme.colorScheme.primary,
            contentColor = MaterialTheme.colorScheme.onPrimary
        )
    ) {
        Text("提交订单")
    }
}

小技巧:如果你需要按钮在加载时禁用,可以用 enabled = !isLoading。我在电商项目里就是这么做的,防止用户重复提交订单。

7.2 IconButton:图标按钮的妙用

IconButton 只包含一个图标,没有文字。它非常适合放在 TopAppBar、列表项尾部等紧凑空间。你想想看,如果每个操作按钮都带文字,界面得多拥挤?

@Composable
fun IconButtonExample() {
    IconButton(
        onClick = { /* 收藏/取消收藏 */ },
        modifier = Modifier.size(48.dp)
    ) {
        Icon(
            imageVector = Icons.Default.FavoriteBorder,
            contentDescription = "收藏",
            tint = MaterialTheme.colorScheme.primary
        )
    }
}

注意:contentDescription 一定要填!无障碍测试会检查这个。我曾经因为漏写被 QA 打回,后来养成了习惯,每个 Icon 都配上描述。

7.3 TextButton:低调的文字按钮

TextButton 没有边框,没有背景色,只有文字本身。它适合「取消」、「跳过」、「了解更多」这类次要操作。说白了,就是给用户一个「退路」。

@Composable
fun TextButtonExample() {
    TextButton(
        onClick = { /* 跳过当前步骤 */ },
        modifier = Modifier.padding(8.dp)
    ) {
        Text("跳过", style = MaterialTheme.typography.labelLarge)
    }
}

嗯,这里要注意:TextButton 的点击区域其实不小,默认有 48dp 的最小高度。所以别担心用户点不到。

7.4 OutlinedButton:带边框的按钮

OutlinedButton 介于 ButtonTextButton 之间。它有边框,但没有填充背景。适合「注册」、「登录」这类并列操作——比如登录页里,「登录」用 Button,「注册」用 OutlinedButton

@Composable
fun OutlinedButtonExample() {
    OutlinedButton(
        onClick = { /* 跳转到注册页 */ },
        border = BorderStroke(1.dp, MaterialTheme.colorScheme.outline),
        modifier = Modifier.fillMaxWidth()
    ) {
        Text("注册新账号")
    }
}

对比总结:

按钮类型 视觉权重 典型场景
Button 主要操作(提交、保存)
IconButton 图标操作(收藏、删除)
TextButton 次要操作(取消、跳过)
OutlinedButton 并列操作(注册、详情)

7.5 点击事件处理:不止是 onClick

所有按钮都通过 onClick 参数处理点击。但实际项目中,我们往往需要更精细的控制。比如:防抖、长按、双击。

// 防抖点击:防止重复提交
var lastClickTime by remember { mutableStateOf(0L) }

Button(
    onClick = {
        val now = System.currentTimeMillis()
        if (now - lastClickTime > 1000) { // 1秒内只响应一次
            lastClickTime = now
            // 执行实际逻辑
        }
    }
) {
    Text("提交")
}

我的经验:在金融类 App 里,防抖是必须的。我曾经遇到用户快速点击两次「转账」,结果发了两次请求。后来我在基类里统一封装了防抖逻辑,所有按钮都继承这个行为。

7.6 自定义按钮样式

Compose 的按钮高度可定制。你可以通过 ButtonDefaults 调整颜色、形状、阴影。甚至可以用 Surface + clickable 完全自定义。

Button(
    onClick = { /* 自定义样式按钮 */ },
    shape = RoundedCornerShape(12.dp),
    elevation = ButtonDefaults.buttonElevation(
        defaultElevation = 4.dp,
        pressedElevation = 8.dp
    ),
    colors = ButtonDefaults.buttonColors(
        containerColor = Color(0xFFFF6B35),
        contentColor = Color.White
    )
) {
    Icon(Icons.Default.ShoppingCart, contentDescription = null)
    Spacer(modifier = Modifier.width(8.dp))
    Text("加入购物车")
}

避坑指南:自定义 elevation 时,记得设置 pressedElevation。我曾经只设了 defaultElevation,结果按下时阴影消失,交互反馈很生硬。

7.7 实战:一个完整的按钮交互示例

最后,我们把这些知识点串起来。下面是一个登录页面的按钮组合:

@Composable
fun LoginButtonGroup(
    onLogin: () -> Unit,
    onRegister: () -> Unit,
    onForgotPassword: () -> Unit,
    isLoading: Boolean
) {
    Column(
        modifier = Modifier.fillMaxWidth().padding(16.dp),
        verticalArrangement = Arrangement.spacedBy(12.dp)
    ) {
        // 主要操作:登录
        Button(
            onClick = onLogin,
            enabled = !isLoading,
            modifier = Modifier.fillMaxWidth()
        ) {
            if (isLoading) {
                CircularProgressIndicator(
                    modifier = Modifier.size(20.dp),
                    strokeWidth = 2.dp,
                    color = MaterialTheme.colorScheme.onPrimary
                )
                Spacer(modifier = Modifier.width(8.dp))
            }
            Text(if (isLoading) "登录中..." else "登录")
        }

        // 次要操作:注册
        OutlinedButton(
            onClick = onRegister,
            modifier = Modifier.fillMaxWidth()
        ) {
            Text("注册新账号")
        }

        // 低强调操作:忘记密码
        TextButton(
            onClick = onForgotPassword,
            modifier = Modifier.align(Alignment.CenterHorizontally)
        ) {
            Text("忘记密码?")
        }
    }
}

你看,三种按钮放在一起,视觉层次非常清晰。用户一眼就能看出「登录」是主要操作,「注册」是次要选择,「忘记密码」是辅助功能。这就是 Material Design 的按钮设计哲学。

核心总结:

  • Button:填充背景,高强调,用于主要操作
  • IconButton:纯图标,紧凑,用于工具栏或列表
  • TextButton:无边框,低强调,用于次要或辅助操作
  • OutlinedButton:有边框,中等强调,用于并列操作
  • 所有按钮通过 onClick 处理点击,注意防抖和加载状态
  • 通过 ButtonDefaultsshape 可以高度自定义样式

嗯,按钮这部分其实不难,但细节很多。我建议你在项目里多试试不同组合,找到最适合自己产品的交互节奏。

公众号:蓝海资料掘金营,微信deep3321