HarmonyOS应用<奇妙科学乐园>开发第45篇:Stack层叠布局——Hero图片区与渐变遮罩实现
引言在上一篇文章中我们实现了科普列表页的核心展示组件 TopicCard通过 Stack Column Row 混合布局将封面图片、分类标签、标题摘要、阅读量等信息优雅地封装在一个可复用的卡片组件中。当用户点击 TopicCard 后将跳转到文章详情页 TopicDetail映入眼帘的是一个极具视觉冲击力的 Hero 图片区域——这就是本文的主角Stack 层叠布局在 Hero 图片区的深度应用。Hero 图片区是文章详情页的门面它在有限的屏幕空间内需要同时展示大尺寸封面图、底部渐变遮罩、左上角毛玻璃分类标签、顶部透明导航栏返回按钮 页面标题 朗读按钮四层内容。这四层内容通过两层嵌套的 Stack 容器实现精确定位外层 Stack 将透明导航栏叠加在图片区上方内层 Stack 将渐变遮罩和分类标签叠加在图片上。更关键的是分类标签使用了backdropBlur(10)毛玻璃效果在保证文字可读性的同时又不完全遮挡背景图片——这种半透明确保信息传达 模糊确保层次分离的设计思路是现代移动端 UI 设计的精髓所在。本文将从 Stack 嵌套对齐、linearGradient 渐变遮罩、backdropBlur 毛玻璃、textShadow 文字阴影四个维度完整拆解 Hero 图片区的实现。源码仓库https://atomgit.com/2301_79280419/WonderSciencePark 学习目标完成本文后你将能够✅ 掌握 Stack 嵌套布局外层 Top 对齐 内层 BottomStart 对齐✅ 使用 linearGradient 实现从透明到半透明黑色的渐变遮罩✅ 运用 backdropBlur 实现毛玻璃效果提升标签可读性✅ 使用 textShadow 为白色文字添加阴影确保在任何背景下的可读性✅ 理解 aspectRatio(16/9) 保持图片宽高比的最佳实践✅ 学会外层 Stack 的透明导航栏叠加技术 需求分析Hero 图片区的视觉层次文章详情页的 Hero 图片区需要实现以下视觉层次┌────────────────────────────────────┐ │ ← 返回 科普知识 朗读 │ ← 第四层透明导航栏 │ │ 外层StackTop对齐 │ ┌────────────────────────────┐ │ │ │ ┌──────────┐ │ │ │ │ │ 太空探索 │ ← 毛玻璃Badge│ │ ← 第三层分类标签 │ │ └──────────┘ │ │ 内层StackTopStart │ │ │ │ │ │ 大尺寸封面图片区域 │ │ ← 第一层封面图 │ │ │ │aspectRatio(16/9)│ │ │ │ │ │ ▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓│ │ ← 第二层渐变遮罩 │ │ ▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓│ │ 从透明到黑色 │ └────────────────────────────┘ │ └────────────────────────────────────┘各层功能需求层级元素对齐方式功能描述第一层Image 封面图铺满容器分类对应的封面大图16:9 宽高比第二层Column 渐变遮罩BottomStart 对齐从透明到半透明黑色底部最深第三层Row 分类 BadgeTopStart 对齐margin 偏移毛玻璃背景 分类名称第四层Row 透明导航栏Top 对齐外层 Stack返回 标题 朗读白色文字带阴影技术需求清单需求技术方案优先级图片宽高比aspectRatio(16 / 9)P0底部渐变遮罩linearGradient GradientDirection.BottomP0毛玻璃分类标签backgroundColor(rgba(255,255,255,0.35)) backdropBlur(10)P0文字阴影textShadow({ radius, color, offsetX, offsetY })P0导航栏叠加外层 Stack(Alignment.Top) 嵌套内层 StackP1分类颜色获取getCategoryGradient() 方法P1️ 核心实现步骤1: 外层 Stack 布局——导航栏叠加 Hero 图片区功能说明TopicDetail 页面使用了两层嵌套的 Stack。外层 Stack 以Alignment.Top对齐将透明导航栏叠加在 Hero 图片区的顶部。内层 Stack 以Alignment.BottomStart对齐将渐变遮罩和分类标签叠加在图片上。这种嵌套结构清晰分离了不同层次的视觉元素。源码实现//文件路径entry/src/main/ets/pages/TopicDetail.ets build(){ Column(){if(this.topic){// 外层 StackHero 图片区 透明导航栏 Stack({ alignContent: Alignment.Top }){//内层 Stack图片 遮罩 Badge Stack({ alignContent: Alignment.BottomStart }){ Image(this.getHeroImage()).width(100%).aspectRatio(16 / 9).objectFit(ImageFit.Cover);//渐变遮罩详见步骤2 Column().width(100%).height(100).linearGradient({ /* ... */ });//分类 Badge详见步骤3 Row(){ Text(this.topic.categoryName).fontSize(12).fontWeight(FontWeight.Bold).fontColor(this.getCategoryGradient()[0]); }.padding({ left: 10, right: 10, top: 4, bottom: 4 }).backgroundColor(rgba(255, 255, 255, 0.35)).backdropBlur(10).borderRadius(9999).margin({ top: 60, left: 16 }); }.width(100%);//透明导航栏详见步骤4 Row(){ Image($r(app.media.icon_back)).width(20).height(20).fillColor(ThemeColors.TEXT_WHITE).onClick(() { this.goBack(); }); Text(科普知识).fontSize(16).fontWeight(FontWeight.Bold).fontColor(ThemeColors.TEXT_WHITE).textShadow({ radius: 4, color: rgba(0, 0, 0, 0.5), offsetX: 0, offsetY: 1 }); Image($r(app.media.icon_speak)).width(20).height(20).fillColor(ThemeColors.TEXT_WHITE).onClick(() { this.handleReadAloud(); }); }.width(100%).padding({ left: 16, right: 16, top: 48 }).alignItems(VerticalAlign.Center).justifyContent(FlexAlign.SpaceBetween); }.width(100%);//下方是可滚动内容区...} } }Stack 嵌套结构图Stack(外层,alignContent:Alignment.Top)├──Stack(内层,alignContent:Alignment.BottomStart)│ ├──Image(封面大图,16:9)│ ├──Column(渐变遮罩,100vp高)│ └──Row(分类Badge,毛玻璃)└──Row(透明导航栏,SpaceBetween)├──Image(返回按钮)├──Text(页面标题)└──Image(朗读按钮)嵌套 Stack 对齐方式对比// ✅ 正确外层 Top 内层 BottomStartStack({alignContent: Alignment.Top }){Stack({alignContent: Alignment.BottomStart }){Image(...)// 底层图片Column(...)// 底部渐变遮罩Row(...)// 左上角 Badge}Row(...)// 顶部导航栏Top 对齐}// ❌ 错误只用一层 Stack所有元素挤在一起Stack({alignContent: Alignment.TopStart }){Image(...)// 图片和导航栏、遮罩都在同一层定位混乱Row(...)// 导航栏Column(...)// 渐变遮罩Row(...)// Badge}// 渐变遮罩无法定位到底部Badge 和导航栏会重叠// ❌ 错误使用 position 绝对定位脱离文档流Stack(){Image(...)Row(...).position({ x:16, y:48})// 硬编码位置不适配不同屏幕Column(...).position({ x:0, y:200})// 200 是假设计算值}步骤2: linearGradient 渐变遮罩——从透明到半黑功能说明渐变遮罩覆盖在封面图片的底部区域高度为 100vp从顶部完全透明过渡到底部半透明黑色rgba(0, 0, 0, 0.6)。它的作用是让图片底部的文字在当前设计中 Badge 定位在左上角但遮罩为后续的标题叠加预留了空间在视觉上更加清晰可读。渐变遮罩是 Hero 图片区的经典设计模式。源码实现//底部渐变遮罩 Column() .width(100%) .height(100) .linearGradient({ direction: GradientDirection.Bottom,//从上到下渐变 colors: [ [rgba(0, 0, 0, 0),0],//顶部完全透明 [rgba(0, 0, 0, 0.6),1]//底部60% 黑色 ] });linearGradient 参数详解参数值说明directionGradientDirection.Bottom渐变方向从上到下colors[0][rgba(0, 0, 0, 0), 0]起始色透明黑色位置 0%顶部colors[1][rgba(0, 0, 0, 0.6), 1]结束色半透明黑色位置 100%底部渐变遮罩方案对比//✅ 正确透明到半黑渐变自然过渡 .linearGradient({ direction: GradientDirection.Bottom, colors: [[rgba(0, 0, 0, 0),0], [rgba(0, 0, 0, 0.6),1]] }) .height(100)//❌ 错误纯黑色背景图片完全被遮挡 .backgroundColor(#000000) .height(100)//整个底部区域变成纯黑图片完全不可见//❌ 错误透明度太高遮罩效果太弱 .linearGradient({ direction: GradientDirection.Bottom, colors: [[rgba(0, 0, 0, 0),0], [rgba(0, 0, 0, 0.2),1]] })//0.2透明度太低白色文字在浅色图片上难以辨认//❌ 错误遮罩高度太小过渡不自然 .linearGradient({ direction: GradientDirection.Bottom, colors: [[rgba(0, 0, 0, 0),0], [rgba(0, 0, 0, 0.6),1]] }) .height(30)//30vp 太小渐变过于急促//❌ 错误渐变方向错误 .linearGradient({ direction: GradientDirection.Top,//从下到上底部反而透明 colors: [[rgba(0, 0, 0, 0),0], [rgba(0, 0, 0, 0.6),1]] })渐变遮罩原理图高度0vp顶部 │ rgba(0,0,0,0)← 完全透明图片完全可见 │ ▓ │ ▓ 透明度逐渐增加 │ ▓▓ │ ▓▓▓ │ ▓▓▓▓ rgba(0,0,0,0.3)←30%位置 │ ▓▓▓▓▓ │ ▓▓▓▓▓▓ │ ▓▓▓▓▓▓▓ │ ▓▓▓▓▓▓▓▓ │ ▓▓▓▓▓▓▓▓▓ │ ▓▓▓▓▓▓▓▓▓▓ 高度100vp底部 │ rgba(0,0,0,0.6)←60%黑色图片暗化但仍可见步骤3: 毛玻璃分类 Badge——backdropBlur 半透明白色背景功能说明分类标签Badge悬浮在图片左上角margin: { top: 60, left: 16 }使用半透明白色背景rgba(255, 255, 255, 0.35)配合backdropBlur(10)毛玻璃效果。毛玻璃效果会将 Badge 下方的图片区域模糊化处理使得白色文字在任何颜色的图片背景上都能清晰可读同时保持整体视觉通透感。源码实现// 左上角分类 BadgeRow(){Text(this.topic.categoryName).fontSize(12).fontWeight(FontWeight.Bold).fontColor(this.getCategoryGradient()[0]);// 分类主题色} .padding({ left:10, right:10, top:4, bottom:4}) .backgroundColor(rgba(255, 255, 255, 0.35))// 35% 不透明度白色.backdropBlur(10)// 10vp 模糊半径.borderRadius(9999)// 胶囊形状.margin({ top:60, left:16});// 距顶 60vp避开导航栏分类颜色获取方法/** * 获取当前分类的渐变色数组 * 用于 Badge 文字颜色和详情页各处的主题色 *returns[渐变起始色, 渐变结束色] */privategetCategoryGradient(): [string, string] {if(!this.topic) {return[ThemeColors.PRIMARY, ThemeColors.WARNING]; }constcategory scienceData.getCategoryById(this.topic.category);returncategory ? [category.gradientStart, category.gradientEnd] : [ThemeColors.PRIMARY, ThemeColors.WARNING]; }毛玻璃效果方案对比//✅ 正确半透明白色背景 backdropBlur通透且可读 .backgroundColor(rgba(255, 255, 255, 0.35)) .backdropBlur(10)//❌ 错误纯白色背景完全遮挡图片 .backgroundColor(#ffffff)//白色方块太突兀割裂感强//❌ 错误半透明背景但无毛玻璃背景图案干扰文字 .backgroundColor(rgba(255, 255, 255, 0.35))//缺少 backdropBlur图片花纹透过半透明背景文字可读性差//❌ 错误毛玻璃但无背景色文字直接在模糊图片上 .backdropBlur(10)//没有背景色仅靠模糊效果不足以保证所有图片下的可读性//❌ 错误模糊半径太小效果不明显 .backdropBlur(2)//2vp 模糊半径几乎看不出来//✅ 正确margin top 设为60vp避开顶部状态栏 导航栏 .margin({ top:60, left:16})//❌ 错误margin top 太小Badge 被导航栏遮挡 .margin({ top:10, left:16})//Badge 与返回按钮重叠backdropBlur 工作原理原始图片 backdropBlur(10)最终效果 ┌──────────┐ ┌──────────┐ ┌──────────┐ │ 星空 │ → │ ░░ 模糊 │ │ ☁️ 毛玻璃 │ │ 深色背景 │ │ ░░ 区域 │ │ 分类文字 │ │ │ │ │ │ 清晰可读 │ └──────────┘ └──────────┘ └──────────┘(10vp 模糊半径)(半透明白色 模糊底图)步骤4: 透明导航栏——textShadow 文字阴影功能说明外层 Stack 的第二个子元素是透明导航栏 Row包含返回按钮、页面标题科普知识和朗读按钮。导航栏没有设置背景色直接叠加在图片上方形成沉浸式体验。由于图片背景颜色不可控可能是深色也可能是浅色标题文字使用白色并添加textShadow阴影确保在任何背景下都能保持清晰可读。源码实现//透明导航栏 Row() {//返回按钮 Image($r(app.media.icon_back)) .width(20) .height(20) .fillColor(ThemeColors.TEXT_WHITE) .onClick(() { this.goBack(); });//页面标题 Text(科普知识) .fontSize(16) .fontWeight(FontWeight.Bold) .fontColor(ThemeColors.TEXT_WHITE) .textShadow({ radius:4,//阴影模糊半径 color:rgba(0, 0, 0, 0.5),//阴影颜色半透明黑色 offsetX:0,//水平偏移0offsetY:1//垂直偏移向下1vp });//朗读按钮 Image($r(app.media.icon_speak)) .width(20) .height(20) .fillColor(ThemeColors.TEXT_WHITE) .onClick(() { this.handleReadAloud(); }); } .width(100%) .padding({ left:16, right:16, top:48})//top48vp 避开状态栏 .alignItems(VerticalAlign.Center) .justifyContent(FlexAlign.SpaceBetween);//左中右均匀分布textShadow 参数说明参数值说明radius4阴影模糊半径值越大阴影越柔和colorrgba(0, 0, 0, 0.5)半透明黑色阴影offsetX0水平方向无偏移居中阴影offsetY1垂直方向向下 1vp模拟自然光源文字可读性方案对比// ✅ 正确白色文字 黑色阴影任何背景下可读.fontColor(ThemeColors.TEXT_WHITE).textShadow({ radius:4,color:rgba(0, 0, 0, 0.5), offsetX:0, offsetY:1})// ❌ 错误纯白色文字无阴影浅色图片上看不清.fontColor(ThemeColors.TEXT_WHITE)// 没有 textShadow在浅色天空图片上白色文字几乎不可见// ❌ 错误阴影参数过大文字变得模糊.textShadow({ radius:20,color:rgba(0, 0, 0, 0.8), offsetX:0, offsetY:1})// 20vp 模糊半径过大文字边缘过度模糊可读性反而下降// ❌ 错误使用不透明黑色背景的导航栏Row(){/* ... */}.backgroundColor(rgba(0, 0, 0, 0.8))// 深色导航栏遮挡 Hero 图片// 失去沉浸式体验图片上半部分被导航栏遮挡// ✅ 正确padding top 48vp 避开系统状态栏.padding({left:16, right:16, top:48})// ❌ 错误padding top 太小被状态栏遮挡.padding({left:16, right:16, top:10})// 返回按钮和标题被系统状态栏时间、信号、电量遮挡步骤5: 封面图片——aspectRatio 保持宽高比功能说明Hero 图片区使用aspectRatio(16 / 9)保持 16:9 的宽高比无论屏幕宽度如何变化图片始终保持标准的电影画幅比例。配合objectFit(ImageFit.Cover)等比裁剪填满确保不留白边。源码实现Image(this.getHeroImage()) .width(100%) .aspectRatio(16/9)// 保持 16:9 宽高比.objectFit(ImageFit.Cover);// 等比裁剪填满图片适配方案对比// ✅ 正确aspectRatio(16/9) CoverImage(this.getHeroImage()).width(100%).aspectRatio(16/9).objectFit(ImageFit.Cover)// ❌ 错误固定高度不同屏幕宽度下比例失调Image(this.getHeroImage()).width(100%).height(200).objectFit(ImageFit.Cover)// 窄屏手机上图片被拉伸变矮宽屏平板上图片被压扁// ❌ 错误使用固定宽高不适配不同设备Image(this.getHeroImage()).width(360).height(202)// 硬编码尺寸不适配折叠屏、平板等设备// ❌ 错误使用 Contain 模式可能出现白边Image(this.getHeroImage()).width(100%).aspectRatio(16/9).objectFit(ImageFit.Contain)// Contain 保持完整图片但可能留白不美观Hero 图片获取方法/** * 获取文章详情页的 Hero 封面图片 * 根据文章分类从 ScienceData 中获取对应的封面图资源 * returns 封面图片资源兜底返回太阳图片 */privategetHeroImage(): Resource {if(!this.topic) { return$r(app.media.topic_sun); } const category scienceData.getCategoryById(this.topic.category); return category ? category.topicCoverImage :$r(app.media.topic_sun); }步骤6: 完整的 Hero 图片区代码整合功能说明将上述所有步骤的代码整合形成完整的 Hero 图片区实现。外层 StackTop 对齐包裹内层 StackBottomStart 对齐和透明导航栏 Row。源码实现//文件路径entry/src/main/ets/pages/TopicDetail.ets//完整 Hero 图片区代码 Stack({ alignContent: Alignment.Top }){// 内层 Stack封面图 遮罩 Badge Stack({ alignContent: Alignment.BottomStart }){//第一层封面大图 Image(this.getHeroImage()).width(100%).aspectRatio(16 / 9).objectFit(ImageFit.Cover);//第二层底部渐变遮罩 Column().width(100%).height(100).linearGradient({ direction: GradientDirection.Bottom, colors: [ [rgba(0, 0, 0, 0), 0], [rgba(0, 0, 0, 0.6), 1] ] });//第三层左上角毛玻璃分类标签 Row(){ Text(this.topic.categoryName).fontSize(12).fontWeight(FontWeight.Bold).fontColor(this.getCategoryGradient()[0]); }.padding({ left: 10, right: 10, top: 4, bottom: 4 }).backgroundColor(rgba(255, 255, 255, 0.35)).backdropBlur(10).borderRadius(9999).margin({ top: 60, left: 16 }); }.width(100%);// 第四层透明导航栏 Row(){ Image($r(app.media.icon_back)).width(20).height(20).fillColor(ThemeColors.TEXT_WHITE).onClick(() { this.goBack(); }); Text(科普知识).fontSize(16).fontWeight(FontWeight.Bold).fontColor(ThemeColors.TEXT_WHITE).textShadow({ radius: 4, color: rgba(0, 0, 0, 0.5), offsetX: 0, offsetY: 1 }); Image($r(app.media.icon_speak)).width(20).height(20).fillColor(ThemeColors.TEXT_WHITE).onClick(() { this.handleReadAloud(); }); }.width(100%).padding({ left: 16, right: 16, top: 48 }).alignItems(VerticalAlign.Center).justifyContent(FlexAlign.SpaceBetween); }.width(100%); 关键技术详解6.1 Stack 的九种对齐方式Stack 组件支持 9 种 Alignment 对齐方式在 Hero 图片区中我们使用了Top和BottomStart两种Alignment说明Hero 图片区中的应用Top子元素顶部对齐外层 Stack导航栏贴合顶部TopStart子元素左上角对齐可选Badge 从左上角开始BottomStart子元素左下角对齐内层 Stack遮罩贴合底部左侧Center子元素居中动画演示区的叠加元素Bottom子元素底部对齐可选底部操作栏BottomEnd子元素右下角对齐可选右下角水印TopEnd子元素右上角对齐可选右上角分享按钮Start子元素左侧对齐较少使用End子元素右侧对齐较少使用6.2 linearGradient 渐变类型详解HarmonyOS 的 linearGradient 支持多种渐变方向// 从上到下Hero 遮罩使用 linearGradient({ direction: GradientDirection.Bottom, colors:[[transparent, 0], [black, 1]]}) // 从左到右 linearGradient({ direction: GradientDirection.Right, colors:[[#ff0000, 0], [#0000ff, 1]]}) // 对角线渐变135度 linearGradient({ direction: GradientDirection.BottomRight, colors:[[#667eea, 0], [#764ba2, 1]]}) // 自定义角度0-360度 linearGradient({ angle:135, colors:[[#667eea, 0], [#764ba2, 1]]})6.3 backdropBlur 与 backgroundColor 的配合backdropBlur 必须配合半透明背景色使用才能达到最佳毛玻璃效果效果 backgroundColor(alpha)backdropBlur(radius)无背景色 backdropBlur 仅模糊文字可能不清晰 纯背景色 无backdropBlur 不透通无毛玻璃质感 半透明背景 backdropBlur 最佳效果毛玻璃 文字可读//最佳实践参数组合 .backgroundColor(rgba(255, 255, 255, 0.35))//30-40% 不透明度 .backdropBlur(10)//8-15vp 模糊半径 .borderRadius(9999)//胶囊形状6.4 textShadow 在不同场景的应用// 场景1白色文字 黑色阴影深色/彩色背景上.textShadow({ radius: 4,color:rgba(0, 0, 0, 0.5),offsetX:0,offsetY:1})// 场景2深色文字 白色阴影浅色背景上.textShadow({ radius: 3,color:rgba(255, 255, 255, 0.8),offsetX:0,offsetY:1})// 场景3强调标题的大阴影.textShadow({ radius: 8,color:rgba(0, 0, 0, 0.3),offsetX:0,offsetY:2})⚠️ 常见问题与排查问题1: 导航栏被系统状态栏遮挡现象原因解决方案返回按钮被状态栏遮挡padding top 不足设置.padding({ top: 48 })避开状态栏标题文字被截断状态栏高度在不同设备上不同使用系统安全区域 API 或适当增大 top 值问题2: 毛玻璃效果不生效现象原因解决方案Badge 背景看起来像纯白backdropBlur 不支持或被禁用确认设备 API 版本 9Badge 文字难以辨认背景色透明度太高降低 alpha 值0.35 → 0.5毛玻璃边缘有锯齿borderRadius 值不合理使用 9999 或与父容器保持一致问题3: 渐变遮罩效果不明显现象原因解决方案看不到渐变过渡遮罩高度太小增大 height30 → 100渐变方向反了direction 设置错误使用GradientDirection.Bottom图片颜色太深遮罩无用遮罩透明度不够增加 alpha0.6 → 0.8 性能优化Stack 嵌套性能考虑Stack 本身的性能开销很小但需要注意以下几点✅ 嵌套Stack数量2层外层内层—— 性能开销可忽略 ✅ 子元素数量每个Stack3-4个子元素 —— 正常范围 ❌ 避免超过5层Stack嵌套 —— 过深嵌套影响渲染效率 ❌ 避免在Stack内使用复杂的ForEach—— 列表渲染应使用List组件图片加载优化//✅ 使用 objectFit(Cover) 避免图片重绘 .objectFit(ImageFit.Cover)//✅ 使用 aspectRatio 避免频繁的布局重计算 .aspectRatio(16/9)//✅ 封面图资源使用压缩后的 JPG 格式//topic_sun.jpg约50KB而非 PNG约200KB 测试验证测试用例清单测试场景预期结果验证方式Hero 图片正常显示16:9 封面图铺满容器目视检查宽高比渐变遮罩效果图片底部逐渐变暗从底部向上观察透明度变化毛玻璃 BadgeBadge 区域图片模糊化文字清晰可读目视对比 Badge 内外区域导航栏不遮挡返回按钮在状态栏下方检查不同设备的状态栏高度白色文字阴影在深色和浅色图片上都清晰分别查看深色/浅色分类的文章无数据兜底显示默认太阳图片传入 category: unknown返回按钮功能点击返回科普列表页点击返回按钮朗读按钮功能显示朗读功能开发中提示点击朗读按钮 总结本文完整拆解了文章详情页 Hero 图片区的实现核心技术点汇总如下技术点实现方式效果Stack 嵌套布局外层 Top 内层 BottomStart分层管理不同视觉元素linearGradient 遮罩Bottom 方向 透明到半黑图片底部自然暗化backdropBlur 毛玻璃半透明白色背景 10vp 模糊Badge 文字清晰且通透textShadow 文字阴影radius:4 黑色半透明白色文字在任何背景可读aspectRatio 宽高比16/9 Cover 裁剪图片在任何屏幕保持比例透明导航栏无背景 SpaceBetween沉浸式体验两层嵌套的 Stack 布局是 Hero 图片区的骨架内层负责图片 遮罩 Badge的内容叠加外层负责导航栏 内容区的层级叠加。linearGradient 和 backdropBlur 则是提升视觉品质的关键手段——前者让图片到内容的过渡更自然后者让悬浮元素不割裂背景画面。 相关链接项目源码Atomgit仓库上一篇HarmonyOS应用奇妙科学乐园开发第44篇:文章卡片组件TopicCard——图片标题分类封装下一篇HarmonyOS应用奇妙科学乐园开发第46篇:冷知识卡片FunFactCard——可折叠内容组件