一直想写一个带倒影效果的3D轮播图,最近有时间,就研究了一下,下面是实现后的效果,可无限循环轮播:
倒影
首先是倒影效果,要想实现倒影,需要了解CALayer复制层。这里要注意的是,复制层CAReplicatorLayer
是针对父视图来说的,比如要给一个UIImageView
添加倒影效果,则要针对UIImageView
的父视图的根层进行设置复制层。
所以这里写了一个继承自UIView
类的HLImageView
,在其上面添加一个显示图片的UIImageView
,并在HLImageView
初始化方法中设置其复制层,效果如下:
具体的代码如下:
- (instancetype)initWithFrame:(CGRect)frame {
self = [super initWithFrame:frame];
if (self) {
CGFloat w = frame.size.width;
CGFloat h = frame.size.height;
_posiP = self.layer.position;
_imgView = [[UIImageView alloc] initWithFrame:CGRectMake(0, 0, w, h / 2.0)];
[self addSubview:_imgView];
CAReplicatorLayer *repL = (CAReplicatorLayer *)self.layer;
repL.instanceCount = 2;
//复制出来的子层,它都是绕着复制层锚点进行旋转.
repL.instanceTransform = CATransform3DMakeRotation(M_PI, 1, 0, 0);
repL.instanceRedOffset -= 0.5;
repL.instanceGreenOffset -= 0.5;
repL.instanceBlueOffset -= 0.5;
repL.instanceAlphaOffset -= 0.5;
}
return self;
}
这里注意的是复制出来的子层都是绕复制层的锚点进行旋转的,CALayer默认的锚点是(0.5,0.5),所以这里默认绕中心点延伸的轴旋转,上面代码是绕x轴旋转,即会产生倒影效果。
3D效果旋转
实现倒影效果后,接下来就是实现图片旋转时远小近大的3D效果。
在这里要用到CATransform3D
这个结构体。API中我们可以看到CATransform3D
是一个4X4的矩阵,如下:
struct CATransform3D
{
CGFloat m11, m12, m13, m14;
CGFloat m21, m22, m23, m24;
CGFloat m31, m32, m33, m34;
CGFloat m41, m42, m43, m44;
};
这里的矩阵乘法公式如下图:
公式中我们可以看到,m34这个值影响了z轴的translation,其默认值是0,这里设置m34值,就可以实现远小近大的效果:
这里需要给一个旋转角度,否则无法看到3D效果。
- (void)viewDidLoad {
[super viewDidLoad];
CATransform3D transform0 = CATransform3DIdentity;
transform0.m34 = -1 / 550.0;
_img.layer.transform = CATransform3DRotate(transform0, M_PI_4, 0, 1, 0);
}
这里要注意的是,旋转轴的原点是图层的锚点anchorPoint
,默认是中心点(0.5,0.5),要想绕边轴旋转则要相应设置设置锚点为(0.0,0.5)或(1.0,0.5)。
实现倒影效果的3D旋转
了解上面的两种效果后,我们就可以实现带倒影的3D轮播图效果了。
具体思路就是用UIScrollView
写一个轮播图,加载图片的是一个继承自UIView
的自定义类,在其上加载图片并实现倒影效果,对其进行轮播旋转操作。
效果问首效果图所示。
这里主要是设置UIScrollView
的代理,并在在代理方式- (void)scrollViewDidScroll:(UIScrollView *)scrollView
中实现3D旋转效果。
在代理方法中分别获得当前展示的图和即将展示的下一张图,在滑动手势下不断改变两张图的旋转角度即可。原理不难,但实现起来还是有点复杂的。
- (void)scrollViewDidScroll:(UIScrollView *)scrollView {
CGFloat c = ceilf(scrollView.contentOffset.x / _w);
if (c > 0 && c < _imgViews.count) {
CGFloat value = (scrollView.contentOffset.x - (c - 1) * _w) / _w; // 滑动时的值范围0~1
// 当前展示图的实时旋转角度
CGFloat angle0 = M_PI_2 * value;
// 当前展示的图
HLImageView *img0 = (HLImageView *)_imgViews[(int)c - 1];
img0.layer.anchorPoint = CGPointMake(0.0, 0.5);
img0.layer.position = CGPointMake(img0.posiP.x - img0.frame.size.width / 2.0 * (1 - value),img0.posiP.y);
// 设置3D旋转效果
CATransform3D transform0 = CATransform3DIdentity;
transform0.m34 = -1 / 550.0;
img0.layer.transform = CATransform3DRotate(transform0, angle0, 0, 1, 0);
// 当前展示图后一张图的实时旋转角度
CGFloat angle1 = M_PI_2 * (1 - value);
// 当前展示图的后一张图
HLImageView *img1 = (HLImageView *)_imgViews[(int)c];
img1.layer.anchorPoint = CGPointMake(1.0, 0.5);
img1.layer.position = CGPointMake(img1.posiP.x + img1.frame.size.width / 2.0 * value, img1.posiP.y);
//设置3D旋转效果
CATransform3D transform1 = CATransform3DIdentity;
transform1.m34 = -1 / 550.0;
img1.layer.transform = CATransform3DRotate(transform1, -angle1, 0, 1, 0);
NSLog(@"===========:%f--------:%f=========:%f",scrollView.contentOffset.x,c,value);
}
if (scrollView.contentOffset.x == _w * (_imgViews.count - 1)) {
HLImageView *firstImg = (HLImageView *)_imgViews[1];
firstImg.layer.transform = CATransform3DIdentity;
scrollView.contentOffset = CGPointMake(_w, 0);
} else if (scrollView.contentOffset.x == 0) {
HLImageView *lastImg = (HLImageView *)_imgViews[imgCount];
lastImg.layer.transform = CATransform3DIdentity;
scrollView.contentOffset = CGPointMake(_w * imgCount, 0);
}
}
无限轮播
可能有人注意到了上面代理方法里最后部分的
if (scrollView.contentOffset.x == _w * (_imgViews.count - 1)) {
HLImageView *firstImg = (HLImageView *)_imgViews[1];
firstImg.layer.transform = CATransform3DIdentity;
scrollView.contentOffset = CGPointMake(_w, 0);
} else if (scrollView.contentOffset.x == 0) {
HLImageView *lastImg = (HLImageView *)_imgViews[imgCount];
lastImg.layer.transform = CATransform3DIdentity;
scrollView.contentOffset = CGPointMake(_w * imgCount, 0);
}
这里实现的就是无限轮播。我的demo中有5张不同的图,按顺序编号从1到5,设置轮播图如下:
图号:5、1、2、3、4、5、1
顺序:1、2、3、4、5、6、7
总共在UIScrollView
中添加了7张图,其中首张为最后一张图5
,最后一张为第一张图1
。
这样在轮播到第7张图1
时立刻跳转到第2张图1
,轮播到第1张图5
时立即跳到第6张图5
。即产生了无限轮播的效果。
更新:自动轮播及点击图片回调。
源码:GitHub地址