iOS用三个手势和仿射变换实现图片的旋转缩放移动效果(类iBooks教科书)
转载自 http://www.xiaoyaoli.com/?p=608
这个例子实现了双手指让图片旋转缩放移动的效果。
最关键的是松手后会根据现有缩放比例来判断在原有的变换基础上到达固定变换,比如缩放比例小于1.5时,无论松手时图片是在旋转状态还是有位移,都会根据现有的旋转角度和位移和缩放状态平滑的恢复到初始状态,大于1.5就放到的合适屏幕尺寸。
之所以要用到仿射变换,因为单纯的通过动画修改frame或者角度或者缩放,经过尝试总有这样那样的问题,比如用拉伸长宽来放大会影响性能,不好设置放大起始点等等。
这个例子下一步有待改进的地方是手指旋转的优化,图片旋转中心点应该是两手指的中心点,目前是图片的中心点不变,这点改进了就挺像iBooks教科书了。
下面是效果图:
首先创建一个SingleView项目,IB里面拖拽一个第一幅图那么大的UIView,UIView里面放一个ImageView,这么做是为了便于以后让Image可以在这个相框里有单独的动画,目前用不着。
头文件:
#import <UIKit/UIKit.h>
@interface ViewController : UIViewController<UIGestureRecognizerDelegate>
{
IBOutlet UIView *moveView;
float lastScale;
float lastRotate;
float lastX;
float lastY;
float ssss;
}
@end
除了必要的记录变换的变量以外,要注意添加UIGestureRecognizerDelegate协议,因为.m文件中会用到代理方法让多个手势并存。
然后是.m文件,添加这个方法:
//给图片的View添加捏合,旋转,拖拽手势
– (void)viewDidLoad
{
[super viewDidLoad];
UIPinchGestureRecognizer *pinchRecognizer = [[UIPinchGestureRecognizer alloc] initWithTarget:self action:@selector(scale:)];
[pinchRecognizer setDelegate:self];
[moveView addGestureRecognizer:pinchRecognizer];
UIRotationGestureRecognizer *rotationRecognizer = [[UIRotationGestureRecognizer alloc] initWithTarget:self action:@selector(rotate:)];
[rotationRecognizer setDelegate:self];
[moveView addGestureRecognizer:rotationRecognizer];
UIPanGestureRecognizer *panRecognizer = [[UIPanGestureRecognizer alloc] initWithTarget:self action:@selector(move:)];
[panRecognizer setMinimumNumberOfTouches:2];
[panRecognizer setMaximumNumberOfTouches:2];
[panRecognizer setDelegate:self];
[moveView addGestureRecognizer:panRecognizer];
}
接下来就要写三个手势对应的方法,但是首先先要添加下面的代理方法,returen YES,这样才可以让多手势并存,否则只会相应一种手势:
- (BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer shouldRecognizeSimultaneouslyWithGestureRecognizer:(UIGestureRecognizer *)otherGestureRecognizer{
return YES;
}
然后先看Pinch手势的Scale方法:
-(void)scale:(id)sender {
if([(UIPinchGestureRecognizer*)sender state] == UIGestureRecognizerStateBegan) {
lastScale = 1.0;
}
if ([(UIPinchGestureRecognizer*)sender state] == UIGestureRecognizerStateChanged) {
CGFloat scale = 1.0 – (lastScale – [(UIPinchGestureRecognizer*)sender scale]);
NSLog(@”scale %f”,[(UIPinchGestureRecognizer*)sender scale]);
ssss=[(UIPinchGestureRecognizer*)sender scale];
CGAffineTransform currentTransform = moveView.transform;
//Scale的仿射变换,只改变缩放比例,其他仿射变换底下会说
CGAffineTransform newTransform = CGAffineTransformScale(currentTransform, scale, scale);
[moveView setTransform:newTransform];
lastScale = [(UIPinchGestureRecognizer*)sender scale];
}
}
思路显而易见,获得Pinch手势Scale系数然后作为缩放仿射变换的系数带入,并添加这个变换,这样随着手指Pinch变化图片会跟着进行缩放变化,这里要注意的是,缩放是从图片中心点缩放的。
接下来是旋转手势:
-(void)rotate:(id)sender {
if([(UIRotationGestureRecognizer*)sender state] == UIGestureRecognizerStateBegan) {
//记录旋转开始时手指中心的坐标
// rotatePoint1=[sender locationOfTouch:0 inView:moveView];
// rotatePoint2=[sender locationOfTouch:1 inView:moveView];
// distanceX=(rotatePoint1.x+rotatePoint2.x)/2;
// distanceY=(rotatePoint1.y+rotatePoint2.y)/2;
// rotateCenterPoint=CGPointMake(distanceX/moveView.bounds.size.width, distanceY/moveView.bounds.size.height);
// //bounds 绝对的
// NSLog(@”r1 %f”,rotateCenterPoint.x);
// NSLog(@”r2 %f”,rotateCenterPoint.y);
// // moveView.layer.anchorPoint=rotateCenterPoint;
}
if([(UIRotationGestureRecognizer*)sender state] == UIGestureRecognizerStateEnded) {
lastRotate = 0.0;
return;
}
CGFloat rotation = 0.0 – (lastRotate – [(UIRotationGestureRecognizer*)sender rotation]);
//设定旋转的仿射变换
CGAffineTransform currentTransform = moveView.transform;
CGAffineTransform newTransform = CGAffineTransformRotate(currentTransform,rotation);
//设定旋转的中心点,这里没成功,会造成图片错位,待解决,先不用
//moveView.layer.anchorPoint=rotateCenterPoint;
//moveView.center=rotateCenterPoint;
[moveView setTransform:newTransform];
lastRotate = [(UIRotationGestureRecognizer*)sender rotation];
}
旋转手势也大同小异,获得手势的rotation值然后带入旋转仿射变换,这里我尝试获取两手指中心点,然后设定为图片旋转点,有图片错位的问题, 目前估计是坐标系的错位造成,因为图片的旋转点确实改变了,以后解决。现在图片应该已经可以跟着手指旋转和缩放了,接下来就是让图片产生位移了,此外还有 松开手时图片的自动过渡变换,我都写在了Pan手势的手势结束方法里:
-(void)move:(id)sender {
//这里记录图片中心点坐标,不是通过改变frame来位移
CGPoint translatedPoint = [(UIPanGestureRecognizer*)sender translationInView:self.view];
if([(UIPanGestureRecognizer*)sender state] == UIGestureRecognizerStateBegan) {
lastX = [moveView center].x;
lastY = [moveView center].y;
}
translatedPoint = CGPointMake(lastX+translatedPoint.x, lastY+translatedPoint.y);
[moveView setCenter:translatedPoint];
//松手时判断缩放倍数若小于1.7,让图片还原,若大于1.7,让图片全屏
if ([(UIPanGestureRecognizer *)sender state]==UIGestureRecognizerStateEnded) {
if(ssss<=1.7){
[UIView animateWithDuration:0.5 delay:0 options:UIViewAnimationCurveEaseOut animations:^{
//让图片平滑从现有状态还原的关键方法,乘以还原矩阵
moveView.transform = CGAffineTransformIdentity;
//恢复起始中心点
CGPoint p = CGPointMake(384, 257);
[moveView setCenter:p];
} completion:nil];
}else {
//缩放倍数大于1.7时,平滑过渡到全屏
[UIView animateWithDuration:0.5 delay:0 options:UIViewAnimationCurveEaseOut animations:^{
//当不仅需要旋转角度变换,还需要缩放和位移变换的时候,需要手工写仿射变换矩阵,不能单纯的用CGAffineTransformRotate或者CGAffineTransformScale,其实后两者都是仿射变换矩阵的一部分
CGAffineTransform newTransform=CGAffineTransformMake(self.view.bounds.size.width/moveView.bounds.size.width, 0, 0, self.view.bounds.size.width/moveView.bounds.size.width, 0 , 0);
//设置图片中心点为屏幕的中心点
CGPoint p = self.view.center;
[moveView setTransform:newTransform];
[moveView setCenter:p];
} completion:nil];
}
}
}
说明一下,这个方法比较重要的是还原初始化变换和全屏变换,这里就有个矩阵的知识,首先说 还原初始化变换:
实际上就是原坐标系乘以这个矩阵,只要记得设定TransformIdentity还原就好,稍微复杂的是全屏变换,黏贴一段官方文档:
可见一个完整的仿射变换应该有6个系数,两两分别控制旋转,位移和缩放的变换,对应的参数如文档所说:
CGAffineTransform newTransform=CGAffineTransformMake(self.view.bounds.size.width/moveView.bounds.size.width, 0, 0, self.view.bounds.size.width/moveView.bounds.size.width, 0 , 0);