Есть ли проблема с этим использованием kCAMediaTimingFunctionEaseIn?
Я использую CALayer
, CAReplicatorLayer
а также CABasicAnimation
оживить гистограмму на место. Гистограмма состоит из "сегментов", которые я представляю как маленькие прямоугольники с CALayer
, Затем я добавляю их в качестве подслоев CAReplicatorLayer
в петле. Наконец, я добавляю анимацию для каждого маленького прямоугольника, который помещает прямоугольник на свое место в диаграмме.
Вот окончательное состояние анимации:
И вот как это выглядит в середине анимации:
Вот код, который делает это возможным:
#define BLOCK_HEIGHT 6.0f
#define BLOCK_WIDTH 8.0f
@interface TCViewController ()
@property (nonatomic, strong) NSMutableArray * blockLayers; // blocks that fall from the sky
@property (nonatomic, strong) NSMutableArray * replicatorLayers; // replicator layers that will replicate the blocks into position
@end
@implementation TCViewController
- (void)viewDidLoad {
[super viewDidLoad];
self.view.backgroundColor = [UIColor lightGrayColor];
self.blockLayers = [NSMutableArray array];
self.replicatorLayers = [NSMutableArray array];
NSArray * dataBlocksData = @[@1,@2,@3,@1,@5,@2,@11,@14,@5,@12,@10,@3,@7,@6,@3,@4,@1];
__block CGFloat currentXPosition = 0.0f;
[dataBlocksData enumerateObjectsUsingBlock:^(NSNumber * obj, NSUInteger idx, BOOL *stop) {
currentXPosition += BLOCK_WIDTH + 2.0f;
if (obj.integerValue == 0) return;
// replicator layer for data blocks in a column
CAReplicatorLayer * replicatorLayer = [[CAReplicatorLayer alloc] init];
replicatorLayer.frame = CGRectMake(currentXPosition, 0.0f, BLOCK_WIDTH, 200.0f);
// single data block layer (must be new for each replicator layer. can't reuse existing layer)
CALayer * blockLayer = [[CALayer alloc] init];
blockLayer.frame = CGRectMake(0, 200-BLOCK_HEIGHT, BLOCK_WIDTH, BLOCK_HEIGHT);
blockLayer.backgroundColor = [UIColor whiteColor].CGColor;
[replicatorLayer addSublayer:blockLayer];
replicatorLayer.instanceCount = obj.integerValue;
replicatorLayer.instanceDelay = 0.1f;
replicatorLayer.instanceTransform = CATransform3DMakeTranslation(0, -BLOCK_HEIGHT, 0); // negative height moves back up the column
[self.blockLayers addObject:blockLayer];
[self.replicatorLayers addObject:replicatorLayer];
}];
}
- (void)viewDidLayoutSubviews {
// add replicator layers as sublayers
[self.replicatorLayers enumerateObjectsUsingBlock:^(CAReplicatorLayer * obj, NSUInteger idx, BOOL *stop) {
[self.view.layer addSublayer:obj];
}];
}
- (void)viewDidAppear:(BOOL)animated {
// add animations to each block layer
[self.blockLayers enumerateObjectsUsingBlock:^(CALayer * obj, NSUInteger idx, BOOL *stop) {
// position animation
CABasicAnimation * animation = [CABasicAnimation animationWithKeyPath:@"transform.translation.y"];
animation.fromValue = @(-300);
animation.toValue = @(0);
animation.duration = 1.0f;
animation.fillMode = kCAFillModeBoth;
animation.removedOnCompletion = NO;
animation.timingFunction = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionDefault];
[obj addAnimation:animation forKey:@"falling-block-animation"];
}];
}
Наконец, вот подвох. Если вы укажете нет timingFunction
, Linear
функция или EaseIn
Функция анимации не полностью завершена. Как блоки не падают полностью на место. Похоже, они близки к завершению, но не совсем. Просто кадр или два. Поменяйте местами назначение функции синхронизации в -viewDidAppear:
и вы получите эту странность
Давай, попробуй. Я даже пытался указать функцию с контрольными точками ( ссылка), которые точно так же, как EaseIn
функция:
// use control points for an EaseIn timing:
animation.timingFunction = [CAMediaTimingFunction functionWithControlPoints:0.42 :0.00 :1.0 :1.0];
// or simply specify the ease in timing function:
animation.timingFunction = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseIn];
По какой-то причине EaseOut
а также Default
функции хронометража работают нормально, но простота в функции стиля, кажется, занимает больше времени, визуально, возможно, из-за длины кривой интерполяции?
Я пробовал много вещей, чтобы сузить вопрос:
- Не зацикливаться на этих структурах и использовать только 1 слой и 1 слой репликатора с одинаковой анимацией. Под этим я подразумеваю анимацию только одного столбца. Неудачно. Циклы не проблема.
- Поместите код создания, добавления подслоя и добавления анимации в
-viewDidAppear
, Похоже, ничего не изменилось, если анимации были добавлены раньше или позже в жизненном цикле представления. - Использование свойств для моих структур. Нет разницы.
- Не используя свойства для моих структур. Опять без разницы.
- Добавлены обратные вызовы для завершения анимации, чтобы удалить анимацию, чтобы столбцы оставались в правильном "конечном" состоянии. Это было не хорошо, но стоит попробовать.
- Эта проблема присутствует в iOS 6 и 7.
Я действительно хочу использовать EaseIn
функция синхронизации, потому что это лучшая анимация для чего-то, что встало на свои места. Я также хочу использовать CAReplicatorLayer
потому что он делает именно то, что я хочу сделать.
Так что не так с EaseIn
функции времени, как я их использую? Это ошибка?
1 ответ
Я согласен с тем, что это, похоже, ошибка в комбинации функции синхронизации CAReplicatorLayer и kCAMediaTimingFunctionEaseIn.
В качестве обходного пути я предлагаю использовать анимацию ключевых кадров с тремя ключевыми кадрами. Первые два ключевых кадра определяют желаемую анимацию с легкостью синхронизации. Последний ключевой кадр определен как идентичный второму ключевому кадру, но дает анимации дополнительное время, чтобы сесть на последний ключевой кадр, что, по-видимому, позволяет слою репликатора анимировать последний реплицированный слой в конечное состояние.
Поэтому мое предложение таково:
CAKeyframeAnimation * animation = [CAKeyframeAnimation animationWithKeyPath:@"transform.translation.y"];
// extra keyframe duplicating final state
animation.values = @[@-300.0f, @0.0f, @0.0f];
// double length of original animation, with last half containing no actual animation
animation.keyTimes = @[@0.0, @0.5, @1.0];
animation.duration = 2.0f;
animation.fillMode = kCAFillModeBoth;
animation.removedOnCompletion = NO;
// ease-in for first half of animation; default after
animation.timingFunctions = @[[CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseIn], [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionDefault]];
[obj addAnimation:animation forKey:@"falling-block-animation"];