【实战Arm-2D】如何实现任意弧度的圆环进度条

文摘   2023-12-14 05:32   英国  

【说在前面的话】


之前我们讲过一个酷炫圆环进度条的制作,忘记的可以看下这篇文章

【玩转Arm-2D】七、制作酷炫圆环进度条

由于之前的弧度只能从0度开始画,有小伙伴就提出能不能从任意角度开始画弧呢?基于此,今天就谈谈怎么用Arm-2D实现以任意角度开始画弧的方法,在讲原理之前,我们先看一下要实现的效果,如下

【制作前的准备】


首先,我们规定画圆弧的方向为顺时针,如下图从45度到180度的圆弧且角度的输入范围为0~360。

有了这个方向,要画圆环,那肯定还需要一些素材,今天的素材(为了方便大家看,把背景颜色弄成了黑色)如下

一个1/4圆环和一个同样大小的黑块就可以了。

接下来就讲一下怎么用这两个素材画出任意角度的圆弧(称之为盖中盖的方法)。

【绘制弧度的原理】


在讲此方法前,我们有必要把使用到的基础知识在简单讲一下。首先,我们可以通过1/4圆环旋转90度、180度和270度很容易就拼接出一个完整的圆环,如下图

第2个知识点就是:利用旋转区域可以帮我们裁剪掉多余的部分,如下图

有了上面的知识点,我们就讲一下怎么绘制从x~y度的圆弧。此方法总共分3步,我们以从45度到120度的圆弧为例来讲解

第一步,先用1/4圆环素材画出0到180度圆弧(目的是覆盖住45~120度的圆弧),如下

第2步,用另一块素材把0~45度多余的圆环盖住就可以,如下图

注意:黑色方块只是为了讲解方便(看起来不美观),如果换成白色是不是就看不到了(* ̄︶ ̄)

3步,同样的,把120到180度的圆环住就可以,如下图

这样我们就得到了45度到120度的圆环了。

你是不是以为就这么简单,其实还有一个小问题的。

比如我们从45度画到30度时,就不行了,如下图所示

此时,你发现到第二步把0到45度的圆环盖住后,0到30度的圆环已经没有了,第三步该肿么办呢?

聪明的你很快也想到了,再把030度的圆弧画出来不就可以了。

没错,是这样的。也就是说当我们画的角度在x~360内(x的取值为0~360),我们用两边盖的方法是可以的。否则需要把多盖主的再画出来,如下图

此时,你好像又发现了问题,那要是从120度画到45度呢?

如下图

是不是又回到了上面两头盖的方法了(* ̄︶ ̄)

基于此,我们得出一个结论:画一段x~y度的圆弧,如果x>y且x和y在同一象限第3步就需要把多盖住的再画出来。除此之外就可以使用两头盖的方法了

【绘制圆弧的实现】


接下来就看看程序是怎么实现绘制圆弧的。首先,定义一个函数,如下

void draw_my_radian(    const arm_2d_tile_t *ptTile,                        bool bIsNewFrame,    int from,    int to,    arm_2d_location_t* tCentre,            arm_2d_location_t* ptTargetCentre,      const arm_2d_tile_t* pt_tileQuater_Mask,          const arm_2d_tile_t* pt_tileQuater_bgMask,             COLOUR_INT tWheelColour,    COLOUR_INT tWheelbgColour    )
  • 前两个参数是调用旋转函数要使用的,就不具体介绍了

  • from就是从多少度开始画弧,其取值范围为0~360

  • to就是圆弧到多少度结束,其取值范围为0~360

  • 接下来两个参数是确定旋转中心的

  • pt_tileQuater_Mask就是1/4圆弧的素材

  • pt_tileQuater_bgMask就是背景素材

  • tWheelColour就是设置圆环的颜色

  • tWheelbgColour就是设置背景素材的颜色(记得和屏幕背景色保持一致)

接下来就开始第一步:根据需要开始绘制1/4、2/4、3/4或4/4圆环,我们先定义一个变量quadrant_flag第一位用来判断第一象限是否需要绘制圆弧,以此类推,为每个象限分配一个标志位,程序如下

if(to > from){    if(from < 90 ){        quadrant_flag |= 0x01;//第1象限画圆弧    }    if(from < 180 && to > 90){        quadrant_flag |= 0x02;//第2象限画圆弧    }    if(from < 270 && to > 180){        quadrant_flag |= 0x04;//第3象限画圆弧    }    if(from < 360 && to > 270){        quadrant_flag |= 0x08;//第4象限画圆弧    }}else {    if(from < 90 || to > 0){        quadrant_flag |= 0x01;//第1象限画圆弧    }    if(from < 180 || to > 90){        quadrant_flag |= 0x02;//第2象限画圆弧    }    if(from < 270 || to > 180){        quadrant_flag |= 0x04;//第3象限画圆弧    }    if(from < 360 || to > 270){        quadrant_flag |= 0x08;//第4象限画圆弧    }    }
  • 段代码就是根据(from和to)两个角度确定是否需要在此象限画圆弧,是则在对应的象限标志位置1,以第二象限为例,如下图

有了标志位,开始在对应象限画圆弧就可以了,程序如下

tRotationRegion.tSize = pt_tileQuater_Mask->tRegion.tSize;if(quadrant_flag & 0x01){    tRotationRegion.tLocation.iX = ptTargetCentre->iX ;    tRotationRegion.tLocation.iY = ptTargetCentre->iY - tCentre->iY;    arm_2dp_fill_colour_with_mask_opacity_and_transform(          &tOP[0],          pt_tileQuater_Mask,//          ptTile,//          &tRotationRegion,//          *tCentre,          ARM_2D_ANGLE(90.0f),          1,          tWheelColour,          255,          ptTargetCentre);}
  • 注意旋转区域的计算,即如果在第一象限,旋转区域就要设置在第一象限,如下图


接着就是第二步,把到x多画出来的圆环盖住,程序如下

if( from <= 90){      tRotationRegion.tLocation.iX = ptTargetCentre->iX ;      tRotationRegion.tLocation.iY = ptTargetCentre->iY - tCentre->iY;             }else if( from <= 180){        tRotationRegion.tLocation.iX = ptTargetCentre->iX ;        tRotationRegion.tLocation.iY = ptTargetCentre->iY ;             }    else if( from <= 270){        tRotationRegion.tLocation.iX = ptTargetCentre->iX - tCentre->iX;        tRotationRegion.tLocation.iY = ptTargetCentre->iY ;             }    else if( from <= 360){        tRotationRegion.tLocation.iX = ptTargetCentre->iX - tCentre->iX;        tRotationRegion.tLocation.iY = ptTargetCentre->iY - tCentre->iY;             }                      arm_2dp_fill_colour_with_mask_opacity_and_transform(          &tOP[4],          pt_tileQuater_bgMask,          ptTile,          &tRotationRegion,          *tCentre,          ARM_2D_ANGLE(from),          1,          tWheelbgColour,          255,          ptTargetCentre);}
  • 同样的,要根据不同的象限计算旋转区域


第三步就需要分两种情况,首先我们把第二步多覆盖掉的再补回来,程序如下

if((from > to) && ((from / 90) == (to / 90))){                     arm_2dp_fill_colour_with_mask_opacity_and_transform(              &tOP[5],              pt_tileQuater_Mask,//ptileArcMask,              ptTile,//&__wheel,              &tRotationRegion,//&tQuater,              *tCentre,              ARM_2D_ANGLE(to),              1,//this.fScale,              tWheelColour,              255,//chOpacity,              ptTargetCentre);}


第二种情况就是把to之后多画的圆弧盖住,程序如下

if((from > to) && ((from / 90) == (to / 90))){ //...                         }else{    if( to <= 90){    tRotationRegion.tLocation.iX = ptTargetCentre->iX ;    tRotationRegion.tLocation.iY = ptTargetCentre->iY - tCentre->iY;         }else if( to <= 180){        tRotationRegion.tLocation.iX = ptTargetCentre->iX ;        tRotationRegion.tLocation.iY = ptTargetCentre->iY ;             }    else if( to <= 270){        tRotationRegion.tLocation.iX = ptTargetCentre->iX - tCentre->iX;        tRotationRegion.tLocation.iY = ptTargetCentre->iY ;             }    else if( to <= 360){        tRotationRegion.tLocation.iX = ptTargetCentre->iX - tCentre->iX;        tRotationRegion.tLocation.iY = ptTargetCentre->iY - tCentre->iY;             }        arm_2dp_fill_colour_with_mask_opacity_and_transform(            &tOP[5],            pt_tileQuater_bgMask,//ptileArcMask,            ptTile,//&__wheel,            &tRotationRegion,//&tQuater,            *tCentre,            ARM_2D_ANGLE(to+90),            1,//this.fScale,            tWheelbgColour,            255,//chOpacity,            ptTargetCentre);    }

到此,我们就可以绘制任意弧度的圆弧了。不过视频中圆弧两端也是小圆弧是怎么弄的呢?

其实这个也简单,如下图所示

只需要把一个小圆点的素材分别旋转x度和y度(即程序中的from和to)就可以了,但要注意旋转中心的设置(tDotCentre ,如下图

程序如下


tRotationRegion.tLocation.iX = ptTargetCentre->iX - tCentre->iX;tRotationRegion.tLocation.iY = ptTargetCentre->iY - tCentre->iY;tRotationRegion.tSize.iHeight = tRotationRegion.tSize.iHeight * 2;tRotationRegion.tSize.iWidth = tRotationRegion.tSize.iWidth * 2;arm_2d_region_t tQuater = tRotationRegion;arm_2d_location_t tDotCentre = { .iX = (c_tileWhiteDotMask.tRegion.tSize.iWidth + 1) >> 1, .iY = pt_tileQuater_Mask->tRegion.tSize.iHeight - 1,};
/* draw the starting point */arm_2dp_fill_colour_with_mask_opacity_and_transform( &tOP[6], &c_tileWhiteDotMask, ptTile,//&__wheel, &tQuater, tDotCentre, ARM_2D_ANGLE(from), 1, tWheelColour, 255, ptTargetCentre);/* draw the end point */arm_2dp_fill_colour_with_mask_opacity_and_transform( &tOP[7], &c_tileWhiteDotMask, ptTile,//&__wheel, &tQuater, tDotCentre, ARM_2D_ANGLE(to), 1, tWheelColour, 255, ptTargetCentre);

程序也很简单,一个旋转from度,一个旋转to度就可以了(* ̄︶ ̄)

【双色圆环进度条】


那能不能弄一个圆环的背景,然后圆弧在上面旋转呢?

答案是肯定的,而且还很简单,只要在准备一个素材就可以了,如下图

哈哈,你是不是瞬间就明白了,我们只要用另一种颜色的圆环进行覆盖就可以了,运行效果如下

你是不是发现这个视频要比上面那个旋转的更快一些,是做了什么优化吗?

哈哈,只是更换了两个函数。首先旋转函数使用的是图片进行旋转的(而不是mask),函数如下

arm_2dp_rgb565_tile_transform_with_colour_keying(&tmyOP[5],                                    pt_tileQuater_bgPic,                              ptTile,                              &tRotationRegion,                           *tCentre,                                     ARM_2D_ANGLE(to+90),                                      1,           tBgColour,          ptTargetCentre);
  • 这个函数对制作1/4圆弧素材要求不高(不需要制作mask),直接使用图片旋转就可以,所以他的优化效果一般

真正使速度加快的是下面这个函数

arm_2dp_rgb16_tile_copy_with_colour_keying_and_x_mirror(NULL,      pt_tileQuater_Mask,                          ptTile,      &tRotationRegion,      bgcolor2)
  • 之前在第一象限画圆弧我们使用的是旋转90度的旋转函数,现在更改为x镜像拷贝函数。(旋转是需要根据角度用三角函数进行计算的,而镜像拷贝则不需要,所以速度会快一些

  • 同样的,第二象限用xy镜像,第三象限用y镜像,第四象限直接贴图就可以了(* ̄︶ ̄)

我把整个函数代码也贴到下面供大家参考,如果有bug也欢迎大家给我指出来(* ̄︶ ̄)

arm_2d_op_trans_t tmyOP[2];
void draw_my_progress_pic( const arm_2d_tile_t *ptTile, bool bIsNewFrame, int from, int to, arm_2d_location_t* tCentre, arm_2d_location_t* ptTargetCentre, const arm_2d_tile_t* pt_tileQuater_Pic, const arm_2d_tile_t* pt_tileQuater_bgPic,    COLOUR_INT tBgColour ){            uint8_t quadrant_flag = 0; arm_2d_region_t tRotationRegion = {}; //===========第一步======================================================= if(to > from){ if(from < 90 ){ quadrant_flag |= 0x01; } if(from < 180 && to > 90){ quadrant_flag |= 0x02; } if(from < 270 && to > 180){ quadrant_flag |= 0x04; } if(from < 360 && to > 270){ quadrant_flag |= 0x08; }    }else {//if(to < from)     if(from < 90 || to > 0){ quadrant_flag |= 0x01; } if(from < 180 || to > 90){ quadrant_flag |= 0x02; } if(from < 270 || to > 180){ quadrant_flag |= 0x04; } if(from < 360 || to > 270){ quadrant_flag |= 0x08; } }//else{ // quadrant_flag = 0x0f; //} tRotationRegion.tSize = pt_tileQuater_Pic->tRegion.tSize; if(quadrant_flag & 0x01){ tRotationRegion.tLocation.iX = ptTargetCentre->iX ; tRotationRegion.tLocation.iY = ptTargetCentre->iY - tCentre->iY;                        arm_2dp_rgb16_tile_copy_with_colour_keying_and_x_mirror(NULL, pt_tileQuater_Pic, ptTile, &tRotationRegion,                tBgColour);                 }else{ tRotationRegion.tLocation.iX = ptTargetCentre->iX ; tRotationRegion.tLocation.iY = ptTargetCentre->iY - tCentre->iY;                 arm_2dp_rgb16_tile_copy_with_colour_keying_and_x_mirror(NULL, pt_tileQuater_bgPic, ptTile, &tRotationRegion,              tBgColour);                 }    if(quadrant_flag & 0x02){        tRotationRegion.tLocation.iX = ptTargetCentre->iX ;        tRotationRegion.tLocation.iY = ptTargetCentre->iY ;                arm_2dp_rgb16_tile_copy_with_colour_keying_and_xy_mirror(NULL, pt_tileQuater_Pic, ptTile, &tRotationRegion, tBgColour); }else{ tRotationRegion.tLocation.iX = ptTargetCentre->iX ; tRotationRegion.tLocation.iY = ptTargetCentre->iY ; arm_2dp_rgb16_tile_copy_with_colour_keying_and_xy_mirror(NULL, pt_tileQuater_bgPic, ptTile, &tRotationRegion, tBgColour); }     if(quadrant_flag & 0x04){         tRotationRegion.tLocation.iX = ptTargetCentre->iX - tCentre->iX; tRotationRegion.tLocation.iY = ptTargetCentre->iY ; arm_2dp_rgb16_tile_copy_with_colour_keying_and_y_mirror(NULL, pt_tileQuater_Pic, ptTile, &tRotationRegion, tBgColour); }else{ tRotationRegion.tLocation.iX = ptTargetCentre->iX - tCentre->iX;        tRotationRegion.tLocation.iY = ptTargetCentre->iY ;         arm_2dp_rgb16_tile_copy_with_colour_keying_and_y_mirror(NULL, pt_tileQuater_bgPic, ptTile, &tRotationRegion, tBgColour); }     if(quadrant_flag & 0x08){         tRotationRegion.tLocation.iX = ptTargetCentre->iX - tCentre->iX;        tRotationRegion.tLocation.iY = ptTargetCentre->iY - tCentre->iY;        arm_2dp_rgb16_tile_copy_with_colour_keying_only(NULL, pt_tileQuater_Pic, ptTile, &tRotationRegion, tBgColour); }else{ tRotationRegion.tLocation.iX = ptTargetCentre->iX - tCentre->iX;        tRotationRegion.tLocation.iY = ptTargetCentre->iY - tCentre->iY;                arm_2dp_rgb16_tile_copy_with_colour_keying_only(NULL, pt_tileQuater_bgPic, ptTile, &tRotationRegion, tBgColour);    } //=======第二步=============================================     if(from != to) { if( from <= 90){ tRotationRegion.tLocation.iX = ptTargetCentre->iX ;            tRotationRegion.tLocation.iY = ptTargetCentre->iY - tCentre->iY;              }else if( from <= 180){ tRotationRegion.tLocation.iX = ptTargetCentre->iX ;            tRotationRegion.tLocation.iY = ptTargetCentre->iY ;              } else if( from <= 270){ tRotationRegion.tLocation.iX = ptTargetCentre->iX - tCentre->iX;            tRotationRegion.tLocation.iY = ptTargetCentre->iY ;              } else if( from <= 360){ tRotationRegion.tLocation.iX = ptTargetCentre->iX - tCentre->iX;            tRotationRegion.tLocation.iY = ptTargetCentre->iY - tCentre->iY;                     }                               arm_2dp_rgb565_tile_transform_with_colour_keying(&tmyOP[0], pt_tileQuater_bgPic, ptTile, &tRotationRegion, *tCentre, ARM_2D_ANGLE(from), 1, tBgColour, ptTargetCentre); //===========第三步=======================================================         if((from > to) && ((from / 90) == (to / 90))){                              arm_2dp_rgb565_tile_transform_with_colour_keying(&tmyOP[1], pt_tileQuater_Pic, ptTile, &tRotationRegion, *tCentre, ARM_2D_ANGLE(to), 1, tBgColour, ptTargetCentre); }else{ if( to <= 90){ tRotationRegion.tLocation.iX = ptTargetCentre->iX ;            tRotationRegion.tLocation.iY = ptTargetCentre->iY - tCentre->iY;              }else if( to <= 180){ tRotationRegion.tLocation.iX = ptTargetCentre->iX ; tRotationRegion.tLocation.iY = ptTargetCentre->iY ; } else if( to <= 270){ tRotationRegion.tLocation.iX = ptTargetCentre->iX - tCentre->iX;                tRotationRegion.tLocation.iY = ptTargetCentre->iY ;                  } else if( to <= 360){ tRotationRegion.tLocation.iX = ptTargetCentre->iX - tCentre->iX;                tRotationRegion.tLocation.iY = ptTargetCentre->iY - tCentre->iY;                             }                        arm_2dp_rgb565_tile_transform_with_colour_keying(&tmyOP[1], pt_tileQuater_bgPic, ptTile, &tRotationRegion, *tCentre, ARM_2D_ANGLE(to+90), 1, tBgColour,                 ptTargetCentre);                         }    }   }

有了这个函数,就可以简单制作一个旋转按钮了,使用的素材如下

实际效果如下

是不是很简单,如果你还有什么好玩的记得告诉我哦!

如果你还想实现微信里面加载数据时的旋转小圈,如下所示

那就更简单了,只需要用下面的素材旋转就可以了

注意:这个最好用mask填充来旋转哦,即下面这个函数

arm_2dp_fill_colour_with_mask_opacity_and_transform()

好了,到这里今天的内容就讲完了欢迎大家和我一起玩转Arm-2D,我们的口号是:一起玩,一起玩才更好玩下期精彩继续(嵌入式UI布局之自适应尺寸)。

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

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

欢迎订阅    嵌入式小书虫

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