UIImageView上的iOS CAKeyframeAnimation旋转模式

UIImageView上的iOS CAKeyframeAnimation旋转模式,ios,rotation,cakeyframeanimation,Ios,Rotation,Cakeyframeanimation,我正在试验沿贝塞尔路径移动的UIImageView对象位置的关键帧动画。这张图片显示了动画之前的初始状态。蓝线是路径-最初笔直向上移动,浅绿色框是初始边界框或图像,深绿色“幽灵”是我正在移动的图像: 当我在rotationMode设置为nil的情况下启动动画时,图像在路径中始终保持与预期相同的方向 但当我在rotationMode设置为kCAAnimationRotateAuto的情况下启动动画时,图像立即逆时针旋转90度,并在路径中始终保持此方向。当它到达路径的末端时,它会以正确的方向重新绘

我正在试验沿贝塞尔路径移动的UIImageView对象位置的关键帧动画。这张图片显示了动画之前的初始状态。蓝线是路径-最初笔直向上移动,浅绿色框是初始边界框或图像,深绿色“幽灵”是我正在移动的图像:

当我在rotationMode设置为
nil
的情况下启动动画时,图像在路径中始终保持与预期相同的方向

但当我在rotationMode设置为
kCAAnimationRotateAuto
的情况下启动动画时,图像立即逆时针旋转90度,并在路径中始终保持此方向。当它到达路径的末端时,它会以正确的方向重新绘制(实际上它显示了我在最终位置重新定位的UIImageView)

我天真地以为rotationMode会将图像定向到路径的切线,而不是法线,尤其是当Apple记录状态时

确定沿路径设置动画的对象是否旋转以匹配路径切线

那么这里的解决方案是什么?我必须将图像顺时针旋转90度吗?还是我遗漏了什么

谢谢你的帮助


编辑3月2日

我使用仿射旋转在路径动画之前添加了旋转步骤,如:

theImage.transform = CGAffineTransformRotate(theImage.transform,90.0*M_PI/180);
然后在路径动画之后,使用以下命令重置旋转:

theImage.transform = CGAffineTransformIdentity;
这使图像以预期的方式跟随路径。然而,我现在遇到了一个不同的问题,图像闪烁。我已经在寻找解决这个问题的方法:

所以现在我不知道我是让事情变得更好还是更糟


编辑3月12日

当Caleb指出是的,我确实需要预先旋转我的图像时,Rob提供了一个很棒的方法 几乎完全解决了我的问题的代码包。Rob唯一没有做的事情是补偿我的资源以垂直方向而不是水平方向绘制,因此在制作动画之前仍然需要将它们预旋转90度。但是,我必须做一些工作才能让事情顺利进行,这才是公平的

因此,我对Rob针对套件的解决方案的细微改动是:

  • 当我添加UIView时,我会预旋转它以抵消通过设置
    旋转模式添加的固有旋转:

    theImage.transform=CGAffineTransformMakeRotation(90*M_PI/180.0)

  • 我需要在动画结束时保持旋转,因此在定义完成块后,我不只是使用新的比例因子爆破视图的变换,而是基于当前变换构建比例:

    theImage.transform=CGAffineTransformScale(theImage.transform,scaleFactor,scaleFactor)

  • 这就是我所要做的一切,让我的形象走上我所期望的道路


    编辑3月22日

    我刚刚向GitHub上传了一个演示项目,展示了一个对象沿贝塞尔路径的移动。有关代码,请访问


    我也在我的博客上写过,你提到你用“rotationMode设置为YES”启动动画,但是文档中说rotationMode应该使用NSString设置

    特别是:

    这些常量由rotationMode属性使用

    NSString*常量kCAAnimationRotateAuto

    NSString*常量kCAAnimationRotateAutoReverse

    您是否尝试设置:

    keyframe.animationMode=kCAAnimationRotateAuto

    文件规定:

    kCAAnimationRotateAuto:对象沿与路径相切的方向移动


    这里的问题是核心动画的自动旋转使视图的水平轴与路径的切线平行。这就是它的工作原理


    如果希望视图的垂直轴沿着路径的切线移动,则在当前操作时旋转视图内容是合理的做法。

    以下是消除闪烁所需了解的内容:

    • 正如Caleb所指出的,核心动画会旋转层,使其正X轴位于路径的切线上。你需要使你的图像的“自然”方向与之配合。因此,假设在您的示例图像中是一个绿色的宇宙飞船,您需要宇宙飞船指向右侧,但它没有应用旋转

    • 设置包含旋转的变换会干扰“kCAAnimationRotateAuto”应用的旋转。在应用动画之前,需要从变换中移除旋转

    • 当然,这意味着您需要在动画完成时重新应用变换。当然,您希望这样做,而不会看到图像外观中的任何闪烁。这并不难,但其中有一些秘密酱,我在下面解释

    • 您可能希望您的宇宙飞船开始指向路径的切线,即使宇宙飞船还未设置动画,仍然静止不动。如果太空船图像指向右侧,但路径向上,则需要将图像的变换设置为包括90°旋转。但也许您不想硬编码旋转,而是想查看路径并找出其起始切线

    我将在这里展示一些重要的代码。你可以找到。你可以下载并试用它。只需点击绿色的“宇宙飞船”即可观看动画

    因此,在我的测试项目中,我将我的
    UIImageView
    连接到一个名为
    animate:
    的动作。当你触摸它时,图像会沿着图8的一半移动,大小会翻倍。
    - (IBAction)animate:(id)sender {
        UIImageView* theImage = self.imageView;
    
        UIBezierPath *path = _isReset ? _path0 : _path1;
        CGFloat newScale = 3 - _currentScale;
    
        CGPoint destination = [path currentPoint];
    
        // Strip off the image's rotation, because it interferes with `kCAAnimationRotateAuto`.
        theImage.transform = CGAffineTransformMakeScale(_currentScale, _currentScale);
    
        [UIView animateWithDuration:3 animations:^{
    
            // Prepare my own keypath animation for the layer position.
            // The layer position is the same as the view center.
            CAKeyframeAnimation *positionAnimation = [CAKeyframeAnimation animationWithKeyPath:@"position"];
            positionAnimation.path = path.CGPath;
            positionAnimation.rotationMode = kCAAnimationRotateAuto;
    
            positionAnimation.removedOnCompletion = NO;
            [CATransaction setCompletionBlock:^{
                CGAffineTransform finalTransform = [theImage.layer.presentationLayer affineTransform];
                [theImage.layer removeAnimationForKey:positionAnimation.keyPath];
                theImage.transform = finalTransform;
            }];
    
            // UIView will add animations for both of these changes.
            theImage.transform = CGAffineTransformMakeScale(newScale, newScale);
            theImage.center = destination;
    
            // Copy properties from UIView's animation.
            CAAnimation *autoAnimation = [theImage.layer animationForKey:positionAnimation.keyPath];
            positionAnimation.duration = autoAnimation.duration;
            positionAnimation.fillMode = autoAnimation.fillMode;
    
            // Replace UIView's animation with my animation.
            [theImage.layer addAnimation:positionAnimation forKey:positionAnimation.keyPath];
        }];
    
        _currentScale = newScale;
        _isReset = !_isReset;
    }
    
    - (void)reset {
        self.imageView.center = _path1.currentPoint;
        self.imageView.transform = CGAffineTransformMakeRotation(startRadiansForPath(_path0));
        _currentScale = 1;
        _isReset = YES;
    }
    
    typedef struct {
        CGPoint p0;
        CGPoint p1;
        CGPoint firstPointOfCurrentSubpath;
        CGPoint currentPoint;
        BOOL p0p1AreSet : 1;
    } PathState;
    
    static inline void updateStateWithMoveElement(PathState *state, CGPathElement const *element) {
        state->currentPoint = element->points[0];
        state->firstPointOfCurrentSubpath = state->currentPoint;
    }
    
    static inline void updateStateWithPoints(PathState *state, CGPoint p1, CGPoint currentPoint) {
        if (!state->p0p1AreSet) {
            state->p0 = state->currentPoint;
            state->p1 = p1;
            state->p0p1AreSet = YES;
        }
        state->currentPoint = currentPoint;
    }
    
    static inline void updateStateWithPointsElement(PathState *state, CGPathElement const *element, int newCurrentPointIndex) {
        updateStateWithPoints(state, element->points[0], element->points[newCurrentPointIndex]);
    }
    
    static void updateStateWithCloseElement(PathState *state, CGPathElement const *element) {
        updateStateWithPoints(state, state->firstPointOfCurrentSubpath, state->firstPointOfCurrentSubpath);
    }
    
    static void updateState(void *info, CGPathElement const *element) {
        PathState *state = info;
        switch (element->type) {
            case kCGPathElementMoveToPoint: return updateStateWithMoveElement(state, element);
            case kCGPathElementAddLineToPoint: return updateStateWithPointsElement(state, element, 0);
            case kCGPathElementAddQuadCurveToPoint: return updateStateWithPointsElement(state, element, 1);
            case kCGPathElementAddCurveToPoint: return updateStateWithPointsElement(state, element, 2);
            case kCGPathElementCloseSubpath: return updateStateWithCloseElement(state, element);
        }
    }
    
    CGFloat startRadiansForPath(UIBezierPath *path) {
        PathState state;
        memset(&state, 0, sizeof state);
        CGPathApply(path.CGPath, &state, updateState);
        return atan2f(state.p1.y - state.p0.y, state.p1.x - state.p0.x);
    }