单片机 GUI 设计(五)- 带背景图片的按键控件
单片机 GUI 设计(五)- 带背景图片的按键控件

单片机 GUI 设计(五)- 带背景图片的按键控件

基于 GD32F303ZET6 苹果派开发板

简介

按键,可以有矩形、圆形、星形等形状。机器做按键扫描时,往往更倾向于扫描矩形按键,因为矩形按键只需要通过简单的比较即可得知按键是否被按下;圆形按键扫描时,往往要通过勾股定理求得触点与按键中心的距离,速度上会慢一些,星形等其它图案则更慢了。

在实际的项目中,我们不可能像之前的实验那样,用简单的蓝色和绿色区分按键按下与否,不然太过于单调,显得不是特别高级。然而用画点画线函数很难做出想要的效果,不是太突兀,就是毛刺特别多,显示效果不好。这时候我们可以选择使用图片包装美化按键,在按键所在位置贴上一张图片即可,简单又实用。如果使用的图片是 32 位位图或 PNG 图片,还可以让按键与背景完美融合,边沿虚化、圆弧效果都可以实现。

按键控制结构体设计

因为引入了图片,原先的按键控制结构体不再适用,需要修改一些,具体就是增加了三个成员变量。pressImage 和 releaseImage,都是 void* 类型指针,用来保存按键图片首地址,这里默认按键的图片类型为位图。background 用于保存按键的背景,因为按键控件涉及到两张图片,分别是按下时和抬起时显示的图片,如果两张图片均使用位图或 PNG,那么就容易产生混叠,和绘制文字时混叠一样,此时需要将背景提前保存下来,每次刷新图片显示之前,首先填充背景,这样就能避免混叠。

//按键控件
typedef struct
{
  u16   x, y, width, height;  //原点、宽度、高度
  char* text;                 //按键名字
  u8    textSize;             //字体大小,可以是 12、16、24
  u16   textColor;            //字体颜色
  u8    lastState;            //按键上一个状态,0-按下,1-抬起
  u16   pressColor;           //按键按下时的颜色
  u16   releaseColor;         //按键抬起时显示的颜色
  void* pressImage;           //按键按下时显示的图片,不用时请填入 NULL
  void* releaseImage;         //按键抬起时显示的图片,不用时请填入 NULL
  u16*  background;           //按键背景
}StructButton;

按键创建函数

按键创建函数,即 ButtonCreate 需要做出修改,如果用户选用了图片包装,那么就显示按键抬起图片。

//创建按键
void ButtonCreate(StructButton* widget)
{
  u16 x, y, x0, y0, x1, y1, i;
  
  //计算按键起点和终点
  x0 = widget->x;
  y0 = widget->y;
  x1 = x0 + widget->width - 1;
  y1 = y0 + widget->height - 1;

  //保存按键背景
  if((NULL != widget->pressImage) || (NULL != widget->releaseImage))
  {
    //为背景申请动态内存
    widget->background = (u16*)malloc(widget->width * widget->height * 2);
    
    //保存按键背景
    i = 0;
    for(y = y0; y <= y1; y++)
    {
      for(x = x0; x < x1; x++)
      {
        widget->background[i++] = LCDReadPoint(x, y);
      }
    }
  }
  else
  {
    widget->background = NULL;
  }

  //设置按键初始状态
  widget->lastState = 0;
  if(NULL != widget->releaseImage)
  {
    DrawBMP(widget->releaseImage, x0, y0);
  }
  else
  {
    LCDFill(x0, y0, x1, y1, widget->releaseColor);
  }
  
  //刷新名字显示
  ShowText(widget);
}

按键扫描函数

同样的,按键扫描函数也要做出修改,用以支持图片包装。

//按键扫描,返回:1-检测到按键按下,0-未检测到按键按下
u8 ButtonScan(StructButton* widget)
{
  u16 x, y, x0, y0, x1, y1, i;
  u8 currentState;
  u8 ret;
  
  //默认返回 0
  ret = 0;
  
  //计算按键起点和终点
  x0 = widget->x;
  y0 = widget->y;
  x1 = x0 + widget->width - 1;
  y1 = y0 + widget->height - 1;
  
  //触屏扫描
  currentState = 0;
  if(ScanTouch(&x, &y))
  {
    //触点落在了目标区域
    if((x >= x0) && (x <= x1) && (y >= y0) && (y <= y1))
    {
      currentState = 1;
    }
    else
    {
      currentState = 0;
    }
  }
  
  //上一个状态是抬起而当前状态为按下,表示捕捉到了一个按下事件
  if((0 == widget->lastState) && (1 == currentState))
  {
    //需要绘制图片
    if(NULL != widget->pressImage)
    {
      //绘制背景
      i = 0;
      for(x = x0; x <= x1; x++)
      {
        for(y = y0; y < y1; y++)
        {
          LCDFastDrawPoint(x, y, widget->background[i++]);
        }
      }

      //绘制图片
      DrawBMP(widget->pressImage, x0, y0);
    }

    //不需要绘制图片
    else
    {
      LCDFill(x0, y0, x1, y1, widget->pressColor);
    }

    ret = 1;
  }
    
  //上一个状态是按下而当前状态为抬起,表示捕捉到了一个抬起事件
  else if((1 == widget->lastState) && (0 == currentState))
  {
    //需要绘制图片
    if(NULL != widget->releaseImage)
    {
      //绘制背景
      i = 0;
      for(x = x0; x <= x1; x++)
      {
        for(y = y0; y < y1; y++)
        {
          LCDFastDrawPoint(x, y, widget->background[i++]);
        }
      }

      //绘制图片
      DrawBMP(widget->releaseImage, x0, y0);
    }

    //不需要绘制图片
    else
    {
      LCDFill(x0, y0, x1, y1, widget->releaseColor);
    }

    ret = 0;
  }
  
  //保存当前状态
  widget->lastState = currentState;
  
  //刷新名字显示
  ShowText(widget);
  
  //返回测量结果
  return ret;
}

测试

经过简单的修改,我们的按键驱动就支持了图片封装,前提是工程里要提前移植好图片显示驱动。用于包装按键的图片可以是 BMP、PNG,甚至是 JPEG 和 GIF,在这里我们限定了只能是 BMP,实际上,我们可以更改为其它任何类型的图片。在这里推荐使用博主提供的 AnythingToC 小工具,可以将任意文件转换成 C 语言数组,方便用户将图片、音频等内嵌到单片机的 Flash 中。

测试代码如下所示。

void main(void)
{
  extern const unsigned char s_arrKeyPressBmpImage[20138];
  extern const unsigned char s_arrKeyReleaseBmpImage[20138];
  static StructButton s_structButton;

  //初始化
  ...

  //创建按键
  s_structButton.x = 350;
  s_structButton.y = 215;
  s_structButton.width = 100;
  s_structButton.height = 50;
  s_structButton.text = "BUTTON";
  s_structButton.textSize = 24;
  s_structButton.textColor = BLACK;
  s_structButton.pressColor = BLUE;
  s_structButton.releaseColor = GREEN;
  s_structButton.pressImage = (void*)s_arrKeyPressBmpImage;
  s_structButton.releaseImage = (void*)s_arrKeyReleaseBmpImage;
  ButtonCreate(&s_structButton);

  //主循环
  while(1)
  {
    //LED 闪烁
    LEDFlicker(50);

    //按键扫描
    if(1 == ButtonScan(&s_structButton))
    {
      printf("Button press\r\n");
    }

    //延时 10 ms
    DelayNms(10);
  }
}

实验结果

实验结果如下所示。点击按键时,会出现闪烁的情况,主要是因为按键切换状态时,首先刷新背景,即背景重绘,然后才显示按键的背景图片。为了避免按键闪烁,我们也可以采样 JPEG 图片装饰按键。因为 JPEG 没有透明度,所以可以直接绘图,因为无需做背景重绘,所以闪烁现象自然就消失了。

源码

本章节中的源码请参考《单片机 GUI 设计(零)- 大纲