30

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

image.png

【说在前面的话】

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

image.png

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

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

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

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吧》已经介绍过了。[](http://mp.weixin.qq.com/s?__b...)

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

【状态栏】

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

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

image.png

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

【布局小工具dock】

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

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

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

image.png

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

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

工具名称

image.png

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

image.png

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

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);
        }
    }
}

这个套娃的区域如下图

image.png

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

【绘制状态栏】

我们今天绘制的状态栏就是上面的套娃区域,然后把一个个小图标(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);
                      
               }
               //...
            }
       }
   }
}

是不是很简单。

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

image.png

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

image.png

修改后的代码如下


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判断语句就可以了

最后运行的效果如下所示

image.png

怎么样,相信大家已经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(30,10,0,0,0) {                           
                    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);                              
               }
               //...
            }
       }
   }
}

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

image.png

  • 第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

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

image.png

第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);
            }
        }
    }
}

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

image.png

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

no!no!no!

其实它还有一种用法哦!

image.png

那就是__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()函数没有传参数

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

image.png

怎么样,是不是很简单。

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

【套娃模式】

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

image.png

  • 第1个区域用arm_2d_dock_top就可以获取到
  • 第2个区域先使用arm_2d_dock_bottom获取下面的区域,然后对获取的__bottom_region使用arm_2d_dock_left就可以了
  • 同样的,第3个区域先用dock_bottom获取,再对此区域使用dock_right即可

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

image.png

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

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

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

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

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

image.png

这个也很简单,因为两个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来举例。

它的界面如下所示

image.png

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

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

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

image.png

程序也很简单,如下

arm_2d_canvas(ptTile, __top_canvas) {
    arm_2d_align_centre(__top_canvas, 140, 200) {
        //。。。
    }
}

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

image.png

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

arm_2d_canvas(ptTile, __top_canvas) {    
    arm_2d_align_centre(__top_canvas, 140, 200) {            
        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, 200, 135) {
        arm_2d_layout(__centre_region) {
            __item_line_dock_horizontal(135) {
                //绘制旋转的风扇
            }
            __item_line_dock_horizontal() {
                //绘制温度值
            }
        }
    }
}

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

image.png

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

image.png

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

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

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

image.png

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

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

image.png

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

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

image.png

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

image.png

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

image.png

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

#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,我们的口号是:一起玩,一起玩才更好玩。下期精彩继续(基于高斯模糊的特效设计)。。。

原文:嵌入式小书虫
作者:FledgingSu 支离苏

专栏推荐文章

如果你喜欢我的思维,欢迎订阅裸机思维欢迎添加极术小姐姐微信(id:aijishu20)加入技术交流群,请备注研究方向。
推荐阅读
关注数
1468
内容数
110
探讨嵌入式系统开发的相关思维、方法、技巧。
目录
极术微信服务号
关注极术微信号
实时接收点赞提醒和评论通知
安谋科技学堂公众号
关注安谋科技学堂
实时获取安谋科技及 Arm 教学资源
安谋科技招聘公众号
关注安谋科技招聘
实时获取安谋科技中国职位信息