首页
学习
活动
专区
圈层
工具
发布
社区首页 >问答首页 >iOS CAKeyframeAnimation rotationMode on UIImageView

iOS CAKeyframeAnimation rotationMode on UIImageView
EN

Stack Overflow用户
提问于 2012-02-27 16:42:45
回答 3查看 5.9K关注 0票数 12

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

当我开始使用rotationMode设置为nil的动画时,图像在路径中始终保持相同的方向。

但是当我启动rotationMode设置为kCAAnimationRotateAuto的动画时,图像立即逆时针旋转90度,并保持这个方向贯穿整个路径。当它到达路径的末尾时,它以正确的方向重新绘制(它实际上显示了我在最后位置重新定位的UIImageView )。

我天真地期待着rotationMode会将图像定位到路径的切线上,而不是正常的位置,特别是当苹果记录到CAKeyframeAnimation rotationMode状态时。

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

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

谢谢你的帮助。

编辑3月2日

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

代码语言:javascript
复制
theImage.transform = CGAffineTransformRotate(theImage.transform,90.0*M_PI/180);

然后在路径动画之后,用以下方式重置旋转:

代码语言:javascript
复制
theImage.transform = CGAffineTransformIdentity;

这使得图像以预期的方式遵循路径。然而,我现在遇到了一个不同的问题,图像闪烁。我已经在寻找解决这个问题中闪烁的问题的办法:

iOS CAKeyFrameAnimation缩放动画末端闪烁

所以现在我不知道我把事情做得更好还是更糟了!

编辑3月12日

虽然Caleb指出,是的,我确实必须预先旋转我的图像,Rob提供了一个非常棒的代码包,几乎完全解决了我的问题。Rob没有做的唯一一件事是补偿我的资产是用垂直方向而不是水平方向绘制的,因此在做动画之前仍然需要将其preRotate 90度。但是,这是唯一公平的,我必须做一些工作,以使事情运行。

因此,我对Rob的解决方案进行了稍微的修改,以实现套件--我的需求是:

  1. 当我添加UIView时,我预先旋转它,以对抗通过设置rotationMode而添加的固有旋转: theImage.transform = CGAffineTransformMakeRotation(90*M_PI/180.0);
  2. 我需要在动画结束时保持旋转,所以在定义完成块之后,不要仅仅用一个新的缩放因子来扩展视图的转换,而是根据当前的转换构建缩放: theImage.transform = CGAffineTransformScale(theImage.transform,scaleFactor,scaleFactor);

这就是我所要做的,让我的形象按照我所期望的方式前进!

编辑3月22日

我刚刚向GitHub上传了一个演示项目,展示了一个对象沿着bezier路径移动的过程。代码可以在PathMove上找到

我还在在iOS中沿bezier路径移动对象的博客上写了这件事

EN

回答 3

Stack Overflow用户

回答已采纳

发布于 2012-03-03 15:00:22

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

如果您希望视图的垂直轴遵循路径的切线,那么按照当前的操作旋转视图的内容是合理的。

票数 8
EN

Stack Overflow用户

发布于 2012-03-06 09:03:55

为了消除闪烁现象,您需要知道以下几点:

  • 正如Caleb所指出的,核心动画旋转你的图层,所以它的正X轴沿着你的路径的切线。你需要使你的形象的“自然”取向与此相配合。所以,假设这是一个绿色的飞船,在你的例子中,你需要飞船指向右边,当它没有旋转的时候。
  • 设置一个包含旋转的转换会干扰“kCA动画”所应用的旋转。在应用动画之前,您需要从转换中删除旋转。
  • 当然,这意味着动画完成时需要重新应用转换。当然,你想这样做,而不想看到任何闪烁的图像外观。这并不难,但有一些秘密的酱汁,我在下面解释。
  • 你大概想让你的宇宙飞船开始指向路径的切线,即使飞船还没有被激活。如果您的宇宙飞船图像指向右侧,但是您的路径向上,那么您需要设置图像的转换,以包括90°旋转。但也许你不想硬编码旋转-相反,你想看看路径,找出它的起始切线。

我会在这里展示一些重要的代码。你可以找到我在github上的测试项目。您可能会发现在下载和试用它的一些用处。点击绿色的“宇宙飞船”看动画。

因此,在我的测试项目中,我已经将我的UIImageView连接到一个名为animate:的操作。当您触摸它时,图像会沿着图8的一半移动,大小会加倍。当您再次触摸它时,图像沿着图8的另一半移动(回到起始位置),并返回到原来的大小。两种动画都使用kCAAnimationRotateAuto,因此图像沿着路径的切线点。

这里是animate:的开始,在这里我确定了图像应该在哪个路径、规模和目标点结束:

代码语言:javascript
复制
- (IBAction)animate:(id)sender {
    UIImageView* theImage = self.imageView;

    UIBezierPath *path = _isReset ? _path0 : _path1;
    CGFloat newScale = 3 - _currentScale;

    CGPoint destination = [path currentPoint];

因此,我需要做的第一件事是从图像的变换中删除任何旋转,因为正如我所提到的,它会干扰kCAAnimationRotateAuto

代码语言:javascript
复制
    // Strip off the image's rotation, because it interferes with `kCAAnimationRotateAuto`.
    theImage.transform = CGAffineTransformMakeScale(_currentScale, _currentScale);

接下来,我将进入一个UIView动画块,以便系统将动画应用于图像视图:

代码语言:javascript
复制
    [UIView animateWithDuration:3 animations:^{

我为这个位置创建了关键帧动画,并设置了它的几个属性:

代码语言:javascript
复制
        // 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;

接下来是秘密沙司,用于防止动画结束时的闪烁。回想一下,动画不会影响您将它们附加到的“模型层”的属性(本例中为theImage.layer)。相反,他们更新了“表示层”的属性,它反映了屏幕上的实际内容。

因此,首先,我将关键帧动画的removedOnCompletion设置为NO。这意味着动画完成后,动画将保持在模型层上,这意味着我可以访问表示层。我从表示层得到转换,删除动画,并将转换应用到模型层。由于这一切都发生在主线程上,这些属性更改都发生在一个屏幕刷新周期中,因此没有闪烁。

代码语言:javascript
复制
        positionAnimation.removedOnCompletion = NO;
        [CATransaction setCompletionBlock:^{
            CGAffineTransform finalTransform = [theImage.layer.presentationLayer affineTransform];
            [theImage.layer removeAnimationForKey:positionAnimation.keyPath];
            theImage.transform = finalTransform;
        }];

现在我已经设置了完成块,我实际上可以更改视图属性了。当我这样做时,系统会自动将动画附加到图层。

代码语言:javascript
复制
        // UIView will add animations for both of these changes.
        theImage.transform = CGAffineTransformMakeScale(newScale, newScale);
        theImage.center = destination;

我将自动添加的位置动画中的一些关键属性复制到关键帧动画中:

代码语言:javascript
复制
        // Copy properties from UIView's animation.
        CAAnimation *autoAnimation = [theImage.layer animationForKey:positionAnimation.keyPath];
        positionAnimation.duration = autoAnimation.duration;
        positionAnimation.fillMode = autoAnimation.fillMode;

最后,我将自动添加的位置动画替换为keyframe动画:

代码语言:javascript
复制
        // Replace UIView's animation with my animation.
        [theImage.layer addAnimation:positionAnimation forKey:positionAnimation.keyPath];
    }];

最后,我更新实例变量以反映对图像视图的更改:

代码语言:javascript
复制
    _currentScale = newScale;
    _isReset = !_isReset;
}

这是它的动画图像视图,没有闪烁。

现在,正如史蒂夫·乔布斯所说的,最后一件事。当我加载视图时,我需要设置图像视图的转换,以便它被旋转到指向第一条路径的切线,我将使用它来动画它。我是在一个名为reset的方法中这样做的

代码语言:javascript
复制
- (void)reset {
    self.imageView.center = _path1.currentPoint;
    self.imageView.transform = CGAffineTransformMakeRotation(startRadiansForPath(_path0));
    _currentScale = 1;
    _isReset = YES;
}

当然,棘手的部分隐藏在startRadiansForPath函数中。其实也没那么难。我使用CGPathApply函数来处理路径的元素,挑选出实际上形成子路径的前两个点,并计算由这两个点形成的直线的角度。(曲线路径段是二次或三次bezier样条,而这些样条具有样条第一点的切线是从第一点到下一个控制点的直线的性质。)

为了子孙后代,我将在这里不加解释地转储代码:

代码语言:javascript
复制
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);
}
票数 6
EN

Stack Overflow用户

发布于 2012-03-03 07:34:09

请注意,您使用"rotationMode设置为YES“启动动画,但是文档指出rotationMode应该使用NSString设置.

特别是:

这些常量由rotationMode属性使用。 NSString * const kCAAnimationRotateAuto NSString * const kCAAnimationRotateAutoReverse

你试过设置:

keyframe.animationMode = kCAAnimationRotateAuto;

这些文件指出:

kCAAnimationRotateAuto:物体在与路径相切的位置上运动。

票数 3
EN
页面原文内容由Stack Overflow提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://stackoverflow.com/questions/9468707

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档