How do I simultaneously animate a UIImageView'

2019-01-23 22:23发布

问题:

The title may not be so clear, but what I want to do is to make the UIImageView display a series of images (sort of like a gif) and I do this by setting the animationImages to an array of images and then calling [imageView startAnimating];. This works fine. I also have code that moves the UIImageView around with a CABasicAnimation, and this animation code also works fine. However, when I try to both animate the images of the UIImageView and try to move the UIImageView around, the images of the UIImageView stop animating. Is there a workaround?

Here's my code for animating the content of the UIImageView:

    self.playerSprite = [[UIImageView alloc] initWithImage:[self.player.animationImages objectAtIndex:0]];
    [self.playerSprite setFrame:CGRectMake(self.center.x, self.center.y, self.tileSet.constants.TILE_WIDTH, self.tileSet.constants.TILE_HEIGHT)];
    self.playerSprite.animationImages = self.player.animationImages;
    self.playerSprite.animationDuration = self.tileSet.constants.animationDuration;
    self.playerSprite.animationRepeatCount = 0; //Makes it repeat indefinitely

And here's my coding for animating the UIImageView with a CABasicAnimation:

float playerSpriteX = self.playerSprite.center.x;
float playerSpriteY = self.playerSprite.center.y;

CABasicAnimation *moveAnimation = [CABasicAnimation animation];
moveAnimation.toValue = [NSValue valueWithCGPoint:CGPointMake(playerSpriteX + TILE_WIDTH, playerSpriteY)];
[moveAnimation setDelegate:self];
[moveAnimation setFillMode:kCAFillModeForwards];
[moveAnimation setRemovedOnCompletion:NO];
[moveAnimation setDuration:MOVE_ANIMATION_DURATION];       
[self.playerSprite.layer addAnimation:moveAnimation forKey:@"position"];

So the gif effect isn't working while the UIImageView's position is being animated. My question is how can I make it so the UIImageView cycles through an array of images while its position is being animated?

回答1:

This is speculation and I haven't tested this idea at all, but have you considered implementing the image animation in the same fashion you're animating the position, with a CAKeyframeAnimation? You'd need to construct an array of CGImage objects from your UIImage array (to set the values property of the keyframe animation) but it looks like a pretty straightforward conversion:

CAKeyframeAnimation *imageAnimation = [CAKeyframeAnimation animation];
imageAnimation.calculationMode = kCAAnimationDiscrete; // or maybe kCAAnimationPaced
imageAnimation.duration = self.tileSet.constants.animationDuration;
imageAnimation.repeatCount = HUGE_VALF;
// the following method will need to be implemented to cast your UIImage array to CGImages
imageAnimation.values = [self animationCGImagesArray];
[self.playerSprite.layer addAnimation:imageAnimation forKey:@"contents"];

-(NSArray*)animationCGImagesArray {
    NSMutableArray *array = [NSMutableArray arrayWithCapacity:[self.player.animationImages count]];
    for (UIImage *image in self.player.animationImages) {
        [array addObject:(id)[image CGImage]];
    }
    return [NSArray arrayWithArray:array];
}


回答2:

I've just done something similar. The code I used looks like this:

    CGRect r = ziv.frame;
    r.origin.x += WrongDistance;
    [ziv startAnimating];
    [UIView animateWithDuration:3.0 animations:^(void){
        [ziv setFrame:r];
    } completion:^(BOOL finished){

        [ziv stopAnimating];

        if (finished){
            // not canceled in flight
            if (NumWrong == MaxWrong)
                [self endOfGame:NO];
            else
                [self nextRound:self];
        }
    }];

Perhaps the issue you're running into is because both animations are on the same thread?



回答3:

Your "basic" problem starts on this line:

CABasicAnimation *moveAnimation = [CABasicAnimation animation];

A CABasicAnimation object uses only a single keyframe. That means that the content of the layer that you're animating is drawn once, and then that image is used for the duration of the animation.

Using a CAKeyframeAnimation as Seamus suggests is one way to deal with the problem -- a keyframe animation will redraw the content of the animated layer multiple times, so drawing successive images for each keyframe will animate the content as well as position.



回答4:

This is not a good way to make a sprite. I could insist that you should be using OpenGL, but there may be no need to go that far; you can do it all with Core Animation of a layer, and I think you'll be better off doing that than trying to use a full-fledged UIImageView.

Using Core Animation of a layer, I was able to make this PacMan sprite animate across the screen while opening and closing his mouth; isn't that the sort of thing you had in mind?

Here is a video showing the animation!

http://www.youtube.com/watch?v=WXCLc9ww8MI

And yet the actual code creating the animation is extremely simple: just two layer animations (Core Animation), one for the changing image, the other for the position.

You should not award this answer the bounty! I am now merely echoing what Seamus Campbell said; I'm just filling out the details of his answer a little.

Okay, so here's the code that generates the movie linked above:

- (void)viewDidLoad {
    [super viewDidLoad];
    // construct arr, an array of CGImage (omitted)
    // it happens I've got 5 images, the last being the same as the first
    self.images = [arr copy];
    // place sprite into the interface
    self.sprite = [CALayer new];
    self.sprite.frame = CGRectMake(30,30,24,24);
    self.sprite.contentsScale = [UIScreen mainScreen].scale;
    [self.view.layer addSublayer:self.sprite];
    self.sprite.contents = self.images[0];
}

- (void)animate {
    CAKeyframeAnimation* anim = 
        [CAKeyframeAnimation animationWithKeyPath:@"contents"];
    anim.values = self.images;
    anim.keyTimes = @[@0,@0.25,@0.5,@0.75,@1];
    anim.calculationMode = kCAAnimationDiscrete;
    anim.duration = 1.5;
    anim.repeatCount = HUGE_VALF;

    CABasicAnimation* anim2 = 
        [CABasicAnimation animationWithKeyPath:@"position"];
    anim2.duration = 10;
    anim2.toValue = [NSValue valueWithCGPoint: CGPointMake(350,30)];

    CAAnimationGroup* group = [CAAnimationGroup animation];
    group.animations = @[anim, anim2];
    group.duration = 10;

    [self.sprite addAnimation:group forKey:nil];
}