15

傻孩子(GorgonMeducer) · 2023年12月15日

任意弧度的圆环进度条是怎么实现的

image.png

【说在前面的话】

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

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

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

image.png

【制作前的准备】

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

image.png

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

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

image.png

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

【绘制弧度的原理】

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

image.png

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

image.png

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

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

image.png

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

image.png

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

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

image.png

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

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

image.png

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

image.png

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

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

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

image.png

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

image.png

如下图

image.png

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

基于此,我们得出一个结论:画一段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,以第二象限为例,如下图

image.png

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


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);
}
  • 注意旋转区域的计算,即如果在第一象限,旋转区域就要设置在第一象限,如下图

image.png

接着就是第二步,把到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);
    
}

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

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

image.png

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

image.png

程序如下



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度就可以了( ̄︶ ̄)

【双色圆环进度条】

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

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

image.png

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

image.png

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

哈哈,只是更换了两个函数。首先旋转函数使用的是图片进行旋转的(而不是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){}            
    else{    
        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);                        
        }
    }   
}

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

image.png

image.png

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

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

image.png

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

image.png

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

arm_2dp_fill_colour_with_mask_opacity_and_transform()

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

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

专栏推荐文章

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