跳转到主要内容

自定义控件

在创建应用时,您可能需要TouchGFX中没有包含的控件。 TouchGFX提供了几种可以用来创建图形元素的方式。 最简单的方式是使用自定义容器法,您可以将现有的控件组合成您自己的控件。 但是,本文将介绍一种更高级的方法,您可以用它真正地创建一个可全面控制帧缓冲的控件,从而能够精确地绘制您想要的内容。

自定义控件法的使用场景

创建自定义控件不是创建控件的最典型方法。 如果适合您的需求,最好选择自定义容器法,但有时此法并不够用。 当您需要全面控制帧缓冲时,您需要使用自定义控件法。

下面是几个可以且应当使用自定义控件法创建的控件的示例:

  • 褐色滤镜
  • 曼德勃罗分形控件
  • 显示文件中数据的控件,如gif动画。

在TouchGFX Designer中

TouchGFX Designer目前不支持自定义控件的创建。 因此,您将需要手动写入自定义控件的代码(请参考本文的剩余部分了解做法),然后在视图的用户代码部分插入控件。

在代码中

通过扩展Widget类创建自己的自定义控件。 这样做会加大用户的工作量,但也可以全面控制控件绘制的所有像素。 您的自定义控件不一定要利用任何现有控件,而是可以通过指定像素颜色来定义应如何绘制。 自定义控件法的存储空间占用量通常较小,这在某些情况下非常重要。

自定义控件的一个示例是二维码控件。 这个特殊的控件是个很好的例子,我们将在下面一节中详细介绍如何创建它。 在本例中,二维码控件是黑白像素的NxN矩阵。 控件的大小和每个像素的颜色将在运行时间确定,并取决于二维码数据对象中的信息。

下面是两个二维码控件可能外观的示例:

二维码控件示例

Caution
可以用自定义容器法创建二维码控件,使容器有n*n个黑或白方块作为子容器。 但是,这会占用许多存储空间,并且很可能不够高效或灵活。
Tip
创建标准 touchgfx::Button (随框架提供)作为自定义控件,而不是自定义容器。 这样可以节省屏幕上每个按钮的存储空间。

您自己的自定义控件

为了创建扩展Widget类的控件,您需要声明两点。

  • 控件的绘制方式
  • 控件的实心部分

部分绘制

任何控件和自定义控件都需要支持部分绘制。 这意味着控件应能只绘制自身的一部分。

其中的原因有两点。 通常不一定要重新绘制整个屏幕,而是只需绘制一部分。 TouchGFX的算法可以将全屏绘制分割成多个部分绘制,以使绘制像素的总数最小化。 然后,屏幕的部分绘制可能要求控件只绘制自身的一部分。

例如,二维码控件需能够支持只绘制自身的高亮部分。

二维码控件部分绘制

在代码中实现这一点的方法是重写draw方法:

virtual void draw(const touchgfx::Rect& invalidatedArea) const
{
//run through the pixels of the invalidated area
//draw a black pixel if the qr data object has a value at this position
//draw a white pixel otherwise
}

invalidatedArea是控件中需要更新的那部分。 在我们的二维码示例中,无效区域是高亮区域。 该区域表示为相对于控件左上角的坐标。

Caution
控件的责任是在无效区域内绘制。 如果在无效区域外绘制,将得不到屏幕的整体正确行为。
Tip
绘制 方法const, 因为最优绘制算法可以将控件的绘制分割成多个调用进行绘制。 绘制 const, 确保控件在多次绘制执行之间不发生移动、 方法 等。

实心区域

此外,控件需能够报告自身多大一部分是实心的。 实心意味着您不能在屏幕上看到它后方的内容。 例如,标准红色方块是完全实心的,而一幅阿尔法值为50%的图像则完全非实心;您可以看到它后方的一切。

实心控件能使TouchGFX加快绘制进度。 由于我们无需在实心控件下方绘制控件,因此要绘制的像素较少。

为了报告代码中的实心区域,重写getSolidRect方法。

virtual Rect getSolidRect() const;

得到的二维码将是完全实心的。 您将不能看到黑白像素后的任何内容。 因此,我们让该方法返回一个矩形,其大小等于控件的完整尺寸:

virtual Rect getSolidRect() const
{
return touchgfx::Rect(0, 0, getWidth(), getHeight());
}

示例源代码

我们最终得到一个应用和一个这样的控件 - 具体取决于提供的数据:

二维码控件屏幕截图

控件的完整代码如下:

gui/include/gui/common/QRCodeWidget.hpp

#ifndef QR_CODE_WIDGET_HPP
#define QR_CODE_WIDGET_HPP

#include <touchgfx/widgets/Widget.hpp>

class QRCodeWidget : public touchgfx::Widget
{
public:
QRCodeWidget();

virtual void draw(const touchgfx::Rect& invalidatedArea) const;
virtual touchgfx::Rect getSolidRect() const;

void setQRCodeData(QRCodeData* data);
void setScale(uint8_t s);

private:
void updateSize();

QRCodeData* data;
uint8_t scale;
};
#endif

在头文件中,将控件定义为touchgfx::Widget类的扩展。 重写drawgetSolidRect方法,以便定义如何绘制控件。 声明设置二维码数据和设置二维码比例因子的方法。

gui/src/common/QRCodeWidget.cpp
#include <gui/common/QRCodeWidget.hpp>

QRCodeWidget::QRCodeWidget() :
data(0),
scale(1)
{
}

void QRCodeWidget::draw(const touchgfx::Rect& invalidatedArea) const
{
if (!data)
{
return;
}

touchgfx::Rect absolute = getAbsoluteRect();

uint16_t* framebuffer = touchgfx::HAL::getInstance()->lockFrameBuffer();

for (int y = invalidatedArea.y; y < invalidatedArea.bottom(); y++)
{
for (int x = invalidatedArea.x; x < invalidatedArea.right(); x++)
{
framebuffer[absolute.x + x + (absolute.y + y) * touchgfx::HAL::FRAME_BUFFER_WIDTH] =
data->at(x / scale, y / scale) ? 0x0000 : 0xffff;
}
}

touchgfx::HAL::getInstance()->unlockFrameBuffer();
}

touchgfx::Rect QRCodeWidget::getSolidRect() const
{
return touchgfx::Rect(0, 0, getWidth(), getHeight());
}

void QRCodeWidget::setQRCodeData(QRCodeData* qrCode)
{
data = qrCode;
updateSize();
}

void QRCodeWidget::setScale(uint8_t s)
{
scale = s;
updateSize();
}

void QRCodeWidget::updateSize()
{
if (data)
{
setWidth(data->getSize() * scale);
setHeight(data->getSize() * scale);
}
}

源文件定义了draw方法。 此方法遍历无效区域中的每个像素,并基于数据对象的内容和比例因子确定帧缓冲的颜色。

getSolidRect方法报告控件为完全实心。

Caution
请注意,我们锁定和解锁了帧缓冲区 方法 方法中。 这样做的目的是在我们开始绘制时确保DMA完成了绘制。

代码还使用小类/接口访问二维码数据:

class QRCodeData
{
public:
uint8_t getSize();
bool at(uint8_t x, uint8_t y);
};
Further reading
下载并查看QRCodeLens控件。
Things to try
  • 修改二维码控件,使白色像素完全透明。
  • 创建显示褐色滤镜、曼德勃罗分形、gif图像或其他内容的自定义控件。
  • 思考使用自定义容器最容易实现哪些控件,以及使用自定义控件法最容易实现哪些控件。
  • 修改标准控件/容器

    在安装TouchGFX时,包含了标准控件的源代码。 为了适应应用需求,这些也都可以进行修改。 标准控件和容器的源代码位于以下文件夹:

    Middlewares\ST\touchgfx\framework\source\touchgfx\widgets

    为了维持向后兼容,核心库包含标准控件和容器的已编译版本。 因此,并非必须在项目中编译这些文件。

    Caution
    不建议直接修改标准控件/容器

    源代码主要用作灵感来源,以及一种学习TouchGFX控件内部工作机制的方式。 如需一些不同于标准实现的行为,可通过子类化或创建自定义容器来实现,这也是推荐方法。

    推荐原因有两个:

    • 首先,由于必须手动合并所做的任何修改,因此会更加难以升级到更新的TouchGFX版本。
    • 其次,存在破坏依赖于特定行为的标准控件和容器的风险。

    因此,如果必须修改标准控件/容器,我们建议您复制它、重命名然后使用,而不是直接修改源代码。

    也就是说,您可以自由地做任何您认为对的事情。 如果将标准控件的源代码添加到项目中,链接器会选择此版本而不是核心库中的版本。 因此,通过将源代码添加到应用,可以修改标准控件。