在轮播图中添加贝塞尔曲线裁切效果

背景:

设计想对现在轮播图的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;
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值