How can I chain Core Animations for different Laye

2019-03-18 17:20发布

问题:

I have a scrollView with paging enabled and a number N of pages, which are UIViews as subviews of the scrollView.

I'm trying to do the following:

User scrolls to page number n. At that point, 7 CALayers which were previously added to page number n (that is, to page [[scrollView subviews] objectAtIndex:n-1].layer subLayers]) fade in, one after the other.

But I can't figure out how to make the CALayers fadeIn sequentially.So far, I've tried the following 3 approaches from my controller's delegate method: (assume I have an array to the layers and that their opacity was set to 0 upon creation)

- (void)scrollViewDidEndDecelerating:(UIScrollView *)scrollView
{
  int pageNumber = floor(self.scrollView.contentOffset.x / self.scrollView.frame.size.width);
  if(pageNumber == (n-1))
  {
    int timeOffset = 0;

    [CATransaction begin];
    for(CALayer *layer in layerArray)
    {
      CABasicAnimation *a = [CABasicAnimation animationWithKeyPath:@"opacity"];
      a.duration = 6;
      a.beginTime = timeOffset++;
      a.fromValue = [NSNumber numberWithFloat:0.];
      a.toValue = [NSNumber numberWithFloat:1.];

      [layer addAnimation:a forKey:nil];
    }
    [CATransaction commit];
  }
}

- (void)scrollViewDidEndDecelerating:(UIScrollView *)scrollView
{
  int pageNumber = floor(self.scrollView.contentOffset.x / self.scrollView.frame.size.width);
  if(pageNumber == (n-1))
  {
    int timeOffset = 0;

    [CATransaction begin];
    for(CALayer *layer in layerArray)
    {
      CABasicAnimation *a = [CABasicAnimation animation];
      a.duration = 6;
      a.beginTime = timeOffset++;
      [layer addAnimation:a forKey:@"opacity"];
      [layer setOpacity:1];
    }
    [CATransaction commit];
  }
}


- (void)scrollViewDidEndDecelerating:(UIScrollView *)scrollView
{
  int pageNumber = floor(self.scrollView.contentOffset.x / self.scrollView.frame.size.width);
  if(pageNumber == (n-1))
  {
    int timeOffset = 0;


    for(CALayer *layer in layerArray)
    {
      [CATransaction begin];
      CABasicAnimation *a = [CABasicAnimation animation];
      a.duration = 6;
      a.beginTime = timeOffset++;
      [layer addAnimation:a forKey:@"opacity"];
      [layer setOpacity:1];
    }

    for(CALayer *layer in layerArray)
      [CATransaction commit];
  }
}

But neither seems to work. When the user scrolls to the right page, all layers become visible at once, without much of a fade and definitely not in any sequential order.

Any ideas?

回答1:

Actually, turns out the key is getting the current time in terms of a frame of reference and adding any time offset to that current time. This works also for non-grouped animations.

For instance, something along the lines of this code would cause n layers (assumed to be stored in some array) to sequentially fade in one after the other, each taking .8 secs.:

  CGFloat timeOffset = 0;
  [CATransaction begin];
  for(CALayer *layer in layers)
  {
    CABasicAnimation *a = [CABasicAnimation animationWithKeyPath:@"opacity"];
    a.fromValue = [NSNumber numberWithFloat:0.];
    a.toValue = [NSNumber numberWithFloat:1.];
    a.fillMode = kCAFillModeForwards;
    a.beginTime = [layer convertTime:CACurrentMediaTime() fromLayer:nil] + timeOffset;
    a.duration = .8;
    a.removedOnCompletion = NO;
    [layer addAnimation:a forKey:nil];

    timeOffset += .8;
  }
  [CATransaction commit];

In the above case, the frame of reference is simply the current time when the invocations take place.



回答2:

The beginTime property of a CAAnimation only seems to work if the CAAnimation is part of a CAAnimationGroup. I think you'll also need to set the duration property of the CAAnimationGroup large enough to last until its final animation completes.

https://stackoverflow.com/a/563486/77567



回答3:

In Swift 3 (layers is an array of CALayer or CAShapeLayer)

var timeOffset:Double = 0
for layer in layers {
    let a = CABasicAnimation(keyPath: "path"
    a.fromValue = layer.ovalPathSmall.cgPath
    a.toValue = layer.ovalPathLarge.cgPath
    a.fillMode = kCAFillModeForwards
    a.beginTime = CACurrentMediaTime() + timeOffset
    a.duration = 0.3
    a.isRemovedOnCompletion = false
    layer.add(a, forKey: nil)

    timeOffset += 0.3
}

And in case you're wondering what ovalPathSmall and ovalPathLarge are:

ovalPathSmall = UIBezierPath(arcCenter: position, radius: smallRadius, startAngle: 0, endAngle: 2 * .pi, clockwise: true)
ovalPathLarge = UIBezierPath(arcCenter: position, radius: largeRadius, startAngle: 0, endAngle: 2 * .pi, clockwise: true)