动画那点事 UIKit, CoreGraphics

之前一直在做应用程序的开发,也没什么动画的事,最近闲着搞了一个游戏,也是为了学习一下简单的移动效果,在这里分享一下几个有用的库方法。
文章将涉及到如下几个效果。

1. 如何设置一个动画块,已达到让一个视图移动的效果、收缩的效果、地震的效果、改变透明度效果。
2. 如何将一个视图旋转。
3. 如何判断触摸点在一个不规则形状内。

有时间在补充…

1. 首先呢,在iOS中,一个view的很多属性都支持动画效果,你需要做的只是在一个动画块中改变这些属性而已。

如何定义一个动画块

//定义一个id为animationID的动画块(id可以为空,nil)
[UIView beginAnimations:@"animationID" context:NULL];
//动画块执行时间
[UIView setAnimationDuration:0.5];
//动画块的内容
//...
[UIView commitAnimations];

这就定义了一个块,你要做的就是在内容里面加上你需要改变的属性,当然,这些属性必须要支持动画。

如何在块中更改属性

//定义一个id为animationID的动画块(id可以为空,nil)
[UIView beginAnimations:@"animationID" context:NULL];
//动画块执行时间
[UIView setAnimationDuration:0.5];

//动画块的内容
//假设现在有个view叫做firstView
firstView.frame = newFrame;//这将移动、伸缩这个view
firstView.alpha = 0.5;//这将改变透明度
[UIView commitAnimations];

补充内容:对layer的动画。
其实一个view可以由多个layer组成,那么每个layer自然也可以接收动画。
下面这段代码就是在didLoadView之后创建了一个subView,然后对subView中的layer的操作。

- (void)viewDidLoad
{
    [super viewDidLoad];
	// Do any additional setup after loading the view, typically from a nib.
    UIView *subView = [[UIView alloc] initWithFrame:CGRectMake(50, 50, 100, 100)];
    subView.backgroundColor = [UIColor redColor];
    
    CALayer *layer = subView.layer;
    [self.view addSubview:subView];
    
    CABasicAnimation *theAnimation;
    
    theAnimation=[CABasicAnimation animationWithKeyPath:@"opacity"];
    theAnimation.duration=3.0;
    theAnimation.repeatCount=2;
    theAnimation.autoreverses=YES;
    theAnimation.fromValue=[NSNumber numberWithFloat:1.0];
    theAnimation.toValue=[NSNumber numberWithFloat:0.0];
    [layer addAnimation:theAnimation forKey:@"animateOpacity"];
}

如何实现地震效果

这需要两个函数来实现,同时需要QuartzCore.framework的支持。

- (void)startWiggle:(UIView *)theView
{
	CALayer *viewLayer = [theView layer];
	CABasicAnimation *anim = [CABasicAnimation animationWithKeyPath:@"transform"];
	anim.duration = 0.1;
	anim.repeatCount = FLT_MAX;
	anim.autoreverses = YES;
	anim.fromValue = [NSValue valueWithCATransform3D:CATransform3DRotate(viewLayer.transform, -0.1,0.0,0.0,0.5)];
	anim.toValue = [NSValue valueWithCATransform3D:CATransform3DRotate(viewLayer.transform, 0.1,0.0,0.0,0.5)];
	[viewLayer addAnimation:anim forKey:@"wiggle"];
}

- (void)stopWiggle:(UIView *)theView
{
	CALayer *viewLayer = [theView layer];
	[viewLayer removeAnimationForKey:@"wiggle"];
}

接下来,在你需要的地方使用这两个方法开始/结束地震效果。
不过值得一提的是考虑到执行顺序的因素,你大可把它加到runloop中让系统决定什么时候执行。

[self startWiggle: firstView];//你自己控制什么时候执行
[self performSelector@selector(startWiggle:) withObject: firstView afterDelay:delayTime];//加入runloop中,大概dalayTime秒钟后执行
[self stopWiggle: firstView];//取消地震效果

2. 如何旋转一个视图。(或者移动一个视图,收缩视图。使用CG框架)

这里需要些线性代数的知识。
事实上,view或者是图片都是按照坐标系一个点一个点“画”在iPhone上的。把这些点想象成一个矩阵,那么你就可以把这个矩阵任意的映射到另外一个矩阵中并显示在iPhone上。
而这个转换的过程就是乘以另外一个3*3矩阵的过程。
好在苹果提供了一个结构体用来表示这个3*3矩阵,并且不用你提供所有的9个元素

struct CGAffineTransform {
  CGFloat a, b, c, d;
  CGFloat tx, ty;
};

你要做的是定义并设置好这样一个结构体,并让你的视图“应用”这个结构体,便可以达到相应的变换。
我先列出一些生成结构体的方法。

/* Return a transform which translates by `(tx, ty)':
     t' = [ 1 0 0 1 tx ty ] */
//位移
CG_EXTERN CGAffineTransform CGAffineTransformMakeTranslation(CGFloat tx, CGFloat ty)

/* Return a transform which scales by `(sx, sy)':
     t' = [ sx 0 0 sy 0 0 ] */
//拉伸,缩放
CG_EXTERN CGAffineTransform CGAffineTransformMakeScale(CGFloat sx, CGFloat sy)

/* Return a transform which rotates by `angle' radians:
     t' = [ cos(angle) sin(angle) -sin(angle) cos(angle) 0 0 ] */
//旋转,PI为180度
CG_EXTERN CGAffineTransform CGAffineTransformMakeRotation(CGFloat angle)

举个例子吧,如果我想将firstView向右旋转90度(也就是PI/2)

//M_PI_2被预定义为2分之PI了
firstView.transform = CGAffineTransformMakeRotation(M_PI_2);
//有了这个东西,你就可以在程序中自由的变换了。
//一个实例是,一个向上箭头的图片,你可以在执行某个动作后让下转180度向下。
//新浪微博知道吧,那个下拉更新的动作,箭头上下变来变去就可以这么实现。

3. 如何如何判断一个点在不在一个不规则图形内

先说一句,这个方法纯属我自己为了解决开发时遇到的问题的方法。不一定是最好的解决办法。
提供个思路,仅供参考。
这个过程将会使用如下结构体

typedef struct CGPath *CGMutablePathRef;
typedef const struct CGPath *CGPathRef;

使用时,首先定义这样一个结构体变量,并加以调整得到想要的不规则图形。

CGMutablePathRef _pathRef = CGPathCreateMutable();
//移动到起始点
CGPathMoveToPoint(_pathRef, NULL, 0, 29);
CGPathAddLineToPoint(_pathRef, NULL, 58, 29);
//加入若干个点,从而组成你想要的图形
CGPathAddLineToPoint(_pathRef, NULL, 0, 29);
//将这个路径封闭起来
CGPathCloseSubpath(_pathRef);

CGPoint touchedPoint = [当前触摸的点];
//判断点是否在这个path中
BOOL b = CGPathContainsPoint(_pathRef, NULL, touchedPoint, NO);
//使用完path记得释放,即便是在ARC模式中
CGPathRelease(_pathRef);

关于CGPathContainsPoint我想多解释一些
查看文档他说

/* Return true if `point' is contained in `path'; false otherwise. A point
   is contained in a path if it is inside the painted region when the path
   is filled; if `eoFill' is true, then the even-odd fill rule is used to
   evaluate the painted region of the path, otherwise, the winding-number
   fill rule is used. If `m' is non-NULL, then the point is transformed by
   `m' before determining whether the path contains it. */

CG_EXTERN bool CGPathContainsPoint(CGPathRef path, const CGAffineTransform *m, CGPoint point, bool eoFill);

//你也看到了,这里有4个参数。
//1. 欲判断的path,也就是那个不规则图形
//2. 这个图形也可以应用前面提到的矩阵转换,比如你可以把这个path转个90度在判断
//3. 欲判断的点
//4. 这个是定义是否要使用复合函数中的奇偶法则还是non-zero winding number fill rule

PS:关于奇偶法则,和non-zero winding number fill rule的一点解释,都是鸟语。您看不看随心情
The even–odd rule: The SVG specification says: “This rule determines the “insideness” of a point on the canvas by drawing a ray from that point to infinity in any direction and counting the number of path segments from the given shape that the ray crosses. If this number is odd, the point is inside; if even, the point is outside.”

non-zero winding number fill rule: This rule determines the “insideness” of a point on the canvas by drawing a ray from that point to infinity in any direction and then examining the places where a segment of the shape crosses the ray. Starting with a count of zero, add one each time a path segment crosses the ray from left to right and subtract one each time a path segment crosses the ray from right to left. After counting the crossings, if the result is zero then the point is outside the path. Otherwise, it is inside.

来个图例,原理差不多一样,只不过一个奇偶,一个是1,0。

发表评论

电子邮件地址不会被公开。 必填项已用*标注