拓十年匠心定制 · 商业建站与技术教学双线并行 咨询热线:400-886-1026 service@lmnt.cn
ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

Qt Widgets新手引导界面实现:全屏遮罩、控件高亮与气泡提示

Qt Widgets新手引导界面实现:全屏遮罩、控件高亮与气泡提示

做 Qt 开发时间久了,手头总会攒下一堆“重复造轮子”的经验,引导界面就是其中一个高频需求。新用户第一次打开你的桌面软件,满屏控件,不知道从哪里开始操作;老版本更新后新增了几个功能,不提醒一下基本没人发现。这种场景下产品经理提的需求往往很统一:第一次启动时用半透明遮罩把整个界面盖住,只亮出目标控件,旁边再弹一个带箭头的说明气泡,用户点“下一步”继续引导下一个功能。这种交互在互联网产品里叫 UITour,在 Qt 项目里也常被叫做 Guide、Onboarding 或新手引导。

需求看着简单,真正实现到“能上线”却不轻松。用普通 QMessageBox 提示,没有箭头、对准不了控件;自己画一个半透明 QWidget,坐标一错位高亮框就飘到奇怪的地方;弄成全屏遮罩后发现业务控件还是能被点到,引导形同虚设。这篇文章要分享的是我落地过多次的 Qt Widgets 引导界面方案:一个独立的遮罩组件,支持任意控件高亮、气泡提示、多步骤管理,不依赖 QML 也能用,并且兼顾单屏和多屏场景。适合谁看?就是需要在 Qt 项目里加新手引导、功能引导、操作说明,又不想在坐标和绘制细节上反复折腾的开发者。

1. 引导界面UITour在Qt里到底怎么做

1.1 先拆解引导界面的真实需求

很多人一听到“引导界面”,第一反应就是做个气泡样式的 QLabel 弹窗,标个文字就完事。但真正能上线的引导界面,至少要具备三个能力:全界面遮罩、目标控件高亮、带箭头的气泡说明。这三个能力缺一个,体验就会打折。

全界面遮罩的作用是压制用户的注意力范围。如果不遮罩,用户一边看气泡一边还能点其他按钮,引导完了他根本不记得你提示过什么。目标控件高亮的作用是告诉你“看这里”,通常用白色描边或浅色光晕把目标区域框出来。气泡说明则负责解释“这个功能是干嘛的、怎么用”,并且要有一个箭头指向目标控件,让人一眼看出它在描述哪个区域。

除了视觉表现,引导流程本身还有状态管理的问题。上一步、下一步、跳过、到最后一步触发完成回调,这本质上是一个简单的状态机。如果不在设计阶段把步骤数据结构化,写到后面代码里全是 if-else 判断当前是第几步,维护起来非常痛苦。所以做这个功能之前,得先把需求拆成“绘制能力”和“流程管理”两条线。

1.2 方案对比:全屏遮罩为什么是最优解

在 Qt 里实现 UITour,常见路线有三条:多个独立气泡窗口、截图覆盖、全屏透明 Widget 遮罩。我实际对比过它们的优劣势。

方案实现思路主要问题
多个独立气泡窗口在每个目标控件附近弹一个带箭头的小窗口无法压暗整个界面,层级管理混乱,多屏移动时位置误差大
截图覆盖对当前界面截图,用带洞的截图盖住原界面动态界面(滚动、动画、输入框光标)会穿帮,点击交互不好处理
全屏透明 Widget 遮罩一个全屏顶层窗口,QPainter 统一绘制遮罩和高亮前期需要处理坐标和事件拦截,但后续扩展性最好

我最终选择全屏透明 Widget 方案。核心原因有三个。第一,遮罩、高亮、气泡都在同一个绘制上下文里,坐标基准统一,不会出现“遮罩是一个窗口、气泡是另一个窗口”导致的层级和坐标换算问题。第二,全屏窗口天然拦截鼠标事件,业务控件不会被误触,用户必须跟着引导走。第三,后续想加动画、快捷键、动态刷新目标位置,都收敛在一个类里改造,对业务代码的侵入极小。

1.3 这套方案最终能做到什么程度

按这套思路做下来的组件,实际能力大概是这样:可以压暗指定区域外的所有内容;可以对任意 QWidget 画高亮框,支持圆角;气泡自动选择上下左右方向,保证不出屏幕;支持上一步、下一步、跳过;步骤可以配置化,甚至抽成 Json 由运营维护;单屏、多屏、DPI 缩放场景下坐标都能保持正确。下面我把设计思路、核心代码、踩坑经验完整过一遍。

2. 整体设计与数据驱动

2.1 一个引导步骤的数据结构

引导功能最容易写崩的地方,是业务方每次都要改代码。所以我从第一步开始就把步骤做成数据驱动:每个步骤描述清楚目标控件、提示文案、气泡方向、进入和离开时执行的动作,类型统一为一个UITourStep结构体。

struct UITourStep { QWidget* target = nullptr; // 目标控件,为空时使用 targetRect QRect targetRect; // 备选:直接指定全局坐标区域 QString title; // 气泡标题(可选) QString text; // 气泡正文 BubbleDirection direction = BubbleDirection::Auto; // 气泡方向 std::function<void()> onEnter; // 进入该步骤时回调 std::function<void()> onExit; // 离开该步骤时回调 };

用结构体而不是把每个步骤硬编码成一段逻辑,好处非常明显:引导流程的增删改就是增删一个列表项。如果产品经理想调整引导顺序,直接拖动列表顺序即可;如果想让引导在特定业务动作之后触发,只要在对应位置调用showGuide(steps)就行。代码里不需要维护“当前是第几步”的散落状态,所有步骤信息都在一个列表里,清晰且可控。

2.2 模块拆分:三块各管各的

组件内部我拆成了三个部分:

  • UITourManager:负责接收步骤列表、维护当前索引、触发上一步/下一步/跳过。
  • UITourMaskWidget:全屏透明遮罩窗口,负责绘制遮罩、高亮、气泡,以及鼠标和键盘事件处理。
  • UITourStep:步骤数据结构,业务方只负责填数据,不关心内部实现。

这种拆分是为了隔离变化。Manager 只管流程,不关心绘制细节;MaskWidget 只管绘制和交互,不知道业务逻辑。后续如果把引导从 Qt Widgets 迁移到 Qt Quick,也只需要重写 MaskWidget 这一层,Manager 和 Step 数据可以直接复用。实际项目中我还遇到过一种需求:同一个引导流程在多个入口处触发,但部分步骤的提示文案不同。这时候只要在入口处微调UITourStep列表即可,组件内部不用动一行代码。

2.3 状态管理:把它当成一个简单状态机

很多人在引导功能上加逻辑时越加越乱,就是因为没把步骤管理当成状态机。简单理解:引导开始(idle 到 running)、当前步骤索引、下一步(index+1)、上一步(index-1)、结束(running 到 done)。在这个模型上扩展跳过、完成回调和中途取消都很自然。

我处理步骤切换时的习惯是:nextStep()里先调用当前步骤的onExit,再更新索引,再执行新步骤的onEnter,最后刷新遮罩层。这样做可以避免在切换过程中界面还没准备好时就去取目标控件坐标。很多高亮位置飘移的问题,根源就在于忽略了onExit到onEnter之间界面状态的变化。状态机的核心意义不是引入复杂的设计模式,而是强制你定义清楚“每一步切换时到底发生了什么”。

3. 核心细节解析与实操要点

3.1 坐标映射:从控件位置到遮罩层坐标

UITour 最容易出错的点,就是坐标映射。新手最先踩的坑是直接拿widget->geometry()当全局坐标用,结果高亮框偏到左上角。geometry()返回的是控件在父窗口坐标系下的局部矩形,不是屏幕坐标。正确做法是用widget->mapToGlobal(QPoint(0,0))拿到控件左上角在屏幕坐标系里的位置,再结合widget->size()得到全局矩形。

QPoint topLeft = targetWidget->mapToGlobal(QPoint(0, 0)); QRect globalRect(topLeft, targetWidget->size());

这个逻辑看起来简单,实际还有三个隐藏问题。第一,控件如果在 QDialog 里,只要弹窗本身正常显示,mapToGlobal依然返回正确的屏幕坐标。但某些 Linux 桌面环境和窗口管理器下,遮罩层覆盖多个窗口时层级会出问题,最容易出现“遮罩盖不住弹窗”的情况。第二,控件刚执行完setVisible(true)或布局刚变化时,尺寸可能还没有及时刷新,拿到的是旧值。第三,分辨率缩放开启后,全局坐标是逻辑坐标,遮罩层绘制时也是逻辑坐标,两者统一就不会偏;最怕的是在代码里手动混入物理像素值。

我的经验是:所有步骤里的目标坐标统一在步骤开始前重新计算,不要建好步骤列表后一次性算完。因为界面可能因为业务状态改变而移动控件位置,尤其在引导中点击“下一步”会触发界面切换的场景,新界面的控件必须在切换完成后再算坐标,而不是在切换前就算好。

3.2 高亮区域绘制:给遮罩层“挖洞”的两种思路

遮罩层绘制的核心是“挖洞”,也就是把目标矩形区域从半透明遮罩中排除。最直观的实现方式是QRegion差集:先用全屏矩形减去高亮区域,得到一个带洞的 QRegion,设置成裁剪区域后填充遮罩色。这种方案简单稳定,但缺点是不支持圆角,高亮框只能画成直角矩形。

如果需要圆角高亮框,推荐用QPainterPath做复合路径。这也是我实际项目中采用的方式。

void UITourMaskWidget::paintEvent(QPaintEvent*) { QPainter painter(this); painter.setRenderHint(QPainter::Antialiasing); QPainterPath cutPath; cutPath.setFillRule(Qt::OddEvenFill); cutPath.addRect(rect()); // 整个屏幕 cutPath.addRoundedRect(m_highlightRect, 10, 10); // 高亮区域 painter.fillPath(cutPath, QColor(0, 0, 0, 130)); // 高亮边框 QPen pen(QColor(255, 255, 255), 2); painter.setPen(pen); painter.setBrush(Qt::NoBrush); painter.drawRoundedRect(m_highlightRect, 10, 10); }

为什么这段代码能挖洞?关键在于OddEvenFill填充规则:如果一个点被路径覆盖的次数是奇数,就认为它属于填充区域。全屏矩形覆盖一次是奇数,高亮圆角矩形再多覆盖一次变成偶数,于是高亮矩形内部不填充,形成镂空效果。这个思路比QRegion差集灵活得多,圆角、异形高亮都能做。

如果一次要高亮多个控件,把多个圆角矩形全部add进路径即可。这里要特别注意:必须在路径上调用setFillRule(Qt::OddEvenFill)。默认的WindingFill用多个 subpath 也能挖洞,但遇到路径方向不一致、自交的情况容易出意外,显式声明OddEvenFill最稳妥。实际项目中我还加了一个细节:高亮区域四周带一圈白色描边,这样即使控件本身颜色很深,视觉层次也能保住。

3.3 气泡智能定位:别让说明飞出屏幕

气泡放置策略,其实和大部分带箭头 Tooltip 的实现一样:优先放在高亮区域的下方,如果下方空间不够就放上方,上下都不够再尝试左右两侧。为了避免代码里写一长串 if-else,我把方向优先级整理成一个数组,按顺序逐个尝试,第一个放得下的方向就用它。

void UITourMaskWidget::layoutBubble() { QSize bubbleSize(m_bubbleWidth, m_bubbleHeight); QRect screen = this->rect(); QVector<BubbleDirection> candidateDirs; BubbleDirection configured = m_currentStep.direction; if (configured == BubbleDirection::Auto) { candidateDirs = { Bottom, Top, Right, Left }; } else { candidateDirs = { configured }; for (auto d : { Bottom, Top, Right, Left }) { if (d != configured) candidateDirs << d; } } for (BubbleDirection dir : candidateDirs) { QRect candidate = computeBubbleRect(m_highlightRect, bubbleSize, dir); if (screen.contains(candidate)) { m_bubbleRect = candidate; m_bubbleDir = dir; return; } } // 兜底:强制放到屏幕中间偏下 m_bubbleRect = QRect(screen.center().x() - bubbleSize.width() / 2, screen.center().y() + 40, bubbleSize.width(), bubbleSize.height()); m_bubbleDir = Bottom; }

computeBubbleRect的逻辑就是把高亮矩形和气泡矩形之间的间距设为固定值,比如 12 像素。方向为 Bottom 时,气泡放在高亮矩形下方,水平方向和高亮矩形中心对齐;方向为 Right 时,气泡放在右侧,垂直方向和高亮矩形中心对齐。

实际操作中还有两个细节容易忽略。第一,气泡里的文字长度是动态的,固定气泡尺寸一定会出现文字被裁剪的情况。建议在设置步骤文本后用QFontMetrics::boundingRect动态计算气泡尺寸,再传入layoutBubble。第二,如果高亮控件恰好也在屏幕边缘,单纯的方向优先级判断仍然可能失败,这时兜底逻辑很重要:直接把气泡放到屏幕中央附近,用箭头指向目标控件。虽然视觉上不算最优,但至少不会让用户看不到提示文字。

3.4 事件拦截与交互控制:引导期间不能误触

全屏遮罩窗口的窗口层级设置非常关键。我在实现里让UITourMaskWidget使用Qt::Tool类型,同时设置Qt::WindowStaysOnTopHint和Qt::FramelessWindowHint,最后调用showFullScreen()。Qt::Tool类型窗口不会出现在任务栏里,比普通顶级窗口更适合做引导层。

鼠标事件默认会被遮罩层拦截,因为遮罩层是顶层窗口。但有一个容易忽略的坑:如果遮罩窗口意外设置了Qt::WA_TransparentForMouseEvents属性,或者某些窗口管理器下出现了输入穿透,点击事件会穿透到业务控件。我的做法是显式调用setAttribute(Qt::WA_TransparentForMouseEvents, false),确保遮罩层层层接收鼠标事件。

键盘方面,我会在 show 时调用grabKeyboard(),在 hide 时releaseKeyboard()。这样可以避免用户按空格、回车时误触发到底下业务控件的快捷键。引导期间按 Esc 可以安全退出,按左方向键回退到上一步,按右方向键进入下一步。这些交互逻辑统一放在遮罩层里,业务代码完全无感知。

4. 实操过程与核心环节实现

4.1 核心类结构设计

先看头文件。这里不贴最完整的商业代码,只保留引导功能最核心的部分,方便你照着搭骨架。

class UITourMaskWidget : public QWidget { Q_OBJECT public: explicit UITourMaskWidget(QWidget* parent = nullptr); ~UITourMaskWidget(); void showGuide(const QList<UITourStep>& steps); void nextStep(); void prevStep(); void closeGuide(); signals: void guideFinished(); void guideCanceled(); protected: void paintEvent(QPaintEvent* event) override; void mousePressEvent(QMouseEvent* event) override; void keyPressEvent(QKeyEvent* event) override; private: void updateStepData(); void layoutBubble(); QList<UITourStep> m_steps; int m_index = -1; QRect m_highlightRect; QRect m_bubbleRect; BubbleDirection m_bubbleDir; };

showGuide负责接管整个引导流程。内部会记录步骤列表、重置索引、显示全屏遮罩。paintEvent里做所有绘制。mousePressEvent里可以设计两种行为:点击遮罩区域关闭引导,或者点击高亮区域进入下一步。我按产品需求做得比较灵活,默认点击遮罩空白处跳过当前步骤,点击高亮区域进入下一步。

4.2 绘制实现细节:遮罩、高亮、气泡一体绘制

前面第 3.2 节已经展示了遮罩和高亮的核心绘制代码,这里补充气泡和文字部分的实现。

void UITourMaskWidget::paintEvent(QPaintEvent*) { QPainter painter(this); painter.setRenderHint(QPainter::Antialiasing); // 1. 遮罩 + 高亮镂空(代码同 3.2 节,省略) ... // 2. 气泡背景 painter.setPen(Qt::NoPen); painter.setBrush(QColor(255, 255, 255, 245)); painter.drawRoundedRect(m_bubbleRect, 8, 8); // 3. 箭头 if (m_bubbleDir == BubbleDirection::Bottom) { QPolygon arrow; arrow << m_bubbleRect.topLeft() + QPoint(10, 0) << m_bubbleRect.topRight() - QPoint(10, 0) << QPoint(m_bubbleRect.center().x(), m_bubbleRect.top() - 12); painter.drawPolygon(arrow); } else if (m_bubbleDir == BubbleDirection::Top) { QPolygon arrow; arrow << m_bubbleRect.bottomLeft() + QPoint(10, 0) << m_bubbleRect.bottomRight() - QPoint(10, 0) << QPoint(m_bubbleRect.center().x(), m_bubbleRect.bottom() + 12); painter.drawPolygon(arrow); } // Left / Right 方向以此类推 // 4. 文字 QRect textRect = m_bubbleRect.adjusted(16, 12, -16, -12); painter.setPen(QColor(40, 40, 40)); painter.setFont(m_font); painter.drawText(textRect, Qt::AlignLeft | Qt::TextWordWrap, m_currentStep.text); }

绘制顺序有讲究:先画遮罩层,再画高亮边框,最后画气泡。如果气泡先画,遮罩层颜色压上去,气泡文字就看不清了。文字区域要预留足够的 padding,尤其在设置Qt::TextWordWrap之后,需要确保气泡宽度足够容纳文本,否则换行后会裁掉底部内容。

4.3 步骤切换:重算坐标并刷新

步骤切换是引导功能最容易出问题的地方。我的nextStep()实现遵循“先退出、再进入、最后刷新”的顺序。

void UITourMaskWidget::nextStep() { if (m_steps.isEmpty()) return; if (m_index >= 0 && m_steps[m_index].onExit) m_steps[m_index].onExit(); m_index++; if (m_index >= m_steps.size()) { hideGuide(); emit guideFinished(); return; } if (m_steps[m_index].onEnter) m_steps[m_index].onEnter(); updateStepData(); } void UITourMaskWidget::updateStepData() { const UITourStep& step = m_steps[m_index]; // 1. 计算高亮区域 if (step.target) { QPoint topLeft = step.target->mapToGlobal(QPoint(0, 0)); m_highlightRect = QRect(topLeft, step.target->size()); } else { m_highlightRect = step.targetRect; } // 2. 计算气泡位置 m_currentStep = step; layoutBubble(); // 3. 刷新绘制 update(); }

这里最核心的一点是updateStepData在onEnter之后才执行。原因前面提过:onEnter可能触发界面切换、弹窗打开、控件位置变化,这些动作完成之后,坐标才是准确的。如果先取坐标再执行回调,取到的往往是旧位置。

void MainWindow::showFirstGuide() { QList<UITourStep> steps; UITourStep step1; step1.target = ui->importBtn; step1.text = QStringLiteral("点击这里导入你的原始数据文件,支持 CSV / Excel。"); step1.direction = BubbleDirection::Bottom; steps << step1; UITourStep step2; step2.target = ui->analyzeBtn; step2.text = QStringLiteral("导入完成后,点这里执行分析,结果会在右侧表格中展示。"); step2.direction = BubbleDirection::Top; steps << step2; UITourStep step3; step3.target = ui->exportBtn; step3.text = QStringLiteral("分析完成后可以一键导出报告。"); steps << step3; createTour()->showGuide(steps); }

业务代码用起来很短。这里要提醒的是:如果触发引导时目标控件还没完全显示,比如窗口还在淡入动画,建议用QTimer::singleShot延迟几百毫秒再取坐标。取坐标的时机不对,高亮框就会跑偏,这是最典型的低级错误,也是我在多个项目里反复踩过的坑。

4.4 动态界面:滚动和布局变化时如何保持高亮位置

在某些页面里,目标控件会随滚动条滚动或随布局动画移动。这种情况纯靠一次坐标计算是撑不住的。解决思路有两个:一是让 MaskWidget 在引导期间用QTimer每 100ms 重算一次高亮区域,简单粗暴,但会引入不必要的重绘开销;二是监听滚动条valueChanged信号或布局动画的状态变化,主动调用updateStepData(),性能更好。

我的实测经验是,大部分引导流程都是静态的,不需要定时刷新。但如果你要引导的目标控件位于 ListView、可折叠区域或者带平滑动画的布局里,就一定要做刷新。不然用户一滚动,高亮框就悬空在原来那个位置,看起来非常奇怪。监听信号的方案其实实现起来也不复杂,你只需要在updateStepData里把当前高亮矩形重新计算一遍,再调用update()触发重绘即可。

5. 常见问题与排查技巧实录

5.1 高亮位置偏移:先检查取坐标方式

高亮位置不对,第一件事就是确认你拿到的矩形是全局坐标。很多同学直接用了widget->geometry(),这个方法返回的是父窗口坐标系下的局部矩形,不是全局坐标,直接用在高亮绘制上必然偏移。正确写法是QRect(widget->mapToGlobal(QPoint(0,0)), widget->size())。

另一个常见原因是遮罩层没有真正覆盖到目标控件所在屏幕。单屏时showFullScreen()没有问题,多屏时showFullScreen()默认只覆盖当前所在屏幕,目标控件在副屏上就不会被正确遮罩。此时应该用setGeometry(screen->geometry())显式指定覆盖范围,而不是依赖showFullScreen()。

5.2 高亮控件在弹窗或对话框中坐标错乱

引导目标是某个 QDialog 里的按钮时,遮罩层和弹窗的层级关系要格外小心。只要遮罩窗口设置了Qt::WindowStaysOnTopHint,大部分情况下能盖住普通弹窗。但弹窗如果也设置了置顶属性,两边都置顶时不同平台会有不同的调度策略,表现不稳定。

我的建议是:引导期间把弹窗的置顶属性统一关掉,并在弹窗完成显示后再计算目标控件坐标。另外,弹窗内部的控件在exec()阻塞调用中也能正常用mapToGlobal,但前提是弹窗已经真正显示出来。如果在show()之后立刻取坐标,布局可能还没完成,踩过这个坑之后我就养成了在QTimer::singleShot(0, ...)回调里取坐标的习惯。

5.3 高分屏和 DPI 缩放导致坐标偏移

Windows 高分屏场景最容易犯的错,是把 Qt 的全局逻辑坐标和物理像素坐标混用。正常情况下mapToGlobal返回的是逻辑坐标,遮罩层绘制也在逻辑坐标下进行,两者统一就没问题。但如果你为了调用原生 API 而手动乘以devicePixelRatio,再拿回去跟 Qt 坐标比较,就会偏移。

建议在 main 函数里尽早开启高 DPI 支持。Qt5 用QApplication::setAttribute(Qt::AA_EnableHighDpiScaling),Qt6 默认开启。所有坐标计算都用 Qt 的 API,不要手动乘以devicePixelRatio。不同屏幕缩放比例不一致时,QScreen::geometry()返回的也是逻辑坐标,可以放心使用。

5.4 遮罩层挡住了页面但点击没有响应

出现这种问题,先看遮罩窗口是否真的接收到了事件。我遇到过两个屏幕使用不同缩放比例时,全屏遮罩窗口无法完全覆盖副屏,导致缝隙里点击事件穿透到业务控件。解决方式是把遮罩窗口的几何范围设置成QApplication::primaryScreen()->virtualGeometry(),覆盖整个虚拟桌面。

还有一种情况:业务代码在引导期间对目标控件调用了setEnabled(false)或hide(),导致高亮区域依然绘制,但目标控件本身不可见或不可点击。这时候高亮框看起来还在,但按钮实际上没响应。排查时在目标控件上打印isVisible()和isEnabled()是最直接的。

5.5 全屏遮罩重绘会不会卡

静态绘制一帧半透明遮罩加气泡,其实 CPU 占用可以忽略不计。实测下来,在普通配置的办公电脑上甚至感觉不到损耗。需要警惕的是动画场景:如果你给高亮框加了呼吸效果、脉冲边框、淡入淡出,每一帧都需要重绘整个屏幕区域。此时应尽量避免全屏update(),而是只更新高亮框所在的小矩形;如果还是卡,就把动画从逐帧 QPainter 绘制换成其他平台优化过的效果,或者降低帧率。

5.6 快速排查速查表

现象可能原因解决建议
高亮框偏移使用了局部坐标 geometry()改用 mapToGlobal 计算全局坐标
高亮框悬空目标控件在滚动或布局动画中监听滚动信号或定时刷新高亮矩形
遮罩盖不住弹窗弹窗置顶、遮罩未覆盖该屏幕统一置顶策略,设置虚拟桌面几何范围
点击穿透窗口属性或平台输入穿透显式关闭 WA_TransparentForMouseEvents
气泡超出屏幕方向判断不足使用方向优先级列表,补兜底逻辑
文字被裁剪固定气泡尺寸用 QFontMetrics 按文本动态计算气泡尺寸

写到这儿,Qt 里做 UITour 引导界面的主要细节基本过完了。我个人实际的感受是,这个功能代码量不大,但要求你同时处理好坐标、绘制、事件、窗口层级这几块内容,每一步都有一个很隐蔽的坑。最稳妥的落地路径是:先做一个能用的静态引导,跑通主流程,再根据实际界面的动态情况补坐标刷新和动画。

最后分享一个我坚持了很长时间的习惯:项目里会把引导步骤抽成 Json 配置,这样产品改文案不用动代码。但配置解析后依然落到UITourStep结构体里,组件内部完全不感知 Json 的存在。另外,引导功能上线前一定要在双屏和不同缩放比例的环境下过一遍,这个环节真的省不了。

返回列表