【说在前面的话】
之前我们讲过一个酷炫圆环进度条的制作,忘记的可以看下这篇文章
由于之前的弧度只能从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度的圆环已经没有了,第三步该肿么办呢?
聪明的你很快也想到了,再把0到30度的圆弧画出来不就可以了。
没错,是这样的。也就是说当我们画的角度在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布局之自适应尺寸)。。。
原创不易,如果你喜欢我的公众号、觉得我的文章对你有所启发,
请务必“点赞、收藏、转发”,这对我很重要,谢谢!
欢迎订阅 嵌入式小书虫