如何让GUI在不同分辨率的屏幕间进行自适应

文摘   2024-05-13 06:52   英国  

【说在前面的话】

做过嵌入式UI的都知道,对一些素材(图片、按钮等)进行适当的排列布局后,会使得界面看起来整齐美观。今天讲的UI布局也是如此,比如让一个圆环显示在屏幕中央,如下图

我们需要设置固定的圆环坐标(x,y)的值来使圆环居于屏幕中央,这样带来的问题就是当屏幕为240*240时需要设置一个(x,y)坐标,当需求改变或者换了一块200*200像素的屏幕时,我们就需要手动更改x,y)坐标来使圆环居于屏幕中央。

基于此种需求,Arm-2D为我们提供了一些布局的小工具,使得我们在更换不同大小的屏幕时,这些小工具会自动计算x,y)坐标,让我们实现屏幕尺寸的自动适应,而不需要自己再手动计算坐标了(* ̄︶ ̄)


例如让圆环居于屏幕中央,就可以这样:

1、先获取到屏幕的大小,在Arm-2D中用canvas就可以

2、然后用居中的小工具arm_2d_align_centre)就可以获取到屏幕中央的区域坐标了

程序如下:

arm_2d_canvas(ptTile, _canvas) {    arm_2d_align_centre(_canvas, 100,100 ) {        arm_2d_rgb565_tile_copy_with_colour_keying_and_opacity(              &c_tilelogo1RGB565,                                  ptTile,              &__centre_region,              100,                                 (arm_2d_color_rgb565_t){GLCD_COLOR_BLACK});    }}
  • 在arm_2d_align_centre中,第一个参数就是获取到的屏幕大小,第2和第3个参数就是圆环素材的宽和高(屏幕大小改变而素材的宽高是不变的),根据这3个参数就可以计算出把圆环放到屏幕中央的坐标了(即__centre_region

  • 然后调用tile_copy函数在此区域进行绘制就可以了

这些布局小工具在Arm-2D集合 中的文章《还在手算坐标?试试Layout Assistant吧》已经介绍过了。

不过,今天要讲的状态栏的设计使用了一些新的布局小工具,涉及到的知识点也会重新简单介绍的(* ̄︶ ̄),大家如果没看过上面的文章,也可以接着往下看哦。

【状态栏】

那今天讲的状态栏是什么样的呢?

顾名思义,它就是用来显示软硬件(蓝牙、wifi等)中的一些状态,比如wifi连接成功显示白色图标,连接失败显示灰色图标。并且它要显示在屏幕的上面,如下图

那怎么能让它显示到屏幕的上方呢?

【布局小工具dock】

这就需要Arm-2D提供的布局小工具dock了。

获取屏幕上方的区域就用arm_2d_dock_top,它的使用方法如下

arm_2d_canvas(ptTile, _canvas) {    arm_2d_dock_top( _canvas,  40 ) {        __top_region    }}
  • __top_region就是描述了屏幕顶部 高度为 40 的一个区域(宽度就是屏幕的宽度),如下图



  • 注意:当我们使用arm_2d_dock_top生成一个区域时,此时生成的区域名称就叫__top_region(也就是说它的名字是固定的)

同样的,Arm-2D还提供了获取屏幕左边、右边和下方区域的小工具,如下表所示

工具名称
对应区域名称
arm_2d_dock_bottom(__region, __height)__bottom_region
arm_2d_dock_left(__region, __width) __left_region
arm_2d_dock_right(__region, __width) __right_region

那我想获取屏幕中间的区域呢,比如高度为30,宽度为屏幕宽度的区域(如下图所示)该怎么办呢?


哈哈,这个也简单,贴心的官方也给我们提供了工具,如下

arm_2d_canvas(ptTile, _canvas) {    arm_2d_dock_vertical( _canvas,  30 ) {        __vertical_region    }}
  • __vertical_region就是我们想要的区域了

同样的,获取屏幕中间竖条区域的小工具也有,如下

arm_2d_dock_horizontal(__region, __width)
  • 它对应的区域名称为__horizontal_region

好了,到这里,dock小工具就介绍完了,大家有没有发现dock工具的第一个参数都是一个区域,而这个区域不一定非得传屏幕大小的区域,也可以在屏幕中任意取一块区域传进去。因此dock的本质就是根据传人的区域计算出一块新区域,只不过传人屏幕大小区域就可以自适应屏幕而已(* ̄︶ ̄)

由此,我们就可以实现dock的套娃模式了(因为传入的是区域,生成的也是区域),比如可以这样使用,如下

arm_2d_canvas(ptTile, _canvas) {    arm_2d_dock_top( _canvas,  40 ) {        arm_2d_dock_vertical(__top_region,30)  {            //__vertical_region        }    }}
  • 此时你知道最里面的__vertical_region是屏幕中的哪一片区域吗?

如果你和我一样有点晕,也不要急,我们可以调用fill_colour函数给这片区域填充一个颜色把它显示出来(这样就方便我们查看区域设置的正不正确),程序如下

arm_2d_canvas(ptTile, _canvas) {    arm_2d_dock_top( _canvas,  40 ) {        arm_2d_dock_vertical(__top_region,30)  {            arm_2d_fill_colour_with_opacity(                   ptTile,                 &__vertical_region,                 (__arm_2d_color_t){GLCD_COLOR_WHITE},                100);        }    }}

这个套娃的区域如下图

怎么样,和你想得一样吗(* ̄︶ ̄)

【绘制状态栏】

我们今天绘制的状态栏就是上面的套娃区域,然后把一个个小图标(30*30像素)绘制在此区域即可。

那么问题又来了,怎么在此区域中绘制图标呢?

此时,就需要使用另一个小工具线性流式布局(Line Stream Layout)了。它就可以按顺序一个接一个地将图标放置在指定区域内。

它的使用方法如下

arm_2d_layout(__vertical_region) {    __item_line_dock_horizontal( <width> )  {        //__item_region                           }   __item_line_dock_horizontal( <width> ) {        //...                           }   //...}
  • 首先把图标绘制的区域传给arm_2d_layout

  • 然后用__item_line_dock_horizontal就可以获取到每个图标的区域(__item_region),它需要传入的参数为图标的宽度

  • 要绘制几个图标就用几个__item_line_dock_horizontal,他会帮我们依次计算出绘制小图标的区域

这样,绘制状态栏的图标就简单了,程序如下

arm_2d_canvas(ptTile, _canvas) {    arm_2d_dock_top( _canvas,  40 ) {        arm_2d_dock_vertical(__top_region,30)  {            arm_2d_layout(__vertical_region) {                //绘制wifi图标                __item_line_dock_horizontal(30) {                                                  arm_2d_fill_colour_with_mask_and_opacity(                             ptTile,                           &__item_region,                           &c_tileWiFiMask,                           (__arm_2d_color_t){GLCD_COLOR_WHITE},                                          250);                         }               //绘制蓝牙图标               __item_line_dock_horizontal(30) {                                                      arm_2d_fill_colour_with_mask_and_opacity(                             ptTile,                           &__item_region,                           &c_tileBlueToothMask,                           (__arm_2d_color_t){GLCD_COLOR_BLUE},                          250);                                     }               //...            }       }   }}

是不是很简单。

此时,如果你想实现下面的需求,即蓝牙连接设备成功,就绘制蓝牙图标,未连接到设备就不绘制蓝牙图标,如下图所示

大家是不是发现未画蓝牙图标时,中间两个图标的间隔变大而显得很不美观,这个也就是固定坐标带来的弊端,不过好在我们使用了线性流式布局,使得这种问题再也不会出现了。代码修改也很简单,只需要添加一个控制显示的变量就可以,需要绘制就赋值为true,否则为false。

修改后的代码如下

arm_2d_canvas(ptTile, _canvas) {    arm_2d_dock_top( _canvas,  40 ) {        arm_2d_dock_vertical(__top_region,30)  {            arm_2d_layout(__vertical_region) {                //绘制wifi图标                __item_line_dock_horizontal(30) {                                                  arm_2d_fill_colour_with_mask_and_opacity(                             ptTile,                           &__item_region,                           &c_tileWiFiMask,                           (__arm_2d_color_t){GLCD_COLOR_WHITE},                                          250);                         }               //绘制蓝牙图标               if(blue_tooth_flag){                   __item_line_dock_horizontal(30) {                                                          arm_2d_fill_colour_with_mask_and_opacity(                                 ptTile,                               &__item_region,                               &c_tileBlueToothMask,                               (__arm_2d_color_t){GLCD_COLOR_BLUE},                              250);                                         }               }               //...            }       }   }}
  • 只在15行添加了一个if判断语句就可以了

最后运行的效果如下所示

怎么样,相信大家已经get到布局小工具的妙用了。

【线性流式布局二】

上面的例子我们只是简单使用了下线性流式布局,其实它还有别的用法。

那就是通__item_line_dock_horizontal宏我们还可以指定当前元素与上下左右之间的间隔,其语法如下:


arm_2d_layout(<the target region: arm_2d_region_t>) { /* Syntax 1 */ __item_line_dock_horizontal(<width>, [, <left>, <right>, <top>, <bottom>]) { //... }     /* more of the __item_line_dock_horizontal segments */ //... }

举例如下

arm_2d_canvas(ptTile, _canvas) {    arm_2d_dock_top( _canvas,  40 ) {        arm_2d_dock_vertical(__top_region,30)  {            arm_2d_layout(__vertical_region) {                __item_line_dock_horizontal(3010000) {                                               arm_2d_fill_colour_with_opacity(                             ptTile,                           &__item_region,                           (__arm_2d_color_t){GLCD_COLOR_BLUE},                          100);                                             }                             __item_line_dock_horizontal(20,10,10,5,5) {                                                    arm_2d_fill_colour_with_opacity(                             ptTile,                           &__item_region,                           (__arm_2d_color_t){GLCD_COLOR_BLUE},                          100);                                 }               __item_line_dock_horizontal(30) {                       arm_2d_fill_colour_with_opacity(                             ptTile,                           &__item_region,                           (__arm_2d_color_t){GLCD_COLOR_BLUE},                          100);                                             }               //...            }       }   }}

为了方便讲解,我们只是在区域中填充了颜色。这个程序绘制的区域如下图所示

  • 第1个区域我们设置width=30,相邻间距只设置了,left=10,其他都为0

  • 2个区域我们设置width=20,相邻间距left和right为10,up和bottom为5。这样就把一个边长为20的区域放到了中间。

  • 3个区域只设置了width=30

注意:当设置上下左右相邻之间的间隔时,四个位置必须同时指定,且顺序不能改变。(即第1个区域虽然只设置了left,但是其他3个0必须写而不能省略

同样的,我们有横向的线性流式布局,那有没有纵向的呢?

答案是肯定的。

纵向的线性流式布局(Vertical Line Stream Layout)与横向的线性流式布局(Horizontal Line Stream Layout)类似,其语法如下:

arm_2d_layout(<the target region: arm_2d_region_t>) {        __item_line_dock_vertical( <height>                         [, <left>, <right>, <top>, <bottom>]) {        // ...    }   
/* more of the __item_line_vertical segments */ //... }

举例如下

arm_2d_canvas(ptTile, _canvas) {          arm_2d_dock_left( _canvas,  50 ) {              arm_2d_layout(__left_region) {                  __item_line_dock_vertical(30,10,10,50,10){                      arm_2d_fill_colour_with_opacity(                                   ptTile,                                 &__item_region,                                 (__arm_2d_color_t){GLCD_COLOR_BLUE},                                100);                  }                                    __item_line_dock_vertical(30){                      arm_2d_fill_colour_with_opacity(                                   ptTile,                                 &__item_region,                                 (__arm_2d_color_t){GLCD_COLOR_BLUE},                                100);                  }              }          }      }
  • 1个区域我们设置height=30,相邻间距left和right为10,up=50和bottom=10。

  • 2个区域直接设置height=30

这两个布局区域如下图所示

第2个区域由于我们只设置了height=30,所以它的宽度还是__left_region的宽度50

那我们想让它变成宽度为30的区域该怎么办呢?

聪明的你很快就想到了,设置间距left和right为10不就可以了,没错,是这样的,程序如下

arm_2d_canvas(ptTile, _canvas) {    arm_2d_dock_left( _canvas,  50 ) {        arm_2d_layout(__left_region) {            __item_line_dock_vertical(30,10,10,50,10){                arm_2d_fill_colour_with_opacity(                             ptTile,                           &__item_region,                           (__arm_2d_color_t){GLCD_COLOR_BLUE},                          100);            }                        __item_line_dock_vertical(30,10,10,0,0){                arm_2d_fill_colour_with_opacity(                             ptTile,                           &__item_region,                           (__arm_2d_color_t){GLCD_COLOR_BLUE},                          100);            }        }    }}

这个程序的布局区域如下图所示

到这里,你以为线性流式布局的用法就讲完了吗?

no!no!no!

其实它还有一种用法哦!

那就是__item_line_dock_horizontal() 以及 __item_line_dock_vertical() 括号里的参数是可以省略的,表示占用剩下所有面积区域。

那这种用法有什么用呢?

这个非常适合类似MDK Project View占用左边的纵向空间,右边的就是用剩下的所有空间作为编辑区。

好,那我们就简单把屏幕分成左右两个区域,程序如下

arm_2d_canvas(ptTile, _canvas) {    arm_2d_layout(_canvas) {        __item_line_dock_horizontal(50){            arm_2d_fill_colour_with_opacity(                             ptTile,                           &__item_region,                           (__arm_2d_color_t){GLCD_COLOR_RED},                          100);          }        __item_line_dock_horizontal(){            arm_2d_fill_colour_with_opacity(                             ptTile,                           &__item_region,                           (__arm_2d_color_t){GLCD_COLOR_GREEN},                          100);          }    }}
  • 1个区域我们设置width=50

  • 2个dock_horizontal()函数没有传参数

这个代码划分的区域如下图所示

怎么样,是不是很简单。

好了,到这里,dock的线性流式布局就介绍完了,大家赶快动手试试把~~~///(^v^)\\\~~~

【套娃模式】

有了布局小工具dock,相信大家在为界面布局时也能得心应手了。因为它很容易就可以把屏幕分成几块,然后分别在分好的区域中绘制图案就可以了。如下图,把屏幕分成3个区域

  • 第1个区域用arm_2d_dock_top就可以获取到

  • 第2个区域先使用arm_2d_dock_bottom获取下面的区域,然后对获取的__bottom_region使用arm_2d_dock_left就可以了

  • 同样的,第3个区域先用dock_bottom获取,再对此区域使用dock_right即可

由于dock工具区域支持套娃模式,我们可以对2、3区域再进行划分,如下图

这样我们很容易就把屏幕划分成若干个区域了。

不过,说起套娃模式,还有一个注意点需要和大家说一下,

那就是使用相同的dock工具进行套娃时,如下所示

arm_2d_canvas(ptTile, _canvas) {    arm_2d_dock_top( _canvas,  40 ) {        arm_2d_dock_top( __top_region,  30 ) {            //。。。        }    }}

这样挨着同时使用两个dock_top,建议最好不要这样用(当然这样用也是没问题的)

这个也很简单,因为两个dock_top嵌套,其实就相当于使用里面的dock_top获取的区域一样,根本没必要使用两个哦!!

上面使用两个top想获取的区域其实就用一个即可,程序如下

arm_2d_canvas(ptTile, _canvas) {    arm_2d_dock_top( _canvas,  30 ) {        //。。。    }    }

哈哈,你现在应该明白了吧~~~///(^v^)\\\~~~

【dock布局综合例子】

最后,在简单制作一个例子来结束今天的文章。

我们就用arm-2d官方demo中的arm_2d_scene_fan来举例。

它的界面如下所示

就是一个旋转的小风扇,并显示温度。

那现在就开始分析一下它的布局。

首先,小风扇和温度值所需要的区域为140*200,我们把这个区域放到屏幕中间,如下图所示

程序也很简单,如下

arm_2d_canvas(ptTile, __top_canvas) {    arm_2d_align_centre(__top_canvas, 140200) {        //。。。    }}

然后用线性流式布局把这个居中的区域分成两块,如下图

上面140*140的区域用来风扇旋转,而剩下的区域则用来显示温度,程序也很简单,如下

arm_2d_canvas(ptTile, __top_canvas) {        arm_2d_align_centre(__top_canvas, 140200) {                    arm_2d_layout(__centre_region) {                __item_line_dock_vertical(140) {                //绘制旋转的风扇            }            __item_line_dock_vertical() {                //绘制温度值            }        }    }}

怎么样,是不是很简单。

不过,虽然简单,但是用了布局小工具之后,我们就很容易适配不同大小的屏幕了。比如上面的视频中用的屏幕像素为240*320的,但是如果你换成240*240的屏幕也可以运行,不需要再修改代码哦。

有小伙伴又要说了,那适配240*135的屏幕呢?

哈哈哈。。。

这个屏幕当然也可以,但是一点代码都不修改是做不到的。

那接下来就一起适配这个屏幕,看看到底需要修改多少代码。

首先,由于我们的屏幕高度只有135,所以竖着显示显然是不行,那我们就改成横着的,把之前的140*200的区域改为200*135

修改后的代码如下

arm_2d_align_centre(__top_canvas, 200, 135) {    arm_2d_layout(__centre_region) {            //。。。        }    }}                

既然要横着显示了,肯定__item_line_dock_vertical要改为__item_line_dock_horizontal了。

修改后的布局代码如下

arm_2d_canvas(ptTile, __top_canvas) {        arm_2d_align_centre(__top_canvas, 200135) {        arm_2d_layout(__centre_region) {            __item_line_dock_horizontal(135) {                //绘制旋转的风扇            }            __item_line_dock_horizontal() {                //绘制温度值            }        }    }}

这样,我们的布局就修改好了。唯一的问题就是把风扇旋转的区域140*140改成了135*135,这样把区域缩小后会导致风扇显示不全。

不过,不要急,先看一下风扇旋转的代码,如下

使用的是transform函数进行旋转的,而transform函数本身就支持缩放。

哈哈哈,你是不是已经明白了,

只要把缩放倍数1.001f改为0.8f就好了,修改后的代码如下

到这里就把240*135的屏幕适配好了,是不是很简单。其实这个也相当于把一个竖屏显示的布局改成了横屏布局,其修改的内容也不多~~~///(^v^)\\\~~~

好了,那我们就看看修改后的代码运行效果,如下

哈哈,各位看官,看到这里还不赶快试试。

这个demo的添加也很简单,如下

然后在弹出的对话框中选择Fan即可,如下

添加成功就会在工程中出现fan.c文件了,如下

它的使用也很简单,程序如下

#include "arm_2d_scene_fan.h"int main(void) {    system_init();        disp_adapter0_init();        __arm_2d_scene_fan_init(   &DISP0_ADAPTER,NULL);    //。。。}
  • 首先添加头文件arm_2d_scene_fan.h

  • 然后调用初始化函数即可。是不是太简单了!


好了,到这里今天的内容就讲完了。欢迎大家和我一起玩转Arm-2D,我们的口号是:一起玩,一起玩才更好玩。下期精彩继续(基于高斯模糊的特效设计)。。。


原创不易,如果你喜欢我的公众号、觉得我的文章对你有所启发,

请务必“点赞、收藏、转发”,这对我很重要,谢谢!

欢迎订阅    嵌入式小书虫

裸机思维
傻孩子图书工作室。探讨嵌入式系统开发的相关思维、方法、技巧。
 最新文章