形状图层的动画路径

Animate path of shape layer

我被我认为是一个简单的问题难住了。

我想绘制由线连接的视图,为视图的位置设置动画,并让连接线也设置动画。我创建视图,并像这样在它们之间创建一条线:

- (UIBezierPath *)pathFrom:(CGPoint)pointA to:(CGPoint)pointB {
    CGFloat halfY = pointA.y + 0.5*(pointB.y - pointA.y);
    UIBezierPath *linePath=[UIBezierPath bezierPath];
    [linePath moveToPoint: pointA];
    [linePath addLineToPoint:CGPointMake(pointA.x, halfY)];
    [linePath addLineToPoint:CGPointMake(pointB.x, halfY)];
    [linePath addLineToPoint:pointB];
    return linePath;
}

-(void)makeTheLine {
    CGPoint pointA = self.viewA.center;
    CGPoint pointB = self.viewB.center;

    CAShapeLayer *lineShape = [CAShapeLayer layer];
    UIBezierPath *linePath=[self pathFrom:pointA to:pointB];
    lineShape.path=linePath.CGPath;

    lineShape.fillColor = nil;
    lineShape.opacity = 1.0;
    lineShape.strokeColor = [UIColor blackColor].CGColor;
    [self.view.layer addSublayer:lineShape];
    self.lineShape = lineShape;
}

它画的正是我想要的样子。我对文档的理解是,我可以通过在动画块中更改形状的路径来为其设置动画,如下所示:

- (void)moveViewATo:(CGPoint)dest {
    UIBezierPath *destPath=[self pathFrom:dest to:self.viewB.center];
    [UIView animateWithDuration:1 animations:^{
        self.viewA.center = dest;
        self.lineShape.path = destPath.CGPath;
    }];
}

但是没有骰子。视图位置按预期设置动画,但连接到另一个视图的线 "jumps" 立即到达目标路径。

This answer implies that what I'm doing should work. And this answer 建议使用 CABasic 动画,这对我来说似乎更糟,因为 (a) 然后我需要与对视图完成的更酷的块动画进行协调,以及 (b) 当我尝试它时这样,这条线就完全没有改变....

// worse way
- (void)moveViewATo:(CGPoint)dest {
    UIBezierPath *linePath=[self pathFrom:dest to:self.viewB.center];
    [UIView animateWithDuration:1 animations:^{
        self.viewA.center = dest;
        //self.lineShape.path = linePath.CGPath;
    }];

    CABasicAnimation *morph = [CABasicAnimation animationWithKeyPath:@"path"];
    morph.duration = 1;
    morph.toValue = (id)linePath.CGPath;
    [self.view.layer addAnimation:morph forKey:nil];
}

提前致谢。

我想这就是您所需要的:



    #import "ViewController.h"

    @interface ViewController ()

    //the view to animate, nothing but a simple empty UIView here.
    @property (nonatomic, strong) IBOutlet UIView *targetView;
    @property (nonatomic, strong) CAShapeLayer *shapeLayer;
    @property NSTimeInterval animationDuration;
    @end

    @implementation ViewController

    - (void)viewDidLoad {
        [super viewDidLoad];

        //the shape layer appearance
        self.shapeLayer = [[CAShapeLayer alloc]init];
        self.shapeLayer.strokeColor = [UIColor blackColor].CGColor;
        self.shapeLayer.fillColor = [UIColor clearColor].CGColor;
        self.shapeLayer.opacity = 1.0;
        self.shapeLayer.lineWidth = 2.0;
        [self.view.layer insertSublayer:self.shapeLayer below:self.targetView.layer];

        //animation config
        self.animationDuration = 2;
    }

    - (UIBezierPath *)pathFrom:(CGPoint)pointA to:(CGPoint)pointB {
        CGFloat halfY = pointA.y + 0.5*(pointB.y - pointA.y);
        UIBezierPath *linePath=[UIBezierPath bezierPath];
        [linePath moveToPoint: pointA];
        [linePath addLineToPoint:CGPointMake(pointA.x, halfY)];
        [linePath addLineToPoint:CGPointMake(pointB.x, halfY)];
        [linePath addLineToPoint:pointB];
        return linePath;
    }

    - (void) moveViewTo: (CGPoint) point {
        UIBezierPath *linePath= [self pathFrom:self.targetView.center to:point];

        self.shapeLayer.path = linePath.CGPath;

        //Use CAKeyframeAnimation to animate the view along the path
        //animate the position of targetView.layer instead of the center of targetView
        CAKeyframeAnimation *viewMovingAnimation = [CAKeyframeAnimation animationWithKeyPath:@"position"];
        viewMovingAnimation.duration = self.animationDuration;
        viewMovingAnimation.path = linePath.CGPath;
        //set the calculationMode to kCAAnimationPaced to make the movement in a constant speed
        viewMovingAnimation.calculationMode =kCAAnimationPaced;
        [self.targetView.layer addAnimation:viewMovingAnimation forKey:viewMovingAnimation.keyPath];

        //draw the path, animate the keyPath "strokeEnd"
        CABasicAnimation *lineDrawingAnimation = [CABasicAnimation animationWithKeyPath:@"strokeEnd"];
        lineDrawingAnimation.duration = self.animationDuration;
        lineDrawingAnimation.fromValue = [NSNumber numberWithDouble: 0];
        lineDrawingAnimation.toValue = [NSNumber numberWithDouble: 1];
        [self.shapeLayer addAnimation:lineDrawingAnimation forKey:lineDrawingAnimation.keyPath];


        //This part is crucial, update the values, otherwise it will back to its original state
        self.shapeLayer.strokeEnd = 1.0;
        self.targetView.center = point;
    }

    //the IBAction for a UITapGestureRecognizer
    - (IBAction) viewDidTapped:(id)sender {
        //move the view to the tapped location
        [self moveViewTo:[sender locationInView: self.view]];
    }

    @end

一些解释:

  • 对于UIViewAnimation,属性值在 动画完成。对于 CALayerAnimation,属性 值为 永远不会改变,它只是一个动画,当动画是 完成后,图层将回到其原始状态(在本例中, 路径).

  • 输入 self.lineShape.path = linePath.CGPath 不起作用是 因为 self.linePathCALayer 而不是 UIView,你 必须使用 CALayerAnimation 来制作 CALayer

  • 的动画
  • 绘制路径最好用keyPath动画路径绘制 strokeEnd 而不是 path。我不确定为什么 path 在 原来的post,但我觉得很奇怪。

  • CAKeyframeAnimation(而不是 CABasicAnimationUIViewAnimation)用于沿路径设置视图动画。 (我猜你更喜欢这个而不是直接从起点到终点的线性动画)。将calculationMode设置为kCAAnimationPaced会使动画速度恒定,否则视图移动不会与画线同步。

感谢大家的帮助。问完这个问题后我发现我制作的动画是错误的 属性。事实证明,您可以在动画中替换图层的形状,如下所示:

CABasicAnimation *morph = [CABasicAnimation animationWithKeyPath:@"path"];
morph.duration  = 1;
morph.fromValue = (__bridge id)oldPath.path;
morph.toValue   = (__bridge id)newPath.CGPath;
[line addAnimation:morph forKey:@"change line path"];
line.path=linePath.CGPath;