RadioButton(单选按钮)&Checkbox(复选框)

RadioButtonCheckBox 是 Android 开发中常用的交互式 View 组件,用于让用户从一组选项中进行选择。RadioButton 用于单选场景(互斥选择),而 CheckBox 用于多选场景。两者都继承自 CompoundButton,支持简单的状态管理。本教程基于 Android Studio(截至 2025 年 9 月,Koala 2024.1.1),详细讲解 RadioButton 和 CheckBox 的概念、属性、使用方法、示例代码和最佳实践,结合 XML 和 Jetpack Compose,适合初学者和需要深入理解的开发者。


1. RadioButton 概念

  • 定义RadioButton 是一个单选按钮,通常与 RadioGroup 配合使用,确保一组选项中只有一个被选中。
  • 作用
  • 提供互斥选择(如选择性别、支付方式)。
  • 显示选中/未选中状态,支持文本标签。
  • android.widget.RadioButton
  • 特点
  • 必须放在 RadioGroup 中以实现互斥逻辑。
  • 支持自定义样式和点击监听。
  • 局限
  • 单选逻辑依赖 RadioGroup,单独使用需手动管理。
  • 不适合多选场景(需用 CheckBox)。

RadioButton 核心属性

属性描述示例
android:text按钮旁文本android:text="@string/male"
android:checked默认选中状态android:checked="true"
android:id唯一标识android:id="@+id/radioMale"
android:contentDescription无障碍描述android:contentDescription="@string/male_desc"
android:buttonTint按钮颜色android:buttonTint="@color/purple_500"

RadioGroup 核心属性

属性描述示例
android:orientation排列方向(horizontal/verticalandroid:orientation="vertical"
android:checkedButton默认选中的 RadioButton IDandroid:checkedButton="@id/radioMale"

2. CheckBox 概念

  • 定义CheckBox 是一个复选框,允许用户选择多个选项,独立管理选中状态。
  • 作用
  • 提供多选功能(如兴趣选择、任务清单)。
  • 显示选中/未选中状态,支持文本标签。
  • android.widget.CheckBox
  • 特点
  • 不需要容器(如 RadioGroup),独立操作。
  • 支持自定义样式和状态监听。
  • 局限
  • 不适合互斥选择(需用 RadioButton)。

CheckBox 核心属性

属性描述示例
android:text复选框旁文本android:text="@string/hobby_read"
android:checked默认选中状态android:checked="true"
android:id唯一标识android:id="@+id/checkRead"
android:contentDescription无障碍描述android:contentDescription="@string/read_desc"
android:buttonTint复选框颜色android:buttonTint="@color/purple_500"

3. RadioButton 使用

RadioButton 通常与 RadioGroup 配合使用,以下展示 XML 和代码方式。

3.1 XML 布局中使用

以下是一个性别选择界面,使用 RadioButton 和 RadioGroup。

<TextView
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:text="@string/gender_label"
    android:textSize="18sp" />

<!-- RadioGroup -->
<RadioGroup
    android:id="@+id/genderGroup"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:orientation="vertical"
    android:checkedButton="@id/radioMale">

    <RadioButton
        android:id="@+id/radioMale"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="@string/male"
        android:textSize="16sp"
        android:buttonTint="@color/purple_500"
        android:contentDescription="@string/male_desc" />

    <RadioButton
        android:id="@+id/radioFemale"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="@string/female"
        android:textSize="16sp"
        android:buttonTint="@color/purple_500"
        android:contentDescription="@string/female_desc" />

</RadioGroup>

<!-- Submit Button -->
<Button
    android:id="@+id/submitButton"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:text="@string/submit"
    android:layout_marginTop="16dp" />
  • 资源文件res/values/strings.xml):
    RadioButton App Select Gender Male Male option Female Female option Submit
  • 资源文件res/values/colors.xml):
    #6200EE
  • ActivityMainActivity.kt):

    package com.example.myapp

import android.os.Bundle
import android.widget.Button
import android.widget.RadioGroup
import android.widget.Toast
import androidx.appcompat.app.AppCompatActivity

class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)

    val genderGroup: RadioGroup = findViewById(R.id.genderGroup)
    val submitButton: Button = findViewById(R.id.submitButton)

    submitButton.setOnClickListener {
        val selectedId = genderGroup.checkedRadioButtonId
        val gender = when (selectedId) {
            R.id.radioMale -> "Male"
            R.id.radioFemale -> "Female"
            else -> "None"
        }
        Toast.makeText(this, "Selected: $gender", Toast.LENGTH_SHORT).show()
    }
}

}

  • 效果
  • RadioGroup 包含“Male”和“Female”选项,默认选中“Male”。
  • 提交按钮显示选中的性别。

3.2 代码中使用

动态创建 RadioButton 和 RadioGroup:

package com.example.myapp

import android.os.Bundle
import android.widget.Button
import android.widget.LinearLayout
import android.widget.RadioButton
import android.widget.RadioGroup
import android.widget.TextView
import android.widget.Toast
import androidx.appcompat.app.AppCompatActivity
import androidx.core.content.ContextCompat

class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)

    // 创建 LinearLayout
    val layout = LinearLayout(this).apply {
        orientation = LinearLayout.VERTICAL
        layoutParams = LinearLayout.LayoutParams(
            LinearLayout.LayoutParams.MATCH_PARENT,
            LinearLayout.LayoutParams.MATCH_PARENT
        )
        setPadding(16, 16, 16, 16)
    }

    // 创建 Label
    val label = TextView(this).apply {
        text = getString(R.string.gender_label)
        textSize = 18f
    }

    // 创建 RadioGroup
    val radioGroup = RadioGroup(this).apply {
        id = R.id.genderGroup
        orientation = RadioGroup.VERTICAL
        layoutParams = LinearLayout.LayoutParams(
            LinearLayout.LayoutParams.MATCH_PARENT,
            LinearLayout.LayoutParams.WRAP_CONTENT
        )
    }

    // 创建 RadioButton
    val radioMale = RadioButton(this).apply {
        id = R.id.radioMale
        text = getString(R.string.male)
        textSize = 16f
        buttonTintList = ContextCompat.getColorStateList(context, R.color.purple_500)
        contentDescription = getString(R.string.male_desc)
        isChecked = true
    }

    val radioFemale = RadioButton(this).apply {
        id = R.id.radioFemale
        text = getString(R.string.female)
        textSize = 16f
        buttonTintList = ContextCompat.getColorStateList(context, R.color.purple_500)
        contentDescription = getString(R.string.female_desc)
    }

    // 创建 Submit Button
    val submitButton = Button(this).apply {
        id = R.id.submitButton
        text = getString(R.string.submit)
        layoutParams = LinearLayout.LayoutParams(
            LinearLayout.LayoutParams.WRAP_CONTENT,
            LinearLayout.LayoutParams.WRAP_CONTENT
        ).apply { topMargin = 16 }
        setOnClickListener {
            val selectedId = radioGroup.checkedRadioButtonId
            val gender = when (selectedId) {
                R.id.radioMale -> "Male"
                R.id.radioFemale -> "Female"
                else -> "None"
            }
            Toast.makeText(this@MainActivity, "Selected: $gender", Toast.LENGTH_SHORT).show()
        }
    }

    // 组装布局
    radioGroup.addView(radioMale)
    radioGroup.addView(radioFemale)
    layout.addView(label)
    layout.addView(radioGroup)
    layout.addView(submitButton)

    // 设置布局
    setContentView(layout)
}

}


4. CheckBox 使用

CheckBox 可独立使用,无需容器。

4.1 XML 布局中使用

以下是一个兴趣选择界面,使用 CheckBox。

<TextView
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:text="@string/hobbies_label"
    android:textSize="18sp" />

<!-- CheckBoxes -->
<CheckBox
    android:id="@+id/checkRead"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:text="@string/read"
    android:textSize="16sp"
    android:buttonTint="@color/purple_500"
    android:contentDescription="@string/read_desc" />

<CheckBox
    android:id="@+id/checkTravel"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:text="@string/travel"
    android:textSize="16sp"
    android:buttonTint="@color/purple_500"
    android:contentDescription="@string/travel_desc" />

<CheckBox
    android:id="@+id/checkMusic"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:text="@string/music"
    android:textSize="16sp"
    android:buttonTint="@color/purple_500"
    android:contentDescription="@string/music_desc" />

<!-- Submit Button -->
<Button
    android:id="@+id/submitButton"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:text="@string/submit"
    android:layout_marginTop="16dp" />
  • 资源文件res/values/strings.xml):
    CheckBox App Select Hobbies Reading Reading hobby Traveling Traveling hobby Music Music hobby Submit
  • ActivityHobbiesActivity.kt):

    package com.example.myapp

import android.os.Bundle
import android.widget.Button
import android.widget.CheckBox
import android.widget.Toast
import androidx.appcompat.app.AppCompatActivity

class HobbiesActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_hobbies)

    val checkRead: CheckBox = findViewById(R.id.checkRead)
    val checkTravel: CheckBox = findViewById(R.id.checkTravel)
    val checkMusic: CheckBox = findViewById(R.id.checkMusic)
    val submitButton: Button = findViewById(R.id.submitButton)

    submitButton.setOnClickListener {
        val hobbies = mutableListOf<String>()
        if (checkRead.isChecked) hobbies.add("Reading")
        if (checkTravel.isChecked) hobbies.add("Traveling")
        if (checkMusic.isChecked) hobbies.add("Music")
        val result = if (hobbies.isEmpty()) "No hobbies selected" else "Hobbies: ${hobbies.joinToString()}"
        Toast.makeText(this, result, Toast.LENGTH_SHORT).show()
    }
}

}

  • 效果
  • 显示“Reading”、“Traveling”、“Music”复选框。
  • 提交按钮显示选中的兴趣。

4.2 代码中使用

动态创建 CheckBox:

package com.example.myapp

import android.os.Bundle
import android.widget.Button
import android.widget.CheckBox
import android.widget.LinearLayout
import android.widget.TextView
import android.widget.Toast
import androidx.appcompat.app.AppCompatActivity
import androidx.core.content.ContextCompat

class HobbiesActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)

    // 创建 LinearLayout
    val layout = LinearLayout(this).apply {
        orientation = LinearLayout.VERTICAL
        layoutParams = LinearLayout.LayoutParams(
            LinearLayout.LayoutParams.MATCH_PARENT,
            LinearLayout.LayoutParams.MATCH_PARENT
        )
        setPadding(16, 16, 16, 16)
    }

    // 创建 Label
    val label = TextView(this).apply {
        text = getString(R.string.hobbies_label)
        textSize = 18f
    }

    // 创建 CheckBoxes
    val checkRead = CheckBox(this).apply {
        id = R.id.checkRead
        text = getString(R.string.read)
        textSize = 16f
        buttonTintList = ContextCompat.getColorStateList(context, R.color.purple_500)
        contentDescription = getString(R.string.read_desc)
    }

    val checkTravel = CheckBox(this).apply {
        id = R.id.checkTravel
        text = getString(R.string.travel)
        textSize = 16f
        buttonTintList = ContextCompat.getColorStateList(context, R.color.purple_500)
        contentDescription = getString(R.string.travel_desc)
    }

    val checkMusic = CheckBox(this).apply {
        id = R.id.checkMusic
        text = getString(R.string.music)
        textSize = 16f
        buttonTintList = ContextCompat.getColorStateList(context, R.color.purple_500)
        contentDescription = getString(R.string.music_desc)
    }

    // 创建 Submit Button
    val submitButton = Button(this).apply {
        id = R.id.submitButton
        text = getString(R.string.submit)
        layoutParams = LinearLayout.LayoutParams(
            LinearLayout.LayoutParams.WRAP_CONTENT,
            LinearLayout.LayoutParams.WRAP_CONTENT
        ).apply { topMargin = 16 }
        setOnClickListener {
            val hobbies = mutableListOf<String>()
            if (checkRead.isChecked) hobbies.add("Reading")
            if (checkTravel.isChecked) hobbies.add("Traveling")
            if (checkMusic.isChecked) hobbies.add("Music")
            val result = if (hobbies.isEmpty()) "No hobbies selected" else "Hobbies: ${hobbies.joinToString()}"
            Toast.makeText(this@HobbiesActivity, result, Toast.LENGTH_SHORT).show()
        }
    }

    // 组装布局
    layout.addView(label)
    layout.addView(checkRead)
    layout.addView(checkTravel)
    layout.addView(checkMusic)
    layout.addView(submitButton)

    // 设置布局
    setContentView(layout)
}

}


5. 使用 Jetpack Compose

Compose 提供 RadioButtonCheckbox 组件,替代传统 View。


package com.example.myapp

import android.os.Bundle
import android.widget.Toast
import androidx.activity.ComponentActivity
import androidx.activity.compose.setContent
import androidx.compose.foundation.layout.*
import androidx.compose.material3.Button
import androidx.compose.material3.Checkbox
import androidx.compose.material3.RadioButton
import androidx.compose.material3.Text
import androidx.compose.runtime.*
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp

class MainActivity : ComponentActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContent {
SelectionScreen()
}
}
}

@Composable
fun SelectionScreen() {
val context = LocalContext.current
var selectedGender by remember { mutableStateOf(“Male”) }
var hobbies by remember { mutableStateOf(listOf()) }

Column(
    modifier = Modifier
        .fillMaxSize()
        .padding(16.dp)
) {
    // Gender Selection
    Text("Select Gender", fontSize = 18.sp)
    Row(
        verticalAlignment = Alignment.CenterVertically,
        modifier = Modifier.padding(vertical = 8.dp)
    ) {
        RadioButton(
            selected = selectedGender == "Male",
            onClick = { selectedGender = "Male" },
            modifier = Modifier.semantics { contentDescription = "Male option" }
        )
        Text("Male", modifier = Modifier.padding(start = 8.dp))
    }
    Row(
        verticalAlignment = Alignment.CenterVertically
    ) {
        RadioButton(
            selected = selectedGender == "Female",
            onClick = { selectedGender = "Female" },
            modifier = Modifier.semantics { contentDescription = "Female option" }
        )
        Text("Female", modifier = Modifier.padding(start = 8.dp))
    }

    // Hobbies Selection
    Text("Select Hobbies", fontSize = 18.sp, modifier = Modifier.padding(top = 16.dp))
    Row(
        verticalAlignment = Alignment.CenterVertically
    ) {
        Checkbox(
            checked = hobbies.contains("Reading"),
            onCheckedChange = { isChecked ->
                hobbies = if (isChecked) hobbies + "Reading" else hobbies - "Reading"
            },
            modifier = Modifier.semantics { contentDescription = "Reading hobby" }
        )
        Text("Reading", modifier = Modifier.padding(start = 8.dp))
    }
    Row(
        verticalAlignment = Alignment.CenterVertically
    ) {
        Checkbox(
            checked = hobbies.contains("Traveling"),
            onCheckedChange = { isChecked ->
                hobbies = if (isChecked) hobbies + "Traveling" else hobbies - "Traveling"
            },
            modifier = Modifier.semantics { contentDescription = "Traveling hobby" }
        )
        Text("Traveling", modifier = Modifier.padding(start = 8.dp))
    }
    Row(
        verticalAlignment = Alignment.CenterVertically
    ) {
        Checkbox(
            checked = hobbies.contains("Music"),
            onCheckedChange = { isChecked ->
                hobbies = if (isChecked) hobbies + "Music" else hobbies - "Music"
            },
            modifier = Modifier.semantics { contentDescription = "Music hobby" }
        )
        Text("Music", modifier = Modifier.padding(start = 8.dp))
    }

    // Submit Button
    Button(
        onClick = {
            val result = "Gender: $selectedGender, Hobbies: ${hobbies.joinToString()}"
            Toast.makeText(context, result, Toast.LENGTH_SHORT).show()
        },
        modifier = Modifier.padding(top = 16.dp)
    ) {
        Text("Submit")
    }
}

}

  • 依赖app/build.gradle):
    dependencies { implementation “androidx.compose.material3:material3:1.3.0” } android { buildFeatures { compose true } composeOptions { kotlinCompilerExtensionVersion “1.5.14” } }

6. RadioButton vs. CheckBox

特性RadioButtonCheckBox
继承CompoundButtonCompoundButton
选择类型单选(互斥)多选
容器需要 RadioGroup独立使用
使用场景性别、支付方式兴趣、任务列表
状态管理通过 RadioGroup 的 checkedRadioButtonId独立 isChecked

7. 示例:综合表单

以下是一个综合表单,结合 RadioButton 和 CheckBox。

<!-- Gender Selection -->
<TextView
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:text="@string/gender_label"
    android:textSize="18sp" />

<RadioGroup
    android:id="@+id/genderGroup"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:orientation="vertical">

    <RadioButton
        android:id="@+id/radioMale"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="@string/male"
        android:textSize="16sp"
        android:buttonTint="@color/purple_500"
        android:contentDescription="@string/male_desc" />

    <RadioButton
        android:id="@+id/radioFemale"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="@string/female"
        android:textSize="16sp"
        android:buttonTint="@color/purple_500"
        android:contentDescription="@string/female_desc" />

</RadioGroup>

<!-- Hobbies Selection -->
<TextView
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:text="@string/hobbies_label"
    android:textSize="18sp"
    android:layout_marginTop="16dp" />

<CheckBox
    android:id="@+id/checkRead"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:text="@string/read"
    android:textSize="16sp"
    android:buttonTint="@color/purple_500"
    android:contentDescription="@string/read_desc" />

<CheckBox
    android:id="@+id/checkTravel"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:text="@string/travel"
    android:textSize="16sp"
    android:buttonTint="@color/purple_500"
    android:contentDescription="@string/travel_desc" />

<!-- Submit Button -->
<Button
    android:id="@+id/submitButton"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:text="@string/submit"
    android:layout_marginTop="16dp" />
  • ActivityFormActivity.kt):

    package com.example.myapp

import android.os.Bundle
import android.widget.Button
import android.widget.CheckBox
import android.widget.RadioGroup
import android.widget.Toast
import androidx.appcompat.app.AppCompatActivity

class FormActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_form)

    val genderGroup: RadioGroup = findViewById(R.id.genderGroup)
    val checkRead: CheckBox = findViewById(R.id.checkRead)
    val checkTravel: CheckBox = findViewById(R.id.checkTravel)
    val submitButton: Button = findViewById(R.id.submitButton)

    submitButton.setOnClickListener {
        val gender = when (genderGroup.checkedRadioButtonId) {
            R.id.radioMale -> "Male"
            R.id.radioFemale -> "Female"
            else -> "None"
        }
        val hobbies = mutableListOf<String>()
        if (checkRead.isChecked) hobbies.add("Reading")
        if (checkTravel.isChecked) hobbies.add("Traveling")
        val result = "Gender: $gender, Hobbies: ${hobbies.joinToString()}"
        Toast.makeText(this, result, Toast.LENGTH_SHORT).show()
    }
}

}


8. 最佳实践

  • 可访问性
  • 添加 contentDescription
    xml <RadioButton android:contentDescription="Male option" ... />
  • 测试 TalkBack 功能。
  • 响应式设计
  • 使用 dpsp 单位。
  • 测试多屏幕适配(Android Studio 的 Layout Editor)。
  • 性能优化
  • 避免过多动态监听,优化 OnCheckedChangeListener
  • 检查 Overdraw(Layout Inspector)。
  • 版本控制
  • 将布局文件纳入 Git,添加 .gitignore
    /build /.idea
  • 迁移到 Compose:新项目优先使用 Compose 的 RadioButton 和 Checkbox。

9. 常见问题与解决方案

问题解决方法
RadioButton 不互斥确保放入 RadioGroup;检查 checkedButton 设置。
CheckBox 状态丢失使用 isChecked 保存状态;考虑 ViewModel 持久化。
样式不统一使用 buttonTint 或自定义主题;应用 Material Design。
无障碍问题添加 contentDescription;测试 TalkBack。

10. 进阶提示

  • 动态添加 RadioButton
  val radioButton = RadioButton(this).apply {
      text = "New Option"
      id = View.generateViewId()
  }
  radioGroup.addView(radioButton)
  • 监听状态变化
  checkRead.setOnCheckedChangeListener { _, isChecked ->
      Toast.makeText(this, "Reading: $isChecked", Toast.LENGTH_SHORT).show()
  }
  • Compose 动态列表
  val options = listOf("Male", "Female")
  options.forEach { option ->
      Row {
          RadioButton(
              selected = selectedGender == option,
              onClick = { selectedGender = option }
          )
          Text(option)
      }
  }

11. 总结

RadioButton 和 CheckBox 是 Android 中处理用户选择的常用组件。RadioButton 适合单选场景(如性别选择),需与 RadioGroup 配合;CheckBox 适合多选场景(如兴趣选择),可独立使用。两者支持样式自定义和状态监听,现代开发推荐 Jetpack Compose 的 RadioButton 和 Checkbox 组件以简化开发。结合 Material Design 和最佳实践,可构建直观、响应式的选择界面。

如果需要更复杂示例(如动态选项、Compose 动画)、特定场景指导,或其他 Android 相关问题(如其他 View 对比),请告诉我!

类似文章

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注