【Unity UGUI】Toggle / ToggleGroup 与 Dropdown

文章目录

    • [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
复制代码
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}");
        }
    }
}
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);
    }
}
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 开关

  1. Hierarchy 右键 → UI → Toggle 创建(或 UI → Toggle - TextMeshPro)
  2. 选中 Toggle 对象,Inspector 中:
    • Is On:设置初始状态
    • Graphic:确认指向 Checkmark 子对象
  3. 挂载脚本,onValueChanged 监听状态变化

ToggleGroup 单选

  1. 创建一个空 Panel 作为父对象
  2. 给 Panel 添加 ToggleGroup 组件
  3. 创建多个 Toggle 作为子对象
  4. 每个 Toggle 的 Group 属性拖拽指向 Panel
  5. Allow Switch Off 设为 false(强制必须选一个)
  1. Hierarchy 右键 → UI → Dropdown - TextMeshPro 创建
  2. Inspector 中 Options 列表手动添加选项(固定选项)
  3. 或用代码 ClearOptions() + AddOptions() 动态填充
  4. onValueChanged 监听选中变化,回调参数是 int 索引

6. 参数说明

Toggle 脚本参数

参数 类型 默认值 说明
_soundToggle Toggle --- 音效开关 Toggle 引用
_musicToggle Toggle --- 音乐开关 Toggle 引用

ToggleGroup 脚本参数

参数 类型 默认值 说明
_difficultyGroup ToggleGroup --- 难度选择的 ToggleGroup 引用
参数 类型 默认值 说明
_languageDropdown TMP_Dropdown --- 语言选择下拉列表
_resolutionDropdown TMP_Dropdown --- 分辨率选择下拉列表

7. 变体与扩展

7.1 Toggle 自定义样式(图标替换)

默认的勾选框太朴素,可以替换为自定义图标:

  1. 准备两张图:未选中状态(如空心圆)和选中状态(如实心圆/星星)
  2. Background 的 Image 设为未选中图
  3. Checkmark 的 Image 设为选中图
  4. 调整 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); // 未选中:灰色
});

当选项很多时(如 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();
}

省 → 市 → 区三级联动:

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 的最大高度(设 ScrollRectContent 高度上限),让选项可滚动而不是无限展开。
相关推荐
ai_coder_ai2 小时前
自动化脚本ui编程之线性布局(linear)
ui·autojs·自动化脚本·冰狐智能辅助·easyclick
雪儿waii3 小时前
Unity 中的 InvokeRepeating 详解
unity·游戏引擎
mxwin3 小时前
Unity Shader 程序化生成:Shader 中的数学宇宙
unity·游戏引擎
HYNuyoah4 小时前
3X-UI Reality 搭建指南
ubuntu·ui·docker
雪儿waii4 小时前
Unity 中的 Quaternion(四元数)详解
unity·游戏引擎
RReality5 小时前
【Unity UGUI】ScrollRect 与 Scrollbar 深度用法
unity·游戏引擎
人邮异步社区5 小时前
如何自学游戏引擎的开发?
unity·程序员·游戏引擎
郝学胜-神的一滴6 小时前
[简化版 Games 101] 计算机图形学 05:二维变换下
c++·unity·图形渲染·three.js·opengl·unreal
charlie11451419115 小时前
通用GUI编程技术——图形渲染实战(三十三)——Direct2D与Win32/GDI互操作:渐进迁移实战
c++·图形渲染·gui·win32