miracle_of_thinking原创,欢迎转载!原文地址:
http://blog.youkuaiyun.com/miracle_of_thinking/article/details/7208415
转载时请在明显处注明,谢谢!
三、实现
在第一部分中,我们定义了UICoverFlowView类的唯一一个类方法:
+ (id)coverFlowViewWithFrame:(CGRect)frame
andImages:(NSArray*)arrImages
sidePieces:(int)SIDEPIECES
sideScale:(CGFloat)SIDESCALE
middleScale:(CGFloat)MIDDLESCALE;
它看上去有点长…是的,我在这里传递image数组、两侧显示的张数,以及尺寸等参数。将其定义成类方法可以方便使用者,使用者只需利用该方法来实例化UICoverFlowView对象而不必关心内存管理,因为在其中我们已经将其设为autorelease。下面是该方法的具体实现:
+ (id)coverFlowViewWithFrame:(CGRect)frame
andImages:(NSArray*)arrImages
sidePieces:(int)SIDEPIECES
sideScale:(CGFloat)SIDESCALE
middleScale:(CGFloat)MIDDLESCALE
{
UICoverFlowView *cfv = [[[UICoverFlowView alloc] initWithFrame:frame] autorelease];
// 左右两侧各显示SIDEPIECES张image,中间显示1张image
cfv._SIDEPIECES = SIDEPIECES;
// 设定左右两侧image的缩放程度,以及中间image的缩放程度
cfv._SIDESCALE = SIDESCALE;
cfv._MIDDLESCALE = MIDDLESCALE;
// 默认初始时显示的是第一张image
cfv._curImageIndex = 0;
// 初始化数组
cfv._arrImages = [NSMutableArray arrayWithArray:arrImages];
cfv._arrLayers = [[NSMutableArray alloc] initWithCapacity:cfv._SIDEPIECES*2+1];
cfv._arrTempleteLayers = [[NSMutableArray alloc] initWithCapacity:(cfv._SIDEPIECES+1)*2+1];
// 注册手势
UIPanGestureRecognizer *gestureRecognizer = [[UIPanGestureRecognizer alloc] initWithTarget:cfv action:@selector(handlePanFrom:)];
[cfv addGestureRecognizer:gestureRecognizer];
// 设置模板
[cfv setTempleteLayers];
// 布局images
[cfv arrangeImages];
[cfv addPageControl];
return cfv;
}
我们首先调用了基类方法:initWithFrame:,因此,我们可以在该方法中对其设置透视:
// 设置sublayer为透视
CATransform3D perspectiveTransform3D = CATransform3DIdentity;
perspectiveTransform3D.m34 = -1.0 / 500.0; // 效果比1000要好
self.layer.sublayerTransform = perspectiveTransform3D;
接着,我们对对象的属性值进行了赋值和初始化(注意三个array,我们要定义成NSMutableArray)。然后我们在UICoverFlowView对象中注册了pan手势,通过该手势,用户可以左右滑动来更换image,其中的selector方法我们将在后面给出。
接下来的工作尤为关键,那就是设置模板,模板中记录着每个位置上CALayer的position、zPosition、transform等。每当用户滑动图片后,每个layer就可以根据模板来进行调整。其实现如下:
// 设置模板
- (void)setTempleteLayers
{
// 1 - UICoverFlowView的中心坐标
CGFloat centerX = self.bounds.size.width/2;
CGFloat centerY = self.bounds.size.height/2;
// 2 - 设定左右两侧image旋转的角度
CGFloat leftRadian = M_PI / 3;
CGFloat rightRadian = -M_PI / 3;
// 3 - 设定左右两侧image之间的间隔
CGFloat space = 20;
// 4 - 设定正中间的image与左右相邻image的间隔
CGFloat theGap = 200.0f;
// 5 - 布置layer模板
// 首先 Left 部分
for (int i = 0; i < _SIDEPIECES + 1; i++) {
CALayer *layer = [CALayer layer];
layer.position = CGPointMake(centerX-theGap-space*(_SIDEPIECES-i), centerY);
layer.zPosition = (i - _SIDEPIECES - 1) * 10;
layer.transform = CATransform3DMakeRotation(leftRadian, 0, 1, 0);
[self._arrTempleteLayers addObject:layer];
}
// 然后 Middle 部分
{
CALayer *layer = [CALayer layer];
layer.position = CGPointMake(centerX, centerY);
[self._arrTempleteLayers addObject:layer];
}
// 最后 Right 部分
for (int i = 0; i <= _SIDEPIECES + 1; i++) {
CALayer *layer = [CALayer layer];
layer.position = CGPointMake(centerX+theGap+space*i, centerY);
layer.zPosition = -i * 10;
layer.transform = CATransform3DMakeRotation(rightRadian, 0, 1, 0);
[self._arrTempleteLayers addObject:layer];
}
}
除了代码中的注释,这里还有个值得注意的地方,那就是布置layer模板的left以及right部分时,都要比实际两侧显示的image张数多1。这样做的原因是,当用户滑动image后最左侧或最右侧的image就可能超出显示范围,此时moveOneStep:方法(后面即将介绍)可以得到良好的执行。
在设置完模板后,我们就可以将image按照模板进行布局了,这在arrangeImages方法中完成:
// 布局images
- (void)arrangeImages
{
// 1 - 设定可浏览范围内,image的起始下标及结束下标
int startIndex = (_curImageIndex-_SIDEPIECES <= 0) ? 0 : _curImageIndex-_SIDEPIECES;
int endIndex = (_curImageIndex+_SIDEPIECES >= _arrImages.count-1) ? _arrImages.count-1 : _curImageIndex+_SIDEPIECES;
// 2 - 布局image
for (int i=startIndex; i<=endIndex; i++) {
UIImage *aImage = (UIImage*)[_arrImages objectAtIndex:i];
CALayer *imgLayer = [CALayer layer];
imgLayer.contents = (id)aImage.CGImage;
CGFloat scale = (i==_curImageIndex) ? _MIDDLESCALE : _SIDESCALE;
imgLayer.bounds = CGRectMake(0, 0, aImage.size.width*scale, aImage.size.height*scale);
[self._arrLayers addObject:imgLayer];
}
// 3 - 根据模板设置position、zPosition、transform等
// 计算对应在模板layer的起始下标
int targetStartIndex = (_curImageIndex < _SIDEPIECES) ? (_SIDEPIECES+1-_curImageIndex) : 1;
for (int i=0; i<_arrLayers.count; i++) {
CALayer *tempLayer = (CALayer*)[_arrTempleteLayers objectAtIndex:(i+targetStartIndex)];
CALayer *layer = (CALayer*)[_arrLayers objectAtIndex:i];
layer.position = tempLayer.position;
layer.zPosition = tempLayer.zPosition;
layer.transform = tempLayer.transform;
// 显示layer及其“倒影”
[self showImageAndReflection:layer];
}
}
上面代码中的注释已经很清楚地介绍了各个步骤的工作,应该比较容易理解。要注意的是数组下标的计算,不能有任何差错哦。完成position、zPosition、transform等设置后,我们需要显示layer及其倒影。showImageAndReflection:具体实现如下:
// 添加layer及其“倒影”
- (void)showImageAndReflection:(CALayer*)layer
{
// 制作reflection
CALayer *reflectLayer = [CALayer layer];
reflectLayer.contents = layer.contents;
reflectLayer.bounds = layer.bounds;
reflectLayer.position = CGPointMake(layer.bounds.size.width/2, layer.bounds.size.height*1.5);
reflectLayer.transform = CATransform3DMakeRotation(M_PI, 1, 0, 0);
// 给该reflection加个半透明的layer
CALayer *blackLayer = [CALayer layer];
blackLayer.backgroundColor = [UIColor blackColor].CGColor;
blackLayer.bounds = reflectLayer.bounds;
blackLayer.position = CGPointMake(blackLayer.bounds.size.width/2, blackLayer.bounds.size.height/2);
blackLayer.opacity = 0.6;
[reflectLayer addSublayer:blackLayer];
// 给该reflection加个mask
CAGradientLayer *mask = [CAGradientLayer layer];
mask.bounds = reflectLayer.bounds;
mask.position = CGPointMake(mask.bounds.size.width/2, mask.bounds.size.height/2);
mask.colors = [NSArray arrayWithObjects:
(id)[UIColor clearColor].CGColor,
(id)[UIColor whiteColor].CGColor, nil];
mask.startPoint = CGPointMake(0.5, 0.35);
mask.endPoint = CGPointMake(0.5, 1.0);
reflectLayer.mask = mask;
// 作为layer的sublayer
[layer addSublayer:reflectLayer];
// 加入UICoverFlowView的sublayers
[self.layer addSublayer:layer];
}
至此,我们的“cover flow”已经可以显示了,如果你已经迫不及待想看一下效果的话,你可以先将那些未介绍的方法注释掉(并取消pan手势的注册),然后实例化UICoverFlowView后作为subView加入你工程中已存在的view中。不过,我觉得你还是因该再忍耐一下,毕竟看着一堆静态且不能移动的图片毫无乐趣可言,这离我们的目标相去甚远!