多彩编程 多彩编程MZPH · CODE BLOG
ARTICLE DETAIL

文章详情

深耕前端与后端开发技术的一线实战笔记与踩坑复盘。

从Basil案例拆解Material Design落地:8dp网格、阴影层级与动效实战

从Basil案例拆解Material Design落地:8dp网格、阴影层级与动效实战 1. 从Basil案例看Material Design的落地逻辑第一次看到Basil这个案例的时候我盯着它的界面看了很久。不是因为多惊艳而是因为它太标准了——标准到几乎可以作为Material Design的教科书级示范。但恰恰是这种标准让我意识到一个问题很多人学MD规范学的是表面长什么样而不是为什么长这样。Basil是一款食谱管理应用功能不复杂浏览食谱、收藏、创建购物清单、按步骤烹饪。但就是这么一个简单的场景把Material Design的核心原则几乎全部覆盖了。我后来反复拆解它的界面发现它真正值得学的地方不在于配色多好看、卡片多圆润而在于每一个视觉决策背后都有明确的逻辑支撑。这篇文章我会从Basil这个案例出发把Material Design在实际项目中怎么落地讲透。不管你是刚接触MD规范的设计新手还是想从其他设计体系迁移过来的开发者或者只是想让自己的产品看起来更对味的独立开发者都能从里面找到可以直接用的东西。我不会只告诉你要用8dp网格而是会告诉你为什么是8dp而不是10dp以及在什么情况下可以打破这个规则。2. Basil案例中Material Design核心原则的具象化2.1 材质隐喻不是装饰是信息层级的表达工具Material Design最核心的概念是材质Material。很多人把它理解成卡片要有阴影这其实只看到了表象。在Basil里材质隐喻承担的是信息层级表达的功能。打开Basil的首页你会看到食谱卡片浮在背景之上卡片有轻微的阴影。这个阴影不是为了好看而是在告诉你这张卡片是可以交互的它和背景不在同一个层级上。当你点击卡片进入详情页时卡片会扩展成全屏——这个过渡动画的本质是同一张材质在改变形态而不是跳转到了另一个页面。我在实际项目中经常看到有人把所有元素都加上阴影结果整个界面像贴满了便利贴。Basil的做法是只有需要表达浮在表面的元素才用阴影比如卡片、FAB浮动操作按钮、底部弹窗。而背景、分割线、文字这些不需要表达层级的元素一律保持扁平。具体到参数上Basil用的阴影层级大概是这样的元素类型阴影高度dp使用场景卡片静止1-2dp食谱列表项卡片悬停/按压4-8dp交互反馈FAB6dp添加食谱按钮底部弹窗8-16dp购物清单展开对话框24dp删除确认这个表格里的数值不是随便定的。Material Design的阴影系统基于一个假设光源在屏幕正上方高度越高阴影越大越柔和。1dp的阴影几乎看不见但足以让卡片和背景产生微妙的分离感24dp的阴影非常明显适合需要用户立即关注的模态对话框。注意阴影不是越多越好。我见过一个项目给每个列表项都加了4dp阴影结果滚动的时候整个界面像在抖动。Basil的做法是列表项静止时只有1dp按压时才提升到4dp这样既有反馈又不会视觉噪音过大。2.2 网格系统8dp不是玄学是数学Basil的界面之所以看起来舒服很大程度上归功于严格的8dp网格系统。所有元素的间距、尺寸、位置都是8的倍数。这不是设计师拍脑袋定的而是有实际原因的。首先8dp网格能保证在不同屏幕密度下都能整除。Android的屏幕密度有mdpi1x、hdpi1.5x、xhdpi2x、xxhdpi3x、xxxhdpi4x。8dp在所有这些密度下都能得到整数像素值8px、12px、16px、24px、32px。如果用10dp在1.5x密度下就是15px虽然也是整数但在3x密度下是30px看起来没问题但和16dp48px的间距放在一起就会显得不协调。其次8dp网格简化了设计决策。当你不知道两个元素之间该留多少间距时从8、16、24、32、40、48这几个值里选就行了。Basil的食谱卡片内边距是16dp卡片之间的间距是8dp页面左右边距是16dp标题和内容之间的间距是8dp。这些数值不是随意选的而是形成了一套节奏。我在实际项目中总结了一个简单的规则小间距用8dp中间距用16dp大间距用24dp或32dp。如果两个元素关系紧密比如标题和副标题用8dp如果关系一般比如卡片和卡片用16dp如果是要明确分隔不同区域用24dp以上。但8dp网格也不是铁律。Basil在几个地方打破了它图标尺寸用的是24dp3的倍数也是8的倍数没问题但文字的行高用的是20dp不是8的倍数。这是因为文字行高需要根据字号来定14sp的字配20dp行高是阅读舒适度最好的组合强行改成24dp会让文字显得太松散。提示8dp网格是工具不是枷锁。当你有充分理由的时候比如文字可读性、图标识别度可以打破它。但如果你只是觉得这样好看那还是老老实实回到8dp。2.3 色彩系统主色、辅色、强调色的分工Basil的色彩搭配看起来很舒服但如果你仔细分析会发现它用的颜色其实很少。主色是一种偏暖的橙色类似#FF7043辅色是深灰色类似#37474F强调色是蓝绿色类似#26A69A。就这三个颜色撑起了整个应用。Material Design的色彩系统核心是主色辅色强调色的三层结构。主色用于顶部导航栏、主要按钮、选中状态辅色用于文字、背景、分割线强调色用于FAB、开关、进度条这些需要吸引注意力的元素。Basil的做法是主色橙色用在顶部导航栏和开始烹饪按钮上强调色蓝绿色用在FAB和步骤完成的勾选状态上辅色深灰色用在所有文字和背景上。这样做的结果是用户一眼就能看出哪些是可交互的哪些是信息展示。我见过很多项目把颜色用得很花哨结果用户根本不知道哪个按钮是主要的。Basil的克制值得学习整个应用只有三个颜色但通过明度变化比如橙色有浅橙、标准橙、深橙三个变体来满足不同场景的需求。具体到实现上Basil的色彩定义大概是这样的:root { --primary: #FF7043; --primary-light: #FFAB91; --primary-dark: #E64A19; --secondary: #37474F; --secondary-light: #62727B; --accent: #26A69A; --accent-light: #64D8CB; --background: #FAFAFA; --surface: #FFFFFF; --text-primary: rgba(0, 0, 0, 0.87); --text-secondary: rgba(0, 0, 0, 0.60); --divider: rgba(0, 0, 0, 0.12); }注意文字颜色用的是带透明度的黑色而不是纯黑。这是Material Design的一个细节纯黑文字在白色背景上对比度太高长时间阅读会累。87%透明度的黑色约等于#212121看起来更柔和同时仍然满足WCAG AA对比度标准。3. 从Basil拆解组件设计卡片、列表与导航的实战细节3.1 食谱卡片信息密度与呼吸感的平衡Basil的食谱卡片是我见过把信息密度控制得最好的案例之一。一张卡片上放了图片、标题、烹饪时间、难度等级、评分五个信息但看起来一点都不拥挤。拆解一下它的布局卡片顶部是16:9的图片图片下方是16dp的内边距区域。标题用16sp的Roboto Medium占一行第二行是三个元信息时间、难度、评分用12sp的Roboto Regular颜色是60%透明度的黑色。标题和元信息之间留了4dp的间距元信息之间用8dp的间距隔开。这个布局的关键在于标题和元信息之间用字号和颜色区分了层级而不是用分割线。很多设计师喜欢在标题和内容之间加一条分割线但Basil没有。它通过字号差异16sp vs 12sp和颜色差异87%黑 vs 60%黑来建立层级这样界面更干净。卡片的高度是固定的图片区域高度是卡片宽度的9/16内容区域高度是72dp16dp上边距 20dp标题行高 4dp间距 16dp元信息行高 16dp下边距。这个72dp不是随便定的它保证了卡片在列表中滚动时每张卡片的高度一致视觉节奏稳定。实操心得做卡片设计时先确定图片比例再确定内容区域的最小高度。内容区域的高度应该能容纳最多三行文字标题一行、副标题一行、元信息一行超过三行就应该考虑用其他组件比如列表项而不是卡片。3.2 底部导航与FAB的配合Basil用了底部导航栏Bottom Navigation加FAB的组合。底部导航有三个标签食谱、收藏、购物清单。FAB悬浮在底部导航上方用于快速添加新食谱。这个组合在Material Design里是有明确规范的底部导航的高度是56dpFAB的尺寸是56dpFAB距离底部导航的间距是16dp。Basil完全遵循了这个规范但有一个细节值得注意FAB的位置不是固定在右下角而是稍微偏左避开了底部导航的第三个标签。为什么要偏左因为如果FAB在右下角它会遮挡底部导航的购物清单标签。Basil的做法是把FAB放在距离右边16dp的位置但底部导航的第三个标签宽度是屏幕宽度的1/3所以FAB实际上是在第二个标签和第三个标签之间。这样既保证了FAB的可见性又不会遮挡导航。这个细节在官方文档里没有明确写但它是实际项目中必须考虑的问题。我见过很多应用把FAB放在右下角结果底部导航的最后一个标签被遮住了一半用户根本点不到。3.3 列表项的三行结构Basil的购物清单用的是三行列表项第一行是食材名称第二行是数量第三行是备注。三行列表项的高度是88dp比两行列表项72dp高了16dp。这个16dp的差值不是随便定的。三行列表项的行高分别是第一行20dp16sp字第二行16dp14sp字第三行16dp14sp字加上上下各16dp的内边距总共是201616161684dp取整到88dp。多出来的4dp是留给文字行高微调的。三行列表项的关键是第三行文字的颜色要更浅。Basil的第三行备注用的是38%透明度的黑色比第二行的60%更浅。这样即使用户不仔细看也能感觉到三行文字的重要性是递减的。注意三行列表项不要用太多。Basil只在购物清单里用了三行食谱列表用的是两行。因为三行列表项的信息密度太高如果整个应用都用三行界面会显得很拥挤。4. 动效与交互Basil中那些容易被忽略的细节4.1 共享元素过渡卡片到详情页的连续感Basil最让我印象深刻的动效是卡片到详情页的过渡。点击食谱卡片时卡片会平滑地扩展成全屏详情页图片从卡片位置移动到详情页顶部标题从卡片位置移动到详情页标题位置。这个效果叫共享元素过渡Shared Element Transition。这个动效的意义不只是好看。它告诉用户你点击的这张卡片变成了你现在看到的页面。这种空间连续性让用户不会迷失方向知道自己从哪里来、要回哪里去。实现上Basil用的是Android的共享元素过渡API。关键是要给卡片和详情页的对应元素设置相同的transitionName。比如卡片的图片设置android:transitionNamerecipe_image详情页的图片也设置android:transitionNamerecipe_image。然后在启动Activity时用ActivityOptions.makeSceneTransitionAnimation()。但这里有个坑如果图片的尺寸在卡片和详情页里不一样卡片里是16:9详情页里是4:3过渡动画会变形。Basil的做法是让卡片和详情页的图片比例保持一致都是16:9。这样过渡的时候图片只需要缩放不需要改变宽高比看起来更自然。4.2 触摸反馈涟漪效果的正确用法Material Design的涟漪效果Ripple是触摸反馈的核心。Basil里所有可点击的元素都有涟漪效果但涟漪的样式不一样。卡片上的涟漪是圆形的从点击位置扩散。按钮上的涟漪是矩形的从按钮中心扩散。列表项上的涟漪是矩形的从点击位置扩散。这些差异不是随意的而是根据元素的形状和用途来定的。卡片的涟漪用圆形是因为卡片本身是圆角矩形圆形涟漪在圆角矩形上看起来更自然。按钮的涟漪用矩形是因为按钮通常是矩形矩形涟漪能覆盖整个按钮区域。列表项的涟漪用矩形是因为列表项是通栏的矩形涟漪能覆盖整行。涟漪的颜色也有讲究。Basil的涟漪颜色是主色的20%透明度。在白色卡片上橙色涟漪很淡不会喧宾夺主在深色背景上橙色涟漪比较明显能提供足够的反馈。实操心得涟漪效果不要用纯黑色或纯白色。纯黑色在浅色背景上太突兀纯白色在深色背景上太刺眼。用主色的低透明度版本既能保持品牌一致性又能提供柔和的反馈。4.3 转场动画的时长与曲线Basil的转场动画时长是300毫秒曲线是标准的Material Design缓动曲线cubic-bezier(0.4, 0.0, 0.2, 1)。这个时长和曲线是经过大量用户测试得出的最优解。300毫秒是什么概念人眼眨一次大约是100-150毫秒300毫秒大约是眨两次眼的时间。这个时长足够让用户感知到动画但又不会觉得慢。如果短于200毫秒用户会觉得动画一闪而过没有过渡感如果长于500毫秒用户会觉得卡顿。缓动曲线的意思是动画开始的时候加速中间匀速结束的时候减速。cubic-bezier(0.4, 0.0, 0.2, 1)这个曲线在开始的时候加速比较快结束的时候减速比较慢整体感觉是轻快但不突兀。Basil在几个地方用了不同的时长页面转场用300毫秒按钮涟漪用200毫秒FAB展开用250毫秒。这些差异是因为不同动效的重量不一样。页面转场是最大的动效需要300毫秒按钮涟漪是最小的动效200毫秒就够了。5. 从Basil案例中提炼的避坑指南5.1 不要为了像Material Design而堆砌组件我见过很多项目为了看起来像Material Design把所有MD组件都用上了顶部导航栏、底部导航、FAB、卡片、列表、对话框、底部弹窗……结果整个应用像一个大杂烩用户根本不知道重点在哪里。Basil的做法是只用在需要的地方。它没有用顶部导航栏因为底部导航已经够了没有用底部弹窗因为购物清单用全屏页面更合适没有用对话框因为删除操作可以用Snackbar撤销。整个应用只用了卡片、列表、底部导航、FAB、Snackbar五个组件。提示Material Design是一套工具不是一套模板。你不需要把所有组件都用上只需要用最适合你场景的组件。5.2 阴影和圆角不要同时用太多Basil的卡片有阴影但圆角很小4dp。我见过一些项目卡片既有大阴影8dp又有大圆角16dp结果卡片看起来像一块浮在空中的肥皂。阴影和圆角的关系是阴影表达层级圆角表达柔和。如果两者都很强视觉上会冲突。Basil的做法是需要表达层级的元素用阴影小圆角需要表达柔和的元素用无阴影大圆角。比如FAB用的是6dp阴影50%圆角圆形因为FAB需要同时表达层级和柔和卡片用的是1dp阴影4dp圆角因为卡片只需要表达层级。5.3 文字对比度不是越高越好Basil的正文文字用的是87%透明度的黑色而不是纯黑。这个细节很多人会忽略但它对阅读体验的影响很大。纯黑文字在白色背景上的对比度是21:1远超WCAG AAA标准的7:1。但对比度太高会导致文字边缘看起来有光晕长时间阅读会累。87%透明度的黑色对比度大约是15:1仍然远超AAA标准但看起来更柔和。Basil的次要文字用的是60%透明度的黑色对比度大约是7:1刚好满足AA标准。辅助文字用的是38%透明度的黑色对比度大约是4.5:1满足AA标准的最低要求。注意不要用纯黑或纯白文字。纯黑在浅色背景上太刺眼纯白在深色背景上太刺眼。用带透明度的黑或白根据背景色调整透明度。5.4 动效不是越多越好Basil的动效很少页面转场、卡片扩展、涟漪、FAB展开。就这四个。但每一个都做得很到位。我见过一些项目每个元素都有动效按钮点击有缩放、列表滚动有视差、图片加载有淡入、文字出现有打字机效果……结果整个应用像在放烟花用户根本没法集中注意力。Material Design的动效原则是动效应该有意义。页面转场告诉用户你到了一个新地方卡片扩展告诉用户你点击的东西变成了这个页面涟漪告诉用户你点击的东西被识别到了FAB展开告诉用户有新操作可以用了。如果动效没有传达信息就不应该存在。6. 把Basil的设计思路迁移到自己的项目6.1 先定网格和色彩再画界面很多设计师的习惯是先画界面再调间距和颜色。Basil的做法反过来先定8dp网格和三个主色再画界面。这样做的好处是你在画界面的时候不需要反复调整间距和颜色因为规则已经定好了。你只需要从8、16、24、32这几个值里选间距从主色、辅色、强调色里选颜色。决策成本大大降低。我的建议是在开始设计之前先花半小时定好网格系统和色彩系统。网格系统包括基础单位8dp、常用间距8/16/24/32、圆角半径4/8/16。色彩系统包括主色、辅色、强调色、背景色、表面色、文字色主/次/辅助、分割线色。6.2 用真实内容做设计不要用Lorem IpsumBasil的食谱卡片上放的是真实的食谱名称、真实的烹饪时间、真实的评分。这样做的好处是你能看到真实内容下的布局效果。我见过很多设计稿用Lorem Ipsum填充文字结果开发出来发现文字长度不一样布局全乱了。Basil的做法是用真实内容做设计如果真实内容太长就截断加省略号如果太短就留白。这样设计出来的界面开发出来和设计稿几乎一模一样。实操心得做列表设计时至少准备三种长度的内容短一行、中两行、长三行。确保你的布局在这三种情况下都好看。6.3 动效先做减法再做加法Basil的动效很少但每一个都很有用。我的建议是先做减法把所有不必要的动效去掉只保留最核心的四个页面转场、点击反馈、加载状态、错误提示。然后再根据实际需要慢慢加回一些动效。这样做的好处是你不会一开始就陷入动效的细节里而是先保证核心体验是流畅的。等核心体验没问题了再考虑加一些锦上添花的动效。6.4 测试不同屏幕尺寸下的表现Basil在手机和平板上的布局是不一样的。手机上底部导航在底部平板上底部导航变成侧边导航。卡片在手机上是一列在平板上是两列。这个适配不是自动的而是需要针对不同屏幕尺寸做不同的布局。我的建议是至少测试三种屏幕尺寸小屏手机5寸以下、大屏手机5.5-6.5寸、平板7寸以上。确保你的布局在这三种尺寸下都好看。注意不要用固定的dp值来定义布局宽度。用match_parent或weight来让布局自适应。Basil的卡片宽度是match_parent但在平板上通过GridLayoutManager设置列数来变成两列。7. 关于Material Design落地的一些个人体会做设计这么多年我越来越觉得Material Design的价值不在于它的视觉风格而在于它的系统化思维。Basil这个案例之所以经典不是因为它用了多少MD组件而是因为它把MD的原则吃透了然后根据自己的场景做了取舍。我见过太多项目要么完全照搬MD的视觉风格结果做出来的东西和别的应用长得一模一样要么完全无视MD的原则结果做出来的东西用户根本不知道怎么用。Basil的做法是遵循MD的核心原则网格、层级、反馈、动效但在具体实现上根据自己的场景做调整。比如Basil的卡片阴影只有1dp比MD推荐的2dp要浅。这是因为食谱应用的卡片很多如果每张卡片都有2dp阴影滚动的时候视觉噪音会很大。1dp阴影足够表达层级又不会太吵。再比如Basil的FAB位置偏左避开了底部导航的第三个标签。这个调整在MD规范里没有写但它是实际项目中必须考虑的问题。这些细节才是Material Design落地的真正难点。不是你知道8dp网格、知道阴影层级、知道色彩系统就够了而是你要知道在什么情况下可以打破这些规则以及打破之后怎么保证整体还是协调的。我自己的经验是先把MD规范学透然后在实际项目中大胆调整。调整的时候问自己三个问题这个调整解决了什么问题这个调整会不会影响其他部分这个调整用户能感知到吗如果三个问题都有明确答案那就大胆调。最后说一个我经常用的技巧如果你不确定某个设计决策对不对把它放到真实场景里测试。比如你不确定卡片阴影用1dp还是2dp就做两个版本让用户滚动列表看哪个版本更舒服。用户可能说不出为什么但他们的行为会告诉你答案。
返回列表