news 2026/9/20 19:58:21

material-dialogs 3.3.0 发布详解:消息行距主题属性、选择列表防护与底部弹窗修复

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
material-dialogs 3.3.0 发布详解:消息行距主题属性、选择列表防护与底部弹窗修复
  • UI组件
  • 移动开发

【免费下载链接】material-dialogs

😍 A beautiful, fluid, and extensible dialogs API for Kotlin & Android.

项目地址:https://gitcode.com/gh_mirrors/ma/material-dialogs
点击查看免费下载

导读

本文基于 RELEASE_NOTES.md 中的 3.3.0 版本发布说明,逐条拆解该版本引入的 6 项功能变更与缺陷修复:新增全局消息行距主题属性、为选择列表适配器补充越界断言、修正底部弹窗圆角绘制、修复自定义字体下的标题截断、调整自定义视图垂直内边距逻辑,以及让输入对话框遵循全局textInputStyle。每一条变更都结合本仓库 core / bottomsheets / input 模块的源码实现展开,读者可据此理解这些改动背后的设计意图,并为自己的主题定制、列表数据变更和自定义视图布局实践提供可直接借鉴的源码级方案。

版本总览:3.3.0 改了什么

3.3.0 是一个以「稳定性与主题一致化」为主基调的小版本,共包含 6 项变更:

  1. 新增md_line_spacing_body全局主题属性,用于统一设置消息行距(对应 issue #1903);
  2. 为选择列表适配器增加断言与健全性检查,避免越界导致崩溃(#1906);
  3. 圆角不再应用到底部弹窗的底部边缘(#1941);
  4. 修复自定义字体下对话框标题被截断的问题(#1936);
  5. customView(...)设置了noVerticalPaddingscrollable开启时,内容ScrollView底部不再被追加内边距(#1834);
  6. 输入对话框不再由对话框自身强制样式,而是改用全局TextInputLayouttextInputStyle默认值(#1857)。

下面逐一结合源码深入说明。

新增全局主题属性md_line_spacing_body(#1903)

属性定义与默认值

md_line_spacing_body是一个float类型的主题属性,定义在 core/src/main/res-public/values/public.xml:

<attr format="float" name="md_line_spacing_body"/>

它被放置在res-public中,意味着该属性属于库对外暴露的公共 API,主题定制者可以在自己的 App 主题中覆盖它。

在消息设置中的消费逻辑

属性消费逻辑位于 DialogMessageSettings.kt 的setText方法中:

internal fun setText( @StringRes res: Int?, text: CharSequence? ) { if (!didSetLineSpacing) { lineSpacing( resolveFloat( context = dialog.windowContext, attr = R.attr.md_line_spacing_body, defaultValue = 1.1f ) ) } messageTextView.text = text.maybeWrapHtml(isHtml) ?: resolveString(dialog, res, html = isHtml) }

这里有两个关键设计点:

  • 默认值 1.1f:当主题中没有显式配置md_line_spacing_body时,消息行距取 1.1(setLineSpacing(0f, multiplier)的 multiplier 语义)。
  • 调用方优先级didSetLineSpacing标志保证了如果调用方通过 lineSpacing(multiplier: Float) 显式设置过行距,则全局主题默认值不会覆盖显式设置:
fun lineSpacing(multiplier: Float): DialogMessageSettings { didSetLineSpacing = true messageTextView.setLineSpacing(0f, multiplier) return this }

实际使用方式

在 App 主题中声明该属性即可让所有对话框消息统一行距,无需逐次调用lineSpacing(...)

<style name="AppTheme" parent="Theme.MaterialComponents.DayNight"> <item name="md_line_spacing_body">1.4</item> </style>

若某个对话框需要特殊行距,仍可通过message { lineSpacing(1.6f) }按需覆盖。该改动把「行距」从逐对话框样板代码提升为全局主题能力,是从「编程式配置」走向「主题式配置」的典型模式。

选择列表适配器的越界断言与健全性检查(#1906)

问题背景

当开发者在列表显示后调用checkItems(...)/uncheckItems(...)等方法,且传入的索引超出当前items集合范围时,旧版本可能抛出难以定位的数组越界或元素找不到异常。3.3.0 在适配器层加入了显式断言,将错误提前暴露并给出可读信息。

多选适配器的实现

在 MultiChoiceDialogAdapter.kt 中,checkItemsuncheckItems都加入了同样的范围校验:

override fun checkItems(indices: IntArray) { val existingSelection = this.currentSelection val indicesToAdd = indices.filter { check(it >= 0 && it < items.size) { "Index $it is out of range for this adapter of ${items.size} items." } !existingSelection.contains(it) } this.currentSelection = this.currentSelection.appendAll(indicesToAdd) ... } override fun uncheckItems(indices: IntArray) { val existingSelection = this.currentSelection val indicesToAdd = indices.filter { check(it >= 0 && it < items.size) { "Index $it is out of range for this adapter of ${items.size} items." } existingSelection.contains(it) } ... }

采用checkIllegalStateException)而非静默忽略,是因为越界索引几乎总是调用方 bug——提前在失败现场抛出带上下文(期望索引 vs 实际条目数)的异常,比后续在notifyItemChanged处崩溃更容易排查。

单选适配器的实现

SingleChoiceDialogAdapter.kt 中的checkItems/uncheckItems同样增加了断言:

override fun checkItems(indices: IntArray) { val targetIndex = if (indices.isNotEmpty()) indices[0] else -1 check(targetIndex >= 0 && targetIndex < items.size) { "Index $targetIndex is out of range for this adapter of ${items.size} items." } if (this.disabledIndices.contains(targetIndex)) return this.currentSelection = targetIndex }

点击事件侧的防护

除 API 入口外,两个适配器的 ViewHolder 点击回调也都加入了if (adapterPosition < 0) return防护(见 MultiChoiceDialogAdapter.kt 与 SingleChoiceDialogAdapter.kt),避免 RecyclerView 复用过程中adapterPositionNO_POSITION(-1)时触发越界访问:

override fun onClick(view: View) { if (adapterPosition < 0) return adapter.itemClicked(adapterPosition) }

对使用replaceItems(...)动态更新条目、随后再以旧索引操作选中的场景,这一组检查能显著降低运行时崩溃概率。

底部弹窗:圆角不再应用到底部边缘(#1941)

背景

Material 底部弹窗(Bottom Sheet)本质上是「贴底浮出」的面板,其视觉语义要求底部边缘与屏幕底部对齐,不应出现圆角;此前版本在部分场景下底部两角仍会绘制圆角,与预期不符。

修复后的圆角绘制

BottomSheet行为类在 BottomSheet.kt 的setBackgroundColor中,通过GradientDrawable.cornerRadii显式将底部两角置为 0:

override fun setBackgroundColor( view: DialogLayout, color: Int, cornerRadius: Float ) { bottomSheetView.background = GradientDrawable().apply { this.cornerRadii = floatArrayOf( cornerRadius, cornerRadius, // top left cornerRadius, cornerRadius, // top right 0f, 0f, // bottom left 0f, 0f // bottom right ) setColor(color) } buttonsLayout.setBackgroundColor(color) }

与模态对话框的对比

对比常规模态对话框ModalDialog的同一方法(DialogBehavior.kt),其cornerRadii同样是「顶部两角圆角、底部两角 0」,但原因不同——模态对话框通常不会将背景一直延伸到屏幕底部,四角本就走顶部圆角方案。两处的cornerRadii最终都会传递到根布局 DialogLayout,在onMeasure中构建addRoundRect路径,并在dispatchDraw中通过canvas.clipPath(cornerRadiusPath)裁剪绘制。

这一改动确保底部弹窗无论配置多大的md_corner_radius或调用 cornerRadius(...),贴底的一侧都保持直角,符合 Material Design 对底部面板的视觉规范。

修复自定义字体下标题被截断(#1936)

问题根因

当通过md_font_title(解析逻辑见 MaterialDialog.kt)配置非常规字体时,部分字体的fontMetrics上下跨度(ascent/descent)会大于系统默认字体,导致按默认字体计算的标题高度在底部被截断。

修复实现:动态计算额外内边距

MDUtil.kt 中新增了additionalPaddingForFont()辅助方法,其源码注释直接引用了该 issue 编号:

/** * See [https://github.com/afollestad/material-dialogs/issues/1936]. Calculates additional * spacing required to prevent a given [TextView] from being cut off at the bottom. */ @RestrictTo(LIBRARY_GROUP) fun TextView.additionalPaddingForFont(): Int { val fm = paint.fontMetrics val textHeight = fm.descent - fm.ascent return if (textHeight > measuredHeight) (textHeight - measuredHeight).toInt() else 0 }

原理:用paint.fontMetrics计算当前字体实际需要的文本高度(descent - ascent),若大于 TextView 的measuredHeight,则返回差值作为额外空间。

在标题布局中的接入

DialogTitleLayout.kt 的onLayout中,标题底部位置加入了这一补偿值:

val titleTop = contentMidPoint - titleHalfHeight val titleBottom = contentMidPoint + titleHalfHeight + titleView.additionalPaddingForFont()

由于该补偿仅在字体实际需求超出测量高度时生效(否则返回 0),对默认字体场景零开销、零影响,实现了「按需补偿」的最小侵入式修复。

customView 的 noVerticalPadding 与滚动容器内边距(#1834)

参数语义

customView(...) 提供noVerticalPadding参数,官方 KDoc 描述为「为 true 时,内容四周不添加垂直内边距」。该参数同时被写入配置键CUSTOM_VIEW_NO_VERTICAL_PADDING(见 DialogCustomViewExt.kt):

fun MaterialDialog.customView( @LayoutRes viewRes: Int? = null, view: View? = null, scrollable: Boolean = false, noVerticalPadding: Boolean = false, horizontalPadding: Boolean = false, dialogWrapContent: Boolean = false ): MaterialDialog { assertOneSet("customView", view, viewRes) config[CUSTOM_VIEW_NO_VERTICAL_PADDING] = noVerticalPadding ... }

修复前的问题

scrollable = true时,自定义视图会被放进对话框的ScrollView(DialogContentLayout.kt)。旧逻辑中即使设置了noVerticalPaddingScrollView底部仍会保留md_dialog_frame_margin_vertical的内边距,导致「无垂直内边距」的承诺无法兑现。

修复后的行为

addContentScrollView在创建滚动容器时读取该开关(DialogContentLayout.kt):

private fun addContentScrollView( noVerticalPadding: Boolean ) { if (scrollView == null) { scrollView = inflate<DialogScrollView>(R.layout.md_dialog_stub_scrollview).apply { this.rootView = rootLayout scrollFrame = this.getChildAt(0) as ViewGroup if (!noVerticalPadding) { val scrollBottomPadding = dimenPx(R.dimen.md_dialog_frame_margin_vertical) updatePadding(bottom = scrollBottomPadding) } } addView(scrollView) } }

即:只有noVerticalPadding = false时才给滚动容器追加底部内边距;为 true 时完全跳过。对于「自定义视图自带底部留白」或「需要贴边滚动到底」的布局,现在可以真正实现无缝衔接。

输入对话框样式改由全局 textInputStyle 控制(#1857)

变更意图

此前输入对话框会由库强制应用自己的输入样式,导致开发者在 App 全局配置的TextInputLayout样式(textInputStyle)无法生效。3.3.0 之后,库不再强制覆盖,输入框外观完全遵循宿主 App 的主题默认值,保持与 App 其余输入框一致。

源码佐证

input() 通过customView(R.layout.md_dialog_stub_input)将 md_dialog_stub_input.xml 作为自定义视图载入,其内部包含TextInputLayout。随后styleInput(DialogInputExt.kt)只负责设置 hint、输入类型、文本/提示文字颜色与 bodyFont,不再触碰输入框的容器样式:

private fun MaterialDialog.styleInput( hint: String?, hintRes: Int?, inputType: Int ) { val resources = windowContext.resources val editText = getInputField() getInputLayout().hint = hint ?: if (hintRes != null) resources.getString(hintRes) else null editText.inputType = inputType editText.maybeSetTextColor( windowContext, attrRes = R.attr.md_color_content, hintAttrRes = R.attr.md_color_hint ) bodyFont?.let(editText::setTypeface) }

对使用者的影响

  • 若你的 App 已在主题中配置textInputStyle(如自定义填充色、圆角、字体),升级到 3.3.0 后对话框输入框会自动跟随,无需额外代码。
  • 若需要为某个输入对话框单独定制,仍可通过 getInputLayout() 拿到TextInputLayout实例,在其上继续做局部定制。
  • 同时,input(...)保留的hint/hintRes/prefill/maxLength/waitForPositiveButton/allowEmpty等参数语义均未改变(DialogInputExt.kt),maxLength仍通过isCounterEnabledcounterMaxLength启用字数计数器。

小结与升级建议

3.3.0 的六项改动可以归纳为三条主线:

  • 主题化能力增强md_line_spacing_body让消息行距进入全局主题体系,与既有的md_font_bodymd_color_content等属性形成一致的主题矩阵;输入框则让位于 App 全局textInputStyle,进一步减少库对视觉的强占。
  • 健壮性提升:选择列表适配器的显式越界断言与 ViewHolder 的adapterPosition防护,把「静默异常」变为「带上下文的即时异常」,尤其利好动态replaceItems+ 程序化选中的场景。
  • 布局细节修正:底部弹窗底部圆角归零、自定义字体标题动态补偿高度、noVerticalPadding对滚动容器真正生效,三处都属于对视觉与测量细节的打磨。

对于已经使用 material-dialogs 的项目,建议升级后在以下位置做一次回归验证:带自定义md_font_title的对话框标题显示、scrollable + noVerticalPadding的自定义视图底部间距、底部弹窗在自定义md_corner_radius下的四角表现,以及 App 全局textInputStyle在输入对话框中的生效情况。相关实现细节均可直接阅读 core、bottomsheets 与 input 模块的对应源码文件。

  • UI组件
  • 移动开发

【免费下载链接】material-dialogs

😍 A beautiful, fluid, and extensible dialogs API for Kotlin & Android.

项目地址:https://gitcode.com/gh_mirrors/ma/material-dialogs
点击查看免费下载
上一篇:Buzz 上手指南:本地离线转录,免费把录音转成文字和字幕
下一篇:离线音频转录完全指南:Buzz如何在本地保护你的数据

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/9/20 19:56:57

Linux二级文件系统课程设计:用户态模拟磁盘与inode位图管理

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/9/20 19:55:39

信创服务器麒麟操作系统配置与管理实战经验

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/9/20 19:55:22

Page Assist:本地AI浏览器助手,让每个网页都能直接提问

Page Assist&#xff1a;本地AI浏览器助手&#xff0c;让每个网页都能直接提问 【免费下载链接】page-assist Use your locally running AI models to assist you in your web browsing 项目地址: https://gitcode.com/GitHub_Trending/pa/page-assist Page Assist 是一…

作者头像 李华
网站建设 2026/9/20 19:51:52

基于1:3电流镜的轨到轨输入级设计:从恒定跨导原理到仿真与版图实战

1. 轨到轨输入级到底难在哪&#xff1a;从恒定跨导说起做模拟IC设计的人&#xff0c;迟早会碰到轨到轨输入级这个坎。单差分对够用的时候谁都不想折腾&#xff0c;可一旦供电电压降到1.8V甚至更低&#xff0c;而信号摆幅又要求贴着两条轨走&#xff0c;单对差分对的共模输入范围…

作者头像 李华
网站建设 2026/9/20 19:50:35

基于FPGA的微波炉控制器设计与实现:并行时序逻辑实战

简介&#xff1a;一份基于FPGA与VHDL的微波炉控制器设计文档&#xff0c;面向电子、通信、自动化等专业学生及嵌入式系统入门开发者&#xff0c;系统解决家用微波炉工作流程控制、定时与显示等功能设计问题。文档完整呈现FPGA开发全流程&#xff1a;先从需求出发编写VHDL底层模…

作者头像 李华
网站建设 2026/9/20 19:50:33

STM32F103裸机移植LVGL 8.2驱动7寸屏完整指南

先说结论&#xff1a;STM32F103裸机跑LVGL 8.2、带一块7寸800x480的屏幕&#xff0c;这事能成&#xff0c;而且没有想象中那么恐怖。网上搜F103LVGL&#xff0c;十个帖子有八个在劝退&#xff0c;理由不外乎内存不够、没有FPU、刷新跟不上。这些理由在F407上成立&#xff0c;放…

作者头像 李华