STTWidget 是所有 STT 控件的基础类,封装 LVGL object、布局、状态、事件、变量绑定、条件显隐、权限、截图和拖拽。同一头文件还声明全局蒙版工具 STTMask。
本文所称 object 是 STTWidget::object 指向的底层 lv_obj_t。
STTWidget 通常不直接实例化,而由 STTLabel、STTButton、STTTable 等派生类构造底层 object。
STTLabel* label = new STTLabel(getRuntime(), parent, "lblStatus");
label->setMainArg(20, 20, 180, 40, "运行", 1);
通用初始化接口:
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 体系使用;父控件必须先创建并比子控件活得久。
STTStyle* style = widget->getStyle();
style->p_bg->setColor(0xffffff)->setOpa(255);
style->p_border->setWidth(1)->setColor(0xcccccc);
getStyle 返回附着到当前 object 的样式包装指针,属于框架管理对象,不要 delete,也不要在控件销毁后使用。
背景图片:
widget->setBgImg("res/panel.png", stt::constant::Part::Main);
实现等价于 setImgSrc(path, part) 后把该 part 背景透明度设为 0。path 的有效性和生命周期没有在 STTWidget 层检查。
widget->refresh();
widget->refreshAll();
需要连当前控件一起刷新时:
widget->refresh();
widget->refreshAll();
原始指针:
Ivariable value = getRuntime()->getIVarMgr()->get("Pressure");
if (value != nullptr) {
widget->bindData(value);
}
共享指针:
shared_ptr<IVariable> value = /* ... */;
widget->bindData(value);
重要所有权规则:
widget->disBindData();
成员函数回调:
widget->onEventHandler(
LV_EVENT_CLICKED,
this,
(EHandler)&Frm01::onWidgetClicked
);
void Frm01::onWidgetClicked(uint16_t code, LvEvent e) {
STTWidget* target = (STTWidget*)e.target;
}
onEventHandler 只把条目追加到 vector,没有去重和移除单条成员回调的公开接口。object/handler 的接收对象必须比注册关系存活更久。
C 风格事件回调:
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 精确区分。
主动发事件:
widget->sendEvent(LV_EVENT_CLICKED, nullptr);
回调可能同步删除页面或控件,sendEvent 返回后不要无条件继续访问对象。
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 要求指定的全部位都存在,状态/标志可以用按位或组合。
void* value = widget->getUserData();
widget->setUserData(custom);
STT 控件创建时会把 LVGL object->user_data 设为 this,基础事件处理、父子检索和 InternalUtil 都依赖这个指针。
因此普通业务代码不要调用 setUserData 覆盖它。覆盖后可能导致:
额外业务数据应保存在页面类或单独 map 中;addEventCB 的 userData 可用于回调参数,但不要与 object user_data 混淆。
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 等逻辑读到旧值。
内容尺寸:
widget->setContentWidth(180);
widget->setContentHeight(60);
内容尺寸不更新 mSettedwidth/height,最终总尺寸还受 padding 和 border 影响。
读取:
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 是相对父对象坐标。
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 树中。
container->setLayout(STT_LAYOUT_FLEX);
container->markLayoutAsDirty();
container->updateLayout();
updateLayout 会同步计算布局,复杂页面上可能阻塞当前 UI 帧。
widget->moveTo(100, 50);
widget->invalidate();
widget->setExtClickArea(10);
bool visible = widget->isVisible();
moveTo 调底层 lv_obj_move_to,但不更新 mSettedX/Y。之后依赖缓存的功能可能不一致;需要一致缓存时优先 setPos。
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。
重新设父对象:
widget->setParent(newParent->object);
widget->setParentWidget(newParent);
getScreen/getDisp/getRootObject 返回底层借用指针;getRootWidget 沿 parentWidget 链查找页面根。父指针链不一致时结果也会不一致。
moveToIndex 调整同一父对象下的层级/子序号。
container->clean();
widget->del();
clean 尝试删除全部子控件:
这进一步说明不能随意覆盖 LVGL user_data。
del 只调用 lv_obj_del(object),不会 delete C++ STTWidget 包装对象,也不会把 object 置空。继续访问或之后按普通析构路径再次删除可能产生悬空/重复删除风险。业务代码通常应交给页面/父控件生命周期管理;确需销毁包装对象时按工程既有所有权规则 delete wrapper,而不是先 del 再 delete。
isValid 调用 lv_obj_is_valid,但 object 本身若已经成为无效悬空地址,只有在 LVGL 支持该检查的前提下才安全。
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。
int visibleRc = widget->opVisibleObj(var, R"({"c":0})");
int enableRc = widget->opEnableObj(var, R"({"c":0})");
这两个接口成功返回 1,重复设置返回 -1,与 opAnimObj/opFlickObj 成功返回 0 不一致。当前方法没有先检查 var 为空,应只传有效变量。
int rc = widget->opFlickObj(
alarm,
R"({"m":0,"p":[],"c":0,"s":1})"
);
成功返回 0,重复配置返回 -1。helper 创建后立即 start。每种 helper 类型在一个控件上最多一个。
widget->setAuth(true, "operator,admin");
widget->authorize(true);
bool ok = widget->isAuthorized();
widget->setConfirm(true, "确认执行?");
widget->setBeep(true);
widget->setOpLog(true);
注意:
void* font = widget->getFont(18, "文泉驿微米黑", 0);
找不到指定字体时退回应用默认字体。
int sharpen = 1;
widget->setFontUserData(font, &sharpen);
void* data = widget->getFontUserData(font);
字体对象可能由字体管理器共享。setFontUserData 会直接修改 lv_font_t::user_data,传入局部变量地址或在共享字体上修改,会产生悬空指针或影响其他控件。只有为当前控件使用独立字体且 userdata 生命周期足够长时才使用。
STTWidget::rotateDisp(90);
推荐 rotateDisp,angle 使用 0、90、180、270。实现只检查能否被 90 整除,其他倍数或负数会转成超出 lv_disp_rot_t 的值。
rotateScreen(angle, pivotX, pivotY) 已弃用,旋转后输入方向不正确。
触摸:
int x, y;
if (STTWidget::getTouchPoint(x, y)) {
// 仅在指针输入事件上下文可靠
}
if (STTWidget::getLastTouchPoint(x, y)) {
// 最近一次全局触摸坐标
}
最近坐标是进程级原子全局值,不区分页面或显示器。
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 的位置换算,不适合一般几何查询。
bool ok = widget->snapshotToPng("/mnt/udisk/widget.png", false);
方法取 TRUE_COLOR_ALPHA 快照并调用 save_png,扩展名虽然可写 png 或 jpg,实际编码函数名和实现以 PNG 为主。父目录不会自动创建。
当前实现没有检查 lv_snapshot_take 返回 nullptr,内存不足时可能崩溃。大控件截图会分配宽 × 高 × 像素字节的大缓冲区,不要在高频定时器中调用。
widget->setButtonerCode(12);
uint8_t code = widget->getButtonerCode();
头文件约定 1..255,实现不校验;0 可被保存但通常表示未配置。
widget->enableDrag(true);
widget->enableDrag(false);
拖拽通过 LV_EVENT_PRESSING 累加输入向量并 setPos。重复 enableDrag(true) 会重复添加回调,没有去重,可能一次移动多倍;启用前应自行维护状态。拖拽不限制父控件边界。
推荐流程:
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/真机验证。
object、mWidget、mSettedX/Y/width/height 与 enWidgetPool 被公开主要是框架兼容需要:
业务代码应尽量使用方法而非直接写这些字段。