一、引言
1.1 为什么需要渐变遮罩?
在移动应用中,经常需要在图片或彩色背景上放置文字——例如轮播图的标题、卡片的内容描述、个人主页的封面信息。
这就引出了一个经典的设计难题:
背景太亮 → 文字看不清 背景太花 → 文字被淹没
直接加纯色遮罩可以解决问题,但会让界面显得生硬、失去背景的视觉层次感。而渐变遮罩(从透明到半透明的过渡)可以在"提升文字可读性"和"保留背景深度"之间取得完美平衡。
1.2 本文核心内容
知识点 说明
Stack 三层层叠 背景层 + 遮罩层 + 文字层
LinearGradient API 线性渐变的配置(方向、颜色数组)
rgba 半透明色 从透明到不透明的无缝过渡
对比度提升原理 暗色遮罩 vs 亮色背景的文字可见度
交互式调参 实时体验不同透明度/方向的效果
二、Stack 层叠布局基础
2.1 Stack 的 Z 轴坐标系
┌──────────────────────────┐
│ 屏幕 (X/Y 平面) │
│ │
│ ┌──────────────────┐ │
│ │ Layer 3 (顶层) │ │ ← Z 轴最大值
│ ├──────────────────┤ │
│ │ Layer 2 (中层) │ │
│ ├──────────────────┤ │
│ │ Layer 1 (底层) │ │ ← Z 轴起点(先声明的组件在最下层)
│ └──────────────────┘ │
└──────────────────────────┘
在 ArkUI 的 Stack 中,先声明的子组件在最底层,后声明的在上面。后声明的子组件会覆盖先声明的。
Stack() {
Text(‘底层 - 先声明’) // → 在 Z 轴最下面
Text(‘中层’) // → 在中间
Text(‘顶层 - 后声明’) // → 在 Z 轴最上面(覆盖前面所有)
}
2.2 Stack 与 Column/Row 的对比
容器 排列方向 适用场景
Column Y 轴(从上到下) 纵向排列
Row X 轴(从左到右) 横向排列
Stack Z 轴(从后到前) 层叠覆盖、叠加效果
渐变遮罩场景必须用 Stack——因为我们需要背景、遮罩、文字三者"叠"在一起,而不是"排"在一起。
2.3 Stack 的常用属性
Stack() {
// 子组件
}
.alignContent(Alignment.Center) // 子组件整体在 Stack 中的对齐方式
.clip(true) // 裁切超出部分
三、LinearGradient 渐变 API 详解
3.1 API 签名
// 在组件上设置线性渐变背景
.linearGradient(value: LinearGradient): T
// LinearGradient 接口
interface LinearGradient {
angle?: number | string; // 角度(与 direction 二选一)
direction?: GradientDirection; // 方向(与 angle 二选一)
colors: Array<[ResourceColor, number]>; // 颜色数组 [颜色, 位置%]
repeating?: boolean; // 是否重复
}
3.2 GradientDirection 枚举
枚举值 方向 示意
GradientDirection.Left 从左到右 ←
GradientDirection.Right 从右到左 →
GradientDirection.Top 从上到下 ↑
GradientDirection.Bottom 从下到上 ↓
GradientDirection.LeftTop 从左上到右下 ↗
GradientDirection.LeftBottom 从左下到右上 ↖
GradientDirection.RightTop 从右上到左下 ↘
GradientDirection.RightBottom 从右下到左上 ↙
GradientDirection.None 无方向 -
3.3 颜色数组格式
colors: [
[color1, position1], // position 范围 0.0 ~ 1.0
[color2, position2],
[color3, position3], // 可以多个颜色节点
]
例如:
// 三色渐变:红(0%) → 绿(50%) → 蓝(100%)
colors: [
[‘#FF0000’, 0.0],
[‘#00FF00’, 0.5],
[‘#0000FF’, 1.0]
]
3.4 半透明颜色的两种写法
// 写法一:8 位 Hex(每两位代表一个通道,最后两位是 Alpha)
‘#000000CC’ // 黑色,Alpha = 0xCC ≈ 80%
‘#00000000’ // 黑色,完全透明
// 写法二:rgba() 字符串
‘rgba(0, 0, 0, 0.8)’ // 黑色 80% 不透明
‘rgba(0, 0, 0, 0.0)’ // 黑色完全透明
Demo 中使用的是 rgba() 写法:
private rgba(r: number, g: number, b: number, a: number): string {
const alpha: number = Math.max(0, Math.min(1, a));
returnrgba(${r}, ${g}, ${b}, ${alpha});
}
因为我们需要动态计算透明度(根据 Slider 的值),用 rgba() 拼接字符串比 Hex 更灵活。
四、Demo 代码逐层剖析
4.1 项目结构与路由
{
“src”: [“pages/StackGradientDemo”]
}
StackGradientDemo.ets 共 408 行,完整结构:
StackGradientDemo.ets (408行)
├── @Component StackGradientDemo
│ ├── @State 变量(6个) ← gradientOpacity / startAlpha / endAlpha / gradientDir / demoCard / showOverlay
│ ├── 常量数组(4组) ← cardColors / cardTitles / cardDescs / dirLabels
│ ├── build()
│ │ ├── 标题区
│ │ ├── Scroll 内容区
│ │ │ ├── 对比演示 Row(左:有遮罩, 右:无遮罩)
│ │ │ ├── Divider 分割线
│ │ │ ├── 卡片选项卡 (4个)
│ │ │ └── 核心 Stack (3层)
│ │ │ ├── Layer1: 背景渐变 Column
│ │ │ ├── Layer2: 半透明遮罩 Column (if showOverlay)
│ │ │ └── Layer3: 文字内容 Column
│ │ └── 控制面板
│ │ ├── 遮罩开关
│ │ ├── 渐变方向 (3个按钮)
│ │ ├── 起始透明度 Slider
│ │ └── 结束透明度 Slider
│ ├── @Builder demoCardWithOverlay() ← 对比卡片
│ └── rgba() 工具方法
4.2 六个 @State 变量的设计
@State private startAlpha: number = 0.0; // 渐变起点透明度(顶部)
@State private endAlpha: number = 0.85; // 渐变终点透明度(底部)
@State private gradientDir: number = 0; // 渐变方向索引
@State private demoCard: number = 0; // 当前卡片索引
@State private showOverlay: boolean = true; // 是否显示遮罩
变量 控制的 UI 交互方式
showOverlay 整个遮罩层的显隐 点击开关
gradientDir 遮罩的渐变方向 三选一按钮
startAlpha 遮罩起点的透明度 Slider 0~100%
endAlpha 遮罩终点的透明度 Slider 0~100%
demoCard 切换 4 种配色方案 选项卡点击
4.3 对比演示:有遮罩 vs 无遮罩
Row() {
this.demoCardWithOverlay(true) // 左侧:有遮罩
this.demoCardWithOverlay(false) // 右侧:无遮罩
}
.height(150)
两个卡片使用完全相同的背景(红→橙渐变),唯一的区别是有无半透明遮罩:
有遮罩 ✓ 无遮罩 ✗
┌─────────────────┐ ┌─────────────────┐
│ │ │ │
│ │ │ │
│ 有遮罩 ✓ │ │ 无遮罩 ✗ │
│ 文字清晰可读 │ │ 文字模糊不清 │
│ ▓▓▓▓▓▓▓▓▓▓▓▓▓▓ │ │ │
└─────────────────┘ └─────────────────┘
↑ 底部有黑色渐变 ↑ 无遮罩,文字与背景混淆
对比效果:有遮罩的一侧文字清晰可见,无遮罩的一侧文字几乎融入背景。
4.4 核心:Stack 三层层叠
Stack() {
// ===== 第 1 层:背景渐变 =====
Column()
.width(‘100%’)
.height(‘100%’)
.linearGradient({
direction: GradientDirection.RightBottom,
colors: [
[this.cardColors[this.demoCard][0], 0.0], // 起始色
[this.cardColors[this.demoCard][1], 1.0] // 结束色
]
})
// ===== 第 2 层:半透明渐变遮罩(★ 核心)=====
if (this.showOverlay) {
Column()
.width(‘100%’)
.height(‘100%’)
.linearGradient({
direction: this.dirValues[this.gradientDir],
colors: [
[this.rgba(0, 0, 0, this.startAlpha), 0.0], // 起点:透明/半透明
[this.rgba(0, 0, 0, this.endAlpha), 1.0] // 终点:半透明/不透明
]
})
}
// ===== 第 3 层:文字内容 =====
Column() {
// 标题(大字号 + 纯白色 + 阴影)
Text(this.cardTitles[this.demoCard])
.fontSize(22).fontColor(Color.White).fontWeight(FontWeight.Bold)
.shadow({ radius: 4, color: ‘#00000066’, offsetX: 1, offsetY: 1 })
// 描述文字(阴影辅助可读性) Text(this.cardDescs[this.demoCard]) .fontSize(13).fontColor(Color.White).opacity(0.9) .shadow({ radius: 3, color: '#00000044', offsetX: 0, offsetY: 1 }) // 底部操作行 Row() { Text('📷 摄影作品').fontSize(11).fontColor(Color.White).opacity(0.7) Text('查看详情 →').fontSize(11).fontColor('#00B4D8') }}
.width(‘100%’).height(‘100%’).padding(16)
.justifyContent(FlexAlign.End) // 文字靠底部对齐
.alignItems(HorizontalAlign.Start) // 文字靠左对齐
}
.width(‘100%’).height(220)
.borderRadius(16).clip(true)
文字可读性的三重保障:
保障 实现 作用
Layer 2 遮罩 rgba(0,0,0,0→0.85) 渐变 从上方半透明到底部较暗,让底部文字更清晰
文字颜色 Color.White 白色在所有深色背景上都有高对比度
文字阴影 shadow({ blur, color }) 在文字边缘添加暗边,与背景产生视觉分离
4.5 文字阴影的辅助作用
.shadow({
radius: 4, // 模糊半径
color: ‘#00000066’, // 黑色半透明
offsetX: 1, // X 偏移
offsetY: 1 // Y 偏移
})
文字阴影即使在渐变遮罩不够暗的区域,也能通过文字边缘的暗色晕边提升可读性。这是一种双重保险——遮罩负责大面积的背景变暗,阴影负责文字边缘的精细对比。
4.6 四个配色方案
private readonly cardColors: string[][] = [
[‘#4A90D9’, ‘#2ECC71’], // 蓝→绿 — 清新自然
[‘#FF6B35’, ‘#E74C3C’], // 橙→红 — 热情活力
[‘#9B59B6’, ‘#3498DB’], // 紫→蓝 — 深邃星空
[‘#1ABC9C’, ‘#F1C40F’] // 青→黄 — 日落暖阳
];
四种方案覆盖了不同的色域,展示渐变遮罩在各种色彩背景上的通用效果。
4.7 控制面板的交互设计
控制面板包含四个维度的参数调节:
① 渐变遮罩开关
Row() {
Text(‘渐变遮罩’)
Text(this.showOverlay ? ‘🟢 已开启’ : ‘🔴 已关闭’)
}
.gesture(TapGesture().onAction(() => {
this.showOverlay = !this.showOverlay;
}))
点击切换,立即看到卡片上的遮罩层出现/消失。
② 渐变方向(3 种)
Button(‘从上到下 (Bottom)’) // 默认:文字在底部,渐变底部最暗
Button(‘从下到上 (Top)’) // 文字在顶部时使用
Button(‘对角线 (RightBottom)’) // 从右上到左下
③ 起始透明度 Slider
控制渐变起点的透明度。0% = 完全透明(保留背景),100% = 完全不透明(纯色)。
④ 结束透明度 Slider
控制渐变终点的透明度。通常设置较高的值(如 85%)让文字区域的背景足够暗。
这些控制组合起来,用户可以实时回答以下问题:
遮罩要还是不遮罩?(对比测试)
遮罩从哪个方向渐变最好?
遮罩要多暗才够?
这个透明度在不同的背景色上效果一样吗?
五、文字对比度提升的原理
5.1 WCAG 对比度标准
Web Content Accessibility Guidelines (WCAG) 建议:
普通文字:对比度 ≥ 4.5:1
大文字(≥18px):对比度 ≥ 3:1
对比度计算公式:(L1 + 0.05) / (L2 + 0.05),其中 L1 是较亮颜色的相对亮度,L2 是较暗颜色的相对亮度。
5.2 渐变遮罩如何提升对比度
无遮罩时:
背景亮度 = 0.7(浅色渐变背景) → 文字对比度不足
文字亮度 = 1.0(白色)
有遮罩时(底部区域):
背景亮度 = 0.7 × (1 - 0.85) = 0.105(大幅降低)
文字亮度 = 1.0(白色)
对比度 = (1.0 + 0.05) / (0.105 + 0.05) ≈ 6.8 : 1 ✅ 达标
遮罩通过降低背景区域的亮度,间接提升了文字与背景的对比度。
5.3 渐变 vs 纯色的对比
纯色遮罩: 渐变遮罩:
▓▓▓▓▓▓▓▓▓▓ ▓▓▓▓▓▓▓▓▓▓ ← 顶部保留原始背景
▓▓▓▓▓▓▓▓▓▓ ░░▓▓▓▓▓▓▓▓▓
▓▓▓▓▓▓▓▓▓▓ ░░░░▓▓▓▓▓▓▓
▓▓▓▓▓▓▓▓▓▓ ░░░░░░▓▓▓▓▓
▓▓▓▓▓▓▓▓▓▓ ░░░░░░░░▓▓▓ ← 底部最深
视觉感受: 视觉感受:
生硬、扁平 自然、有深度
文字区域暗但 文字区域暗,
上方损失了 但上方保留了
背景层次感 背景的层次感
渐变遮罩的核心优势:既保证了底部文字区域的对比度,又保留了顶部背景的视觉效果。
六、常见问题与坑点
6.1 .clip(true) 缺失导致圆角失效
// ❌ borderRadius 不生效
Stack() {
// …
}
.borderRadius(16)
// ✅ 需要 clip(true) 让 borderRadius 裁剪子组件
Stack() {
// …
}
.borderRadius(16)
.clip(true)
borderRadius 只作用于 Stack 容器本身,如果不设置 .clip(true),内部的 Column 背景渐变会超出圆角边界。
6.2 渐变色数组的格式
// ✅ 正确格式
colors: [
[‘#FF0000’, 0.0], // [颜色, 位置]
[‘#0000FF’, 1.0]
]
// ❌ 错误格式
colors: [‘#FF0000’, ‘#0000FF’] // 数组元素必须是 [color, position] 对
6.3 rgba 字符串中的透明度
// ✅ 正确:rgba 的 alpha 范围 0.0 ~ 1.0
‘rgba(0, 0, 0, 0.85)’
// ❌ 常见错误:使用 0~255 的 alpha
‘rgba(0, 0, 0, 85)’ // 85 会被当作 0.85 吗?不会,rgba 规范中 alpha 是 0~1
6.4 渐变方向与文字位置不匹配
// 如果文字在底部 → 遮罩应从上到下渐变(底部最暗)
.gradient({ direction: Bottom, colors: [[透明], [半透明]] })
// ↑ 0% 在顶部,100% 在底部
// 如果文字在顶部 → 遮罩应从下到上渐变(顶部最暗)
.gradient({ direction: Top, colors: [[透明], [半透明]] })
6.5 多个渐变叠加的顺序
Stack 中叠加多个 linearGradient 时,只有最上层的渐变可见(不透明区域会覆盖下层)。
Stack() {
Column().linearGradient({ /* 底层渐变/ })
Column().linearGradient({ /上层渐变 - 半透明 */ })
// 上层半透明区域 → 可以看到下层
// 上层不透明区域 → 完全覆盖下层
}
6.6 if 条件在 Stack 中的使用
Stack() {
// … 背景层
if (this.showOverlay) {
// 遮罩层:只有条件为 true 时才渲染
Column().linearGradient({ … })
}
// … 文字层(始终渲染)
}
在 Stack 中使用 if 控制某层的显隐是完全合法的——if 在 ArkTS 中本质上是条件渲染。
七、实战:不同场景的文字对比度方案
7.1 轮播图标题
Stack() {
Image($r(‘app.media.banner’))
.width(‘100%’).height(200)
// 底部渐变遮罩
Column()
.width(‘100%’).height(‘100%’)
.linearGradient({
direction: GradientDirection.Bottom,
colors: [[‘#00000000’, 0.5], [‘#000000CC’, 1.0]]
})
// 标题文字
Column() {
Text(‘轮播标题’).fontSize(20).fontColor(Color.White)
Text(‘副标题描述’).fontSize(13).fontColor(Color.White).opacity(0.8)
}
.width(‘100%’).padding(16)
.justifyContent(FlexAlign.End)
.alignItems(HorizontalAlign.Start)
}
.clip(true).borderRadius(12)
7.2 个人主页封面
Stack() {
Image($r(‘app.media.cover’))
.width(‘100%’).height(250)
// 顶部 + 底部双渐变
Column()
.width(‘100%’).height(‘100%’)
.linearGradient({
direction: GradientDirection.Bottom,
colors: [[‘#00000088’, 0.0], [‘#00000000’, 0.3], [‘#00000000’, 0.7], [‘#000000CC’, 1.0]]
})
// 用户信息
Column() {
Text(‘用户名’).fontSize(24)
Text(‘简介’).fontSize(14)
}
.width(‘100%’).padding(16)
.justifyContent(FlexAlign.End)
.alignItems(HorizontalAlign.Center)
}
7.3 暗色主题卡片
Stack() {
// 深色背景
Column().backgroundColor(‘#1a1a2e’).width(‘100%’).height(‘100%’)
// 高光渐变(左上到右下,营造立体感)
Column()
.width(‘100%’).height(‘100%’)
.linearGradient({
direction: GradientDirection.RightBottom,
colors: [[‘#ffffff11’, 0.0], [‘#00000000’, 1.0]]
})
// 白色文字(在深色背景上天然高对比度)
Text(‘暗色卡片’).fontSize(18).fontColor(Color.White)
}
.borderRadius(12).clip(true)
八、最佳实践清单
8.1 渐变遮罩参数建议
场景 起始透明度 结束透明度 方向
轮播图底部标题 0% 70~85% Bottom
卡片顶部标题 70~85% 0% Top
全屏背景文字 0% 80% Bottom
头像/图标底部 0% 60% Bottom
8.2 文字对比度自检清单
文字是否为白色(推荐)或深灰色(在浅色遮罩上)?
是否添加了 shadow() 作为辅助保障?
遮罩终点透明度是否 ≥ 70%?
是否在模拟器和真机上分别测试了不同背景色?
渐变方向是否与文字位置匹配?
8.3 Stack 层叠的通用模板
Stack() {
// Layer 1:背景
Image() / Column().linearGradient() / Any
// Layer 2:遮罩(可选)
if (needOverlay) {
Column().linearGradient({ /* 渐变遮罩 */ })
}
// Layer 3:内容
Column() {
/* 文字、按钮等 */
}
.padding(16)
.justifyContent(FlexAlign.End) // 内容靠底部
.alignItems(HorizontalAlign.Start) // 内容靠左
}
.width(‘100%’).height(desiredHeight)
.borderRadius(radius).clip(true)
8.4 性能注意事项
.linearGradient() 是 GPU 加速的,性能开销很小
避免在滚动列表中过多使用 Stack + 渐变(可能导致重绘)
对于静态卡片,使用 @Builder 拆分可以提升复用效率
九、结语
9.1 核心回顾
Stack + 渐变遮罩 = 背景深度 + 文字可读性
三行代码解决"背景太花、文字不清"的设计难题:
第 1 行:.linearGradient({ direction, colors }) → 背景
第 2 行:.linearGradient({ colors: [[透明],[半透明]] }) → 遮罩
第 3 行:Text().fontColor(White).shadow(…) → 文字
9.2 从本 Demo 学到的技术
技术 要点
Stack 层叠 先声明底层,后声明顶层
LinearGradient direction + colors: [[color, pos]]
rgba 透明度 rgba(r,g,b,a) 中 a 的范围 0~1
文字阴影 shadow() 作为对比度辅助
clip 属性 让 borderRadius 裁切内部子组件
9.3 下一步探索
RadialGradient 径向渐变:中心向外发散的渐变效果
SweepGradient 扫描渐变:围绕中心旋转的渐变(类似色相环)
BackgroundBlurStyle 背景模糊:用毛玻璃效果替代渐变遮罩
多渐变叠加:同时使用线性渐变 + 径向渐变创造复杂视觉效果
动画渐变:使用 animateTo 让渐变参数随时间变化
附录 A:完整 Demo 代码
/*
- StackGradientDemo.ets —— Stack + 渐变背景提升文字可读性
- ===== 核心技术 =====
- Stack() —— 层叠布局,将背景/渐变/文字叠加在不同层
- .linearGradient() —— 线性渐变色背景
- 半透明渐变色遮罩 —— 背景图上方叠加渐变层提升文字对比度
- ===== Stack 三层层叠原理 =====
- Stack()
- Layer1: 背景渐变 Column
- Layer2: 半透明遮罩 Column (if showOverlay)
- Layer3: 文字内容 Column (底部对齐)
*/
@Entry
@Component
struct StackGradientDemo {
@State private startAlpha: number = 0.0;
@State private endAlpha: number = 0.85;
@State private gradientDir: number = 0;
@State private demoCard: number = 0;
@State private showOverlay: boolean = true;
private readonly cardColors: string[][] = [
[‘#4A90D9’, ‘#2ECC71’], [‘#FF6B35’, ‘#E74C3C’],
[‘#9B59B6’, ‘#3498DB’], [‘#1ABC9C’, ‘#F1C40F’]
];
private readonly cardTitles: string[] = [
‘自然风光’, ‘城市夜景’, ‘星空宇宙’, ‘日落余晖’
];
private readonly dirValues: GradientDirection[] = [
GradientDirection.Bottom, GradientDirection.Top, GradientDirection.RightBottom
];
build() {
Column() {
Text(‘Stack + 渐变提升文字对比度’).fontSize(20)
.fontWeight(FontWeight.Bold).fontColor(Color.White)
.textAlign(TextAlign.Center).width(‘100%’).padding({ top: 14, bottom: 2 })
Text(‘用 Stack 叠加半透明渐变遮罩提升文字可读性’)
.fontSize(12).fontColor(Color.Gray).textAlign(TextAlign.Center)
.width(‘100%’).padding({ bottom: 4 })
Scroll() { Column() { // 对比演示 Text('对比:有渐变遮罩 → 文字清晰 | 无遮罩 → 模糊') .fontSize(11).fontColor(Color.Gray).width('100%').textAlign(TextAlign.Center) Row() { this.demoCardWithOverlay(true) this.demoCardWithOverlay(false) }.width('100%').height(150).padding({ left: 4, right: 4 }) Divider().height(1).color('#ffffff22').margin({ top: 10, bottom: 8 }) // 核心 Stack Stack() { // Layer 1: 背景 Column().width('100%').height('100%') .linearGradient({ direction: GradientDirection.RightBottom, colors: [[this.cardColors[this.demoCard][0], 0.0], [this.cardColors[this.demoCard][1], 1.0]] }) // Layer 2: 遮罩 if (this.showOverlay) { Column().width('100%').height('100%') .linearGradient({ direction: this.dirValues[this.gradientDir], colors: [[this.rgba(0,0,0,this.startAlpha), 0.0], [this.rgba(0,0,0,this.endAlpha), 1.0]] }) } // Layer 3: 文字 Column() { Text(this.cardTitles[this.demoCard]).fontSize(22) .fontColor(Color.White).fontWeight(FontWeight.Bold) .shadow({ radius:4, color:'#00000066', offsetX:1, offsetY:1 }) Text(this.cardDescs[this.demoCard]).fontSize(13) .fontColor(Color.White).opacity(0.9).lineHeight(20).margin({ top:6 }) .shadow({ radius:3, color:'#00000044', offsetX:0, offsetY:1 }) }.width('100%').height('100%').padding(16) .justifyContent(FlexAlign.End).alignItems(HorizontalAlign.Start) }.width('100%').height(220).borderRadius(16).clip(true).margin({ left:12, right:12 }) }.width('100%').padding({ top: 8, bottom: 16 }) }.layoutWeight(1).width('100%') // 控制面板 Column() { Row() { Text('渐变遮罩').fontSize(12).fontColor(Color.White) Text(this.showOverlay ? '🟢 已开启' : '🔴 已关闭').fontSize(11) .fontColor(this.showOverlay ? '#2ECC71' : '#E74C3C').margin({ left: 6 }) }.width('100%').gesture(TapGesture().onAction(() => { this.showOverlay = !this.showOverlay; })) Row() { this.dirButton('从上到下', 0) this.dirButton('从下到上', 1) this.dirButton('对角线', 2) }.width('100%').margin({ top: 4 }) Row() { Text(`起始透明度 ${(this.startAlpha*100).toFixed(0)}%`).fontSize(11).fontColor(Color.Gray) Slider({ value: this.startAlpha*100, min:0, max:100, step:5 }) .layoutWeight(1).margin({ left:8, right:8 }) .onChange((v) => { this.startAlpha = v/100; }) }.width('100%').margin({ top: 4 }) Row() { Text(`结束透明度 ${(this.endAlpha*100).toFixed(0)}%`).fontSize(11).fontColor(Color.Gray) Slider({ value: this.endAlpha*100, min:0, max:100, step:5 }) .layoutWeight(1).margin({ left:8, right:8 }) .onChange((v) => { this.endAlpha = v/100; }) }.width('100%').margin({ top: 4 }) }.width('100%').backgroundColor('#1a1a3e').borderRadius(12) .padding(12).margin({ left:12, right:12, bottom:10 }) }.width('100%').height('100%').backgroundColor('#0f3460')}
@Builder
demoCardWithOverlay(hasOverlay: boolean) {
Stack() {
Column().width(‘100%’).height(‘100%’)
.linearGradient({
direction: GradientDirection.RightBottom,
colors: [[‘#E74C3C’, 0.0], [‘#F39C12’, 1.0]]
})
if (hasOverlay) {
Column().width(‘100%’).height(‘100%’)
.linearGradient({
direction: GradientDirection.Bottom,
colors: [[‘#00000000’, 0.0], [‘#000000CC’, 1.0]]
})
}
Column() {
Text(hasOverlay ? ‘有遮罩 ✓’ : ‘无遮罩 ✗’).fontSize(13)
.fontColor(Color.White).fontWeight(FontWeight.Bold)
Text(hasOverlay ? ‘文字清晰可读’ : ‘文字模糊不清’).fontSize(11)
.fontColor(Color.White).opacity(hasOverlay ? 0.9 : 0.4)
}.width(‘100%’).padding(10).justifyContent(FlexAlign.End).alignItems(HorizontalAlign.Start)
}.width(‘100%’).height(‘100%’).borderRadius(10).clip(true).margin({ left:4, right:4 })
}
@Builder
dirButton(label: string, idx: number) {
Button(label).height(28).fontSize(10)
.backgroundColor(this.gradientDir === idx ? ‘#9B59B6’ : ‘#333’)
.fontColor(Color.White).borderRadius(6).layoutWeight(1).margin({ left:2, right:2 })
.gesture(TapGesture().onAction(() => { this.gradientDir = idx; }))
}
private rgba(r: number, g: number, b: number, a: number): string {
returnrgba(${r}, ${g}, ${b}, ${Math.max(0, Math.min(1, a))});
}
}
附录 B:参考资料
HarmonyOS NEXT 开发者文档 — Stack 容器
HarmonyOS NEXT 开发者文档 — LinearGradient
HarmonyOS NEXT 开发者文档 — GradientDirection
WCAG 对比度规范
版权声明:本文为 HarmonyOS NEXT 技术分享系列的第八篇,遵循 CC BY-NC 4.0 协议。欢迎转载,但请注明出处。
系列文章:
第一篇:TapGesture 点击手势布局
第二篇:PanGesture 拖拽手势布局
第三篇:GestureGroup 组合手势布局
第四篇:Column 垂直排列入门
第五篇:Column + Scroll 可滚动列表
第六篇:Column + Flex 弹性混合布局
第七篇:Column 垂直时间轴组件
第八篇:Stack + 渐变文字对比度(本文)