I’m working on a control panel for a 3D printer recently. It also needs a lot of screens to show different functions. I can tell you how I solved this problem.
When multiple screens exist, you need to consume a lot of memory. This is obviously unreasonable. So we need to do a good job of screen mutual exclusion. Only one screen can be instantiated and displayed at a time.
To do this, I designed a Screen class :
screen.h
/**
* @brief 定义一个屏幕界面的基类
* 屏幕管理
*
* 控制显示
* 控制隐藏
*
* 管理与屏幕相关的任务
*
*/
class Screen : public LVObject
{
LV_OBJECT
friend class MarlinUi;
friend class ScreenTask;
friend lv_res_t screenSignalFunc(struct _lv_obj_t * obj, lv_signal_t sign, void * param);
protected:
////////// 形成屏幕的显示链 ////////////////
Screen * m_preScreen = nullptr; //!< 前一个屏幕
Screen * m_nextScreen = nullptr; //!< 后一个屏幕
static Screen * s_lastScreen; //!< 之前显示的屏幕
static Screen * s_currScreen; //!< 当前显示的屏幕
/////////// 任务列表 ////////////
LVLinkList m_taskList; //!< 屏幕拥有的任务列表
//////////// 外观属性 /////////////////
LVColor m_screenColor; //!< 屏幕颜色
bool m_inited = false; //!< 标识屏幕是否初始化
bool m_deleteAfterHide = false; //!< 隐藏后清理屏幕数据
bool m_clearAfterHide = true; //!< 隐藏后清理屏幕(子对象)数据
int32_t m_memoryUsed = -1; //!< 统计内存消耗
public:
Screen();
~Screen();
/**
* @brief 显示屏幕
* @return true 显示成功 ; false 显示失败
*/
bool show();
/**
* @brief 显示之前运行的函数
* 可用来判断屏幕是否能够显示
* @return true 可以显示 ; false 不能显示
*/
virtual bool beforeShow();
/**
* @brief 显示之后
*/
virtual void afterShow(){}
/**
* @brief 隐藏屏幕
* 跳转到上一个屏幕或者主屏幕
* @return true 显示成功 ; false 显示失败
*/
bool hide();
/**
* @brief 隐藏之前
* 可用来判断屏幕是否能够隐藏
* @return true 可以显示 ; 不能隐藏
*/
virtual bool beforeHide(){return true;}
/**
* @brief 隐藏之后
*/
virtual void afterHide(){}
/**
* @brief 退回到上一个显示的屏幕
*/
void backScreen();
bool hasBackScreen();
/**
* @brief 显示前一个屏幕
*/
void preScreen();
bool hasPreScreen();
void setPreScreen(Screen * screen);
/**
* @brief 显示后一个屏幕
*/
void nextScreen();
bool hasNextScreen();
void setNextScreen(Screen * screen);
/**
* @brief 在屏幕上是否可见
* @return
*/
bool isVisible();
/**
* @brief 设置屏幕的显隐
* @param value
*/
void setVisible(bool value);
/**
* @brief 最后显示的一个屏幕界面
* @return
*/
static Screen * LastScreen();
/**
* @brief 当前显示的屏幕界面
* @return
*/
static Screen * CurrScreen();
bool isDeleteAfterHide();
void setDeleteAfterHide(bool value);
bool isInited(){ return m_inited; }
bool isClearAfterHide() const;
void setClearAfterHide(bool isClearAfterHide);
/**
* @brief 清除屏幕
*/
void cleanScreen();
protected:
/**
* @brief 屏幕清理前
*/
virtual void beforeCleanScreen();
/**
* @brief 屏幕清理后
*/
virtual void afterCleanScreen();
/**
* @brief 初始化屏幕
* 初始化屏幕显示和任务
* @return
*/
virtual bool initScreen();
};
Some important functions
screen.cpp
void Screen::beforeCleanScreen()
{
//NOTE: 子类中完成屏幕清理动作
}
void Screen::afterCleanScreen()
{
//NOTE: 子类中完成屏幕清理动作
}
bool Screen::initScreen()
{
LV_LOG_INFO("Screen::initScreen() empty function");
return false;
}
bool Screen::show()
{
bool ret = false;
//如果存在其他的屏幕界面正在显示,需要先隐藏当前的界面
if(CurrScreen())
CurrScreen()->hide();
if(beforeShow())
{
screenLoad();
setCurrScreen(this);
//开启相关任务
startScreenTask();
afterShow();
ret = true;
}
//安装界面的公共元素
installPublicComponents(this);
return ret;
}
bool Screen::beforeShow()
{
//初始化界面
//将初始化放到这理执行,有助于减少内存占用情况
//没有用到的界面就不会消耗内存了
if(!m_inited)
{
//统计初始化屏幕用了多少内存
//方便在屏幕清理的时候发现内存泄露
memory_monitor(nullptr);
m_memoryUsed = getUsedMemorySize();
m_inited = initScreen();
m_memoryUsed = getUsedMemorySize() - m_memoryUsed;
memory_monitor(nullptr);
{
char str[40];
//警告内存有泄露
sprintf(str,"Memory used : %d Bytes!", m_memoryUsed);
LV_LOG_WARN(str);
}
if(!m_inited)
LV_LOG_INFO("Screen::beforeShow() initScreen false");
}
return m_inited;
}
bool Screen::hide()
{
bool ret = false;
if(beforeHide())
{
//关闭相关任务
stopScreenTask();
//if(s_lastScreen != nullptr)
// s_lastScreen->show();
//else if(((Screen*) MarlinUi::getInstance()->homeScreen()) != this )
// //BUG:存在主屏幕无法显示的情况
// MarlinUi::getInstance()->showHomeScreen();
setCurrScreen(nullptr);
setLastScreen(this);
//卸载界面公共元素,避免被删除
uninstallPublicComponents();
//隐藏后清理屏幕
if(isClearAfterHide())
{
int32_t memoryRecovery = getUsedMemorySize();
cleanScreen();
memoryRecovery = memoryRecovery - getUsedMemorySize();
if(m_memoryUsed != memoryRecovery)
{
char str[40];
//警告内存有泄露
sprintf(str,"Memory leak deteted : %d Bytes!", m_memoryUsed - memoryRecovery);
LV_LOG_WARN(str);
}
}
afterHide();
//隐藏后删除屏幕
if(isDeleteAfterHide())
{
setLastScreen(nullptr);
//延后清理对象,防止对象此时正在被使用
//deleteLater();
delete this;
}
ret = true;
}
return ret;
}
As you can see, screen has several important virtual functions:
virtual bool Screen::hide();
virtual bool Screen::beforeHide();
virtual bool Screen::afterHide();
virtual bool Screen::show();
virtual bool Screen::beforeShow();
virtual bool Screen::afterShow();
virtual bool Screen::initScreen();
I use these functions to guarantee the absolute mutual exclusion between the screens.
Every time I need to show a screen, show
will check whether other interfaces are showing. If so, let other screen hide.Then show
will call initScreen
to initialize new interface. Like this:
bool HomeScreen::initScreen()
{
/*
* 实现主界面
*
==============================================
= =
= 000000000000 11111111111 22222222222 =
= 000000000000 11111111111 22222222222 =
= 000000000000 11111111111 22222222222 =
= 000000000000 11111111111 22222222222 =
= 000000000000 11111111111 22222222222 =
= =
= 333333333333 4444444444444444444444444 =
= 333333333333 4444444444444444444444444 =
= 333333333333 4444444444444444444444444 =
= 333333333333 4444444444444444444444444 =
= 333333333333 4444444444444444444444444 =
= =
= ..........................5555..6666..7777 =
= ..........................5555..6666..7777 =
==============================================
*/
//构建主界面
int screenWidth = this->width();
int screenHeight = this->height();
int screenDpi = this->dpi();
//计算出各区域大小
#if SCREEN_SIZE == 28
int16_t safeMargin = 6;
int16_t spacing = 4;
int16_t barHeight = 26;
int16_t butAreaWidth = screenWidth;
int16_t butAreaHeight = screenHeight - barHeight;
int16_t butWidth = 100;
int16_t butHeight = 100;
int16_t fontSize = 16;
int16_t iconSize = 24;
#elif SCREEN_SIZE == 35
// scale 120%
int16_t safeMargin = 7;
int16_t spacing = 5;
int16_t barHeight = 32;
int16_t butAreaWidth = screenWidth;
int16_t butAreaHeight = screenHeight - barHeight;
int16_t butWidth = 152;
int16_t butHeight = 138;
int16_t fontSize = 20;
int16_t iconSize = 28;
#endif
//主界面按钮区域
{
LVObject * butArea = new LVObject(this,nullptr);
butArea->setPos(0,0);
butArea->setSize(butAreaWidth,butAreaHeight);
butArea->setStyle(THEME_STYLE_GET(Theme_screen_ButtonArea));
//移动按钮
LVButton * butMove = new LVButton(butArea,nullptr);
butMove->setSize(butWidth,butHeight);
butMove->alignOrigo(LV_ALIGN_IN_TOP_MID,0,safeMargin + butHeight / 2);
butMove->setStyle(LV_BTN_STYLE_PR,THEME_STYLE_GET(Theme_Screen_ButtonPr));
butMove->setStyle(LV_BTN_STYLE_REL,THEME_STYLE_GET(Theme_Screen_ButtonRel));
butMove->setAction(LV_BTN_ACTION_CLICK,onMoveButClocked);
//移动按钮图标
LVImage * imgMove = new LVImage(butMove,nullptr);
imgMove->setSize(iconSize,iconSize);
imgMove->align(LV_ALIGN_CENTER,0,0);
imgMove->setSrc(&move_24);
//移动按钮文本
labMove = new LVLabel(butMove,nullptr);
labMove->setText(Lang_HomeScreen_ButMove); //"Move"
//预热按钮
LVButton * butPreheat = new LVButton(butArea,butMove);
butPreheat->align(butMove,LV_ALIGN_OUT_LEFT_TOP,-spacing,0);
butPreheat->setAction(LV_BTN_ACTION_CLICK,onTempButClocked);
//预热按钮图标
LVImage * imgPreheat = new LVImage(butPreheat,nullptr);
imgPreheat->setSize(iconSize,iconSize);
imgPreheat->align(LV_ALIGN_CENTER,0,0);
imgPreheat->setSrc(&temp_24);
//预热按钮文本
labPreheat = new LVLabel(butPreheat,nullptr);
labPreheat->setText(Lang_HomeScreen_ButPreHeat);//"Preheat"
//挤出按钮
LVButton * butExtrude = new LVButton(butArea,butMove);
butExtrude->align(butMove,LV_ALIGN_OUT_RIGHT_TOP,spacing,0);
butExtrude->setAction(LV_BTN_ACTION_CLICK,onExtrudeButClocked);
//挤出按钮图标
LVImage * imgExtrude = new LVImage(butExtrude,nullptr);
imgExtrude->setSize(iconSize,iconSize);
imgExtrude->align(LV_ALIGN_CENTER,0,0);
imgExtrude->setSrc(&extrude_24);
//挤出按钮文本
labExtrude = new LVLabel(butExtrude,nullptr);
labExtrude->setText(Lang_HomeScreen_ButExtrude); //"Extrude"
//工具按钮
LVButton * butTool = new LVButton(butArea,butMove);
butTool->align(butPreheat,LV_ALIGN_OUT_BOTTOM_LEFT,0,spacing);
butTool->setAction(LV_BTN_ACTION_CLICK,onToolButClocked);
//工具按钮图标
LVImage * imgTool = new LVImage(butTool,nullptr);
imgTool->setSize(iconSize,iconSize);
imgTool->align(LV_ALIGN_CENTER,0,0);
imgTool->setSrc(&tool_24);
//工具按钮文本
labTool = new LVLabel(butTool,nullptr);
labTool->setText(Lang_HomeScreen_ButTool);//"Tool"
//打印按钮
LVButton * butPrint = new LVButton(butArea,butMove);
butPrint->setSize(butWidth*2 + spacing , butHeight);
butPrint->align(butTool,LV_ALIGN_OUT_RIGHT_TOP,spacing,0);
butPrint->setAction(LV_BTN_ACTION_CLICK,onPrintButClocked);
//打印按钮图标
LVImage * imgPrint = new LVImage(butPrint,nullptr);
imgPrint->setSize(iconSize,iconSize);
imgPrint->align(LV_ALIGN_CENTER,0,0);
imgPrint->setSrc(&print_24);
//打印按钮文本
labPrint = new LVLabel(butPrint,nullptr);
labPrint->setText(Lang_HomeScreen_ButPrint);//"Print"
}
//主界面消息区
{
//状态栏
LVObject * stateBar = new LVObject(this,nullptr);
stateBar->setSize(screenWidth,barHeight);
stateBar->align(LV_ALIGN_IN_BOTTOM_LEFT,0,0);
stateBar->setStyle(THEME_STYLE_GET(Theme_Screen_StateBar));
//状态栏信息
stateMesg = new LVLabel(stateBar,nullptr);
stateMesg->align(LV_ALIGN_IN_LEFT_MID,safeMargin,0);
stateMesg->setText(Lang_HomeScreen_MsgConnected); //"Connected!"
stateMesg->align(LV_ALIGN_IN_LEFT_MID,safeMargin,0);
//热床温度
labHeatbed = new LVLabel(stateBar,nullptr);
labHeatbed->setText("999");
labHeatbed->align(LV_ALIGN_IN_RIGHT_MID,-safeMargin,0);
labHeatbed->setValue((int16_t)ExtUI::getActualTemp_celsius(ExtUI::BED));
//热床温度图标
imgHeatbed = new LVImage(stateBar,nullptr);
imgHeatbed->setSrc(&heatbed_20_12);
imgHeatbed->align(labHeatbed,LV_ALIGN_OUT_LEFT_MID,-spacing,0);
//喷嘴温度
labHeatend = new LVLabel(stateBar,nullptr);
labHeatend->setText("999");
labHeatend->align(imgHeatbed,LV_ALIGN_OUT_LEFT_MID,-safeMargin*2,0);
labHeatend->setValue((int16_t)ExtUI::getActualTemp_celsius(ExtUI::E0));
//喷嘴温度图标
imgHeatend = new LVImage(stateBar,nullptr);
imgHeatend->setSrc(&heatend_20_12);
imgHeatend->align(labHeatend,LV_ALIGN_OUT_LEFT_MID,-spacing,0);
}
LV_LOG_INFO(__FUNCTION__);
return true;
}
It works like this:
Every time screen need to hide, hide
will call cleanScreen
to release all subobjects . Free up memory.
I only need 25KB of memory to implement more than 50 interfaces. All interfaces can switch to each other.