iOS animate/morph shape from circle to square

♀尐吖头ヾ 提交于 2019-12-28 05:41:27

问题


I try to change a circle into a square and vice versa and I am almost there. However it doesn't animates as expected. I would like all corners of a square to be animated/morphed at the same time but what I get is the following:

I use CAShapeLayer and CABasicAnimation in order to animate shape property.

Here is how I create the circle path:

- (UIBezierPath *)circlePathWithCenter:(CGPoint)center radius:(CGFloat)radius
{    
    UIBezierPath *circlePath = [UIBezierPath bezierPath];
    [circlePath addArcWithCenter:center radius:radius startAngle:0 endAngle:M_PI/2 clockwise:YES];
    [circlePath addArcWithCenter:center radius:radius startAngle:M_PI/2 endAngle:M_PI clockwise:YES];
    [circlePath addArcWithCenter:center radius:radius startAngle:M_PI endAngle:3*M_PI/2 clockwise:YES];
    [circlePath addArcWithCenter:center radius:radius startAngle:3*M_PI/2 endAngle:M_PI clockwise:YES];
    [circlePath closePath];
    return circlePath;
}

Here is the square path:

- (UIBezierPath *)squarePathWithCenter:(CGPoint)center size:(CGFloat)size
{
    CGFloat startX = center.x-size/2;
    CGFloat startY = center.y-size/2;

    UIBezierPath *squarePath = [UIBezierPath bezierPath];
    [squarePath moveToPoint:CGPointMake(startX, startY)];
    [squarePath addLineToPoint:CGPointMake(startX+size, startY)];
    [squarePath addLineToPoint:CGPointMake(startX+size, startY+size)];
    [squarePath addLineToPoint:CGPointMake(startX, startY+size)];
    [squarePath closePath];
    return squarePath;
}

I apply the circle path to one of my View's layers, set the fill etc. It draws perfectly. Then in my gestureRecognizer selector I create and run the following animation:

CABasicAnimation *animation = [CABasicAnimation animationWithKeyPath:@"path"];
    animation.duration = 1;
    animation.timingFunction = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseInEaseOut];

 animation.fromValue = (__bridge id)(self.stateLayer.path);
        animation.toValue = (__bridge id)(self.stopPath.CGPath);
self.stateLayer.path = self.stopPath.CGPath;

[self.stateLayer addAnimation:animation forKey:@"animatePath"];

As you can notice in the circlePathWithCenter:radius: and squarePathWithCenter:size: I follow the suggestion from here (to have the same number of segments and control points): Smooth shape shift animation

The animation looks better then in the post from above but it is still not the one I want to achieve :(

I know that I can do it with simple CALayer and then set appropriate level of cornerRadius to make a circle out of square/rectangle and after that animate cornerRadius property to change it from circle to square but I'm still extremaly curious if it is even possible to do it with CAShapeLayer and path animation.

Thanks in advance for help!


回答1:


After playing with it for a little while in the playground I noticed that each arc adds two segments to the bezier path, not just one. Moreover, calling closePath adds two more. So to keep the number of segments consistent, I ended up with adding fake segments to my square path. The code is in Swift, but I think it doesn't matter.

func circlePathWithCenter(center: CGPoint, radius: CGFloat) -> UIBezierPath {
    let circlePath = UIBezierPath()
    circlePath.addArcWithCenter(center, radius: radius, startAngle: -CGFloat(M_PI), endAngle: -CGFloat(M_PI/2), clockwise: true)
    circlePath.addArcWithCenter(center, radius: radius, startAngle: -CGFloat(M_PI/2), endAngle: 0, clockwise: true)
    circlePath.addArcWithCenter(center, radius: radius, startAngle: 0, endAngle: CGFloat(M_PI/2), clockwise: true)
    circlePath.addArcWithCenter(center, radius: radius, startAngle: CGFloat(M_PI/2), endAngle: CGFloat(M_PI), clockwise: true)
    circlePath.closePath()
    return circlePath
}

func squarePathWithCenter(center: CGPoint, side: CGFloat) -> UIBezierPath {
    let squarePath = UIBezierPath()
    let startX = center.x - side / 2
    let startY = center.y - side / 2
    squarePath.moveToPoint(CGPoint(x: startX, y: startY))
    squarePath.addLineToPoint(squarePath.currentPoint)
    squarePath.addLineToPoint(CGPoint(x: startX + side, y: startY))
    squarePath.addLineToPoint(squarePath.currentPoint)
    squarePath.addLineToPoint(CGPoint(x: startX + side, y: startY + side))
    squarePath.addLineToPoint(squarePath.currentPoint)
    squarePath.addLineToPoint(CGPoint(x: startX, y: startY + side))
    squarePath.addLineToPoint(squarePath.currentPoint)
    squarePath.closePath()
    return squarePath
}




回答2:


I know this is an old question but this might help someone.

I think its better to animate corner radius to get this effect.

    let v1 = UIView(frame: CGRect(x: 100, y: 100, width: 50, height: 50))
    v1.backgroundColor = UIColor.green
    view.addSubview(v1)

animation:

    let animation = CABasicAnimation(keyPath: "cornerRadius")
    animation.timingFunction = CAMediaTimingFunction(name: kCAMediaTimingFunctionLinear)
    animation.fillMode = kCAFillModeForwards
    animation.isRemovedOnCompletion = false
    animation.fromValue = v1.layer.cornerRadius
    animation.toValue = v1.bounds.width/2
    animation.duration = 3
    v1.layer.add(animation, forKey: "cornerRadius")



回答3:


I can highly recommend the library CanvasPod, it also has a predefined "morph" animation and is easy to integrate. You can also check out examples on the website canvaspod.io.




回答4:


Based on @Danchoys answer, here it is for Objective-C.

info is a button of 60px and infoVC is the next ViewController being pushed:

    UIBezierPath * maskPath = [UIBezierPath new];
    [maskPath addArcWithCenter:info.center radius:15.0f startAngle:DEGREES_TO_RADIANS(180) endAngle:DEGREES_TO_RADIANS(270) clockwise:true];
    [maskPath addArcWithCenter:info.center radius:15.0f startAngle:DEGREES_TO_RADIANS(270) endAngle:DEGREES_TO_RADIANS(0) clockwise:true];
    [maskPath addArcWithCenter:info.center radius:15.0f startAngle:DEGREES_TO_RADIANS(0) endAngle:DEGREES_TO_RADIANS(90) clockwise:true];
    [maskPath addArcWithCenter:info.center radius:15.0f startAngle:DEGREES_TO_RADIANS(90) endAngle:DEGREES_TO_RADIANS(180) clockwise:true];
    [maskPath closePath];

    UIBezierPath * endPath = [UIBezierPath new];
    [endPath moveToPoint:CGPointMake(0,0)];
    [endPath addLineToPoint:endPath.currentPoint];
    [endPath addLineToPoint:CGPointMake(w, 0)];
    [endPath addLineToPoint:endPath.currentPoint];
    [endPath addLineToPoint:CGPointMake(w, h)];
    [endPath addLineToPoint:endPath.currentPoint];
    [endPath addLineToPoint:CGPointMake(0, h)];
    [endPath addLineToPoint:endPath.currentPoint];
    [endPath closePath];

    CAShapeLayer *maskLayer = [CAShapeLayer layer];
    maskLayer.frame = info.bounds;
    maskLayer.path = maskPath.CGPath;
    _infoVC.view.layer.mask = maskLayer;

    CABasicAnimation * animation = [CABasicAnimation animationWithKeyPath:@"path"];
    animation.fromValue = (id)maskPath.CGPath;
    animation.toValue = (id)endPath.CGPath;
    animation.duration = 0.3f;
    [maskLayer addAnimation:animation forKey:nil];
    [maskLayer setPath:endPath.CGPath];


来源:https://stackoverflow.com/questions/24788283/ios-animate-morph-shape-from-circle-to-square

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!