使用图像数组制作动画序列

18

我有一组图片,希望通过按顺序播放这些图像来实现动画效果,并且希望重复整个循环多次。我正在为iPad开发游戏。请给我建议,如何使用Objective-C和Cocoa框架实现这个功能。

6个回答

52
NSArray *animationArray = [NSArray arrayWithObjects:
                          [UIImage imageNamed:@"images.jpg"],
                          [UIImage imageNamed:@"images1.jpg"],
                          [UIImage imageNamed:@"images5.jpg"],
                          [UIImage imageNamed:@"index3.jpg"],
                          nil];
UIImageView *animationView = [[UIImageView alloc]initWithFrame:CGRectMake(0, 0,320, 460)];
animationView.backgroundColor      = [UIColor purpleColor];
animationView.animationImages      = animationArray;
animationView.animationDuration    = 1.5;
animationView.animationRepeatCount = 0;
[animationView startAnimating]; 
[self.view addSubview:animationView];       
[animationView release];

将您自己的图像添加到数组中。重复计数0意味着无限循环。您也可以提供自己的数字。


@Gypsa,我们可以添加页面控制器吗? - Subhash Sharma
感谢简单而有效的方法。 - Yucel Bayram
这也适用于PDF图像文件吗? - abc123

19

通过UIImageView至少有3种方式来对图像数组进行动画。我将添加3个链接,以下载这3种可能性的示例代码。

第一个是大家都知道的。其他的则不太为人所知。

- UIImageView.animationImages
示例链接

这个问题是没有委托告诉我们动画何时结束。因此,如果我们想在动画后显示某些内容,就可能会遇到问题。

同样地,没有自动在UIImageView中保留动画的最后一帧的可能性。如果我们结合两个问题,当我们想在屏幕上保留最后一帧时,就可能出现动画末尾的空白。

self.imageView.animationImages = self.imagesArray; // the array with the images
self.imageView.animationDuration = kAnimationDuration; // static const with your value
self.imageView.animationRepeatCount = 1;
[self.imageView startAnimating];

- CAKeyframeAnimation
示例链接

这种动画方式通过CAAnimation实现。它具有易于使用的委托,并且我们可以知道动画何时结束。

这可能是最好的动画图像数组的方式。

- (void)animateImages
{
    CAKeyframeAnimation *keyframeAnimation = [CAKeyframeAnimation animationWithKeyPath:@"contents"];
    keyframeAnimation.values = self.imagesArray;

    keyframeAnimation.repeatCount = 1.0f;
    keyframeAnimation.duration = kAnimationDuration; // static const with your value

    keyframeAnimation.delegate = self;

    //    keyframeAnimation.removedOnCompletion = YES;
    keyframeAnimation.removedOnCompletion = NO;
    keyframeAnimation.fillMode = kCAFillModeForwards;

    CALayer *layer = self.animationImageView.layer;

    [layer addAnimation:keyframeAnimation
                 forKey:@"girlAnimation"];
}

代表:

- (void)animationDidStop:(CAAnimation *)anim finished:(BOOL)flag
{
    if (flag)
    {
        // your code
    }
}

- CADisplayLink
示例链接

CADisplayLink对象是一个计时器对象,允许您的应用程序将绘图与显示器的刷新率同步。

这种方法非常有趣,并且开启了许多操作屏幕显示内容的可能性。

DisplayLink 获取器:

- (CADisplayLink *)displayLink
{
    if (!_displayLink)
    {
        _displayLink = [CADisplayLink displayLinkWithTarget:self                                                   selector:@selector(linkProgress)];
    }

    return _displayLink;
}

方法:

- (void)animateImages
{
    self.displayLink.frameInterval = 5;
    self.frameNumber = 0;
    [self.displayLink addToRunLoop:[NSRunLoop mainRunLoop]
                           forMode:NSRunLoopCommonModes];
}

- (void)linkProgress
{
    if (self.frameNumber > 16)
    {
        [self.displayLink invalidate];
        self.displayLink = nil;
        self.animationImageView.image = [UIImage imageNamed:@"lastImageName"];
        self.imagesArray = nil;
        return;
    }

    self.animationImageView.image = self.imagesArray[self.frameNumber++];
    self.frameNumber++;
}

问题概述:

尽管我们有三种可能性,但如果您的动画使用了很多大图片,请考虑使用视频。这样可以大大减少内存的使用。

这样做时你会遇到一个普遍的问题——分配图片的时刻。

如果您使用 [UIImage imageNamed:@"imageName"],则会出现缓存问题。

来自 Apple 的说明:

该方法在系统缓存中查找指定名称的图像对象,并返回该对象(如果存在)。如果匹配的图像对象不在缓存中,则此方法将从磁盘或资产目录中查找并加载图像数据,然后返回结果对象。不能假定此方法是线程安全的。

因此,imageNamed: 将图像存储在私有缓存中。
- 第一个问题是您无法控制缓存大小。
- 第二个问题是缓存没有及时清除,如果您使用 imageNamed: 分配了大量图片,则您的应用程序很可能会崩溃。

解决方案:

直接从Bundle中分配图像:

NSString *imageName = [NSString stringWithFormat:@"imageName.png"];
NSString *path = [[NSBundle mainBundle] pathForResource:imageName

// Allocating images with imageWithContentsOfFile makes images to do not cache.
UIImage *image = [UIImage imageWithContentsOfFile:path];

小问题:

Images.xcassets中的图像永远不会被分配。因此,将您的图像移出Images.xcassets直接从Bundle中分配。


11

查看UIImageViewanimationImages属性。由于您没有提供细节,因此很难说它是否符合您的需求,但这是一个很好的开始。


有哪些细节会有帮助? - Yadnesh
什么类型的游戏?动作,桌面?(这会影响预期的动画性能。)有多少张图片? - zoul
这是我正在开发的一个小型老虎机游戏,我有13-20张图片存放在一个数组中,当游戏满足特定条件时将会播放这些图片。同时,在游戏中最多同时有5个(至少3个)这种类型的数组处于可用状态(即正在进行动画效果)。如果您想了解更多信息,请让我知道! - Yadnesh
听起来很合理,我会尝试使用 UIImageView - zoul
好的,我会尝试。如果我能够按照这个链接中提到的方法,使用QTmovie将这些数组制作成视频,那将非常有帮助。http://stackoverflow.com/questions/3592167/cocoa-objective-c-how-can-i-convert-an-array-of-images-to-a-video-file-and-sav - Yadnesh
“QTMovie”是QTKit的一部分,但在iOS上不可用。 - zoul

2
我已经为此添加了一个Swift 3.0扩展。
extension UIImageView {

    func animate(images: [UIImage], index: Int = 0, completionHandler: (() -> Void)?) {

        UIView.transition(with: self, duration: 0.5, options: .transitionCrossDissolve, animations: {
            self.image = images[index]

        }, completion: { value in
            let idx = index == images.count-1 ? 0 : index+1

            if idx == 0 {
                completionHandler!()

            } else {
                self.animate(images: images, index: idx, completionHandler: completionHandler)
            }

        })
    }

}

1

对于我来说,最好的解决方案是使用CADisplayLink。UIImageView没有完成块,并且无法捕获动画步骤。在我的任务中,我必须逐步使用图像序列更改视图的背景。因此,CADisplayLink允许您处理步骤并完成动画。如果我们谈论内存使用,我认为最好的解决方案是从束中加载图像并在完成后删除数组。

ImageSequencer.h

typedef void (^Block)(void);

@protocol ImageSequencerDelegate;

@interface QSImageSequencer : UIImageView
@property (nonatomic, weak) id <ImageSequencerDelegate> delegate;

- (void)startAnimatingWithCompletionBlock:(Block)block;
@end

@protocol ImageSequencerDelegate <NSObject>
@optional
- (void)animationDidStart;
- (void)animationDidStop;
- (void)didChangeImage:(UIImage *)image;
@end

ImageSequencer.m

- (instancetype)init {
    if (self = [super init]) {

        _imagesArray = [NSMutableArray array];

        self.image = [self.imagesArray firstObject];
    }

    return self;
}


#pragma mark - Animation

- (void)startAnimating {
    [self startAnimatingWithCompletionBlock:nil];
}


- (void)startAnimatingWithCompletionBlock:(Block)block {
    self.frameNumber = 0;

    [self setSuccessBlock:block];

    self.displayLink.frameInterval = 5;

    if (self.delegate && [self.delegate respondsToSelector:@selector(animationDidStart)]) {
        [self.delegate animationDidStart];
    }

    [self.displayLink addToRunLoop:[NSRunLoop mainRunLoop]
                           forMode:NSRunLoopCommonModes];
}


-(void)stopAnimating {
     self.image = [self.imagesArray lastObject];

    [self.displayLink invalidate];
    [self setDisplayLink:nil];
    Block block_ = [self successBlock];
    if (block_) {
        block_();
    }

    if (self.delegate && [self.delegate respondsToSelector:@selector(animationDidStop)]) {
        [self.delegate animationDidStop];
    }

    [self.imagesArray removeAllObjects];
}


- (void)animationProgress {
    if (self.frameNumber >= self.imagesArray.count) {
        [self stopAnimating];
        return;
    }

    if (self.delegate && [self.delegate respondsToSelector:@selector(didChangeImage:)]) {
        [self.delegate didChangeImage:self.imagesArray[self.frameNumber]];
    }

    self.image = self.imagesArray[self.frameNumber];
    self.frameNumber++;
}


#pragma mark - Getters / Setters

- (CADisplayLink *)displayLink {
    if (!_displayLink){
        _displayLink = [CADisplayLink displayLinkWithTarget:self selector:@selector(animationProgress)];
    }

    return _displayLink;
}

- (NSMutableArray<UIImage *> *)imagesArray {
    if (_imagesArray.count == 0) {
        // get images from bundle and set to array
    }
    return _imagesArray;
}

@end

0

这是一个简单且可用于动画的代码。

-(void)move
{
    [UIView animateWithDuration:5
                          delay:0.0
                        options: UIViewAnimationCurveEaseOut
                     animations:^{
                         [_imgbox setFrame:CGRectMake(100, 100, _imgbox.frame.size.width, _imgbox.frame.size.height)];
                     }
                     completion:^(BOOL finished){
                         NSLog(@"Done!");
                     }];
}

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接