Welcome to OGeek Q&A Community for programmer and developer-Open, Learning and Share
Welcome To Ask or Share your Answers For Others

Categories

0 votes
296 views
in Technique[技术] by (71.8m points)

objective c - Animate infinite scrolling of an image in a seamless loop

Currently I have an image of clouds of 2048x435 that scrolls across a Landscaped oriented UIImageView of 1024x435 using CABasicAnimation. The cloud image scrolls as it should, however I am having some trouble trying to to get a duplicate clouds image to connect to the back of the the current clouds image so that there is no gap between the clouds images. I've been struggling for about a day trying to find a solution, so any help would be greatly appreciated. My current code:

developing on Xcode 4.2 for iOS 5 with ARC non-storyboard ipad landscaped orientation.

-(void)cloudScroll
{
    UIImage *cloudsImage = [UIImage imageNamed:@"TitleClouds.png"];
    CALayer *cloud = [CALayer layer];
    cloud.contents = (id)cloudsImage.CGImage;
    cloud.bounds = CGRectMake(0, 0, cloudsImage.size.width, cloudsImage.size.height);
    cloud.position = CGPointMake(self.view.bounds.size.width / 2, cloudsImage.size.height / 2);
    [cloudsImageView.layer addSublayer:cloud];

    CGPoint startPt = CGPointMake(self.view.bounds.size.width + cloud.bounds.size.width / 2, cloud.position.y);
    CGPoint endPt = CGPointMake(cloud.bounds.size.width / -2, cloud.position.y);
    CABasicAnimation *anim = [CABasicAnimation animationWithKeyPath:@"position"];
    anim.timingFunction = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionLinear];
    anim.fromValue = [NSValue valueWithCGPoint:startPt];
    anim.toValue = [NSValue valueWithCGPoint:endPt];
    anim.repeatCount = HUGE_VALF;
    anim.duration = 60.0;
    [cloud addAnimation:anim forKey:@"position"];
}

-(void)viewDidLoad
{
    [self cloudScroll];
    [super viewDidLoad];
}
See Question&Answers more detail:os

与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
Welcome To Ask or Share your Answers For Others

1 Reply

0 votes
by (71.8m points)

You say that your image is 2048 wide and your view is 1024 wide. I don't know if this means you have duplicated the contents of a 1024-wide image to make a 2048-wide image.

Anyway, here's what I suggest. We'll need to store the cloud layer and its animation in instance variables:

@implementation ViewController {
    CALayer *cloudLayer;
    CABasicAnimation *cloudLayerAnimation;
}

Instead of setting the cloud layer's content to the cloud image, we set its background color to a pattern color created from the image. That way, we can set the layer's bounds to whatever we want and the image will be tiled to fill the bounds:

-(void)cloudScroll {
    UIImage *cloudsImage = [UIImage imageNamed:@"TitleClouds.png"];
    UIColor *cloudPattern = [UIColor colorWithPatternImage:cloudsImage];
    cloudLayer = [CALayer layer];
    cloudLayer.backgroundColor = cloudPattern.CGColor;

However, a CALayer's coordinate system puts the origin at the lower left instead of the upper left, with the Y axis increasing up. This means that the pattern will be drawn upside-down. We can fix that by flipping the Y axis:

    cloudLayer.transform = CATransform3DMakeScale(1, -1, 1);

By default, a layer's anchor point is at its center. This means that setting the layer's position sets the position of its center. It will be easier to position the layer by setting the position of its upper-left corner. We can do that by moving its anchor point to its upper-left corner:

    cloudLayer.anchorPoint = CGPointMake(0, 1);

The width of the layer needs to be the width of the image plus the width of the containing view. That way, as we scroll the layer so that the right edge of the image comes into view, another copy of the image will be drawn to the right of the first copy.

    CGSize viewSize = self.cloudsImageView.bounds.size;
    cloudLayer.frame = CGRectMake(0, 0, cloudsImage.size.width + viewSize.width, viewSize.height);

Now we're ready to add the layer to the view:

    [self.cloudsImageView.layer addSublayer:cloudLayer];

Now let's set up the animation. Remember that we changed the layer's anchor point, so we can control its position by setting the position of its upper-left corner. We want the layer's upper-left corner to start at the view's upper-left corner:

    CGPoint startPoint = CGPointZero;

and we want the layer's upper-left corner to move left by the width of the image:

    CGPoint endPoint = CGPointMake(-cloudsImage.size.width, 0);

The rest of the animation setup is the same as your code. I changed the duration to 3 seconds for testing:

    cloudLayerAnimation = [CABasicAnimation animationWithKeyPath:@"position"];
    cloudLayerAnimation.timingFunction = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionLinear];
    cloudLayerAnimation.fromValue = [NSValue valueWithCGPoint:startPoint];
    cloudLayerAnimation.toValue = [NSValue valueWithCGPoint:endPoint];
    cloudLayerAnimation.repeatCount = HUGE_VALF;
    cloudLayerAnimation.duration = 3.0;

We'll call another method to actually attach the animation to the layer:

    [self applyCloudLayerAnimation];
}

Here's the method that applies the animation:

- (void)applyCloudLayerAnimation {
    [cloudLayer addAnimation:cloudLayerAnimation forKey:@"position"];
}

When the application enters the background (because the user switched to another app), the system removes the animation from the cloud layer. So we need to reattach it when we enter the foreground again. That's why we have the applyCloudLayerAnimation method. We need to call that method when the app enters the foreground.

In viewDidAppear:, we can start observing the notification that tells us the app has entered the foreground:

- (void)viewDidAppear:(BOOL)animated {
    [super viewDidAppear:animated];
    [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(applicationWillEnterForeground:) name:UIApplicationWillEnterForegroundNotification object:nil];
}

We need to stop observing the notification when our view disappears, or when the view controller is deallocated:

- (void)viewWillDisappear:(BOOL)animated {
    [super viewWillDisappear:animated];
    [[NSNotificationCenter defaultCenter] removeObserver:self name:UIApplicationDidBecomeActiveNotification object:nil];
}

- (void)dealloc {
    [[NSNotificationCenter defaultCenter] removeObserver:self name:UIApplicationWillEnterForegroundNotification object:nil];
}

When the view controller actually receives the notification, we need to apply the animation again:

- (void)applicationWillEnterForeground:(NSNotification *)note {
    [self applyCloudLayerAnimation];
}

Here's all the code together for easy copy and paste:

- (void)viewDidLoad {
    [self cloudScroll];
    [super viewDidLoad];
}

-(void)cloudScroll {
    UIImage *cloudsImage = [UIImage imageNamed:@"TitleClouds.png"];
    UIColor *cloudPattern = [UIColor colorWithPatternImage:cloudsImage];
    cloudLayer = [CALayer layer];
    cloudLayer.backgroundColor = cloudPattern.CGColor;

    cloudLayer.transform = CATransform3DMakeScale(1, -1, 1);

    cloudLayer.anchorPoint = CGPointMake(0, 1);

    CGSize viewSize = self.cloudsImageView.bounds.size;
    cloudLayer.frame = CGRectMake(0, 0, cloudsImage.size.width + viewSize.width, viewSize.height);

    [self.cloudsImageView.layer addSublayer:cloudLayer];

    CGPoint startPoint = CGPointZero;
    CGPoint endPoint = CGPointMake(-cloudsImage.size.width, 0);
    cloudLayerAnimation = [CABasicAnimation animationWithKeyPath:@"position"];
    cloudLayerAnimation.timingFunction = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionLinear];
    cloudLayerAnimation.fromValue = [NSValue valueWithCGPoint:startPoint];
    cloudLayerAnimation.toValue = [NSValue valueWithCGPoint:endPoint];
    cloudLayerAnimation.repeatCount = HUGE_VALF;
    cloudLayerAnimation.duration = 3.0;
    [self applyCloudLayerAnimation];
}

- (void)applyCloudLayerAnimation {
    [cloudLayer addAnimation:cloudLayerAnimation forKey:@"position"];
}

- (void)viewDidUnload {
    [self setCloudsImageView:nil];
    [super viewDidUnload];
    // Release any retained subviews of the main view.
    // e.g. self.myOutlet = nil;
}

- (void)viewDidAppear:(BOOL)animated {
    [super viewDidAppear:animated];
    [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(applicationWillEnterForeground:) name:UIApplicationWillEnterForegroundNotification object:nil];
}

- (void)viewWillDisappear:(BOOL)animated {
    [super viewWillDisappear:animated];
    [[NSNotificationCenter defaultCenter] removeObserver:self name:UIApplicationDidBecomeActiveNotification object:nil];
}

- (void)dealloc {
    [[NSNotificationCenter defaultCenter] removeObserver:self name:UIApplicationWillEnterForegroundNotification object:nil];
}

- (void)applicationWillEnterForeground:(NSNotification *)note {
    [self applyCloudLayerAnimation];
}

与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
OGeek|极客中国-欢迎来到极客的世界,一个免费开放的程序员编程交流平台!开放,进步,分享!让技术改变生活,让极客改变未来! Welcome to OGeek Q&A Community for programmer and developer-Open, Learning and Share
Click Here to Ask a Question

...