保家卫国,杀鬼子简单动画

230 阅读3分钟

开篇废话:简单的利用贝塞尔曲线实现一个简单的切图动画。

屏幕录制2021-10-05 下午3.54.58.gif

步骤一、搭建外层view视图

这里就是 创建一个 KDSShredPhotoImageView 视图展示。


- (void)customClip

{

    self.gzImageView = [[KDSShredPhotoImageView alloc] init];

    self.gzImageView.contentMode = UIViewContentModeScaleAspectFit;

    self.gzImageView.image = [UIImage imageNamed:@"gz"];

    [self addSubview:self.gzImageView];

    [self.gzImageView mas_makeConstraints:^(MASConstraintMaker *make) {

        make.center.equalTo(self);

    }];

    

    UIPanGestureRecognizer * pan = [[UIPanGestureRecognizer alloc] initWithTarget:self action: @selector(panAction:)];

    self.userInteractionEnabled = YES;

    [self addGestureRecognizer:pan];

}

- (void)panAction:(UIPanGestureRecognizer *)pan

{

    switch (pan.state) {

        case UIGestureRecognizerStateBegan:
        {
            [self.gzImageView judgePanInSelfRect:pan];
        }
            break;
        case UIGestureRecognizerStateChanged:
        {
            [self.gzImageView judgePanInSelfRect:pan];
        }
            break;
        case UIGestureRecognizerStateEnded:

        {
            [self.gzImageView judgePanInSelfRect:pan]; 
        }

            break;
        default:
            break;

    }

}

步骤二、封装 KDSShredPhotoImageView

1、声明属性


@interface** KDSShredPhotoImageView()
//进入点

@property (nonatomic,assign) CGPoint enterPoint;

//离开点

@property (nonatomic,assign) CGPoint levelPoint;

//内部移动点

@property (nonatomic,assign) CGPoint movePoint;

//刀痕贝塞尔曲线

@property (nonatomic,strong) UIBezierPath * bezierPath;

//模拟刀痕线

@property (nonatomic,strong) CAShapeLayer * shapelayer;

//隐藏消除的shapelayer

@property (nonatomic,strong) CAShapeLayer * deleteShapelayer;

@end

@implementation KDSShredPhotoImageView

- (instancetype)init

{

    self = [super init];

    if (self) {

        self.clipsToBounds = YES;

    }

    return self;

}

2、跟踪贝塞尔曲线

这里记录 UIPanGestureRecognizer 手势的触电变更,并记录于贝塞尔曲线


- (void)judgePanInSelfRect:(UIPanGestureRecognizer *)pan

{

    //进行剪切

    if (!self.bezierPath) {
        self.bezierPath = [[UIBezierPath alloc] init];
    }

    if (!self.shapelayer) {
        self.shapelayer = [[CAShapeLayer alloc] init];
        self.shapelayer.fillColor = nil;
        self.shapelayer.strokeColor = [UIColor blackColor].CGColor;
        self.shapelayer.lineWidth = 1;
        [self.layer addSublayer:elf.shapelayer];

    }

    

    if (!self.deleteShapelayer) {

        self.deleteShapelayer = [[CAShapeLayer alloc] init];
        self.deleteShapelayer.fillColor = [UIColor whiteColor].CGColor;
        self.deleteShapelayer.strokeColor = nil;

    }

    CGPoint point = [pan locationInView:**self**];

    switch (pan.state) {
        case UIGestureRecognizerStateBegan:
        {
            //记录开始点
            [self recordEnterPoint:point];

        }
            break;
        case UIGestureRecognizerStateChanged:
        {
            //记录当前点
            [self recordMovePoint:point];

        }
            break;
        case UIGestureRecognizerStateEnded:
        {
            //记录结束点
            [self recordLevelPoint:point];
            //开始计算切割区域
            [self judgeXYDirectAndClip];

        }
            break;
        default:
            break;
    }
}

//记录进入点
- (void)recordEnterPoint:(CGPoint)point
{
    self.enterPoint = point;
    [self.bezierPath moveToPoint:point];
    self.shapelayer.path = self.bezierPath.CGPath;
}

//记录离开点
- (void)recordLevelPoint:(CGPoint)point
{
    self.levelPoint = point;
    [self.bezierPath addLineToPoint:point];
    self.shapelayer.path = **self**.bezierPath.CGPath;
    [self judgeXYDirectAndClip];
    //重置进入点
    self.enterPoint = CGPointZero;
    //重置移动点
    self.movePoint = CGPointZero;
    //重置贝塞尔
    self.bezierPath = nil;
}

//记录移动点
- (void)recordMovePoint:(CGPoint)point

{

    self.movePoint = point;

    [self.bezierPath addLineToPoint:point];

    self.shapelayer.path = self.bezierPath.CGPath;

}

3、矩形任意切割得到较小的贝塞尔封闭区域

到底任意一刀如何切割这里利用的是线性方程,enterPoint开始点及levelPoint结束点来确定一条线性方程 y = kx + b

image.png

- (void)definedKTypeFunctionWithStartPoint:(CGPoint)startPoint endPoint:(CGPoint)endPoint

{

    CGPoint descartesStartPoint = CGPointMake(startPoint.x, self.height - startPoint.y);

    CGPoint descartesEndPoint = CGPointMake(endPoint.x, self.height - endPoint.y);

    CGFloat k = (descartesStartPoint.y - descartesEndPoint.y) / (descartesStartPoint.x - descartesEndPoint.x);

    //保存满足的矩形角点
    NSMutableArray * saveContainCorners = @[].mutableCopy;
    //需要判断的矩形4个角点。
    NSArray * allNeedCheckPoint = @[@(CGPointMake(self.width, 0)),@(CGPointMake(self.width, self.height)),@(CGPointMake(0, self.height)),@(CGPointMake(0, 0))];

    //这里记录 x,y的目的很简单,就是计算当一个矩形3个角都满足条件的时候,反取另一个不满足的角,组合成贝塞尔曲线。
    CGFloat x = 0;
    CGFloat y = 0;

    for (int i = 0; i < allNeedCheckPoint.count; i++) {
        //这里,配合笛卡尔坐标系将Y值反转一下。
        CGPoint checkPoint = [allNeedCheckPoint[i] CGPointValue];
        if ([self isCornerIsContainWithK:k startPoint:descartesStartPoint checkPoint:CGPointMake(checkPoint.x, self.height - checkPoint.y)]) {
            [saveContainCorners addObject:@(checkPoint)];
        }
        x += checkPoint.x;
        y += checkPoint.y;
    }
    
    if (saveContainCorners.count != 3) {
        //满足条件的不是3个角
        if (k > 0) {
            for (int i = 0; i < saveContainCorners.count; i++) {
                CGPoint point = [saveContainCorners[i] CGPointValue];
                [self.bezierPath addLineToPoint:point];
            }
        } else {
            for (NSInteger i = saveContainCorners.count - 1; i >= 0; i--) {
                CGPoint point = [saveContainCorners[i] CGPointValue];
                [self.bezierPath addLineToPoint:point];
            }
        }
    } else {

        //满足条件的是3个角,利用总数求剩余的不满足条件的角

        for (int i = 0; i < saveContainCorners.count; i++) {

            CGPoint point = [saveContainCorners[i] CGPointValue];
            x -= point.x;
            y -= point.y;
        }
        [self.bezierPath addLineToPoint:CGPointMake(x, y)];
    }

}

//判断矩形的角点是否满足在线性方程上,在上面就记录下来。
- (BOOL)isCornerIsContainWithK:(CGFloat)k startPoint:(CGPoint)startPoint checkPoint:(CGPoint)checkPoint
{
    CGFloat y = k * (checkPoint.x - startPoint.x) + startPoint.y;
    return checkPoint.y > y;
}

4、切鬼子动画

这里逻辑也很简单,将根据贝塞尔曲线区域切下的部分生成一张图片赋值给新的 UIImageView ,给它一个旋转及位移动画。在原来的 KDSShredPhotoImageView 上添加一个空白的贝塞尔曲线区域,目的是遮盖。

//进行剪切
- (void)judgeXYDirectAndClip
{
    //判断起始点是否在 imageView 外面,如果在里面就不进行切割,因为 拖拽手势的承载视图是当前 imageView 的父视图。
    if ([self.layer containsPoint:self.enterPoint] || [self.layer containsPoint:self.levelPoint]) {
        return;
    }
    //计算完整贝塞尔曲线
    [self definedKTypeFunctionWithStartPoint:self.enterPoint endPoint:self.levelPoint];

    //绘制贝塞尔曲线封闭区域图片
    UIGraphicsBeginImageContextWithOptions(self.frame.size, false, 1);

    UIBezierPath * bzPath = self.bezierPath;

    [bzPath addClip];

    UIImage * image = self.image;

    [image drawInRect:self.bounds];

    UIImage * clipImgae = UIGraphicsGetImageFromCurrentImageContext();
    UIGraphicsEndImageContext();

    //添加被切割部分图片展示及动画效果
    UIImageView * clipImageView = [[UIImageView alloc] initWithFrame:self.frame];

    clipImageView.backgroundColor = [UIColor clearColor];

    clipImageView.image = clipImgae;

    [self.superview addSubview:clipImageView];

    [UIView animateWithDuration:4.5 animations:^{

        clipImageView.center = CGPointMake(clipImageView.center.x, clipImageView.center.y + 500);

        CGAffineTransform t = CGAffineTransformIdentity;

        t = CGAffineTransformRotate(t, M_PI_2 / 2.0);

        clipImageView.transform = t;

    }];

    //原图添加遮盖
    UIBezierPath *path = self.bezierPath;

    self.deleteShapelayer.path = path.CGPath;

    [self.layer addSublayer:self.deleteShapelayer];

}

代码拙劣,大神勿笑。