菜单

STTWidget使用手册

下载

STTWidget.h 使用手册

STTWidget 是所有 STT 控件的基础类,封装 LVGL object、布局、状态、事件、变量绑定、条件显隐、权限、截图和拖拽。同一头文件还声明全局蒙版工具 STTMask。

本文所称 object 是 STTWidget::object 指向的底层 lv_obj_t。

1. 生命周期与基本初始化

STTWidget 通常不直接实例化,而由 STTLabel、STTButton、STTTable 等派生类构造底层 object。

cpp 复制代码
STTLabel* label = new STTLabel(getRuntime(), parent, "lblStatus");
label->setMainArg(20, 20, 180, 40, "运行", 1);

通用初始化接口:

cpp 复制代码
widget->setMainArg(x, y, width, height);
widget->setMainStyle(radius, color1, color2, gradDir, opacity,
                     borderWidth, borderColor,
                     shadowWidth, shadowColor, selector);
widget->setFontStyle(textColor, fontSize, fontName, selector, fontStyle);

setMainStyle 同时把 opacity 用于背景、边框和阴影。若三者透明度要不同,应通过 getStyle 的对应子样式分别设置。

构造参数 rt 当前由 SttObject/全局 Runtime 体系使用;父控件必须先创建并比子控件活得久。

2. 样式

cpp 复制代码
STTStyle* style = widget->getStyle();
style->p_bg->setColor(0xffffff)->setOpa(255);
style->p_border->setWidth(1)->setColor(0xcccccc);

getStyle 返回附着到当前 object 的样式包装指针,属于框架管理对象,不要 delete,也不要在控件销毁后使用。

背景图片:

cpp 复制代码
widget->setBgImg("res/panel.png", stt::constant::Part::Main);

实现等价于 setImgSrc(path, part) 后把该 part 背景透明度设为 0。path 的有效性和生命周期没有在 STTWidget 层检查。

3. 刷新

cpp 复制代码
widget->refresh();
widget->refreshAll();
  • refresh 先用已绑定变量调用 onDataChanged,再刷新动画、显隐、闪烁、权限和使能 helper。
  • refreshAll 只遍历 C++ m_children,依次对子控件调用 refresh 和 refreshAll;它不会先刷新当前 widget 本身。
  • 直接由 LVGL 创建、未加入 SttObject 子列表的内部对象不会被 refreshAll 遍历。

需要连当前控件一起刷新时:

cpp 复制代码
widget->refresh();
widget->refreshAll();

4. 变量绑定

原始指针:

cpp 复制代码
Ivariable value = getRuntime()->getIVarMgr()->get("Pressure");
if (value != nullptr) {
    widget->bindData(value);
}

共享指针:

cpp 复制代码
shared_ptr<IVariable> value = /* ... */;
widget->bindData(value);

重要所有权规则:

  • bindData 先断开旧变量,再连接新变量的 valueChanged。
  • 传 nullptr 时直接返回,不会解除旧绑定;解除必须调用 disBindData。
  • shared_ptr 重载只调用 data.get(),STTWidget 不保存 shared_ptr,不会延长变量生命周期。外部最后一个 shared_ptr 释放后将留下悬空 bindVar。
  • onDataChanged 基类为空,由派生控件实现。
  • 析构会断开当前 bindVar。
cpp 复制代码
widget->disBindData();

5. 事件

成员函数回调:

cpp 复制代码
widget->onEventHandler(
    LV_EVENT_CLICKED,
    this,
    (EHandler)&Frm01::onWidgetClicked
);
cpp 复制代码
void Frm01::onWidgetClicked(uint16_t code, LvEvent e) {
    STTWidget* target = (STTWidget*)e.target;
}

onEventHandler 只把条目追加到 vector,没有去重和移除单条成员回调的公开接口。object/handler 的接收对象必须比注册关系存活更久。

C 风格事件回调:

cpp 复制代码
static void onLongPress(LvEvent* e) {
    STTWidget* widget = (STTWidget*)e->user_data;
}

widget->addEventCB(onLongPress, LV_EVENT_LONG_PRESSED, widget);
widget->removeEventCB(onLongPress);

addEventCB 把函数指针强转为 LVGL 回调类型。LvEvent 与 lv_event_t 布局必须兼容。removeEventCB 按函数移除,没有按 eventCode/userData 精确区分。

主动发事件:

cpp 复制代码
widget->sendEvent(LV_EVENT_CLICKED, nullptr);

回调可能同步删除页面或控件,sendEvent 返回后不要无条件继续访问对象。

6. 标志、状态与使能

cpp 复制代码
widget->addFlag(FLAG::CLICKABLE | FLAG::SCROLLABLE);
widget->clearFlag(FLAG::SCROLLABLE);
bool clickable = widget->hasFlag(FLAG::CLICKABLE);

widget->setHidden(true);
widget->setEnable(false);
widget->setClickable(false);
widget->setScrollable(false);

widget->addState(WidgetState::CHECKED);
widget->clearState(WidgetState::CHECKED);
int state = widget->getState();
bool checked = widget->hasState(WidgetState::CHECKED);

setHidden 参数 true 表示隐藏;setEnable 参数 true 表示使能,两者布尔语义不要混淆。

hasFlag 要求指定的全部位都存在,状态/标志可以用按位或组合。

7. user_data 的高风险限制

cpp 复制代码
void* value = widget->getUserData();
widget->setUserData(custom);

STT 控件创建时会把 LVGL object->user_data 设为 this,基础事件处理、父子检索和 InternalUtil 都依赖这个指针。

因此普通业务代码不要调用 setUserData 覆盖它。覆盖后可能导致:

  • 事件回调把任意数据误转为 STTWidget*。
  • getChildWidget 返回错误地址。
  • clean 把自定义指针误当 STTWidget 并 delete。

额外业务数据应保存在页面类或单独 map 中;addEventCB 的 userData 可用于回调参数,但不要与 object user_data 混淆。

8. 位置、尺寸和布局

8.1 位置与尺寸

cpp 复制代码
widget->setPos(20, 30)
      ->setSize(200, 80);
widget->setX(40);
widget->setY(50);
widget->setWidth(240);
widget->setHeight(100);

这些方法同时更新 mSettedX/Y/width/height。直接调用底层 lv_obj_set_* 会绕过缓存,导致拖拽、LineArray、widget pool 等逻辑读到旧值。

内容尺寸:

cpp 复制代码
widget->setContentWidth(180);
widget->setContentHeight(60);

内容尺寸不更新 mSettedwidth/height,最终总尺寸还受 padding 和 border 影响。

读取:

cpp 复制代码
int x = widget->getX();
int y = widget->getY();
int w = widget->getWidth();
int h = widget->getHeight();
int cw = widget->getContentWidth();
int ch = widget->getContentHeight();

LvArea area;
widget->getCoords(&area);

getCoords 是屏幕绝对坐标;getX/getY 是相对父对象坐标。

8.2 对齐

cpp 复制代码
widget->setAlign(ALIGN::CENTER);
widget->align(ALIGN::CENTER, 10, -5);
widget->alignTo(other, ALIGN::OUT_RIGHT_MID, 8, 0);
widget->center();

alignTo 不检查 base,不能传 nullptr,且两个底层对象必须在同一有效 LVGL 树中。

8.3 布局

cpp 复制代码
container->setLayout(STT_LAYOUT_FLEX);
container->markLayoutAsDirty();
container->updateLayout();

updateLayout 会同步计算布局,复杂页面上可能阻塞当前 UI 帧。

8.4 移动与重绘

cpp 复制代码
widget->moveTo(100, 50);
widget->invalidate();
widget->setExtClickArea(10);
bool visible = widget->isVisible();

moveTo 调底层 lv_obj_move_to,但不更新 mSettedX/Y。之后依赖缓存的功能可能不一致;需要一致缓存时优先 setPos。

9. 父子关系

cpp 复制代码
void* parentObject = widget->getParent();
STTWidget* parentWidget = widget->getParentWidget();

int count = widget->getChildCnt();
STTWidget* child = widget->getChildWidget(0);
void* rawChild = widget->getChildObject(0);
int index = widget->getIndex();

getChildWidget 直接把底层 child->user_data 转为 STTWidget*。LVGL 内部子对象或自定义 user_data 不满足此约定时,结果无效;此时使用 getChildObject。

重新设父对象:

cpp 复制代码
widget->setParent(newParent->object);
widget->setParentWidget(newParent);
  • setParent 只改 LVGL 父对象,不更新 parentWidget。
  • setParentWidget 更新逻辑父控件;非 widget pool 模式下也调用 setParent。
  • 需要两套关系一致时优先 setParentWidget。

getScreen/getDisp/getRootObject 返回底层借用指针;getRootWidget 沿 parentWidget 链查找页面根。父指针链不一致时结果也会不一致。

moveToIndex 调整同一父对象下的层级/子序号。

10. 删除与清理

cpp 复制代码
container->clean();
widget->del();

clean 尝试删除全部子控件:

  • user_data 非空的 child 会被当成 STTWidget* 并 delete。
  • 全部 child 的 user_data 都为空时调用 lv_obj_clean。
  • 对常驻嵌套页有特殊缓存逻辑。

这进一步说明不能随意覆盖 LVGL user_data。

del 只调用 lv_obj_del(object),不会 delete C++ STTWidget 包装对象,也不会把 object 置空。继续访问或之后按普通析构路径再次删除可能产生悬空/重复删除风险。业务代码通常应交给页面/父控件生命周期管理;确需销毁包装对象时按工程既有所有权规则 delete wrapper,而不是先 del 再 delete。

isValid 调用 lv_obj_is_valid,但 object 本身若已经成为无效悬空地址,只有在 LVGL 支持该检查的前提下才安全。

11. 条件动画、显隐、使能与闪烁

11.1 动画

cpp 复制代码
int rc = widget->opAnimObj(
    value,
    R"({"t":4,"p":{"ml":0,"vl":0,"mu":100,"vu":200}})"
);

t 支持文字、前景、背景、边框、水平移动、垂直移动和尺寸变化,使用 Constants.h 的 ANIM_TYPE。

返回 0 成功,-1 表示 var 为空、JSON/类型错误。已有动画 helper 时会复用,因此可以配置不同类型,但同一 helper 的具体覆盖规则取决于 WidgetAnimHelper。

11.2 可见与使能

cpp 复制代码
int visibleRc = widget->opVisibleObj(var, R"({"c":0})");
int enableRc = widget->opEnableObj(var, R"({"c":0})");

这两个接口成功返回 1,重复设置返回 -1,与 opAnimObj/opFlickObj 成功返回 0 不一致。当前方法没有先检查 var 为空,应只传有效变量。

11.3 闪烁

cpp 复制代码
int rc = widget->opFlickObj(
    alarm,
    R"({"m":0,"p":[],"c":0,"s":1})"
);

成功返回 0,重复配置返回 -1。helper 创建后立即 start。每种 helper 类型在一个控件上最多一个。

12. 权限、确认、蜂鸣和日志

cpp 复制代码
widget->setAuth(true, "operator,admin");
widget->authorize(true);
bool ok = widget->isAuthorized();

widget->setConfirm(true, "确认执行?");
widget->setBeep(true);
widget->setOpLog(true);

注意:

  • setAuth 的第一个参数并非真正开关。b=false 会直接返回,无法关闭已启用权限。
  • 同一控件 setAuth 只能创建一次 helper。
  • setAuth 依赖系统 UserGrp 变量存在。
  • authorize 直接写当前授权状态。
  • 是否实际弹确认框、记录日志取决于具体派生控件事件实现,不是所有 STTWidget 都自动处理。

13. 字体

cpp 复制代码
void* font = widget->getFont(18, "文泉驿微米黑", 0);

找不到指定字体时退回应用默认字体。

cpp 复制代码
int sharpen = 1;
widget->setFontUserData(font, &sharpen);
void* data = widget->getFontUserData(font);

字体对象可能由字体管理器共享。setFontUserData 会直接修改 lv_font_t::user_data,传入局部变量地址或在共享字体上修改,会产生悬空指针或影响其他控件。只有为当前控件使用独立字体且 userdata 生命周期足够长时才使用。

14. 显示旋转与触摸点

cpp 复制代码
STTWidget::rotateDisp(90);

推荐 rotateDisp,angle 使用 0、90、180、270。实现只检查能否被 90 整除,其他倍数或负数会转成超出 lv_disp_rot_t 的值。

rotateScreen(angle, pivotX, pivotY) 已弃用,旋转后输入方向不正确。

触摸:

cpp 复制代码
int x, y;
if (STTWidget::getTouchPoint(x, y)) {
    // 仅在指针输入事件上下文可靠
}

if (STTWidget::getLastTouchPoint(x, y)) {
    // 最近一次全局触摸坐标
}

最近坐标是进程级原子全局值,不区分页面或显示器。

15. 绝对坐标

cpp 复制代码
LvArea a = widget->getAbsPosition();

int x = 0, y = 0;
widget->getAbsPosition(x, y, widget->object);

无参版本调用 lv_obj_get_coords,要求控件已布局/显示。引用参数版本沿 LVGL 父链累加相对位置并扣除滚动。

getAbsPosition(x, y) 重载依赖 mSetted 坐标,当前实现只加一层父控件位置,并且返回的 x2/y2 初始化为 0,不是完整矩形;主要供 widget pool 的位置换算,不适合一般几何查询。

16. 截图

cpp 复制代码
bool ok = widget->snapshotToPng("/mnt/udisk/widget.png", false);

方法取 TRUE_COLOR_ALPHA 快照并调用 save_png,扩展名虽然可写 png 或 jpg,实际编码函数名和实现以 PNG 为主。父目录不会自动创建。

当前实现没有检查 lv_snapshot_take 返回 nullptr,内存不足时可能崩溃。大控件截图会分配宽 × 高 × 像素字节的大缓冲区,不要在高频定时器中调用。

17. 外部按键与拖拽

cpp 复制代码
widget->setButtonerCode(12);
uint8_t code = widget->getButtonerCode();

头文件约定 1..255,实现不校验;0 可被保存但通常表示未配置。

cpp 复制代码
widget->enableDrag(true);
widget->enableDrag(false);

拖拽通过 LV_EVENT_PRESSING 累加输入向量并 setPos。重复 enableDrag(true) 会重复添加回调,没有去重,可能一次移动多倍;启用前应自行维护状态。拖拽不限制父控件边界。

18. STTMask

推荐流程:

cpp 复制代码
STTMask::addRadius(image, 0, 0, image->getWidth(), image->getHeight(),
                   12, false);

// 在 LV_EVENT_DRAW_MAIN_BEGIN
STTMask::apply();

// 绘制完成,在 LV_EVENT_DRAW_MAIN_END
STTMask::clear();

支持:

方法 蒙版
addAngle 角度扇区
addFade 垂直透明渐变
addLine1 点角式直线一侧
addLine2 两点式直线一侧
addMap 逐像素透明度图
addPolygon 多边形
addRadius 圆角矩形内/外
apply 注册全部蒙版
clear 删除 LVGL 蒙版并清空配置

蒙版容器是 cpp 文件级全局静态 vector,对所有控件共享且没有线程锁。add 后到 clear 前不要让其他控件交叉修改。addMap 不复制 opamap,内存必须保持到 clear。

已知实现问题:addFade 构造区域时下边界使用 absy + y,而不是 absy + h,h 参数没有用于该字段,渐变区域可能错误。使用前应在目标 SDK/真机验证。

19. 公共内部字段

object、mWidget、mSettedX/Y/width/height 与 enWidgetPool 被公开主要是框架兼容需要:

  • object 是借用的 LVGL 指针。
  • mWidget 是内部 STTWidgetInfo。
  • mSetted* 只由部分 STT API 更新。
  • enWidgetPool 是全局模式,切换会影响所有控件的位置/创建路径。

业务代码应尽量使用方法而非直接写这些字段。

20. 高风险检查表

  • 不调用 setUserData 覆盖框架 this 指针。
  • shared_ptr bindData 后在外部保留共享所有权。
  • 不在 del 后继续使用包装对象。
  • setParent 与 setParentWidget 保持一致。
  • 避免重复 enableDrag(true)。
  • setAuth(false) 不能关闭权限 helper。
  • 截图前确保目录存在并控制内存。
  • UI、布局、事件和蒙版全部在 LVGL 线程执行。
最近修改: 2026-10-21Powered by