背景:
设计想对现在轮播图的UI效果做调整
现有样式:普通轮播效果
目标样式:贝塞尔曲线裁剪
实现逻辑:
1. 监听拖动,获取当前轮播图的偏移位置
轮播图视图直接继承UIView,通过监听拖动手势(UIPanGestureRecognizer)获取
UIPanGestureRecognizer *panGesture = [[UIPanGestureRecognizer alloc] initWithTarget:self action:@selector(didPan:)];
- (void)didPan:(UIPanGestureRecognizer *)panGesture
{
if (_scrollEnabled && _numberOfItems) {
switch (panGesture.state) {
// 其它情况根据自己的业务逻辑需要自行补充
case UIGestureRecognizerStateChanged: {
/*
1. 计算出轮播图的本次滑动偏移量
2. 使用本次的偏移量 更新到成员变量中。(成员变量记录了此刻轮播图的偏移offset
*/
}
}
}
}
2. 使用CATransform3D调整子视图位置与层级
监听轮播图内容偏移量变化,回调中将每个视图重新布局
布局方式可以选择使用CATransform3D,这样可以有更多可扩展性 做出更多高级动画
CATransform3D使用基础教程:https://www.jianshu.com/p/f14c05425739
// x y z 三个值设置会 可以调整子视图的 x轴 y轴 z轴 属性
// 如果仅需要贝塞尔曲线效果,那么子视图不存在任何横向或者纵向位移的需求,所以 x y 可以写死为0.0f
transform = CATransform3DTranslate(transform, x, y, z);
// z轴 的值是重点
// 可以通过当前卡片的currentIndex 和 轮播图的offSet 计算。差值的绝对值越小 越应该在最上层
3. 滚动监听,使用贝塞尔曲线裁剪
注意⚠️:前翻和后翻的裁剪区域计算是不同的。
因为我们在第二步中使用的是将currentIndex 和 轮播图的offSet 差值绝对值最小的置于顶层。那假设我们现在从图1后翻到图2 后翻滑动超过图1的中点时 最顶层视图被替换为图2,这时需要将裁剪的逻辑从后翻裁剪图1改为前翻裁剪图2。这样就可以确保裁剪动画看起来始终是衔接的并且是可连续执行的。
- (void)carouselDidScroll:(iCarousel *)carousel {
[super carouselDidScroll:carousel];
CGFloat scrollRatio = (carousel.scrollOffset - carousel.currentItemIndex);
// 从第一张向后翻的位置 需要添加这个兜底逻辑
if (scrollRatio > 1) {
scrollRatio -= carousel.numberOfItems;
}
if (scrollRatio != 0) {
UIView *viewNeedCut = [self.carouselView itemViewAtIndex:self.viewIndexNeedCut];
CGFloat imageWidth = viewNeedCut.width;
CGFloat imageHeight = viewNeedCut.height;
CGFloat ratio = imageHeight / gCarouselBezierCurveEndPointY;
CGFloat leftOffSet = -gCarouselBezierCurveRightmostX * ratio;
CGFloat targetX = leftOffSet + (scrollRatio < 0 ? fabs(scrollRatio) : 1 - scrollRatio) * (carousel.itemWidth + fabs(leftOffSet));
UIBezierPath * bezierPath;
if ([self isMovingForward:carousel scrollRatio:scrollRatio]) {
// 下一张
bezierPath = [self createBezierPathWithStartOffset:leftOffSet
bezierCurveOffset:targetX
ratio:ratio];
} else {
// 上一张
bezierPath = [self createBezierPathWithStartOffset:imageWidth
bezierCurveOffset:targetX
ratio:ratio];
}
self.shapeLayer.path = bezierPath.CGPath;
viewNeedCut.layer.mask = self.shapeLayer;
}
}
- (UIBezierPath *)createBezierPathWithStartOffset:(CGFloat)startOffset
bezierCurveOffset:(CGFloat)bezierCurveOffset
ratio:(CGFloat)ratio {
UIBezierPath * bezierPath = [UIBezierPath bezierPath];
[bezierPath moveToPoint:CGPointMake(startOffset + gCarouselBezierCurveStartPointX * ratio, gCarouselBezierCurveStartPointY * ratio)];
[bezierPath addLineToPoint:CGPointMake(bezierCurveOffset + gCarouselBezierCurveStartPointX * ratio, gCarouselBezierCurveStartPointY * ratio)];
[bezierPath addCurveToPoint:CGPointMake(bezierCurveOffset + gCarouselBezierCurveEndPointX * ratio, gCarouselBezierCurveEndPointY * ratio)
controlPoint1:CGPointMake(bezierCurveOffset + gCarouselBezierCurveFirstControlPointX * ratio, gCarouselBezierCurveFirstControlPointY * ratio)
controlPoint2:CGPointMake(bezierCurveOffset + gCarouselBezierCurveSecondControlPointX * ratio, gCarouselBezierCurveSecondControlPointY * ratio)];
[bezierPath addLineToPoint:CGPointMake(startOffset + gCarouselBezierCurveEndPointX * ratio, gCarouselBezierCurveEndPointY * ratio)];
[bezierPath closePath];
return bezierPath;
}