文章目录
-
- [0. 效果预览](#0. 效果预览)
- [1. 需求分析](#1. 需求分析)
- [2. Hierarchy 搭建](#2. Hierarchy 搭建)
-
- [Toggle 层级结构](#Toggle 层级结构)
- [ToggleGroup 用法](#ToggleGroup 用法)
- [Dropdown 层级结构](#Dropdown 层级结构)
- [3. 核心组件配置](#3. 核心组件配置)
-
- [Toggle 组件参数](#Toggle 组件参数)
- [ToggleGroup 组件参数](#ToggleGroup 组件参数)
- [TMP_Dropdown 组件参数](#TMP_Dropdown 组件参数)
- [4. 完整代码](#4. 完整代码)
-
- [4.1 Toggle 事件监听](#4.1 Toggle 事件监听)
- [4.2 ToggleGroup 单选获取选中项](#4.2 ToggleGroup 单选获取选中项)
- [4.3 Dropdown 基础用法与动态填充](#4.3 Dropdown 基础用法与动态填充)
- [4.4 Dropdown 带图标选项](#4.4 Dropdown 带图标选项)
- [5. 使用方法](#5. 使用方法)
-
- [Toggle 开关](#Toggle 开关)
- [ToggleGroup 单选](#ToggleGroup 单选)
- [Dropdown 下拉列表](#Dropdown 下拉列表)
- [6. 参数说明](#6. 参数说明)
-
- [Toggle 脚本参数](#Toggle 脚本参数)
- [ToggleGroup 脚本参数](#ToggleGroup 脚本参数)
- [Dropdown 脚本参数](#Dropdown 脚本参数)
- [7. 变体与扩展](#7. 变体与扩展)
-
- [7.1 Toggle 自定义样式(图标替换)](#7.1 Toggle 自定义样式(图标替换))
- [7.2 Dropdown 搜索过滤](#7.2 Dropdown 搜索过滤)
- [7.3 多级联动 Dropdown](#7.3 多级联动 Dropdown)
- [8. 常见问题](#8. 常见问题)
- [9. 性能 / 适配建议](#9. 性能 / 适配建议)
0. 效果预览

Toggle 和 Dropdown 是 UGUI 中最常用的选择类组件:Toggle 做开关/单选/多选,Dropdown 做下拉列表。看似简单,但 ToggleGroup 的互斥逻辑、Dropdown 的动态填充、自定义样式这些细节,不搞清楚就容易踩坑。
1. 需求分析
核心思路:Toggle = 一个可切换的 bool 状态 + 视觉反馈(勾选图标),ToggleGroup 让多个 Toggle 互斥;Dropdown = 一个可展开的选项列表 + 选中回调。
典型使用场景:
- 设置界面的开关(音效开/关、推送开/关)
- 难度选择(简单/普通/困难,单选互斥)
- 装备筛选标签(多选:武器、防具、饰品)
- 语言切换下拉列表
- 分辨率 / 画质选择
- 角色职业选择
需要实现的功能点:
- Toggle 基础用法与事件回调
- ToggleGroup 互斥逻辑(单选模式)
- Toggle 多选模式(不加 Group)
- Toggle 自定义样式(替换勾选图标)
- Dropdown 基础用法与选项填充
- Dropdown 动态添加/删除选项(代码控制)
- Dropdown 自定义样式
- TMP_Dropdown 对比
前置知识:建议先阅读本系列 文章 1(总领篇),了解 Canvas、RectTransform、EventSystem 的基本概念。
2. Hierarchy 搭建
Toggle 层级结构
Canvas
└── Toggle ← Toggle 组件 + Layout Element(可选)
├── Background(Image) ← 未选中时的背景框
│ └── Checkmark(Image) ← 选中时显示的勾选图标
└── Label(Text / TMP_Text) ← 文字标签(可选)
- Toggle :父对象,挂
Toggle组件。Graphic属性指向 Background,Target Graphic控制交互高亮 - Background:未选中状态的视觉(通常是一个方框/圆框)
- Checkmark :选中状态的视觉(勾号/填充色/图标),Toggle 组件会自动控制它的
gameObject.SetActive - Label:文字说明,非必须

ToggleGroup 用法
在父对象上挂 ToggleGroup 组件,然后把每个 Toggle 的 Group 属性拖拽指向这个 ToggleGroup。同一 Group 内的 Toggle 自动互斥------选中一个,其他自动取消。
Canvas
└── DifficultyPanel
└── ToggleGroup 组件(挂在 Panel 上)
├── Toggle_Easy ← Group 指向 DifficultyPanel
├── Toggle_Normal ← Group 指向 DifficultyPanel
└── Toggle_Hard ← Group 指向 DifficultyPanel

Dropdown 层级结构
Canvas
└── Dropdown (TMP) ← TMP_Dropdown 组件 + Image(背景)
├── Label(TMP_Text) ← 当前选中项的显示文字
├── Arrow(Image) ← 下拉箭头图标
└── Template(ScrollRect) ← 下拉列表模板(默认隐藏)
└── Viewport(Mask)
└── Content
└── Item(Toggle) ← 单个选项的模板
├── Item Background(Image)
├── Item Checkmark(Image)
└── Item Label(TMP_Text)
- Dropdown :父对象,挂
TMP_Dropdown(或原生Dropdown)组件 - Template :下拉列表的模板,平时
SetActive(false)隐藏,点击时克隆并显示 - Item:单个选项的模板,Dropdown 会根据选项数量克隆多份

3. 核心组件配置
Toggle 组件参数
| 参数 | 说明 |
|---|---|
Is On |
初始选中状态(true/false) |
Toggle Transition |
切换动画:None(无)/ Fade(淡入淡出) |
Graphic |
选中时显示的图形(通常指向 Checkmark) |
Group |
所属的 ToggleGroup(不设 = 独立 Toggle,可多选) |
On Value Changed |
状态变化时的回调事件(参数为 bool) |
ToggleGroup 组件参数
| 参数 | 说明 |
|---|---|
Allow Switch Off |
是否允许全部取消选中。false = 必须有一个选中(单选模式),true = 可以全不选 |
关键 :
Allow Switch Off = false时,用户点击已选中的 Toggle 不会取消它------必须点另一个才能切换。这是单选按钮的标准行为。
TMP_Dropdown 组件参数
| 参数 | 说明 |
|---|---|
Options |
选项列表(可在 Inspector 中手动添加,也可代码动态填充) |
Value |
当前选中项的索引(从 0 开始) |
Caption Text |
指向显示当前选中项文字的 TMP_Text |
Caption Image |
指向显示当前选中项图标的 Image(可选) |
Item Text |
指向选项模板中的 TMP_Text |
Item Image |
指向选项模板中的 Image(可选) |
Template |
指向下拉列表模板的 RectTransform |
On Value Changed |
选中项变化时的回调事件(参数为 int 索引) |
4. 完整代码
4.1 Toggle 事件监听
csharp
using UnityEngine;
using UnityEngine.UI;
/// <summary>
/// Toggle 基础事件监听示例
/// 挂载到任意对象,Inspector 中拖入对应的 Toggle 引用
/// </summary>
public class ToggleExample : MonoBehaviour
{
[SerializeField] private Toggle _soundToggle;
[SerializeField] private Toggle _musicToggle;
void Start()
{
// ===== 监听 Toggle 状态变化 =====
_soundToggle.onValueChanged.AddListener(OnSoundToggleChanged);
_musicToggle.onValueChanged.AddListener(OnMusicToggleChanged);
}
private void OnSoundToggleChanged(bool isOn)
{
Debug.Log($"音效: {(isOn ? "开" : "关")}");
// AudioManager.Instance.SetSoundEnabled(isOn);
}
private void OnMusicToggleChanged(bool isOn)
{
Debug.Log($"音乐: {(isOn ? "开" : "关")}");
// AudioManager.Instance.SetMusicEnabled(isOn);
}
void OnDestroy()
{
// ===== 移除监听,防止内存泄漏 =====
_soundToggle.onValueChanged.RemoveListener(OnSoundToggleChanged);
_musicToggle.onValueChanged.RemoveListener(OnMusicToggleChanged);
}
}
4.2 ToggleGroup 单选获取选中项
csharp
using UnityEngine;
using UnityEngine.UI;
using System.Linq;
/// <summary>
/// ToggleGroup 单选模式:获取当前选中的 Toggle
/// </summary>
public class ToggleGroupExample : MonoBehaviour
{
[SerializeField] private ToggleGroup _difficultyGroup;
/// <summary>
/// 获取当前选中的 Toggle(按钮点击时调用)
/// </summary>
public void OnConfirmDifficulty()
{
// ===== 获取 Group 中当前选中的 Toggle =====
// ActiveToggles() 返回所有 isOn=true 的 Toggle
Toggle activeToggle = _difficultyGroup.ActiveToggles().FirstOrDefault();
if (activeToggle != null)
{
Debug.Log($"选中难度: {activeToggle.name}");
}
}
/// <summary>
/// 也可以给每个 Toggle 单独监听,通过名字或索引判断
/// </summary>
public void OnDifficultyChanged(bool isOn)
{
// 注意:ToggleGroup 切换时会触发两次回调
// 一次是旧 Toggle 的 isOn=false,一次是新 Toggle 的 isOn=true
// 只处理 isOn=true 的那次
if (!isOn) return;
Toggle current = _difficultyGroup.ActiveToggles().FirstOrDefault();
if (current != null)
{
Debug.Log($"切换到: {current.name}");
}
}
}
4.3 Dropdown 基础用法与动态填充
csharp
using UnityEngine;
using TMPro;
using System.Collections.Generic;
/// <summary>
/// Dropdown 基础用法 + 动态填充选项
/// </summary>
public class DropdownExample : MonoBehaviour
{
[SerializeField] private TMP_Dropdown _languageDropdown;
[SerializeField] private TMP_Dropdown _resolutionDropdown;
void Start()
{
// ===== 方式 1:Inspector 中手动添加选项(适合固定选项) =====
// 直接在 TMP_Dropdown 组件的 Options 列表中添加即可
// ===== 方式 2:代码动态填充(适合运行时生成的选项) =====
SetupResolutionDropdown();
// ===== 监听选中变化 =====
_languageDropdown.onValueChanged.AddListener(OnLanguageChanged);
_resolutionDropdown.onValueChanged.AddListener(OnResolutionChanged);
}
private void SetupResolutionDropdown()
{
// 清空已有选项
_resolutionDropdown.ClearOptions();
// 创建新选项列表
List<string> options = new List<string>
{
"1920 x 1080",
"1280 x 720",
"2560 x 1440",
"3840 x 2160"
};
// 添加选项
_resolutionDropdown.AddOptions(options);
// 设置默认选中项(索引从 0 开始)
_resolutionDropdown.value = 0;
// 刷新显示(确保 Caption 文字更新)
_resolutionDropdown.RefreshShownValue();
}
private void OnLanguageChanged(int index)
{
// index 是选中项的索引
string selected = _languageDropdown.options[index].text;
Debug.Log($"语言切换为: {selected}");
}
private void OnResolutionChanged(int index)
{
string selected = _resolutionDropdown.options[index].text;
Debug.Log($"分辨率切换为: {selected}");
}
/// <summary>
/// 运行时动态添加单个选项
/// </summary>
public void AddCustomOption(string optionText)
{
_resolutionDropdown.options.Add(new TMP_Dropdown.OptionData(optionText));
// 注意:添加后不会自动刷新 UI,需要手动调用
_resolutionDropdown.RefreshShownValue();
}
void OnDestroy()
{
_languageDropdown.onValueChanged.RemoveListener(OnLanguageChanged);
_resolutionDropdown.onValueChanged.RemoveListener(OnResolutionChanged);
}
}
4.4 Dropdown 带图标选项
csharp
using UnityEngine;
using TMPro;
using System.Collections.Generic;
/// <summary>
/// Dropdown 带图标的选项(如职业选择带职业图标)
/// </summary>
public class DropdownWithIconExample : MonoBehaviour
{
[SerializeField] private TMP_Dropdown _classDropdown;
[SerializeField] private Sprite[] _classIcons; // Inspector 中拖入职业图标
void Start()
{
_classDropdown.ClearOptions();
// ===== 使用 OptionData 同时设置文字和图标 =====
List<TMP_Dropdown.OptionData> options = new List<TMP_Dropdown.OptionData>
{
new TMP_Dropdown.OptionData("战士", _classIcons[0]),
new TMP_Dropdown.OptionData("法师", _classIcons[1]),
new TMP_Dropdown.OptionData("弓箭手", _classIcons[2]),
new TMP_Dropdown.OptionData("牧师", _classIcons[3])
};
_classDropdown.AddOptions(options);
// 需要在 Dropdown 的 Template > Item 中配置 Item Image 引用
// 否则图标不会显示
}
}
5. 使用方法
Toggle 开关
- Hierarchy 右键 → UI → Toggle 创建(或 UI → Toggle - TextMeshPro)
- 选中 Toggle 对象,Inspector 中:
Is On:设置初始状态Graphic:确认指向 Checkmark 子对象
- 挂载脚本,
onValueChanged监听状态变化
ToggleGroup 单选
- 创建一个空 Panel 作为父对象
- 给 Panel 添加
ToggleGroup组件 - 创建多个 Toggle 作为子对象
- 每个 Toggle 的
Group属性拖拽指向 Panel Allow Switch Off设为 false(强制必须选一个)
Dropdown 下拉列表
- Hierarchy 右键 → UI → Dropdown - TextMeshPro 创建
- Inspector 中
Options列表手动添加选项(固定选项) - 或用代码
ClearOptions()+AddOptions()动态填充 onValueChanged监听选中变化,回调参数是 int 索引
6. 参数说明
Toggle 脚本参数
| 参数 | 类型 | 默认值 | 说明 |
|---|---|---|---|
_soundToggle |
Toggle | --- | 音效开关 Toggle 引用 |
_musicToggle |
Toggle | --- | 音乐开关 Toggle 引用 |
ToggleGroup 脚本参数
| 参数 | 类型 | 默认值 | 说明 |
|---|---|---|---|
_difficultyGroup |
ToggleGroup | --- | 难度选择的 ToggleGroup 引用 |
Dropdown 脚本参数
| 参数 | 类型 | 默认值 | 说明 |
|---|---|---|---|
_languageDropdown |
TMP_Dropdown | --- | 语言选择下拉列表 |
_resolutionDropdown |
TMP_Dropdown | --- | 分辨率选择下拉列表 |
7. 变体与扩展
7.1 Toggle 自定义样式(图标替换)
默认的勾选框太朴素,可以替换为自定义图标:
- 准备两张图:未选中状态(如空心圆)和选中状态(如实心圆/星星)
- Background 的 Image 设为未选中图
- Checkmark 的 Image 设为选中图
- 调整 Checkmark 的 RectTransform 大小和位置
也可以用颜色变化代替图标切换:
csharp
// 选中时改变背景颜色
toggle.onValueChanged.AddListener(isOn =>
{
toggle.GetComponent<Image>().color = isOn
? new Color(0.2f, 0.8f, 0.4f) // 选中:绿色
: new Color(0.8f, 0.8f, 0.8f); // 未选中:灰色
});
7.2 Dropdown 搜索过滤
当选项很多时(如 100+ 个道具),加一个搜索框过滤:
csharp
// 在 Dropdown 上方放一个 InputField
[SerializeField] private TMP_InputField _searchInput;
[SerializeField] private TMP_Dropdown _dropdown;
private List<string> _allOptions; // 完整选项列表
void Start()
{
_allOptions = new List<string> { "长剑", "短剑", "法杖", "弓", "盾牌" /* ... */ };
_dropdown.AddOptions(_allOptions);
_searchInput.onValueChanged.AddListener(FilterOptions);
}
private void FilterOptions(string keyword)
{
_dropdown.ClearOptions();
if (string.IsNullOrEmpty(keyword))
{
_dropdown.AddOptions(_allOptions);
}
else
{
// 过滤包含关键词的选项
var filtered = _allOptions.FindAll(opt =>
opt.Contains(keyword, System.StringComparison.OrdinalIgnoreCase));
_dropdown.AddOptions(filtered);
}
_dropdown.RefreshShownValue();
}
7.3 多级联动 Dropdown
省 → 市 → 区三级联动:
csharp
// 省份变化时,更新城市列表
_provinceDropdown.onValueChanged.AddListener(index =>
{
string province = _provinceDropdown.options[index].text;
_cityDropdown.ClearOptions();
_cityDropdown.AddOptions(GetCities(province));
_cityDropdown.value = 0;
_cityDropdown.RefreshShownValue();
});
8. 常见问题
Q: ToggleGroup 切换时回调触发了两次?
A: 正常行为。切换时旧 Toggle 的 onValueChanged(false) 和新 Toggle 的 onValueChanged(true) 各触发一次。在回调中加 if (!isOn) return; 只处理选中事件。
Q: Toggle 的 Checkmark 不显示/不隐藏?
A: 检查 Toggle 组件的 Graphic 属性是否正确指向 Checkmark 对象。如果 Checkmark 是 Background 的子对象,确认 Background 的 Raycast Target 没有遮挡 Toggle 的点击区域。
Q: Dropdown 展开后选项列表位置偏了?
A: 检查 Template 的 RectTransform 锚点和 Pivot。Template 默认锚在 Dropdown 底部向下展开。如果 Dropdown 在屏幕底部,列表会被裁切------可以改 Template 的 Pivot 让它向上展开。
Q: 代码 AddOptions 后 Dropdown 显示的还是旧文字?
A: 添加选项后需要调用 RefreshShownValue() 刷新 Caption 文字。或者设置 dropdown.value = 0 触发一次更新。
Q: 原生 Dropdown 和 TMP_Dropdown 有什么区别?
A: 功能完全一致,区别只在文字渲染:原生用 Text,TMP 用 TextMeshProUGUI。推荐统一用 TMP 版本,文字渲染质量更好,且 Unity 新版本已经默认使用 TMP。
9. 性能 / 适配建议
- Toggle 数量:几十个 Toggle 完全没有性能问题。如果需要上百个(如大型筛选面板),考虑用 ScrollRect + 对象池动态生成。
- Dropdown 选项数量:Dropdown 展开时会一次性实例化所有选项的 UI 对象。100 个以内没问题,1000+ 个会有明显卡顿。大量选项场景建议自己实现虚拟滚动列表(参考本系列后续文章 11)。
- ToggleGroup 的 Allow Switch Off :设为 false 时,代码中
toggle.isOn = false不会生效(Group 会强制保持至少一个选中)。如果需要代码重置,先临时设allowSwitchOff = true,改完再设回来。 - Dropdown Template 的 Canvas 层级:Dropdown 展开时会创建一个新的 Canvas 来渲染列表(确保列表在最上层)。这个临时 Canvas 会增加一次 Draw Call,但关闭后自动销毁。
- 移动端适配 :Dropdown 在小屏幕上展开可能超出屏幕范围。建议限制 Template 的最大高度(设
ScrollRect的Content高度上限),让选项可滚动而不是无限展开。