多点触控操作对象时,对象自由旋转。但是如果想要正向摆放,光靠两个手指,非常困难,除去精度问题,手指抖动再所难免,所以有必要为旋转添加一个磁吸特性。
所谓磁吸就是当旋转到0度、90度、180度、270度附近时,对象角度被吸入,当继续操作超过一定度数,操作才能继续。QT QGraphicsItem有roation和setRotation,使用的都是度数值。当要做磁吸效果时,不能当真调用setRotation,需做一层截留。
带入一个假设数据:欲设置角度90.1度,截留此值,判定认为需要磁吸,直接设置角度为90度。当多点操作需要获取当前角度值时,要将截留值90.1返回,否则影响旋转计算阻碍正常操作。
问题的关键最终总结为:输入一个角度值,识别是否4个特定角度,是的话将对象角度设置为接近的此四个角度。输入值可能大于360也可能小于0。目前采用算法如下:
void CXXXXXXXItem::setRotationR(qreal degree)
{
fsRotation = degree;
qreal showDegree = this->parentItem()->rotation() + degree;
showDegree -= qFloor(showDegree/360)*360; //在0~360之间
int x = qFloor(showDegree/90);
int y = qFloor(showDegree/45);
if(qAbs(showDegree-x*90-45) > 40)
{
if(y%2)x++;
degree = x*90 - this->parentItem()->rotation();
}
this->setRotation(degree);
}
注1:qFloor(x)函数是取小于x的最大整数,负数同样奏效。C中也有同名floor函数。
注2:qAbs(x)函数取绝对值,可用#define ABS(x) ((x)>0?(x):-(x))
算法首先记录设置的角度到成员变量fsRotation,获取用户眼中看到的角度showDegree(因为父对象可能旋转过),通过-=qFloor(showDegree/360)*360将showDegree转换为等效的0~360之间,此时再通过qFloor(showDegree/90)和qFloor(showDegree/45)将角度按两种区间分区记录算得x,y。将有八种组合:(0,0)(0,1) (1,2)(1,3) (2,4)(2,5) (3,6)(3,7)而showDegree-x*90则会得到0~90之间的值,若值与0或90接近,则可磁吸。将值-45取绝对值判断是否大于40的方法可以免去if.else的使用而将磁吸控制在正负5度之间。设0度、90度、180度、270度分别为0、1、2、3角度位,则角度位*90度即是磁吸到的目标角度。观察可知(0,0)(1,2)(2,4)(3,6)的角度位等于x,(0,1)(1,3)(2,5)(3,7)的角度位等于x+1。所以总结规律有y/2不等于x的角度位=x+1.但x、y都是整型不便于除法,换用y%2有余则区数=x+1的办法。
如有更好更省的算法,欢迎交流!