ARTICLE DETAIL

资讯详情

深耕网站SEO优化与搜索引擎排名提升的一线实战洞察。

Android Material Design 组件实战:SwitchMaterial、Chip与ChipGroup深度解析

Android Material Design 组件实战:SwitchMaterial、Chip与ChipGroup深度解析 1. 从 Material Design 2 到 Material 3为什么你还在用 SwitchMaterial如果你最近几年一直在做 Android 开发打开一个几年前的老项目或者在网上搜索“Android Switch 开关”大概率会看到SwitchMaterial这个组件。它曾经是 Material Design 组件库MDC中用于替代原生Switch的明星控件提供了符合 Material Design 规范的视觉设计和开箱即用的无障碍支持。但今天当 Material Design 3Material You已经成为 Android 13 的默认设计语言Google 也大力推广 Jetpack Compose 时我们为什么还要花时间了解SwitchMaterial、Chip和ChipGroup这些“老古董”呢原因很简单存量与过渡。市面上仍有海量的 App 基于传统的 View 系统开发它们可能短期内无法、也没必要全面迁移到 Compose。对于这些项目Material Components for AndroidMDC库依然是构建现代化 UI 最可靠、最高效的选择。SwitchMaterial作为 MDC 对开关控件的标准化实现理解了它你就能明白 Material Design 在交互细节上的考量。而Chip和ChipGroup更是筛选、标签、输入等场景下不可或缺的“瑞士军刀”它们的灵活性和功能完整性即使在今天看也毫不过时。更重要的是学习这些组件不仅仅是学习 API 调用更是理解 Material Design 的设计模式。比如SwitchMaterial的触摸区域thumb track设计、Chip的各种形态Input, Filter, Choice, Action所对应的不同交互语义这些都是构建优秀用户体验的基石。无论你未来是继续深耕 View 系统还是转向 Compose这些设计思想都是相通的。Compose 的 Material 3 组件库中同样有Switch、Chip、FilterChip、ChipGroup等其核心概念一脉相承。所以这篇文章将深入这三个组件的每一个角落。我不会只给你一堆代码片段而是会结合 Material Design 规范告诉你为什么要这样设计在什么场景下该用哪种变体以及在实际开发中容易踩哪些坑。我们从那个看似简单却暗藏玄机的SwitchMaterial开始。2. SwitchMaterial 深度解析不只是个会动的开关androidx.appcompat.widget.SwitchCompat是支持库时代的产物而com.google.android.material.switchmaterial.SwitchMaterial则是它的 Material Design 升级版继承自SwitchCompat并添加了 Material 主题的样式支持。它的核心价值在于提供了符合 Material Design 2 规范的视觉表现包括更大的触摸目标、更流畅的动画以及更协调的颜色系统集成。2.1 基础使用与主题集成在布局文件中使用SwitchMaterial非常简单com.google.android.material.switchmaterial.SwitchMaterial android:idid/switch_notification android:layout_widthwrap_content android:layout_heightwrap_content android:text推送通知 android:checkedtrue app:useMaterialThemeColorstrue /这里有几个关键属性android:text开关旁边的描述文本。android:checked初始选中状态。app:useMaterialThemeColorstrue这是一个非常重要的属性。当设置为true时开关的颜色包括开启状态的轨道和拇指颜色将自动从你的 Material 主题中提取colorSecondaryMD2或colorPrimaryMD3等颜色属性。这确保了开关的颜色与你的应用整体配色方案保持一致。如果你不设置这个属性或者设置为false它将使用默认的 Android 系统配色通常是蓝色这可能会破坏你的应用视觉统一性。在代码中监听状态变化是最常见的操作binding.switchNotification.setOnCheckedChangeListener { buttonView, isChecked - // isChecked 是新的状态 if (isChecked) { // 开启通知逻辑 saveNotificationPreference(true) } else { // 关闭通知逻辑 saveNotificationPreference(false) } // 注意buttonView 就是 SwitchMaterial 实例本身 }注意setOnCheckedChangeListener有一个常见的“坑”。这个监听器不仅在用户交互时触发当你通过代码调用setChecked()方法改变状态时它同样会触发。如果你在初始化视图或者在某个业务逻辑中通过代码设置状态这可能会导致你的业务逻辑被意外执行两次。为了避免这个问题你可以在需要代码设置状态前先移除监听器设置完成后再添加回去。或者更优雅的做法是在监听器内部通过判断buttonView.isPressed或记录事件来源来区分是用户操作还是代码调用。2.2 自定义与样式覆盖虽然主题集成很方便但总有需要微调的时候。SwitchMaterial的自定义主要通过覆盖其样式属性实现。1. 修改轨道Track和拇指Thumb的颜色你可以直接通过 XML 属性或样式主题来覆盖。XML 属性方式优先级高com.google.android.material.switchmaterial.SwitchMaterial ... app:trackTintcolor/custom_track_tint_selector app:thumbTintcolor/custom_thumb_tint_selector /这里的color/...应该是一个 ColorStateList颜色选择器而不是一个简单的颜色值。因为开关在不同状态checked/unchecked, enabled/disabled下需要显示不同的颜色。例如custom_track_tint_selector.xml可能长这样?xml version1.0 encodingutf-8? selector xmlns:androidhttp://schemas.android.com/apk/res/android item android:colorcolor/green_500 android:state_checkedtrue android:state_enabledtrue/ item android:colorcolor/grey_300 android:state_checkedfalse android:state_enabledtrue/ item android:alpha0.38 android:colorcolor/black android:state_enabledfalse/ !-- 禁用状态 -- /selector主题样式方式统一管理在你的styles.xml中定义一个继承自 Material 组件样式的新样式style nameWidget.MyApp.SwitchMaterial parentWidget.MaterialComponents.CompoundButton.Switch item nametrackTintcolor/custom_track_tint_selector/item item namethumbTintcolor/custom_thumb_tint_selector/item item nameandroid:minWidth?attr/switchMinWidth/item item nameandroid:minHeight?attr/switchMinHeight/item /style然后在你的应用主题或控件上应用这个样式stylestyle/Widget.MyApp.SwitchMaterial。2. 修改文本样式SwitchMaterial的文本实际上是一个TextView你可以通过android:textAppearance属性或者直接设置android:textSize、android:textColor等来修改。3. 关于app:thumbIcon和app:trackDecoration在一些 Material Design 示例或更高级的定制中你可能会看到为拇指添加图标比如一个小太阳或月亮或者在轨道上添加装饰。这些属性在SwitchMaterial中并不直接存在。它们是 Material Design 3 中Switch的 Filled 或 Outlined 变体才支持的特性。在 MDC 的SwitchMaterial中要实现类似效果通常需要更复杂的自定义 Drawable 或考虑使用其他第三方库这超出了基础使用的范围。如果你的设计强烈要求这种效果可能需要评估是接受 MD2 的标准样式还是推动设计向 MD3 靠拢或者投入成本进行深度自定义。2.3 无障碍支持与测试要点SwitchMaterial一个重要的优点是它内置了较好的无障碍支持。开关状态选中/未选中会被自动报告给屏幕阅读器如 TalkBack。但作为开发者你仍需确保有意义的文本android:text属性必须清晰描述这个开关控制什么例如“开启夜间模式”比“开关”要好得多。状态描述在某些复杂场景下如果开关状态需要更详细的描述可以考虑使用android:contentDescription属性但通常text已足够。测试在真机上开启 TalkBack导航到你的开关听听朗读的内容是否准确、易懂。同时测试开关操作是否顺畅。一个常见的无障碍问题是触摸目标大小。Material Design 指南要求触摸目标至少为 48dp x 48dp。SwitchMaterial默认已经考虑了这一点它的可点击区域是大于视觉上的拇指和轨道的。你可以通过android:minWidth和android:minHeight属性来进一步调整但一般不需要。3. Chip 组件全览四种形态应对四大场景Chip是 Material Design 中一个极其灵活的组件它本质是一个紧凑的按钮可以显示文本、图标并能被选中或删除。根据其行为和用途主要分为四种类型在 MDC 中通过style属性来指定输入型芯片Input Chipstylestyle/Widget.MaterialComponents.Chip.Input筛选型芯片Filter Chipstylestyle/Widget.MaterialComponents.Chip.Filter选择型芯片Choice Chipstylestyle/Widget.MaterialComponents.Chip.Choice操作型芯片Action Chipstylestyle/Widget.MaterialComponents.Chip.Action此外还有一个基础的Chip样式 (style/Widget.MaterialComponents.Chip.Entry)但通常我们直接使用上述四种语义更明确的变体。这四种芯片在 UI 上看起来可能相似但它们的交互语义和用途有本质区别。用错类型会导致用户困惑。3.1 输入型芯片Input Chip代表一个输入实体这是最常见于搜索框、邮件收件人输入框的芯片。它代表用户已经输入或选择的一个实体如联系人、标签并且通常带有一个删除图标“X”允许用户移除它。典型场景邮件应用的收件人输入框、标签添加、搜索过滤器已添加的筛选条件。关键特性可删除点击尾部的删除图标移除芯片。可能可点击点击芯片主体可能触发查看详情等操作但移除操作优先通过删除图标。通常放在一个可编辑的文本输入框附近或内部。示例代码com.google.android.material.chip.Chip stylestyle/Widget.MaterialComponents.Chip.Input android:layout_widthwrap_content android:layout_heightwrap_content android:text张三 app:chipIcondrawable/ic_person // 可选头像图标 app:closeIconVisibletrue // 显示删除图标 /监听事件chip.setOnClickListener { // 处理点击芯片主体如查看联系人详情 } chip.setOnCloseIconClickListener { // 处理点击删除图标通常是从列表中移除该芯片 (parent as? ViewGroup)?.removeView(chip) // 同时更新底层数据模型 }3.2 筛选型芯片Filter Chip用于多选筛选这种芯片用于筛选内容类似于多选框Checkbox。它有两种状态选中和未选中。选中状态通常有更显著的视觉填充。同一组筛选芯片允许多个同时被选中。典型场景电商网站的商品分类筛选如“手机”、“电脑”、“家电”可以多选、内容标签过滤。关键特性可选择点击芯片在选中/未选中状态间切换。多选一组内的筛选芯片是独立的多选框。选中状态有视觉强化。示例代码com.google.android.material.chip.Chip stylestyle/Widget.MaterialComponents.Chip.Filter android:layout_widthwrap_content android:layout_heightwrap_content android:text仅看有货 android:checkedtrue app:checkabletrue // 对于Filter Chip这个属性默认为true /监听事件chip.setOnCheckedChangeListener { buttonView, isChecked - // isChecked 为新的选中状态 applyFilter(isChecked) }3.3 选择型芯片Choice Chip用于单选选择这种芯片用于在多个互斥的选项中进行单选类似于单选框RadioButton。同一组内任何时候只能有一个选择型芯片被选中。典型场景排序方式选择“最新”、“最热”、“价格”、尺寸选择“S”、“M”、“L”。关键特性可选择点击选中。单选需要与ChipGroup配合使用才能实现真正的单选行为见下一节。视觉上与 Filter Chip 选中态类似但逻辑是互斥的。示例代码com.google.android.material.chip.Chip stylestyle/Widget.MaterialComponents.Chip.Choice android:layout_widthwrap_content android:layout_heightwrap_content android:text价格升序 app:checkabletrue /重要单个Choice Chip不会自动实现单选。你必须将它放入ChipGroup并设置app:singleSelectiontrue才能获得单选行为。3.4 操作型芯片Action Chip触发一个动作这种芯片用于触发一个与主要内容相关的动作它看起来像一个扁平的按钮Flat Button但形状是胶囊形的。点击它执行一个操作它没有选中状态。典型场景在卡片底部显示“分享”、“查看更多”、“播放视频”等辅助操作。关键特性可点击点击触发一个动作。无状态没有选中/未选中概念。类似于按钮但样式更轻量用于次级操作。示例代码com.google.android.material.chip.Chip stylestyle/Widget.MaterialComponents.Chip.Action android:layout_widthwrap_content android:layout_heightwrap_content android:text分享结果 app:chipIcondrawable/ic_share // 操作图标很常见 /监听事件chip.setOnClickListener { // 执行分享操作 performShareAction() }3.5 Chip 的通用属性与自定义无论哪种类型的 Chip都共享一系列强大的属性来控制其外观文本与图标android:text主文本。app:chipIcon设置左侧的图标通常是一个小头像或标志。app:closeIcon设置输入型 Chip 的删除图标默认为“X”。app:checkedIcon设置选中状态下显示的图标Filter/Choice Chip 用。形状与大小app:chipCornerRadius控制圆角半径实现“胶囊”形状。app:chipMinHeight最小高度。颜色app:chipBackgroundColor背景颜色ColorStateList支持不同状态。app:chipStrokeColor边框颜色ColorStateList。app:chipStrokeWidth边框宽度。app:textStartPadding/app:textEndPadding文本内边距。交互app:checkable定义 Chip 是否可被选中对于 Filter/Choice Chip 应为 true。app:closeIconVisible控制删除图标是否可见Input Chip 用。深度自定义示例创建一个带有渐变边框的 Filter Chip这需要结合 ShapeAppearance 和 ColorStateList。首先定义一个渐变的边框颜色选择器gradient_stroke_selector.xml这里简化用纯色示例和形状!-- res/drawable/chip_stroke_background.xml -- shape xmlns:androidhttp://schemas.android.com/apk/res/android stroke android:width2dp android:colorcolor/purple_500 / corners android:radius16dp / !-- 匹配 chipCornerRadius -- solid android:colorandroid:color/transparent / /shape然后在 Chip 上应用com.google.android.material.chip.Chip stylestyle/Widget.MaterialComponents.Chip.Filter android:layout_widthwrap_content android:layout_heightwrap_content android:text自定义边框 app:checkabletrue app:chipStrokeColorcolor/purple_500 app:chipStrokeWidth2dp app:chipCornerRadius16dp /更复杂的渐变需要使用android:gradient并可能需在代码中动态创建GradientDrawable。4. ChipGroup 的布局管理与单选控制单个 Chip 很有用但 Chip 的真正威力在于成群出现。ChipGroup是一个特殊的FlowLayout它专门用于排列多个Chip子视图并提供了两个核心功能流式布局和单选行为管理。4.1 流式布局自动换行的容器ChipGroup默认继承自FlowLayout这意味着其内部的 Chip 会从左到右排列当一行放不下时会自动换到下一行。这非常适合处理数量动态变化、长度不一的标签集合。基本用法com.google.android.material.chip.ChipGroup android:idid/chip_group_tags android:layout_widthmatch_parent android:layout_heightwrap_content app:singleLinefalse !-- 允许多行默认为 false -- app:singleSelectionfalse !-- 默认 false允许多选 -- com.google.android.material.chip.Chip stylestyle/Widget.MaterialComponents.Chip.Filter android:layout_widthwrap_content android:layout_heightwrap_content android:textAndroid / com.google.android.material.chip.Chip stylestyle/Widget.MaterialComponents.Chip.Filter android:layout_widthwrap_content android:layout_heightwrap_content android:textKotlin / !-- 可以动态添加更多 Chip -- /com.google.android.material.chip.ChipGroup关键布局属性app:singleLine如果设为true则所有 Chip 会挤在一行超出部分不可见。通常保持false。app:chipSpacingHorizontal/app:chipSpacingVertical分别控制 Chip 之间的水平和垂直间距。你可以用?attr/chipSpacing来引用主题中定义的全局间距。android:layout_gravity/app:alignmentMode可以控制 Chip 在组内的对齐方式如center_horizontal,center等。4.2 单选行为管理实现互斥选择这是ChipGroup最常用的功能之一尤其是配合Choice Chip。通过设置app:singleSelectiontrueChipGroup会确保其内部所有checkabletrue的 Chip 中有且只有一个处于选中状态。实现单选筛选的完整示例 假设我们有一个排序选项组。com.google.android.material.chip.ChipGroup android:idid/chip_group_sort android:layout_widthmatch_parent android:layout_heightwrap_content app:singleSelectiontrue !-- 关键启用单选模式 -- app:selectionRequiredfalse !-- 是否必须选中一个false表示可以不选 -- com.google.android.material.chip.Chip stylestyle/Widget.MaterialComponents.Chip.Choice android:layout_widthwrap_content android:layout_heightwrap_content android:text最新 android:checkedtrue / !-- 默认选中 -- com.google.android.material.chip.Chip stylestyle/Widget.MaterialComponents.Chip.Choice android:layout_widthwrap_content android:layout_heightwrap_content android:text最热 / com.google.android.material.chip.Chip stylestyle/Widget.MaterialComponents.Chip.Choice android:layout_widthwrap_content android:layout_heightwrap_content android:text价格 / /com.google.android.material.chip.ChipGroup在代码中获取选中的 Chipval chipGroup findViewByIdChipGroup(R.id.chip_group_sort) // 方法1设置组级别的监听器推荐 chipGroup.setOnCheckedStateChangeListener { group, checkedIds - // checkedIds 是一个 ListInt但在 singleSelectiontrue 时它通常只有一个元素 val selectedChipId checkedIds.firstOrNull() selectedChipId?.let { id - val selectedChip group.findViewByIdChip(id) val sortBy selectedChip.text.toString() applySorting(sortBy) } // 如果 selectionRequiredfalse 且用户取消了所有选择checkedIds 会是空的 if (checkedIds.isEmpty()) { clearSorting() } } // 方法2也可以通过遍历查找 val checkedChipId chipGroup.checkedChipId // 返回选中的 Chip 的 ID如果没有选中则返回 View.NO_ID if (checkedChipId ! View.NO_ID) { val selectedChip chipGroup.findViewByIdChip(checkedChipId) // ... 处理选中项 }重要提示app:singleSelectiontrue只对ChipGroup内的 Chip 有效。如果你有多个ChipGroup它们之间的选择是独立的。app:selectionRequiredfalse允许用户取消所有选择例如再次点击已选中的 Chip 会取消选中这在某些场景下是需要的。4.3 动态管理 ChipGroup 中的 Chip在实际开发中Chip 的数据往往来自网络或数据库需要动态增删。动态添加 Chipfun addChipsToGroup(tags: ListString, chipGroup: ChipGroup) { chipGroup.removeAllViews() // 清空现有 Chip tags.forEach { tagName - val chip Chip(chipGroup.context).apply { layoutParams ChipGroup.LayoutParams( ChipGroup.LayoutParams.WRAP_CONTENT, ChipGroup.LayoutParams.WRAP_CONTENT ) text tagName style R.style.Widget_MaterialComponents_Chip_Filter // 通过资源 ID 设置样式 isCheckable true // 设置其他属性... } chip.setOnCheckedChangeListener { buttonView, isChecked - // 处理单个 Chip 的选中变化 } chipGroup.addView(chip) } }动态移除 Chip 对于 Input Chip用户点击关闭图标时你需要从ChipGroup和你的数据源中移除它。chip.setOnCloseIconClickListener { chipGroup.removeView(it) // 从视图层级移除 dataList.remove(tagAssociatedWithChip) // 从数据模型移除 // 可选通知适配器或更新 UI }4.4 常见问题与性能考量Chip 数量过多如果一个ChipGroup内可能有成百上千个 Chip比如所有用户标签一次性创建所有视图会导致性能问题内存和布局计算。解决方案是使用RecyclerView配合FlexboxLayoutManager来自com.google.android.flexbox:flexbox库来替代ChipGroup实现 Chip 的流式布局和视图复用。// 在 RecyclerView 中使用 FlexboxLayoutManager val layoutManager FlexboxLayoutManager(context) layoutManager.flexDirection FlexDirection.ROW layoutManager.flexWrap FlexWrap.WRAP recyclerView.layoutManager layoutManager recyclerView.adapter MyChipAdapter(tagList)在 Adapter 中每个 Item 都是一个Chip。Chip 状态保存在配置变更如屏幕旋转或页面重建时ChipGroup中 Chip 的选中状态需要保存。如果 Chip 是静态写在 XML 中的并且设置了android:id系统会自动保存其checked状态。如果是动态添加的你需要自己在onSaveInstanceState和onRestoreInstanceState中手动保存和恢复选中 Chip 的 ID 或位置信息。与 EditText 结合实现“输入型 Chip”常见的模式是在EditText上方或下方放置一个ChipGroup用于显示已输入的标签。当用户在EditText中输入内容并按下逗号、回车或特定按钮时动态创建一个 Input Chip 添加到ChipGroup中并清空EditText。这需要处理文本输入监听和 Chip 的动态添加逻辑。ChipGroup 内嵌其他视图虽然ChipGroup设计用来放Chip但它本质上是一个ViewGroup。理论上你可以放其他视图进去但这可能会破坏其布局逻辑和单选管理功能不推荐这样做。5. 实战构建一个完整的标签筛选与管理系统让我们结合SwitchMaterial、Chip和ChipGroup构建一个模拟电商商品筛选侧边栏的完整示例。这个示例将包含一个全局开关SwitchMaterial控制是否只显示有货商品。一个品牌单选选择区ChipGroupChoice Chip。一个标签多选筛选区ChipGroupFilter Chip。一个已选标签展示区ChipGroupInput Chip展示已选的多选标签并可删除。1. 布局文件 (activity_filter_demo.xml) 概览?xml version1.0 encodingutf-8? LinearLayout xmlns:androidhttp://schemas.android.com/apk/res/android xmlns:apphttp://schemas.android.com/apk/res-auto android:layout_widthmatch_parent android:layout_heightmatch_parent android:orientationvertical android:padding16dp !-- 1. 有货开关 -- TextView android:layout_widthwrap_content android:layout_heightwrap_content android:text库存筛选 android:textAppearance?attr/textAppearanceSubtitle1 / com.google.android.material.switchmaterial.SwitchMaterial android:idid/switch_in_stock android:layout_widthwrap_content android:layout_heightwrap_content android:text仅显示有货 app:useMaterialThemeColorstrue / View stylestyle/Divider / !-- 2. 品牌单选 -- TextView android:layout_widthwrap_content android:layout_heightwrap_content android:text品牌 android:textAppearance?attr/textAppearanceSubtitle1 / com.google.android.material.chip.ChipGroup android:idid/chip_group_brand android:layout_widthmatch_parent android:layout_heightwrap_content app:singleSelectiontrue app:selectionRequiredtrue !-- Chip 可以通过代码动态添加这里写死示例 -- com.google.android.material.chip.Chip stylestyle/Widget.MaterialComponents.Chip.Choice ... android:text品牌A / com.google.android.material.chip.Chip stylestyle/Widget.MaterialComponents.Chip.Choice ... android:text品牌B / com.google.android.material.chip.Chip stylestyle/Widget.MaterialComponents.Chip.Choice ... android:text品牌C / /com.google.android.material.chip.ChipGroup View stylestyle/Divider / !-- 3. 标签多选 -- TextView android:layout_widthwrap_content android:layout_heightwrap_content android:text商品标签 android:textAppearance?attr/textAppearanceSubtitle1 / com.google.android.material.chip.ChipGroup android:idid/chip_group_all_tags android:layout_widthmatch_parent android:layout_heightwrap_content app:singleSelectionfalse com.google.android.material.chip.Chip stylestyle/Widget.MaterialComponents.Chip.Filter ... android:text包邮 / com.google.android.material.chip.Chip stylestyle/Widget.MaterialComponents.Chip.Filter ... android:text新品 / com.google.android.material.chip.Chip stylestyle/Widget.MaterialComponents.Chip.Filter ... android:text促销 / com.google.android.material.chip.Chip stylestyle/Widget.MaterialComponents.Chip.Filter ... android:text耐用 / /com.google.android.material.chip.ChipGroup View stylestyle/Divider / !-- 4. 已选标签展示区 -- TextView android:layout_widthwrap_content android:layout_heightwrap_content android:text已选标签 android:textAppearance?attr/textAppearanceSubtitle1 / com.google.android.material.chip.ChipGroup android:idid/chip_group_selected_tags android:layout_widthmatch_parent android:layout_heightwrap_content app:singleSelectionfalse / !-- 这里初始为空将通过代码动态添加 Input Chip -- Button android:idid/btn_apply_filter android:layout_widthmatch_parent android:layout_heightwrap_content android:layout_marginTop24dp android:text应用筛选 / /LinearLayout2. 在 Activity/Fragment 中的逻辑处理class FilterDemoActivity : AppCompatActivity() { private lateinit var binding: ActivityFilterDemoBinding private val selectedFilterTags mutableSetOfString() // 存储已选的多选标签 override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) binding ActivityFilterDemoBinding.inflate(layoutInflater) setContentView(binding.root) // 1. 监听有货开关 binding.switchInStock.setOnCheckedChangeListener { _, isChecked - // 这里可以实时响应也可以等点击“应用”按钮再统一处理 Log.d(Filter, 仅显示有货: $isChecked) } // 2. 监听品牌单选组 binding.chipGroupBrand.setOnCheckedStateChangeListener { group, checkedIds - checkedIds.firstOrNull()?.let { checkedId - val selectedChip group.findViewByIdChip(checkedId) Log.d(Filter, 选中品牌: ${selectedChip.text}) } } // 3. 监听标签多选组并同步到已选展示区 // 假设所有 Filter Chip 已经预先在 XML 中定义好了 // 我们需要为每一个 Chip 设置监听器 for (i in 0 until binding.chipGroupAllTags.childCount) { val chip binding.chipGroupAllTags.getChildAt(i) as Chip chip.setOnCheckedChangeListener { buttonView, isChecked - val tagText buttonView.text.toString() if (isChecked) { selectedFilterTags.add(tagText) addSelectedTagChip(tagText) // 添加到展示区 } else { selectedFilterTags.remove(tagText) removeSelectedTagChip(tagText) // 从展示区移除 } } } // 4. 应用筛选按钮 binding.btnApplyFilter.setOnClickListener { applyAllFilters() } } private fun addSelectedTagChip(tag: String) { // 避免重复添加 if (binding.chipGroupSelectedTags.findViewWithTagView(tag) ! null) return val inputChip Chip(this).apply { text tag style R.style.Widget_MaterialComponents_Chip_Input isCloseIconVisible true tag tag // 设置 Tag方便后续查找移除 } inputChip.setOnCloseIconClickListener { // 点击删除图标时不仅要移除这个 Input Chip还要取消对应 Filter Chip 的选中状态 binding.chipGroupSelectedTags.removeView(it) selectedFilterTags.remove(tag) // 找到对应的 Filter Chip 并取消选中 for (i in 0 until binding.chipGroupAllTags.childCount) { val filterChip binding.chipGroupAllTags.getChildAt(i) as Chip if (filterChip.text.toString() tag) { filterChip.isChecked false break } } } binding.chipGroupSelectedTags.addView(inputChip) } private fun removeSelectedTagChip(tag: String) { val chipToRemove binding.chipGroupSelectedTags.findViewWithTagChip(tag) chipToRemove?.let { binding.chipGroupSelectedTags.removeView(it) } } private fun applyAllFilters() { val inStockOnly binding.switchInStock.isChecked val selectedBrandId binding.chipGroupBrand.checkedChipId val selectedBrand if (selectedBrandId ! View.NO_ID) { (binding.chipGroupBrand.findViewByIdChip(selectedBrandId)).text.toString() } else { null } val selectedTags selectedFilterTags.toList() // 构建筛选参数对象传递给 ViewModel 或发起网络请求 val filterParams FilterParams(inStockOnly, selectedBrand, selectedTags) Log.d(Filter, 应用筛选: $filterParams) // viewModel.applyFilters(filterParams) // 或者 startActivity(ResultActivity.newIntent(filterParams)) } } data class FilterParams( val inStockOnly: Boolean, val brand: String?, val tags: ListString )这个实战示例涵盖了从布局到交互的完整链路展示了如何将这三个组件有机结合起来构建一个功能清晰、交互流畅的筛选界面。关键点在于理解不同 Chip 类型的语义并妥善管理它们之间的状态同步例如Filter Chip 的选中状态与 Input Chip 的增删联动。
返回列表