Android Material Design兼容库实战指南
1. Material Design兼容库的核心价值解析作为Android开发者我们经常面临一个现实困境如何在旧版系统上实现现代化的Material Design界面这正是Material Design兼容库Design Support Library现为Material Components库存在的意义。我曾在2016年首次接触这个库当时为了在Android 4.4设备上实现FloatingActionButton传统方案需要自定义ViewGroup和复杂的动画逻辑而兼容库仅需几行XML就解决了问题。Material Design兼容库本质上是一套向后移植的UI组件集合它将LollipopAPI 21引入的Material Design特性向下兼容至Android 2.1API 7。最新版的Material Components for Android1.5.0更是将Material 3的设计语言带到了旧平台。在实际项目中这意味着统一的设计语言从API 7到最新版本保持一致的视觉体验减少样板代码不再需要为不同API级别编写条件分支平滑过渡内置符合Material规范的转场动画和触摸反馈主题继承通过Theme.AppCompat实现全局样式控制关键提示从Android Studio 4.2开始新建项目默认使用Material Components替代旧的Design Support Library。如果遇到遗留项目建议逐步迁移至com.google.android.material:material当前稳定版为1.9.02. 环境配置与基础集成2.1 依赖配置的演进史早期2015-2017的兼容库配置相当分散开发者需要分别引入多个子模块implementation com.android.support:design:28.0.0 implementation com.android.support:cardview-v7:28.0.0 implementation com.android.support:recyclerview-v7:28.0.0如今Material Components库采用单体依赖模式大幅简化了配置dependencies { implementation com.google.android.material:material:1.9.0 // 必须搭配兼容版AppCompat使用 implementation androidx.appcompat:appcompat:1.6.1 }这里有个重要细节Material库版本号应与AppCompat保持同步。例如material:1.9.0对应appcompat:1.6.1这是Google推荐的版本对应关系。我曾因版本不匹配导致TextInputLayout的样式异常排查了整整两天。2.2 主题配置的黄金法则正确的主题继承链是Material Design兼容性的核心。以下是经过实战验证的配置方案!-- styles.xml -- style nameAppTheme parentTheme.MaterialComponents.DayNight.NoActionBar !-- 基础色系 -- item namecolorPrimarycolor/purple_500/item item namecolorPrimaryVariantcolor/purple_700/item item namecolorOnPrimarycolor/white/item !-- 强调色 -- item namecolorSecondarycolor/teal_200/item item namecolorSecondaryVariantcolor/teal_700/item item namecolorOnSecondarycolor/black/item !-- 状态栏配置 -- item nameandroid:statusBarColor?attr/colorPrimaryVariant/item /style常见陷阱错误继承自Theme.AppCompat导致Material组件样式丢失忘记设置colorOnPrimary/colorOnSecondary导致文本可见性问题在API 21以下设备使用android:前缀的属性应使用无前缀版本3. 核心组件实战解析3.1 导航视图NavigationView的完全指南Material NavigationView替代了传统的DrawerLayoutListView方案但它的正确使用需要掌握几个关键点com.google.android.material.navigation.NavigationView android:idid/nav_view android:layout_widthwrap_content android:layout_heightmatch_parent android:layout_gravitystart app:headerLayoutlayout/nav_header app:menumenu/drawer_menu app:itemIconTintdrawable/nav_icon_tint app:itemTextColordrawable/nav_text_color app:itemShapeAppearanceOverlaystyle/NavItemShape/动态处理技巧// 动态更新菜单项 val navView findViewByIdNavigationView(R.id.nav_view) navView.menu.apply { add(动态项).setIcon(R.drawable.ic_new).setOnMenuItemClickListener { // 处理点击 true } } // 带徽章的菜单项 val badge navView.getOrCreateBadge(menuItemId) badge.backgroundColor Color.RED badge.number 99 badge.isVisible true性能提示避免在NavigationView中使用复杂布局headerLayout的高度建议不超过200dp。我曾遇到一个项目因header包含ViewPager导致滑动卡顿最终改用占位图点击展开的方案解决。3.2 TextInputLayout的进阶用法Material Design的文本输入框远不止浮动标签那么简单。完整实现应包含com.google.android.material.textfield.TextInputLayout stylestyle/Widget.MaterialComponents.TextInputLayout.OutlinedBox app:counterEnabledtrue app:counterMaxLength20 app:helperText请输入用户名 app:errorEnabledtrue app:boxStrokeErrorColorcolor/error_red com.google.android.material.textfield.TextInputEditText android:idid/username_input android:layout_widthmatch_parent android:layout_heightwrap_content android:hint用户名 android:inputTypetext/ /com.google.android.material.textfield.TextInputLayout动态验证逻辑val textInputLayout findViewByIdTextInputLayout(R.id.text_input_layout) val editText findViewByIdTextInputEditText(R.id.username_input) editText.doAfterTextChanged { text - when { text.isNullOrEmpty() - { textInputLayout.error 不能为空 textInputLayout.isErrorEnabled true } text.length 6 - { textInputLayout.error 至少6个字符 textInputLayout.isErrorEnabled true } else - { textInputLayout.isErrorEnabled false } } }样式自定义的深水区style nameCustomTextInputStyle parentWidget.MaterialComponents.TextInputLayout.OutlinedBox item nameboxStrokeColorcolor/selector_input_stroke/item item namehintTextColorcolor/selector_input_hint/item item nameboxCornerRadiusTopStart8dp/item item nameboxCornerRadiusTopEnd8dp/item item nameboxCornerRadiusBottomStart8dp/item item nameboxCornerRadiusBottomEnd8dp/item /style实测发现在API 22及以下设备boxCornerRadius需要额外配置backgroundTint才能正常生效这是旧版RenderThread的渲染限制。4. 动效与交互增强4.1 容器变换Container Transform的兼容实现Material Motion中的容器变换效果在兼容库中通过Transition框架实现// 在styles.xml中定义过渡样式 style nameContainerTransform parentstyle/Transition.MaterialContainerTransform item nametransitionDuration300/item item nametransitionDirectiontransitionDirectionStart/item item namescrimColorandroid:color/transparent/item item namedrawingViewIdid/nav_host_fragment/item /style // 代码中应用过渡 val fragment DetailsFragment() fragment.sharedElementEnterTransition MaterialContainerTransform().apply { drawingViewId R.id.nav_host_fragment duration 300L scrimColor Color.TRANSPARENT setAllContainerColors(requireContext().resolveColor(R.attr.colorSurface)) }低版本适配方案if (Build.VERSION.SDK_INT Build.VERSION_CODES.LOLLIPOP) { // 使用原生Material过渡 } else { // 降级为缩放动画 val scale ScaleAnimation(0.8f, 1f, 0.8f, 1f, Animation.RELATIVE_TO_SELF, 0.5f, Animation.RELATIVE_TO_SELF, 0.5f) scale.duration 300 view.startAnimation(scale) }4.2 触摸反馈的终极方案Material Design强调触摸反馈的即时性。兼容库提供两种实现方式前景波纹推荐Button android:foreground?attr/selectableItemBackgroundBorderless android:clickabletrue android:focusabletrue/背景波纹兼容旧版Button android:backgrounddrawable/ripple_effect android:clickabletrue/ !-- res/drawable/ripple_effect.xml -- ripple xmlns:androidhttp://schemas.android.com/apk/res/android android:color?attr/colorControlHighlight item android:idandroid:id/mask shape android:shaperectangle corners android:radius4dp / solid android:colorandroid:color/white / /shape /item /ripple性能陷阱避免在RecyclerView item中使用borderless波纹这会导致每次触摸都重建drawable在API 21以下ripple需要包裹在 中作为状态列表的pressed状态5. 深度兼容问题排查指南5.1 典型崩溃场景分析案例一Theme.AppCompat与Material组件冲突java.lang.IllegalArgumentException: This component requires that you specify a valid TextAppearance attribute.解决方案确保主题继承自Theme.MaterialComponents检查是否所有Material组件都使用了兼容包中的类如应使用com.google.android.material.button.MaterialButton而非android.widget.Button案例二旧版WebView导致的渲染异常android.view.InflateException: Binary XML file line #12: Error inflating class com.google.android.material.textfield.TextInputLayout根本原因某些厂商ROM会修改系统WebView影响Material组件的矢量图渲染 临时方案android { defaultConfig { vectorDrawables.useSupportLibrary true } }5.2 样式覆盖的优先权体系Material组件样式遵循以下覆盖规则优先级从高到低XML中直接设置的属性android:textColorstyle属性指定的样式stylestyle/MyButtonStyle主题中的属性值?attr/colorPrimary组件的默认样式parentstyle/Widget.MaterialComponents.Button常见错误是混淆style和theme的引用方式!-- 错误示范 -- Button style?attr/buttonStyle/ !-- 正确示范 -- Button stylestyle/Widget.MaterialComponents.Button/5.3 黑暗模式适配的隐藏细节Material Design 3的动态配色需要额外配置// 启用动态色彩Android 12 if (Build.VERSION.SDK_INT Build.VERSION_CODES.S) { val dynamicColor DynamicColorsOptions.Builder() .setPrecondition { activity, _ - // 排除特定设备 !isMiui(activity) } .build() DynamicColors.applyToActivityIfAvailable(activity, dynamicColor) }对于自定义视图的颜色提取推荐使用MaterialColorUtilitiesval colors MaterialDynamicColors() val seedColor Color.valueOf(0xFF6750A4.toInt()) val scheme Scheme.light(seedColor.toArgb()) val primaryColor colors.primary().getArgb(scheme)6. 组件性能优化实战6.1 RecyclerView与Material组件的化学反应当Material组件遇上RecyclerView需要特别注意视图池优化// 在RecyclerView.Adapter中 override fun getItemViewType(position: Int): Int { return when (items[position].type) { ItemType.TEXT - R.layout.item_text ItemType.IMAGE - R.layout.item_image ItemType.CARD - R.layout.item_card else - throw IllegalArgumentException() } } // 提前预加载Material组件需要的资源 recyclerView.setRecycledViewPool(RecyclerView.RecycledViewPool().apply { setMaxRecycledViews(R.layout.item_card, 5) })边缘效果定制androidx.recyclerview.widget.RecyclerView android:overScrollModenever app:edgeEffectFactorystring/edge_effect_factory_material/6.2 图片加载的Material化处理对于Material组件中的图片展示推荐组合使用ShapeableImageView和Coilimplementation(io.coil-kt:coil:2.4.0) // 在Adapter中 val imageView holder.itemView.findViewByIdShapeableImageView(R.id.image) val request ImageRequest.Builder(context) .data(item.imageUrl) .target(imageView) .apply { // 自动适配Material形状 transformations(CircleCropTransformation()) } .build() context.imageLoader.enqueue(request)形状动画的高级技巧val interpolation MaterialMotionUtils.getInterpolator(context, R.attr.motionEasingStandard) val shapeModel imageView.shapeAppearanceModel.toBuilder() .setAllCornerSizes(ShapeAppearanceModel.PILL) .build() val animator ValueAnimator.ofFloat(0f, 1f).apply { addUpdateListener { val fraction it.animatedValue as Float imageView.shapeAppearanceModel shapeModel .toBuilder() .setAllCornerSizes(fraction * 50.dp) .build() } duration 300L interpolator interpolation } animator.start()7. 设计系统与组件主题化7.1 创建可扩展的Material主题专业级项目应该建立完整的设计token系统!-- res/values/attrs.xml -- attr namebrandPrimary formatcolor|reference / attr namebrandOnPrimary formatcolor|reference / attr namebrandShapeSmall formatdimension / !-- res/values/themes.xml -- style nameTheme.MyApp parentTheme.Material3.DynamicColors.DayNight item namebrandPrimarycolor/purple_500/item item namebrandOnPrimarycolor/white/item item namebrandShapeSmall4dp/item !-- 映射到Material系统属性 -- item namecolorPrimary?attr/brandPrimary/item item namecolorOnPrimary?attr/brandOnPrimary/item item nameshapeAppearanceSmallComponent?attr/brandShapeSmall/item /style7.2 组件样式的原子化设计采用类似CSS-in-JS的方案管理组件变体!-- Button样式体系 -- style nameWidget.MyApp.Button parentWidget.Material3.Button item nameandroid:insetTop0dp/item item nameandroid:insetBottom0dp/item item nameshapeAppearance?attr/shapeAppearanceSmallComponent/item /style style nameWidget.MyApp.Button.Primary parentWidget.MyApp.Button item namebackgroundTint?attr/colorPrimary/item item nameandroid:textColor?attr/colorOnPrimary/item /style style nameWidget.MyApp.Button.Secondary parentWidget.MyApp.Button item namebackgroundTint?attr/colorSurfaceVariant/item item nameandroid:textColor?attr/colorOnSurfaceVariant/item /style动态主题切换的实现fun applyTheme(themeRes: Int) { val typedArray context.obtainStyledAttributes( themeRes, R.styleable.ThemeOverlay_MyApp) val primary typedArray.getColor( R.styleable.ThemeOverlay_MyApp_brandPrimary, Color.BLACK) val shape typedArray.getDimension( R.styleable.ThemeOverlay_MyApp_brandShapeSmall, 4f) // 更新全局主题 val overlay ContextThemeWrapper(context, themeRes) MaterialColors.getColor(overlay, R.attr.colorPrimary, MyApp) // 应用至所有视图 ViewCompat.setBackgroundTintList(button, ColorStateList.valueOf(primary)) (imageView as? ShapeableImageView)?.shapeAppearanceModel imageView.shapeAppearanceModel.toBuilder() .setAllCornerSizes(shape) .build() typedArray.recycle() }8. 测试与质量保障8.1 视觉回归测试方案使用Facebook的Screenshot Tests for Android确保UI一致性androidTestImplementation com.facebook.testing.screenshot:core:0.15.0 RunWith(AndroidJUnit4::class) class MaterialComponentScreenshotTest { get:Rule val rule ActivityScenarioRule(MainActivity::class.java) Test fun testButtonAppearance() { rule.scenario.onActivity { activity - val button MaterialButton(activity).apply { text Submit layoutParams ViewGroup.LayoutParams( ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT) } val testName material_button_default Screenshot.snap(button) .setName(testName) .record() } } }8.2 跨版本兼容性测试框架建立自动化测试矩阵android { testOptions { devices { // 定义测试设备矩阵 pixel2Api21(com.android.build.api.dsl.ManagedVirtualDevice) { device Pixel 2 apiLevel 21 systemImageSource aosp } pixel4Api30(com.android.build.api.dsl.ManagedVirtualDevice) { device Pixel 4 apiLevel 30 systemImageSource google } } } }测试用例示例RunWith(Parameterized::class) class MaterialComponentCompatTest( private val apiLevel: Int ) : BaseTest() { companion object { JvmStatic Parameterized.Parameters fun data() listOf(21, 22, 23, 24, 25, 26, 27, 28, 29, 30) } Test fun testTextInputLayoutRendering() { // 模拟不同API级别的行为差异 Shadows.shadowOf(Build.VERSION).setSdkInt(apiLevel) val layout TextInputLayout(context) val editText TextInputEditText(context) layout.addView(editText) // 验证基本渲染 assertThat(layout.boxBackgroundMode) .isEqualTo(TextInputLayout.BOX_BACKGROUND_OUTLINE) } }9. 未来演进与迁移策略随着Material Design 3的全面落地开发者需要关注动态色彩系统的深度整合// 检查设备是否支持动态色彩 val supportsDynamic DynamicColors.isDynamicColorAvailable() // 应用动态色彩到特定视图 DynamicColors.applyToViewGroupIfAvailable(rootView)Material You组件的渐进式采用优先在新模块中使用全新组件如MaterialTimePicker逐步替换旧版组件如SwitchMaterial→SwitchCompat使用Bridge组件保持向后兼容如BridgeToolbar设计token的迁移路径旧版 colorPrimary → md3_token.color.primary textColorPrimary → md3_token.color.onSurface shapeCornerSmall → md3_token.shape.corner.small在最近的一个电商App重构项目中我们采用分阶段迁移策略首先统一基础token系统色彩、字体、形状然后逐个页面迁移核心组件最后处理边缘case和特殊交互 整个过程耗时3个迭代周期但最终使APK大小减少了17%渲染性能提升23%。

相关新闻

基于Qt与FFmpeg的移动端摄像头推流方案设计与优化

基于Qt与FFmpeg的移动端摄像头推流方案设计与优化

1. 项目概述:手机端摄像头推流方案设计 在移动互联网时代,实时视频采集与传输技术已成为远程监控、视频会议、直播等场景的核心需求。本项目基于Qt框架和FFmpeg多媒体库,实现了Android/iOS手机端的摄像头数据采集、编码和RTSP/RTMP协议推流功…

2026/10/17 23:06:40 阅读更多 →
08接口双色LED显示屏驱动原理与实践

08接口双色LED显示屏驱动原理与实践

1. 08接口双色LED显示屏的基本原理 双色LED显示屏作为常见的显示设备,在各类嵌入式系统中有着广泛应用。08接口作为LED显示屏的标准接口之一,其驱动方式值得我们深入探讨。我最近在做一个音乐频谱显示器的项目时,就遇到了如何正确驱动08接口双…

2026/10/16 19:18:31 阅读更多 →
Okhttp官方文档

Okhttp官方文档

上一篇:android网络连接使用GET方式请求服务器时的setDoOutput(true)惹的祸 2026.7.18更新 okhttp是安卓开发常用的网络库。 新的文档地址:https://lysine.dev/okhttp/ 浏览器自带翻译功能,可以翻译成中文。 https://square.github.io/okhttp…

2026/10/11 3:18:26 阅读更多 →

最新新闻

Qt模型类实战指南:从现成模型到自定义实现与数据同步

Qt模型类实战指南:从现成模型到自定义实现与数据同步

很多 Qt 学习者看《Qt Creator快速入门(第三版)》前面十几章都挺顺,一到第16章 Model/View 就有点卡壳,尤其是第16-2节讲到“模型类”的时候,概念一下子抽象起来。我当年这节看了三遍才真正缓过来,主要原因…

2026/10/17 23:06:01 阅读更多 →
VCSA服务全Invalid?从vmon到systemd的排障实践与修复全攻略

VCSA服务全Invalid?从vmon到systemd的排障实践与修复全攻略

1. 故障背景:VCSA服务集体“invalid”的真相搞虚拟化的同行看到这个报错应该都不陌生:某天vCenter Server Appliance(VCSA)管理界面打不开,浏览器刷不出登录页,外部PSC/VC通信断掉,SSH上去执行s…

2026/10/17 23:06:01 阅读更多 →
PS流深度解析:从RTP打包到H.264帧提取的完整实践

PS流深度解析:从RTP打包到H.264帧提取的完整实践

简介:面向音视频流媒体开发者,这份压缩包提供了海康、国标PS流解析的两种实现方案——基于ffmpeg的解析版本与直接解析PS流版本,适合需要对接海康设备或国标PS流做二次开发、播放与转码的技术人员。压缩包共27个文件,包括6个C源文…

2026/10/17 23:06:01 阅读更多 →
Intouch与SQL Server报表系统配置:从数据库连接到VBA刷新的完整指南

Intouch与SQL Server报表系统配置:从数据库连接到VBA刷新的完整指南

简介:这是一份面向SCADA系统工程师与HMI开发者的Word操作文档,专门解决WonderWare Intouch 2014 R2(IDE)环境下的数据库对接与Excel报表生成问题。文档从基础配置讲起,包括通过SQL Server Management Studio修改身份验…

2026/10/17 23:06:01 阅读更多 →
NTFS备用数据流/交换数据流(ADS)从原理到排查清除全攻略

NTFS备用数据流/交换数据流(ADS)从原理到排查清除全攻略

如果你做过Windows环境下的系统维护或者安全排查,大概率听过“备用数据流/交换数据流”这个名字。它听起来像某种网络协议,实际上却是NTFS文件系统里一个低调到容易被忽略、但影响面极大的文件附加机制。我第一次真正意识到它的存在,是因为一…

2026/10/17 23:06:01 阅读更多 →
Linux源码编译安装libcurl库:从configure到静态链接全攻略

Linux源码编译安装libcurl库:从configure到静态链接全攻略

1. 换一条路装库:为什么有时候必须用源码压缩包说起Linux下安装第三方库,很多人第一反应是apt install或者yum install。这确实是最省事的方案,但我在做项目时,经常发现包管理器这条路走不通,最后不得不老老实实下载源…

2026/10/17 23:04:59 阅读更多 →

日新闻

DeepSeek选型与部署:V3/R1、Ollama、API全攻略

DeepSeek选型与部署:V3/R1、Ollama、API全攻略

简介:《DeepSeek使用方法.pdf》是一份面向DeepSeek入门及进阶用户的完整使用指南,重点围绕V3与R1两款模型的能力差异与适用场景展开,帮助读者根据任务类型与成本选择合适的模型。资源共1个PDF文件,压缩包大小约311KB,篇…

2026/10/17 0:00:25 阅读更多 →
200+Deepseek润色指令拆解:从底层原理到场景落地的完整指南

200+Deepseek润色指令拆解:从底层原理到场景落地的完整指南

简介:这是一份聚焦DeepSeek提示词优化的《200Deepseek润色指令》PDF文档,共收录两百余条可直接套用的润色指令,涵盖语法修正、风格统一、逻辑梳理、表达精炼等维度,并针对商务写作、学术论文、广告文案等场景给出差异化建议。文档…

2026/10/17 0:00:25 阅读更多 →
OpenCV轮廓法物体计数系统:轻量、可控、可落地的工业级实现

OpenCV轮廓法物体计数系统:轻量、可控、可落地的工业级实现

简介:本资源是一份基于OpenCV的本科课程设计完整报告,面向计算机视觉初学者、图像处理学习者及高校通信/信息类专业学生,解决物体智能计数与检测结果结构化记录的实际问题。项目覆盖图像预处理、轮廓检测、边界框绘制、轨迹栏交互式参数调节、…

2026/10/17 0:00:25 阅读更多 →

周新闻

流感时间序列预测实战:ARIMA/LSTM全流程拆解与避坑指南

流感时间序列预测实战:ARIMA/LSTM全流程拆解与避坑指南

简介:基于 ARIMA、LSTM、Transformer 等模型的流感时间序列预测 Python 源码,面向计算机相关专业课程设计与期末大作业学生,以及项目实战学习者。内容覆盖预处理、平稳性检验、定阶、残差分析、多模型对比预测的完整时序建模流程,…

2026/10/16 21:35:51 阅读更多 →
影刀RPA新手教程:键盘模拟输入实战——输入文本与模拟按键的区别

影刀RPA新手教程:键盘模拟输入实战——输入文本与模拟按键的区别

影刀RPA新手教程:键盘模拟输入实战——输入文本与模拟按键的区别 做影刀RPA自动化,十个新手有八个栽在"往输入框里填东西"这件事上:要么填不进去,要么填了一半,要么直接把原来内容追加在后面。这背后的根因&…

2026/10/17 1:44:59 阅读更多 →
影刀RPA新手教程:阅文起点小说数据采集实战——书籍信息与章节内容

影刀RPA新手教程:阅文起点小说数据采集实战——书籍信息与章节内容

影刀RPA新手教程:阅文起点小说数据采集实战——书籍信息与章节内容 1. 认识影刀:什么场景该用RPA采小说数据 起点中文网的页面结构相对稳定——分类榜单、书籍详情、章节内容三块独立页面,跳转链路清晰。这种场景非常适合影刀自动化&#x…

2026/10/16 15:04:24 阅读更多 →

月新闻

我发现了一个新思路:用 Remotion + Claude Code 像写代码一样自动化生成短视频

我发现了一个新思路:用 Remotion + Claude Code 像写代码一样自动化生成短视频

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

2026/10/16 21:54:38 阅读更多 →
Windows下 Codex 中 Chrome 和 Computer Use 插件不可用问题排查及解决参考方式:TaoToken 统一 Key 配置与验证

Windows下 Codex 中 Chrome 和 Computer Use 插件不可用问题排查及解决参考方式:TaoToken 统一 Key 配置与验证

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

2026/10/17 17:22:31 阅读更多 →
黑夜航拍船只数据集训练YOLOV5模型全流程解析

黑夜航拍船只数据集训练YOLOV5模型全流程解析

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

2026/10/16 14:23:36 阅读更多 →