
IOS之Metro实现,拥有windows磁帖效果(一 动画效果的实现)

图
所有转出“博客园”的朋友请您注明出处:http://www.cnblogs.com/xiaobajiu/p/4084717.html
扁平化来袭,微软的metro风格看起来很有科技感。于是想写个IOS的metro控件。搞了一段时间,有了模样。我的想法是metro和wp8的差不多,展示信息通过旋转来展示,触摸metro它会撬动或者吸弹。点击metro会执行事件,长按磁帖又可以执行其他事件。该帖中将详细展示我的设计思路希望能给初学者更多启发和学习资源。
metro的结构:metro最外层是骨架层,该层的作用是站住位置,内部的大小变化和metro的拖拽都不会影响到外部的结构。骨架外是蒙板层和内容层。蒙板层在下方作用是metro主题色,内容层存放要展示的两面视图。值得注意的是蒙板层在不设置主题的情况下是隐藏的。

metro结构-大图
那么直接进入最关心的地方,动画。IOS的动画是廉价的,不像Windows,其他平台都像拿笔在画,IOS就像打印机在刷图。IOS上主要的绘图相关的有Quartz2D,核心动画等应付简单的旋转和伸缩的画使用UIViewAnimation就足够了,而且代码简短。旋转的效果竟然只要一行: view.layer.transform= CATransform3DMakeRotation(CGFloat angle, CGFloat x, CGFloat y, CGFloat z) 这样子就可以实现旋转效果。下面是一段完整的磁帖旋转代码的截取:
/**
* (PRivate)metro的旋转动画
*/
- (void)revolveMetro
{
_isViewDleaying= NO;
[UIView animateWithDuration:_turnTimes[_currentViewID] delay:0.0 options:UIViewAnimationOptionCurveEaseIn|UIViewAnimationOptionAllowUserInteraction animations:^{
_isAnimating= YES;//修改动画状态
_container.layer.transform= CATransform3DMakeRotation(M_PI_2, 1.0, 0.0, 0.0);
if(!_maskingView.hidden)
_maskingView.layer.transform= CATransform3DMakeRotation(M_PI_2, 1.0, 0.0, 0.0);
} completion:^(BOOL finished) {
((UIView*)self.views[_currentViewID]).hidden= YES;
((UIView*)self.views[[self nextIndexNowNextView]]).hidden= NO;
[UIView animateWithDuration:_turnTimes[_currentViewID] delay:0.0 options:UIViewAnimationOptionCurveEaSEOut animations:^{
_container.layer.transform= CATransform3DMakeRotation(- M_PI_4/2, 1.0, 0.0, 0.0);
if(!_maskingView.hidden)
_maskingView.layer.transform= CATransform3DMakeRotation(- M_PI_4/2, 1.0, 0.0, 0.0);
} completion:^(BOOL finished) {
[UIView animateWithDuration:_turnTimes[_currentViewID] delay:0.0 options:UIViewAnimationOptionCurveEaseIn animations:^{
_container.layer.transform= CATransform3DMakeRotation(0, 1.0, 0.0, 0.0);
if(!_maskingView.hidden)
_maskingView.layer.transform= CATransform3DMakeRotation(0, 1.0, 0.0, 0.0);
} completion:^(BOOL finished) {
_isAnimating= NO;//修改动画状态
}];
}];
}];
}
一共组合了三次动画,每个动画都可以通过options选项设置很多有用的属性。比如动画时间曲线,简单说就是动画随渐渐的快慢,它画到坐标轴上就是各种曲线。在磁帖的旋转中有切换图片的这种情况,有很多方法可以做比如我没有用的一个方法: [view exchangeSubviewAtIndex:0 withSubviewAtIndex:1]; 这个方法可以直接交换父图层中子级索引0和1的两个子图层的位置。从而达到切换图层的目的。我使用的是隐藏图层的方法,目的是不想打乱图层顺序,方便找到某图层。
除了metro的旋转还有一个动画效果就是metro被触摸的时候的倾斜效果。通过观察wp8,触摸点离中心点越远下沉力度越大。但是靠近中心区域又是下沉效果而不是倾斜效果。通过观察还能发现到这个动画效果的触发应该是在触摸到metro的时候触发。那么这个动画效果可以在 - (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event 这个函数中执行。
那么还剩一个繁琐的问题就是倾斜的计算的问题。诺以几何中心为对称轴来进行旋转,只加以控制旋转的角度即可实现在metro一条通过几何中心的直线上触摸时根据到几何中心的距离来计算应该旋转多少角度。而旋转所绕对称轴就是上一句所说的通过几何中心的直线(触摸点和几何中心的连线)旋转90度所得直线。
可以创建一个结构结构体来代表这种向量:
/**
* 带权值的向量
*/
struct MetroVector{
CGFloat x;
CGFloat y;
CGFloat power;//权值标明metro下沉力度<-[0.0,1.0]
};
typedef struct MetroVector MetroVector;
下一步是当触摸metro时获得触摸点a。由a和中心o计算出一条向量V1,V1旋转90度既是倾斜所需的对称轴V2。直接将V2的x,y填入旋转函数中即可。值得注意的是 view.layer.transform= CATransform3DMakeRotation(CGFloat angle, CGFloat x, CGFloat y, CGFloat z)这个函数的x,y,z值的取值区间是[0.0,1.0]。关于力度我采用的是线段ao的长度,对比的是o到metro任意角的距离即对角线的半长。两只相除得到一个0~1的数即可衡量力度。
计算向量V2的代码:
/**
* (private)计算metro点击时 3D倾斜的对称轴的向量
*
* @param view 触摸的视图
* @param touchPoint 其中的触摸点
*/
- (MetroVector)getVectorByTouchedView:(UIView*)view in:(CGPoint)touchPoint
{
MetroVector vector;
CGFloat width= view.frame.size.width;
CGFloat height= view.frame.size.height;
CGFloat diagonal= sqrt(width* width+ height* height);//对角线
vector.x= touchPoint.x- width/2;//以几何中心为原点的向量
vector.y= touchPoint.y- height/2;
CGFloat lenTouchBetweenCenter= sqrt(vector.x*vector.x+ vector.y* vector.y);//触摸点到几何中心的距离
CGFloat max= MAX(vector.x, vector.y);//取最大值,做除数,使得两者最大值为1.0
CGFloat temp= vector.x;//交换x,y值,其中之一取反(表示将向量转动90度,成为图层的转轴)
vector.x= - vector.y/max;
vector.y= temp/max;
CGFloat len4maxPower= diagonal/2;//取对角线二分之一作为力度最大的衡量
vector.power= lenTouchBetweenCenter/ len4maxPower;
return vector;
}
有了V2那么倾斜就可以直接上代码了
- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event
{
[super touchesBegan:touches withEvent:event];
if(self.isAnimating) return;//允许摸,不允许再次动画
CGPoint touchPos= [(UITouch*)[touches anyObject] locationInView:self];
//中心区域
CGSize viewSize= self.frame.size;
CGRect centerRect= CGRectMake(viewSize.width/4, viewSize.height/4, viewSize.width/2, viewSize.height/2);
if(CGRectContainsPoint(centerRect, touchPos)){//是否在中心区域,是则下沉动画
[UIView animateWithDuration:macroTouchOneAnimationInterval/5 delay:0.0 options:UIViewAnimationOptionCurveEaseOut|UIViewAnimationOptionAllowUserInteraction animations:^{
_isAnimating= YES;
self.layer.transform= CATransform3DMakeScale(0.975, 0.975, 1.0);
} completion:^(BOOL finished) {
[UIView animateWithDuration:macroTouchOneAnimationInterval/2 delay:0.0 options:UIViewAnimationOptionCurveEaseOut animations:^{
self.layer.transform= CATransform3DMakeScale(1.0, 1.0, 1.0);
} completion:^(BOOL finished) {
_isAnimating= NO;
//..
}];
}];
}else{//偏斜动画
MetroVector vecor= [self getVectorByTouchedView:self in:touchPos];
[UIView animateWithDuration:macroTouchOneAnimationInterval delay:0.0 options:UIViewAnimationOptionCurveEaseOut|UIViewAnimationOptionAllowUserInteraction animations:^{
_isAnimating= YES;
self.layer.transform= CATransform3DMakeRotation(randianFromAngle(30* vecor.power),vecor.x, vecor.y, 0.0);//
} completion:^(BOOL finished) {
[UIView animateWithDuration:macroTouchOneAnimationInterval/2 delay:0.0 options:UIViewAnimationOptionCurveEaseOut animations:^{
self.layer.transform= CATransform3DMakeRotation(0 ,vecor.x, vecor.y, 0.0);
} completion:^(BOOL finished) {
_isAnimating= NO;
//..
}];
}];
}
}
到目前为最为核心的动画内容已经实现。还剩下触摸事件的处理,和一堆所谓的业务逻辑和一些功能上的实现。楼主会在下一篇博客另外介绍。并赋上代码下载。
如有错误请朋友指正,以免误人子弟。
下一篇博文地址:http://www.cnblogs.com/xiaobajiu/p/4106663.html