ArkUI Marquee 组件完整知识库
文档版本:v1.0 更新时间:2026-02-01 源码版本:OpenHarmony ace_engine (master 分支) 作者:基于 CLAUDE.md 规范自动生成
📚 目录
- 概述
- 完整调用链
- 目录结构
- 核心类继承关系
- Pattern层详解
- Model层详解
- LayoutAlgorithm层详解
- 属性系统
- 动画控制
- 特殊功能
- 执行流程
- 完整API清单
- 关键实现细节
- 使用示例
- 调试指南
- 常见问题
- 附录
概述
组件定位
Marquee 组件是 OpenHarmony ArkUI 框架中的滚动文本组件,提供单行文本的滚动播放功能,支持自定义滚动方向、速度、循环次数等特性,常用于公告、广告、通知等场景。
技术栈:
- 前端:ArkTS/TypeScript
- 桥接层:MarqueeModelNG
- 核心层:NG Pattern 架构 (MarqueePattern)
- 布局算法:MarqueeLayoutAlgorithm
- 渲染层:Rosen + Skia
代码规模:
- 总文件数:约 15 个文件
- 核心代码:约 3,000+ 行 C++ 代码
- 涉及 4 个架构层次
功能特性
Marquee 组件支持:
- 滚动方向:
- 从左到右 (MarqueeDirection.LEFT - 默认)
- 从右到左 (MarqueeDirection.RIGHT)
- 滚动控制:
- 播放/暂停状态控制
- 滚动步长(速度)设置
- 循环次数控制(-1 表示无限循环)
- 文本样式:
- 字体大小、颜色、粗细
- 字体族设置
- 文本方向自动检测(RTL/LTR)
- 动画效果:
- 平滑滚动动画
- 可见性自动暂停/恢复
- 事件回调:
- onStart - 滚动开始
- onBounce - 到达边缘
- onFinish - 滚动完成
设计模式
Marquee 组件采用 NG Pattern 架构:
前端 (ArkTS)
↓ (Model Layer)
MarqueeModelNG (marquee_model_ng.cpp)
↓ (NG Pattern Layer)
MarqueePattern (marquee_pattern.cpp)
├─ 文本节点管理 (Text FrameNode)
├─ 动画控制 (Animation Controller)
└─ 事件处理 (Event Hub)
↓ (NG LayoutAlgorithm Layer)
MarqueeLayoutAlgorithm (marquee_layout_algorithm.cpp)
↓
渲染显示
核心概念
滚动方向:
MarqueeDirection.LEFT // 从右向左滚动(默认)
MarqueeDirection.RIGHT // 从左向右滚动
关键配置:
src- 滚动文本内容start- 是否立即开始播放step- 滚动步长(速度),默认 50loop- 循环次数,-1 表示无限循环fromStart- 是否从起始位置开始
完整调用链
1. 从 ArkTS 到 Pattern 的调用链
调用链图
┌─────────────────────────────────────────────────────────┐
│ 前端 ArkTS │
│ │
│ Marquee({ │
│ src: "Running Marquee starts rolling", │
│ start: true, │
│ step: 50, │
│ loop: -1 │
│ }) │
│ .fontSize(30) │
│ .fontColor(Color.Red) │
│ .width(200) │
└─────────────────────────────────────────────────────────┘
↓
┌─────────────────────────────────────────────────────────┐
│ Model Layer (NG) │
│ │
│ class MarqueeModelNG │
│ 源码:frameworks/core/components_ng/pattern/marquee/ │
│ marquee_model_ng.cpp │
│ │
│ 关键方法: │
│ - Create() - 创建 Marquee 节点 │
│ - SetValue() - 设置文本内容 │
│ - SetPlayerStatus() - 设置播放状态 │
│ - SetScrollAmount() - 设置滚动步长 │
│ - SetLoop() - 设置循环次数 │
│ │
│ 执行流程: │
│ 1. 创建 FrameNode (MARQUEE_ETS_TAG) │
│ 2. 创建 MarqueePattern │
│ 3. 创建文本子节点 (TEXT_ETS_TAG) │
│ 4. 应用布局和绘制属性 │
└─────────────────────────────────────────────────────────┘
↓
┌─────────────────────────────────────────────────────────┐
│ Pattern Layer (业务逻辑) │
│ │
│ class MarqueePattern : public Pattern │
│ 源码:frameworks/core/components_ng/pattern/marquee/ │
│ marquee_pattern.cpp │
│ │
│ 核心职责: │
│ - 管理文本子节点 │
│ - 控制滚动动画 │
│ - 处理可见性变化 │
│ - 触发事件回调 │
└─────────────────────────────────────────────────────────┘
↓
┌─────────────────────────────────────────────────────────┐
│ LayoutAlgorithm Layer (布局计算) │
│ │
│ class MarqueeLayoutAlgorithm : public BoxLayoutAlgorithm │
│ 源码:frameworks/core/components_ng/pattern/marquee/ │
│ marquee_layout_algorithm.cpp │
│ │
│ 核心方法: │
│ - Measure() - 测量 Marquee 和文本尺寸 │
│ - Layout() - 定位文本子节点 │
└─────────────────────────────────────────────────────────┘
↓
[滚动文本显示在屏幕上]
2. 创建完整调用链
ArkTS: Marquee({ src: "Hello World" })
↓
MarqueeModelNG::Create()
↓
ViewStackProcessor::ClaimNodeId()
↓
FrameNode::GetOrCreateFrameNode(MARQUEE_ETS_TAG, nodeId, []() {
return AceType::MakeRefPtr<MarqueePattern>();
})
↓
创建文本子节点
↓
FrameNode::CreateFrameNode(TEXT_ETS_TAG, uniqueId, []() {
return AceType::MakeRefPtr<TextPattern>();
})
↓
设置文本属性
↓
ViewStackProcessor::Push(frameNode)
↓
[创建完成]
3. 属性设置调用链
.start(true)
↓
MarqueeModelNG::SetPlayerStatus(true)
↓
ACE_UPDATE_PAINT_PROPERTY(MarqueePaintProperty, PlayerStatus, true)
↓
MarqueePattern::OnModifyDone()
↓
ChangeAnimationPlayStatus()
↓
[动画开始]
.step(50)
↓
MarqueeModelNG::SetScrollAmount(50)
↓
ACE_UPDATE_PAINT_PROPERTY(MarqueePaintProperty, ScrollAmount, 50)
↓
MarqueePattern::StartMarqueeAnimation()
↓
[更新滚动速度]
目录结构
完整目录树
frameworks/core/components_ng/pattern/marquee/
├── marquee_pattern.h # MarqueePattern 主类定义
├── marquee_pattern.cpp # MarqueePattern 实现
├── marquee_layout_algorithm.h # 布局算法定义
├── marquee_layout_algorithm.cpp # 布局算法实现
├── marquee_layout_property.h # 布局属性定义
├── marquee_paint_property.h # 绘制属性定义
├── marquee_event_hub.h # 事件中心
├── marquee_accessibility_property.h # 无障碍属性定义
├── marquee_accessibility_property.cpp # 无障碍属性实现
│
├── Model 层
├── marquee_model_ng.h # NG Model 定义
├── marquee_model_ng.cpp # NG Model 实现
│
└── Bridge 层 (桥接层)
└── bridge/
├── marquee_dynamic_modifier.cpp # 动态修饰器
├── marquee_static_modifier.cpp # 静态修饰器
├── marquee_dynamic_module.h # 动态模块
└── marquee_dynamic_module.cpp # 动态模块实现
关键文件说明
| 文件 | 职责 | 核心类/方法 |
|---|---|---|
| marquee_pattern.h/cpp | 核心业务逻辑 | MarqueePattern::OnModifyDone(), StartMarqueeAnimation() |
| marquee_layout_algorithm.h/cpp | 布局计算 | MarqueeLayoutAlgorithm::Measure(), Layout() |
| marquee_model_ng.h/cpp | ArkTS API 接口 | MarqueeModelNG::Create(), SetValue() |
| marquee_layout_property.h | 布局属性定义 | 文本相关属性 |
| marquee_paint_property.h | 绘制属性定义 | 动画相关属性 |
| marquee_event_hub.h | 事件处理 | onStart, onBounce, onFinish |
核心类继承关系
1. Pattern 层继承关系
Pattern (基类)
↓
MarqueePattern (Marquee 组件实现)
├─ 文本子节点管理
├─ 动画控制
├─ 事件触发
└─ 可见性处理
2. Model 层继承关系
MarqueeModel (基类)
↓
MarqueeModelNG (NG 架构实现)
├─ Create() - 创建组件
├─ SetValue() - 设置文本
├─ SetPlayerStatus() - 设置播放状态
├─ SetScrollAmount() - 设置步长
└─ SetLoop() - 设置循环次数
3. LayoutAlgorithm 层继承关系
BoxLayoutAlgorithm (基类)
↓
MarqueeLayoutAlgorithm (Marquee 布局算法)
└─ Measure() - 测量内容
└─ Layout() - 定位文本
4. 关键类定义
MarqueePattern
class MarqueePattern : public Pattern {
DECLARE_ACE_TYPE(MarqueePattern, Pattern);
public:
MarqueePattern();
~MarqueePattern() override = default;
// 创建布局算法
RefPtr<LayoutAlgorithm> CreateLayoutAlgorithm() override
{
return MakeRefPtr<MarqueeLayoutAlgorithm>();
}
// 属性修改完成
void OnModifyDone() override;
// 布局交换
bool OnDirtyLayoutWrapperSwap(
const RefPtr<LayoutWrapper>& dirty,
const DirtySwapConfig& config) override;
// 可见性变化
void OnVisibleAreaChange(bool isVisible) override;
// 获取文本方向
TextDirection GetTextDirection(const std::string& content, MarqueeDirection direction);
private:
// 动画控制
void StartMarqueeAnimation();
void StopMarqueeAnimation(bool playStatus);
void ChangeAnimationPlayStatus();
void PauseAnimation();
void ResumeAnimation();
// 文本位置更新
void UpdateTextTranslateXY(float position);
// 事件触发
void FireStartEvent();
void FireBounceEvent();
void FireFinishEvent();
// 成员变量
RefPtr<FrameNode> textNode_;
float marqueeWidth_ = 0.0f;
bool measureChanged_ = true;
LastAnimationParam lastAnimationParam_;
};
MarqueeModelNG
class ACE_EXPORT MarqueeModelNG : public OHOS::Ace::MarqueeModel {
public:
// 创建 Marquee
void Create() override;
// 设置文本内容
void SetValue(const std::optional<std::string>& value) override;
// 设置播放状态
void SetPlayerStatus(const std::optional<bool>& playerStatus) override;
// 设置滚动步长
void SetScrollAmount(const std::optional<double>& scrollAmount) override;
// 设置循环次数
void SetLoop(const std::optional<int32_t>& loop) override;
// 设置滚动方向
void SetDirection(const std::optional<MarqueeDirection>& direction) override;
// 文本样式
void SetTextColor(const std::optional<Color>& textColor) override;
void SetFontSize(const std::optional<Dimension>& fontSize) override;
void SetFontWeight(const std::optional<FontWeight>& fontWeight) override;
void SetFontFamily(const std::optional<std::vector<std::string>>& fontFamilies) override;
// 事件回调
void SetOnStart(std::function<void()>&& onChange) override;
void SetOnBounce(std::function<void()>&& onChange) override;
void SetOnFinish(std::function<void()>&& onChange) override;
};
Pattern层详解
MarqueePattern 核心职责
MarqueePattern 是 Marquee 组件的核心逻辑层,负责:
-
文本子节点管理
- 创建和管理 Text FrameNode
- 更新文本内容和样式
-
动画控制
- 启动/停止滚动动画
- 暂停/恢复动画
- 计算滚动位置和速度
-
事件处理
- 触发 onStart 事件
- 触发 onBounce 事件
- 触发 onFinish 事件
-
可见性管理
- 组件不可见时自动暂停
- 组件可见时自动恢复
关键生命周期方法
1. OnModifyDone()
调用时机:组件属性修改完成时
源码位置:marquee_pattern.cpp:116-161
执行流程:
void MarqueePattern::OnModifyDone()
{
Pattern::OnModifyDone();
// 1. 获取主机节点和属性
auto host = GetHost();
auto layoutProperty = host->GetLayoutProperty<MarqueeLayoutProperty>();
auto paintProperty = host->GetPaintProperty<MarqueePaintProperty>();
auto textChild = DynamicCast<FrameNode>(host->GetFirstChild());
// 2. 更新文本方向
UpdateTextDirection(layoutProperty, textLayoutProperty);
// 3. 设置文本节点属性
textLayoutProperty->UpdateFontSize(fontSize);
textLayoutProperty->UpdateFontWeight(fontWeight);
textLayoutProperty->UpdateTextColor(textColor);
// 4. 处理属性变更
if (CheckMeasureFlag() || CheckLayoutFlag()) {
measureChanged_ = true;
} else if (OnlyPlayStatusChange()) {
// 仅播放状态变化
ChangeAnimationPlayStatus();
} else {
// 停止当前动画
auto playStatus = paintProperty->GetPlayerStatus().value_or(false);
StopMarqueeAnimation(playStatus);
}
}
注意事项:
- ⚠️ 不要在 OnModifyDone 中触发布局
- ✅ 可以访问 PaintProperty 和 LayoutProperty
- ✅ 此时 geometryNode 可能还没有更新
2. OnDirtyLayoutWrapperSwap()
调用时机:布局结果交换时
源码位置:marquee_pattern.cpp:97-114
执行流程:
bool MarqueePattern::OnDirtyLayoutWrapperSwap(
const RefPtr<LayoutWrapper>& dirty, const DirtySwapConfig& config)
{
auto host = GetHost();
CHECK_NULL_RETURN(host, false);
auto geoNode = host->GetGeometryNode();
auto marqueeWidth = geoNode->GetFrameSize().Width();
// 检查尺寸是否变化
if (measureChanged_ || marqueeWidth_ != marqueeWidth) {
measureChanged_ = true;
auto paintProperty = host->GetPaintProperty<MarqueePaintProperty>();
auto playStatus = paintProperty->GetPlayerStatus().value_or(false);
StopMarqueeAnimation(playStatus);
}
marqueeWidth_ = marqueeWidth;
return false;
}
注意事项:
- ✅ 这是访问布局结果的正确时机
- ✅ 可以安全地获取 geometryNode 数据
- ⚠️ 不要在此方法中修改 LayoutProperty
动画控制方法
StartMarqueeAnimation()
源码位置:marquee_pattern.cpp:163-184
功能:启动滚动动画
void MarqueePattern::StartMarqueeAnimation()
{
// 1. 检查是否需要滚动
if (!IsRunMarquee()) {
UpdateTextTranslateXY(0.0f);
StopAndResetAnimation();
return;
}
// 2. 获取循环次数
auto repeatCount = paintProperty->GetLoop().value_or(DEFAULT_MARQUEE_LOOP);
// 3. 触发开始事件
FireStartEvent();
// 4. 判断是否需要第二次播放
bool needSecondPlay = repeatCount != 1;
// 5. 获取起始位置
auto startPosition = GetTextOffset();
// 6. 播放动画
PlayMarqueeAnimation(startPosition, repeatCount, needSecondPlay);
}
调用时机:
- 组件首次加载且
start = true - 播放状态从 false 变为 true
- 布局完成后需要重新播放
StopMarqueeAnimation()
功能:停止滚动动画
void MarqueePattern::StopMarqueeAnimation(bool playStatus)
{
StopAndResetAnimation();
// 如果播放状态为 true,重新启动动画
if (playStatus) {
StartMarqueeAnimation();
}
}
PauseAnimation() / ResumeAnimation()
功能:暂停/恢复动画(用于可见性控制)
void MarqueePattern::PauseAnimation()
{
if (animator_) {
animator_->Pause();
}
}
void MarqueePattern::ResumeAnimation()
{
if (animator_) {
animator_->Resume();
}
}
可见性处理
OnVisibleAreaChange()
源码位置:marquee_pattern.cpp:396-405
功能:响应可见性变化
void MarqueePattern::OnVisibleAreaChange(bool isVisible)
{
if (isVisible) {
// 组件可见,恢复动画
ResumeAnimation();
} else {
// 组件不可见,暂停动画
PauseAnimation();
}
}
优化效果:当 Marquee 滚出屏幕时自动暂停,节省 CPU 和电池资源。
Model层详解
MarqueeModelNG 核心职责
MarqueeModelNG 提供 ArkTS 声明式 API,负责:
- 创建 Marquee 节点
- 创建文本子节点
- 设置属性(布局属性和绘制属性)
- 注册事件回调
关键 API 方法
1. Create()
源码位置:marquee_model_ng.cpp:24-45
功能:创建 Marquee FrameNode
void MarqueeModelNG::Create()
{
// 1. 获取 ViewStack 实例
auto* stack = ViewStackProcessor::GetInstance();
// 2. 获取节点 ID
auto nodeId = stack->ClaimNodeId();
// 3. 创建或获取 FrameNode
auto frameNode = FrameNode::GetOrCreateFrameNode(
MARQUEE_ETS_TAG, nodeId, []() { return AceType::MakeRefPtr<MarqueePattern>(); });
// 4. 创建文本子节点(如果不存在)
if (frameNode->GetChildren().empty()) {
auto textNode = FrameNode::CreateFrameNode(
TEXT_ETS_TAG,
ElementRegister::GetInstance()->MakeUniqueId(),
AceType::MakeRefPtr<TextPattern>()
);
// 设置文本属性
auto textLayoutProperty = textNode->GetLayoutProperty<TextLayoutProperty>();
textLayoutProperty->UpdateMaxLines(1);
// 添加为子节点
frameNode->AddChild(textNode);
}
// 5. 压入栈
stack->Push(frameNode);
}
对应 ArkTS:
Marquee({ src: "Hello World" })
2. SetValue()
源码位置:marquee_model_ng.cpp:47-52
功能:设置滚动文本内容
void MarqueeModelNG::SetValue(const std::optional<std::string>& value)
{
ACE_UPDATE_NODE_LAYOUT_PROPERTY(MarqueeLayoutProperty, Src, value, frameNode);
}
对应 ArkTS:
// 初始化设置
Marquee({ src: "滚动文本" })
// 动态更新
@State text: string = "Hello"
Marquee({ src: this.text })
Button('Update').onClick(() => {
this.text = "Updated Text"
})
3. SetPlayerStatus()
源码位置:marquee_model_ng.cpp:54-59
功能:设置播放状态
void MarqueeModelNG::SetPlayerStatus(const std::optional<bool>& playerStatus)
{
ACE_UPDATE_NODE_PAINT_PROPERTY(MarqueePaintProperty, PlayerStatus, playerStatus, frameNode);
}
对应 ArkTS:
.start(true) // 开始播放
.start(false) // 暂停播放
4. SetScrollAmount()
源码位置:marquee_model_ng.cpp:61-66
功能:设置滚动步长(速度)
void MarqueeModelNG::SetScrollAmount(const std::optional<double>& scrollAmount)
{
ACE_UPDATE_NODE_PAINT_PROPERTY(MarqueePaintProperty, ScrollAmount, scrollAmount, frameNode);
}
对应 ArkTS:
.step(50) // 滚动步长为 50
5. SetLoop()
源码位置:marquee_model_ng.cpp:68-73
功能:设置循环次数
void MarqueeModelNG::SetLoop(const std::optional<int32_t>& loop)
{
ACE_UPDATE_NODE_PAINT_PROPERTY(MarqueePaintProperty, Loop, loop, frameNode);
}
对应 ArkTS:
.loop(-1) // 无限循环
.loop(0) // 不循环(播放一次)
.loop(5) // 循环 5 次
6. 事件回调方法
onStart:
void MarqueeModelNG::SetOnStart(std::function<void()>&& onChange)
{
auto eventHub = frameNode->GetEventHub<MarqueeEventHub>();
CHECK_NULL_VOID(eventHub);
eventHub->SetOnStart(std::move(onChange));
}
对应 ArkTS:
.onStart(() => {
console.info('Marquee started')
})
完整 API 映射表
| ArkTS API | MarqueeModelNG 方法 | Property 类型 | 说明 |
|---|---|---|---|
Marquee({ src }) |
Create() |
- | 创建组件 |
.src(value) |
SetValue() |
LayoutProperty | 设置文本 |
.start(value) |
SetPlayerStatus() |
PaintProperty | 播放状态 |
.step(value) |
SetScrollAmount() |
PaintProperty | 滚动步长 |
.loop(value) |
SetLoop() |
PaintProperty | 循环次数 |
.direction(value) |
SetDirection() |
PaintProperty | 滚动方向 |
.fontSize(value) |
SetFontSize() |
LayoutProperty | 字体大小 |
.fontColor(value) |
SetTextColor() |
LayoutProperty | 字体颜色 |
.fontWeight(value) |
SetFontWeight() |
LayoutProperty | 字体粗细 |
.fontFamily(value) |
SetFontFamily() |
LayoutProperty | 字体族 |
.onStart(callback) |
SetOnStart() |
EventHub | 开始事件 |
.onBounce(callback) |
SetOnBounce() |
EventHub | 到达边缘事件 |
.onFinish(callback) |
SetOnFinish() |
EventHub | 完成事件 |
LayoutAlgorithm层详解
MarqueeLayoutAlgorithm 核心职责
MarqueeLayoutAlgorithm 负责 Marquee 的布局计算:
-
Measure(测量):
- 测量 Marquee 自身尺寸
- 测量文本子节点尺寸
- 计算自适应高度
-
Layout(布局):
- 定位文本子节点
- 处理文本方向(LTR/RTL)
Measure() 方法详解
源码位置:marquee_layout_algorithm.cpp:30-77
执行流程:
void MarqueeLayoutAlgorithm::Measure(LayoutWrapper* layoutWrapper)
{
// 1. 获取布局约束和 padding
const auto& layoutConstraint = layoutWrapper->GetLayoutProperty()->GetLayoutConstraint();
const auto& padding = layoutWrapper->GetLayoutProperty()->CreatePaddingAndBorder();
// 2. 获取文本子节点
auto child = layoutWrapper->GetAllChildrenWithBuild().front();
// 3. 设置文本约束
LayoutConstraintF textLayoutConstraint;
textLayoutConstraint.UpdateMaxSizeWithCheck(SizeF(Infinity<float>(), maxSize.Height()));
// 4. 添加 padding 到文本节点
PaddingProperty textPadding;
textPadding.left = CalcLength(padding.left.value());
textPadding.right = CalcLength(padding.right.value());
child->GetLayoutProperty()->UpdatePadding(textPadding);
// 5. 测量文本
child->Measure(textLayoutConstraint);
// 6. 计算自适应高度
auto adaptiveSize = GetMeasureAdaptiveHeight(layoutWrapper, child);
// 7. 设置 Marquee 尺寸
auto frameSize = layoutWrapper->GetGeometryNode()->GetFrameSize();
layoutWrapper->GetGeometryNode()->SetFrameSize(frameSize.ConvertToSizeT());
}
关键计算:
- 文本宽度:设置为无限大,让文本完整显示
- 文本高度:跟随 Marquee 高度
- 自适应高度:根据字体大小和 padding 计算
Layout() 方法详解
源码位置:marquee_layout_algorithm.cpp:79-110
执行流程:
void MarqueeLayoutAlgorithm::Layout(LayoutWrapper* layoutWrapper)
{
auto frameSize = layoutWrapper->GetGeometryNode()->GetFrameSize();
auto child = layoutWrapper->GetAllChildrenWithBuild().front();
CHECK_NULL_VOID(child);
// 1. 获取文本内容和方向
auto marqueeLayoutProperty = layoutWrapper->GetLayoutProperty<MarqueeLayoutProperty>();
auto content = marqueeLayoutProperty->GetSrc().value_or("");
auto direction = marqueeLayoutProperty->GetMarqueeDirection().value_or(MarqueeDirection::LEFT);
// 2. 获取 Pattern 以检测文本方向
auto pattern = layoutWrapper->GetHostNode()->GetPattern<MarqueePattern>();
auto textDirection = pattern->GetTextDirection(content, direction);
// 3. 计算对齐方式
auto align = (textDirection == TextDirection::RTL) ?
Alignment::CENTER_RIGHT : Alignment::CENTER_LEFT;
// 4. 计算文本位置
auto textGeoNode = child->GetGeometryNode();
auto minusLen = frameSize.Width() - textGeoNode->GetMarginFrameSize().Width();
OffsetF translate;
translate.SetX((1.0 + align.GetHorizontal()) * minusLen / MULTIPLE);
textGeoNode->SetMarginFrameOffset(translate);
// 5. 布局文本子节点
child->Layout();
}
对齐逻辑:
- LTR 文本:左对齐,从右向左滚动
- RTL 文本:右对齐,从左向右滚动
属性系统
MarqueeLayoutProperty
核心属性:
// 文本内容
ACE_DEFINE_PROPERTY_ITEM_WITHOUT_GROUP(Src, std::string, PROPERTY_UPDATE_LAYOUT);
// 字体样式
ACE_DEFINE_PROPERTY_ITEM_WITHOUT_GROUP(FontSize, Dimension, PROPERTY_UPDATE_LAYOUT);
ACE_DEFINE_PROPERTY_ITEM_WITHOUT_GROUP(FontWeight, FontWeight, PROPERTY_UPDATE_LAYOUT);
ACE_DEFINE_PROPERTY_ITEM_WITHOUT_GROUP(TextColor, Color, PROPERTY_UPDATE_RENDER);
ACE_DEFINE_PROPERTY_ITEM_WITHOUT_GROUP(FontFamily, std::vector<std::string>, PROPERTY_UPDATE_LAYOUT);
// 滚动方向
ACE_DEFINE_PROPERTY_ITEM_WITHOUT_GROUP(MarqueeDirection, MarqueeDirection, PROPERTY_UPDATE_RENDER);
MarqueePaintProperty
核心属性:
// 播放控制
ACE_DEFINE_PROPERTY_ITEM_WITHOUT_GROUP(PlayerStatus, bool, PROPERTY_UPDATE_RENDER);
// 动画参数
ACE_DEFINE_PROPERTY_ITEM_WITHOUT_GROUP(ScrollAmount, double, PROPERTY_UPDATE_RENDER);
ACE_DEFINE_PROPERTY_ITEM_WITHOUT_GROUP(Loop, int32_t, PROPERTY_UPDATE_RENDER);
属性传递流程
ArkTS API
↓
MarqueeModelNG::SetXXX()
↓
ACE_UPDATE_NODE_LAYOUT_PROPERTY / ACE_UPDATE_NODE_PAINT_PROPERTY
↓
MarqueeLayoutProperty / MarqueePaintProperty::UpdateXXX()
↓
FrameNode::MarkDirtyNode(PROPERTY_UPDATE_MEASURE / PROPERTY_UPDATE_RENDER)
↓
Pattern::OnModifyDone()
↓
应用相应逻辑
动画控制
滚动原理
Marquee 使用 平移动画 实现滚动效果:
- 起始位置:文本在视口右侧(从右向左滚动)
- 结束位置:文本完全移出视口左侧
- 动画过程:不断更新文本的 X 偏移量
动画参数计算
滚动距离
float MarqueePattern::GetScrollDistance()
{
auto textWidth = textNode_->GetGeometryNode()->GetMarginFrameSize().Width();
auto marqueeWidth = marqueeWidth_;
// 总滚动距离 = 文本宽度 + Marquee 宽度
return textWidth + marqueeWidth;
}
动画持续时间
float MarqueePattern::GetDuration(float distance, float step)
{
// 持续时间 = 距离 / 步长 * 默认延迟
constexpr float DEFAULT_DELAY = 85.0f; // 每像素滚动延迟
return distance / step * DEFAULT_DELAY;
}
动画执行
void MarqueePattern::PlayMarqueeAnimation(float start, int32_t repeatCount, bool needSecondPlay)
{
// 1. 计算滚动距离和持续时间
auto distance = GetScrollDistance();
auto step = paintProperty->GetScrollAmount().value_or(DEFAULT_MARQUEE_SCROLL_AMOUNT);
auto duration = GetDuration(distance, step);
// 2. 创建动画
auto animation = AnimationUtils::GetAnimation(
duration,
Curves::LINEAR,
repeatCount
);
// 3. 设置动画回调
animation->AddListener([this](float value) {
UpdateTextTranslateXY(value);
});
// 4. 启动动画
animator_->RunAnimation(animation);
}
滚动方向
MarqueeDirection 枚举:
enum class MarqueeDirection {
LEFT, // 从右向左(默认)
RIGHT, // 从左向右
DEFAULT, // 默认方向(从右向左)
DEFAULT_REVERSE // 默认反向(从左向右)
};
实现逻辑:
- LEFT: 起始位置在右侧,向左滚动
- RIGHT: 起始位置在左侧,向右滚动
- 自动检测文本方向(RTL/LTR)并调整
特殊功能
1. 文本方向自动检测
功能:根据文本内容自动检测 RTL/LTR 方向
实现:
TextDirection MarqueePattern::GetTextDirection(const std::string& content, MarqueeDirection direction)
{
// 检测是否为 RTL 文本
if (!content.empty() && (content[0] == 0x0591 || content[0] == 0x0592)) {
return TextDirection::RTL;
}
return TextDirection::LTR;
}
支持的 RTL 语言:
- 阿拉伯语
- 希伯来语
- 其他从右向左书写语言
2. 可见性自动暂停
功能:组件不可见时自动暂停动画
实现:
void MarqueePattern::OnVisibleAreaChange(bool isVisible)
{
if (isVisible) {
ResumeAnimation();
} else {
PauseAnimation();
}
}
优化效果:
- 节省 CPU 资源
- 减少电池消耗
- 提升整体性能
3. 循环控制
循环次数含义:
| loop 值 | 行为 |
|---|---|
-1 |
无限循环 |
0 |
不循环(播放一次) |
> 0 |
循环指定次数 |
实现逻辑:
void MarqueePattern::PlayMarqueeAnimation(float start, int32_t repeatCount, bool needSecondPlay)
{
// repeatCount = -1 表示无限循环
if (repeatCount == -1) {
animation->SetIterationCount(ANIMATION_REPEAT_INFINITE);
} else {
animation->SetIterationCount(repeatCount);
}
}
4. 事件系统
支持的事件:
| 事件 | 触发时机 | 回调函数 |
|---|---|---|
| onStart | 滚动开始 | SetOnStart() |
| onBounce | 到达边缘 | SetOnBounce() |
| onFinish | 滚动完成 | SetOnFinish() |
ArkTS 使用:
Marquee({ src: "Scrolling Text" })
.onStart(() => {
console.info('Animation started')
})
.onBounce(() => {
console.info('Reached edge')
})
.onFinish(() => {
console.info('Animation finished')
})
执行流程
1. 初始化流程
ArkTS: Marquee({ src: "Hello World", start: true })
↓
MarqueeModelNG::Create()
↓
FrameNode::GetOrCreateFrameNode(MARQUEE_ETS_TAG)
↓
创建 MarqueePattern
↓
创建文本子节点
↓
设置初始属性
↓
OnAttachToFrameNode()
↓
OnModifyDone()
↓
StartMarqueeAnimation()
↓
[初始化完成,动画开始]
2. 布局流程
触发布局(属性修改 / 尺寸变化)
↓
CreateLayoutAlgorithm()
↓
MarqueeLayoutAlgorithm::Measure()
├─ 测量文本子节点
├─ 设置文本约束(无限宽度)
├─ 计算自适应高度
└─ 设置 Marquee 尺寸
↓
MarqueeLayoutAlgorithm::Layout()
├─ 检测文本方向
├─ 计算对齐方式
├─ 定位文本子节点
└─ 布局文本子节点
↓
OnDirtyLayoutWrapperSwap()
├─ 检查尺寸变化
├─ 停止当前动画
└─ 重新启动动画
↓
[布局完成]
3. 动画流程
启动动画
↓
计算滚动距离
↓
计算动画持续时间
↓
创建动画对象
↓
设置动画回调
↓
逐帧更新文本位置
↓
UpdateTextTranslateXY(position)
↓
检查是否到达边缘
↓
FireBounceEvent()
↓
检查是否完成
↓
FireFinishEvent()
↓
[动画完成]
完整API清单
ArkTS API 列表
创建和基础配置
// 创建 Marquee
Marquee(options: {
src: string;
start?: boolean;
step?: number;
loop?: number;
fromStart?: boolean;
})
// 滚动方向
enum MarqueeDirection {
Left, // 从右向左
Right, // 从左向右
Default, // 默认方向
DefaultReverse // 默认反向
}
属性配置
// 设置文本内容
.src(value: string): this
// 播放控制
.start(value: boolean): this
// 滚动步长(速度)
.step(value: number): this
// 循环次数
.loop(value: number): this
// 滚动方向
.direction(value: MarqueeDirection): this
文本样式
// 字体大小
.fontSize(value: Length): this
// 字体颜色
.fontColor(value: ResourceColor): this
// 字体粗细
.fontWeight(value: FontWeight | number | string): this
// 字体族
.fontFamily(value: string | string[]): this
事件回调
// 滚动开始
.onStart(callback: () => void): this
// 到达边缘
.onBounce(callback: () => void): this
// 滚动完成
.onFinish(callback: () => void): this
C++ API 列表
MarqueeModelNG 公共方法
// 创建 Marquee
void Create() override;
// 设置文本
void SetValue(const std::optional<std::string>& value) override;
// 播放控制
void SetPlayerStatus(const std::optional<bool>& playerStatus) override;
// 滚动步长
void SetScrollAmount(const std::optional<double>& scrollAmount) override;
// 循环次数
void SetLoop(const std::optional<int32_t>& loop) override;
// 滚动方向
void SetDirection(const std::optional<MarqueeDirection>& direction) override;
// 文本样式
void SetTextColor(const std::optional<Color>& textColor) override;
void SetFontSize(const std::optional<Dimension>& fontSize) override;
void SetFontWeight(const std::optional<FontWeight>& fontWeight) override;
void SetFontFamily(const std::optional<std::vector<std::string>>& fontFamilies) override;
// 事件回调
void SetOnStart(std::function<void()>&& onChange) override;
void SetOnBounce(std::function<void()>&& onChange) override;
void SetOnFinish(std::function<void()>&& onChange) override;
关键实现细节
1. 文本子节点管理
设计:Marquee 内部包含一个 Text FrameNode 作为子节点
创建时机:在 MarqueeModelNG::Create() 中
更新方式:通过 TextLayoutProperty 设置文本属性
优势:
- 复用 Text 组件的文本渲染能力
- 支持所有 Text 样式属性
- 自动处理文本测量和换行
2. 滚动距离计算
公式:
scrollDistance = textWidth + marqueeWidth
说明:
- 文本需要完全滚动出视口
- 因此需要额外滚动一个 Marquee 宽度的距离
3. 动画持续时间计算
公式:
duration = (distance / step) * DEFAULT_DELAY
参数:
distance- 滚动距离(像素)step- 滚动步长(像素/帧),默认 6.0DEFAULT_DELAY- 每像素滚动延迟(毫秒),默认 85
示例:
- 距离:300px
- 步长:6px
- 持续时间:(300 / 6) * 85 = 4250ms
4. RTL 文本处理
检测方法:
bool IsRTLText(const std::string& content)
{
if (content.empty()) return false;
return content[0] == 0x0591 || content[0] == 0x0592;
}
处理逻辑:
- RTL 文本:右对齐,从左向右滚动
- LTR 文本:左对齐,从右向左滚动
5. 性能优化
可见性优化:
void OnVisibleAreaChange(bool isVisible)
{
if (isVisible) {
ResumeAnimation();
} else {
PauseAnimation();
}
}
优化效果:
- 滚出屏幕时暂停动画
- 节省 CPU 和电池资源
使用示例
示例 1:基础 Marquee
@Entry
@Component
struct BasicMarquee {
build() {
Marquee({
src: 'Running Marquee starts rolling',
start: true,
step: 50,
loop: -1
})
.width(200)
.fontSize(30)
.fontColor(Color.Red)
}
}
示例 2:自定义样式
@Entry
@Component
struct StyledMarquee {
build() {
Marquee({
src: 'This is a styled marquee text',
start: true,
step: 100,
loop: 5
})
.width(300)
.height(50)
.fontSize(24)
.fontColor(Color.Blue)
.fontWeight(FontWeight.Bold)
.fontFamily('Arial')
}
}
示例 3:控制播放状态
@Entry
@Component
struct ControlledMarquee {
@State isPlaying: boolean = true
build() {
Column() {
Marquee({
src: 'Controllable Marquee',
start: this.isPlaying,
step: 50,
loop: -1
})
.width(250)
.fontSize(20)
Row() {
Button('Play')
.onClick(() => {
this.isPlaying = true
})
Button('Pause')
.onClick(() => {
this.isPlaying = false
})
}
.margin({ top: 20 })
}
}
}
示例 4:事件监听
@Entry
@Component
struct EventMarquee {
@State message: string = 'Waiting...'
build() {
Column() {
Marquee({
src: 'Marquee with Events',
start: true,
step: 50,
loop: 3
})
.width(300)
.fontSize(22)
.onStart(() => {
this.message = 'Started!'
})
.onBounce(() => {
this.message = 'Bounced!'
})
.onFinish(() => {
this.message = 'Finished!'
})
Text(this.message)
.margin({ top: 20 })
}
}
}
示例 5:滚动方向控制
@Entry
@Component
struct DirectionMarquee {
build() {
Column() {
// 从右向左(默认)
Marquee({
src: 'Scrolling Left',
start: true,
step: 50,
loop: -1
})
.width(250)
.direction(MarqueeDirection.Left)
.margin({ bottom: 20 })
// 从左向右
Marquee({
src: 'Scrolling Right',
start: true,
step: 50,
loop: -1
})
.width(250)
.direction(MarqueeDirection.Right)
}
}
}
示例 6:动态更新内容
@Entry
@Component
struct DynamicMarquee {
@State texts: string[] = ['Text 1', 'Text 2', 'Text 3']
@State currentIndex: number = 0
build() {
Column() {
Marquee({
src: this.texts[this.currentIndex],
start: true,
step: 50,
loop: 1
})
.width(300)
.fontSize(24)
.onFinish(() => {
// 切换到下一个文本
this.currentIndex = (this.currentIndex + 1) % this.texts.length
})
Button('Change Text')
.onClick(() => {
this.currentIndex = (this.currentIndex + 1) % this.texts.length
})
.margin({ top: 20 })
}
}
}
调试指南
1. 日志调试
使用专用日志标签:
#include "base/log/ace_trace.h"
// Marquee 相关日志
TAG_LOGI(AceLogTag::ACE_MARQUEE, "Marquee src: %{public}s", src.c_str());
TAG_LOGD(AceLogTag::ACE_MARQUEE, "step: %{public}f, loop: %{public}d", step, loop);
// 动画相关日志
TAG_LOGD(AceLogTag::ACE_ANIMATION, "Animation duration: %{public}f", duration);
关键日志点:
OnModifyDone()- 属性修改完成StartMarqueeAnimation()- 动画启动UpdateTextTranslateXY()- 文本位置更新FireStartEvent()/FireBounceEvent()/FireFinishEvent()- 事件触发
2. 实现dump 方法
示例:
void MarqueePattern::DumpInfo(std::unique_ptr<JsonValue>& json)
{
auto host = GetHost();
CHECK_NULL_VOID(host);
auto paintProperty = host->GetPaintProperty<MarqueePaintProperty>();
CHECK_NULL_VOID(paintProperty);
auto layoutProperty = host->GetLayoutProperty<MarqueeLayoutProperty>();
CHECK_NULL_VOID(layoutProperty);
json->Put("src", layoutProperty->GetSrc().value_or("").c_str());
json->Put("playerStatus", paintProperty->GetPlayerStatus().value_or(false) ? "true" : "false");
json->Put("scrollAmount", paintProperty->GetScrollAmount().value_or(DEFAULT_MARQUEE_SCROLL_AMOUNT));
json->Put("loop", paintProperty->GetLoop().value_or(DEFAULT_MARQUEE_LOOP));
json->Put("marqueeWidth", marqueeWidth_);
}
3. 常见问题定位
问题 1:文本不滚动
检查点:
start是否设置为trueplayerStatus是否为true- 文本宽度是否大于 Marquee 宽度
定位方法:
auto playStatus = paintProperty->GetPlayerStatus().value_or(false);
TAG_LOGI(AceLogTag::ACE_MARQUEE, "playerStatus: %{public}d", playStatus);
auto textWidth = textNode_->GetGeometryNode()->GetMarginFrameSize().Width();
TAG_LOGI(AceLogTag::ACE_MARQUEE, "textWidth: %{public}f, marqueeWidth: %{public}f",
textWidth, marqueeWidth_);
问题 2:动画速度异常
检查点:
step值是否合理- 动画持续时间计算是否正确
定位方法:
auto step = paintProperty->GetScrollAmount().value_or(DEFAULT_MARQUEE_SCROLL_AMOUNT);
auto duration = GetDuration(distance, step);
TAG_LOGI(AceLogTag::ACE_MARQUEE, "step: %{public}f, duration: %{public}f", step, duration);
问题 3:事件不触发
检查点:
- 是否正确注册事件回调
- 循环次数设置是否正确
定位方法:
void MarqueePattern::FireStartEvent()
{
TAG_LOGI(AceLogTag::ACE_MARQUEE, "Firing onStart event");
// ... 触发事件
}
常见问题
Q1: Marquee 组件和 Text 组件有什么区别?
A:
- Marquee:专用于滚动文本,自动处理滚动动画
- Text:静态文本显示,不包含滚动功能
选择建议:
- 需要滚动效果 → 使用 Marquee
- 静态文本显示 → 使用 Text
Q2: 如何控制滚动速度?
A:通过 step 属性控制
.step(50) // 默认速度
.step(100) // 更快
.step(25) // 更慢
Q3: 如何实现无限循环?
A:设置 loop 为 -1
.loop(-1) // 无限循环
Q4: 如何动态更新文本内容?
A:使用 @State 装饰器
@State text: string = "Hello"
Marquee({ src: this.text })
Button('Update').onClick(() => {
this.text = "Updated Text"
})
Q5: 为什么文本不滚动?
A:检查以下几点:
start是否设置为true- 文本宽度是否大于 Marquee 宽度
- 组件是否可见
// 正确的配置
Marquee({
src: 'This is a long text that will scroll',
start: true, // 必须设置为 true
step: 50
})
.width(200) // 宽度小于文本宽度
Q6: 如何监听滚动事件?
A:使用事件回调
.onStart(() => {
console.info('Started')
})
.onBounce(() => {
console.info('Bounced')
})
.onFinish(() => {
console.info('Finished')
})
Q7: 如何设置滚动方向?
A:使用 direction 属性
.direction(MarqueeDirection.Left) // 从右向左
.direction(MarqueeDirection.Right) // 从左向右
Q8: Marquee 支持多行文本吗?
A:不支持。Marquee 只支持单行文本滚动。
Q9: 如何暂停和恢复滚动?
A:通过修改 start 属性
@State isPlaying: boolean = true
Marquee({
src: 'Text',
start: this.isPlaying
})
Button('Toggle').onClick(() => {
this.isPlaying = !this.isPlaying
})
Q10: Marquee 性能如何?
A:Marquee 具有以下性能优化:
- 组件不可见时自动暂停动画
- 使用高效的平移动画
- 文本测量和布局优化
附录
A. 术语表
| 术语 | 英文 | 描述 |
|---|---|---|
| 跑马灯 | Marquee | 滚动文本组件 |
| 滚动步长 | Scroll Amount | 每帧滚动的像素数 |
| 循环次数 | Loop | 动画重复次数 |
| 播放状态 | Player Status | 动画播放/暂停状态 |
| 滚动方向 | Direction | 文本滚动方向 |
| RTL | Right-to-Left | 从右向左的文本方向 |
| LTR | Left-to-Right | 从左向右的文本方向 |
B. 源码文件索引
| 文件 | 行数 | 描述 |
|---|---|---|
marquee_pattern.h |
~80 | MarqueePattern 类定义 |
marquee_pattern.cpp |
~450 | MarqueePattern 实现 |
marquee_model_ng.h |
~50 | NG Model 定义 |
marquee_model_ng.cpp |
~100 | NG Model 实现 |
marquee_layout_algorithm.h |
~40 | 布局算法定义 |
marquee_layout_algorithm.cpp |
~120 | 布局算法实现 |
marquee_layout_property.h |
~60 | 布局属性定义 |
marquee_paint_property.h |
~50 | 绘制属性定义 |
marquee_event_hub.h |
~40 | 事件中心定义 |
C. 相关文档
D. 常量定义
// 默认滚动步长(像素/帧)
constexpr double DEFAULT_MARQUEE_SCROLL_AMOUNT = 6.0;
// 默认循环次数(-1 表示无限循环)
constexpr int32_t DEFAULT_MARQUEE_LOOP = -1;
// 默认滚动延迟(毫秒/像素)
constexpr double DEFAULT_MARQUEE_SCROLL_DELAY = 85.0;
// 对齐计算常量
constexpr double MULTIPLE = 2.0;
E. 版本历史
| 版本 | 日期 | 变更 |
|---|---|---|
| v1.0 | 2026-02-01 | 初始版本 |
文档结束
本文档基于 OpenHarmony ace_engine 源码分析生成,如有错误或遗漏,欢迎指正。