xcode 在无缝循环中动画无限滚动图像

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/8790079/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-09-14 22:56:52  来源:igfitidea点击:

Animate infinite scrolling of an image in a seamless loop

objective-cxcodecocoa-touchipad

提问by r00tb0x

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:

目前,我有一张 2048x435 的云图像,它使用 CABasicAnimation 在 1024x435 的面向景观的 UIImageView 上滚动。云图像按原样滚动,但是我在尝试获取重复的云图像以连接到当前云图像的背面时遇到了一些麻烦,因此云图像之间没有间隙。我一直在努力寻找解决方案大约一天,所以任何帮助将不胜感激。我目前的代码:

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

在 Xcode 4.2 for iOS 5 上开发,ARC 非故事板 ipad 横向。

-(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];
}

回答by rob mayoff

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.

你说你的图像是 2048 宽,你的视图是 1024 宽。我不知道这是否意味着您已将 1024 宽图像的内容复制为 2048 宽图像。

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:

但是,CALayer 的坐标系将原点放在左下角而不是左上角,Y 轴向上增加。这意味着图案将被颠倒绘制。我们可以通过翻转 Y 轴来解决这个问题:

    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:

其余的动画设置与您的代码相同。我将持续时间更改为 3 秒进行测试:

    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 applyCloudLayerAnimationmethod. We need to call that method when the app enters the foreground.

当应用程序进入后台时(因为用户切换到另一个应用程序),系统将动画从云层中移除。所以当我们再次进入前台时需要重新附加它。这就是为什么我们有这个applyCloudLayerAnimation方法。当应用程序进入前台时,我们需要调用该方法。

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

在 中viewDidAppear:,我们可以开始观察告诉我们应用程序已进入前台的通知:

- (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];
}

回答by Wesley Smith

Rob, you rock brother. I was looking for a way to do the same thing but vertically. After reading your answer above I had no problem adjusting it to suit my needs. For anyone that may end up here on their hunt for a vertical solution this is what I ended up with, modeled after the above:

罗布,你这个摇滚兄弟。我正在寻找一种方法来做同样的事情,但垂直。阅读您上面的答案后,我可以轻松调整它以满足我的需求。对于任何可能最终在这里寻找垂直解决方案的人来说,这就是我最终得到的,以上述为模型:

- (IBAction)animateBackground6
{
    UIImage *backgroundImage = [UIImage imageNamed:@"space.png"];
    UIColor *backgroundPattern = [UIColor colorWithPatternImage:backgroundImage];

    CALayer *background = [CALayer layer];
    background.backgroundColor = backgroundPattern.CGColor;
    background.transform = CATransform3DMakeScale(1, -1, 1);
    background.anchorPoint = CGPointMake(0, 1);

    CGSize viewSize = self.backgroundImageView.bounds.size;
    background.frame = CGRectMake(0, 0, viewSize.width,  backgroundImage.size.height +   viewSize.height);
    [self.backgroundImageView.layer addSublayer:background];

    CGPoint startPoint = CGPointZero;
    CGPoint endPoint = CGPointMake(0, -backgroundImage.size.height);

    CABasicAnimation *animation = [CABasicAnimation animationWithKeyPath:@"position"];
    animation.timingFunction = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionLinear];
    animation.fromValue = [NSValue valueWithCGPoint:endPoint];
    animation.toValue = [NSValue valueWithCGPoint:startPoint];
    animation.repeatCount = HUGE_VALF;
    animation.duration = 5.0;
    [background addAnimation:animation forKey:@"position"];
}

回答by JonK

original solution with options for vertical and horizontal scroll

带有垂直和水平滚动选项的原始解决方案

//
//  originally found here: http://stackoverflow.com/questions/8790079/animate-infinite-scrolling-of-an-image-in-a-seamless-loop
//

#import <UIKit/UIKit.h>
#import <QuartzCore/QuartzCore.h>

@interface TiledCloundScrollViewController : UIViewController {
    CALayer *cloudLayer;
    CABasicAnimation *cloudLayerAnimation;

    UIImage *cloudsImage;
    BOOL verticalScroll;
    CFTimeInterval animationDuration;
}

- (id) initWithImage:(UIImage*)cloudsImage verticalScroll:(BOOL)verticalScroll animationDuration:(CFTimeInterval)animationDuration;

@end



#import "TiledCloundScrollViewController.h"

@interface TiledCloundScrollViewController ()
@end

@implementation TiledCloundScrollViewController

- (id) init {
    [self doesNotRecognizeSelector:_cmd];
    return nil;
}

- (id) initWithImage:(UIImage*)image verticalScroll:(BOOL)vScroll animationDuration:(CFTimeInterval)duration {
    self = [super init];
    if (self ) {
        cloudsImage = image;
        verticalScroll = vScroll;
        animationDuration = duration;
    }
    return self;
}

- (void) viewDidLoad {
    [super viewDidLoad];

    self.view.clipsToBounds = YES;
    const CGSize viewSize = self.view.bounds.size;
    const CGSize imageSize = cloudsImage.size;

    UIColor *cloudPattern = [UIColor colorWithPatternImage:cloudsImage];
    cloudLayer = [CALayer layer];
    cloudLayer.backgroundColor = cloudPattern.CGColor;
    cloudLayer.transform = CATransform3DMakeScale(1, -1, 1);
    cloudLayer.anchorPoint = CGPointMake(0, 1);
    [self.view.layer addSublayer:cloudLayer];

    CGPoint startPoint = CGPointZero;
    CGPoint endPoint;
    if (verticalScroll) {
        endPoint = CGPointMake(0, -imageSize.height);
        cloudLayer.frame = CGRectMake(0, 0, viewSize.width, viewSize.height + imageSize.height);
    } else {
        endPoint = CGPointMake(-imageSize.width, 0);
        cloudLayer.frame = CGRectMake(0, 0, viewSize.width + imageSize.width, viewSize.height);
    }

    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 = animationDuration;
    [self applyCloudLayerAnimation];
}

- (void) viewDidUnload {
    cloudLayer = nil;
    cloudLayerAnimation = nil;
    [super viewDidUnload];
}

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

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

- (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];
}


@end