ARTICLE DETAIL

建站实战干货

来自一线的建站与推广经验沉淀,每一条都经过真实交付验证。

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

2026/9/20 19:58:26 拓冰建站 浏览量
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 项变更新增md_line_spacing_body全局主题属性用于统一设置消息行距对应 issue #1903为选择列表适配器增加断言与健全性检查避免越界导致崩溃#1906圆角不再应用到底部弹窗的底部边缘#1941修复自定义字体下对话框标题被截断的问题#1936当customView(...)设置了noVerticalPadding且scrollable开启时内容ScrollView底部不再被追加内边距#1834输入对话框不再由对话框自身强制样式而是改用全局TextInputLayout的textInputStyle默认值#1857。下面逐一结合源码深入说明。新增全局主题属性md_line_spacing_body#1903属性定义与默认值md_line_spacing_body是一个float类型的主题属性定义在 core/src/main/res-public/values/public.xmlattr formatfloat namemd_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.1setLineSpacing(0f, multiplier)的 multiplier 语义。调用方优先级didSetLineSpacing标志保证了如果调用方通过 lineSpacing(multiplier: Float) 显式设置过行距则全局主题默认值不会覆盖显式设置fun lineSpacing(multiplier: Float): DialogMessageSettings { didSetLineSpacing true messageTextView.setLineSpacing(0f, multiplier) return this }实际使用方式在 App 主题中声明该属性即可让所有对话框消息统一行距无需逐次调用lineSpacing(...)style nameAppTheme parentTheme.MaterialComponents.DayNight item namemd_line_spacing_body1.4/item /style若某个对话框需要特殊行距仍可通过message { lineSpacing(1.6f) }按需覆盖。该改动把「行距」从逐对话框样板代码提升为全局主题能力是从「编程式配置」走向「主题式配置」的典型模式。选择列表适配器的越界断言与健全性检查#1906问题背景当开发者在列表显示后调用checkItems(...)/uncheckItems(...)等方法且传入的索引超出当前items集合范围时旧版本可能抛出难以定位的数组越界或元素找不到异常。3.3.0 在适配器层加入了显式断言将错误提前暴露并给出可读信息。多选适配器的实现在 MultiChoiceDialogAdapter.kt 中checkItems与uncheckItems都加入了同样的范围校验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 复用过程中adapterPosition为NO_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显式将底部两角置为 0override 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.ktfun 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时自定义视图会被放进对话框的ScrollViewDialogContentLayout.kt。旧逻辑中即使设置了noVerticalPaddingScrollView底部仍会保留md_dialog_frame_margin_vertical的内边距导致「无垂直内边距」的承诺无法兑现。修复后的行为addContentScrollView在创建滚动容器时读取该开关DialogContentLayout.ktprivate fun addContentScrollView( noVerticalPadding: Boolean ) { if (scrollView null) { scrollView inflateDialogScrollView(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。随后styleInputDialogInputExt.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.ktmaxLength仍通过isCounterEnabled与counterMaxLength启用字数计数器。小结与升级建议3.3.0 的六项改动可以归纳为三条主线主题化能力增强md_line_spacing_body让消息行距进入全局主题体系与既有的md_font_body、md_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点击查看免费下载相关推荐告别繁琐弹窗开发Material Dialogs Bottom Sheets 快速实现优雅底部交互告别繁琐弹窗开发Material Dialogs Bottom Sheets 快速实现优雅底部交互 还在为Android底部弹窗适配头疼Material DUI组件移动开发攻克Material Dialogs布局难题内边距与外边距全解析攻克Material Dialogs布局难题内边距与外边距全解析 你是否还在为对话框布局错位而头疼是否因边距设置不当导致UI在不同设备上显示异常本文将带你UI组件移动开发BottomSheet 项目推荐Android 底部弹窗组件的革命性选择BottomSheet 项目推荐Android 底部弹窗组件的革命性选择 痛点传统对话框的局限性 在 Android 应用开发中对话框Dialog和菜UI组件上一篇Buzz 上手指南本地离线转录免费把录音转成文字和字幕下一篇离线音频转录完全指南Buzz如何在本地保护你的数据创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考