Как вы устанавливаете продолжительность анимации UICollectionView?

У меня есть пользовательский макет потока, который настраивает атрибуты для ячеек, когда они вставляются и удаляются из CollectionView, с помощью следующих двух функций, но я не могу понять, как вы могли бы настроить продолжительность анимации по умолчанию.

- (UICollectionViewLayoutAttributes *)initialLayoutAttributesForAppearingItemAtIndexPath:(NSIndexPath *)itemIndexPath {
    UICollectionViewLayoutAttributes* attributes = [self layoutAttributesForItemAtIndexPath:itemIndexPath];

    // Assign the new layout attributes
    attributes.transform3D = CATransform3DMakeScale(0.5, 0.5, 0.5);
    attributes.alpha = 0;

    return attributes;
}

- (UICollectionViewLayoutAttributes *)finalLayoutAttributesForDisappearingItemAtIndexPath:(NSIndexPath *)itemIndexPath {

    UICollectionViewLayoutAttributes* attributes = [self layoutAttributesForItemAtIndexPath:itemIndexPath];

    // Assign the new layout attributes
    attributes.transform3D = CATransform3DMakeScale(0.5, 0.5, 0.5);
    attributes.alpha = 0;

    return attributes;
}

person James Parker    schedule 16.10.2012    source источник
comment
Согласно документации Apple, при анимации изменений макета время и параметры анимации контролируются представлением коллекции. Это относится к методу setCollectionView:animated:, но я подозреваю, что то же самое верно и для изменения границ представления коллекции. Извините, я больше не могу помочь, я застрял в той же проблеме. Я подозреваю, что ответ лежит где-то внутри самого объекта UICollectionView.   -  person Ash    schedule 24.02.2013


Ответы (9)


Чтобы решить проблему без взлома, которая была предложена в ответе gavrix, вы можете создать подкласс UICollectionViewLayoutAttributes с новым свойством CABasicAnimation *transformAnimation, а затем создать пользовательское преобразование с подходящей продолжительностью и назначьте его атрибутам в initialLayoutAttributesForAppearingItemAtIndexPath, затем в UICollectionViewCell примените атрибуты по мере необходимости:

@interface AnimationCollectionViewLayoutAttributes : UICollectionViewLayoutAttributes
@property (nonatomic, strong)  CABasicAnimation *transformAnimation;
@end

@implementation AnimationCollectionViewLayoutAttributes
- (id)copyWithZone:(NSZone *)zone
{
    AnimationCollectionViewLayoutAttributes *attributes = [super copyWithZone:zone];
    attributes.transformAnimation = _transformAnimation;
    return attributes;
}

- (BOOL)isEqual:(id)other {
    if (other == self) {
        return YES;
    }
    if (!other || ![[other class] isEqual:[self class]]) {
        return NO;
    }
    if ([(( AnimationCollectionViewLayoutAttributes *) other) transformAnimation] != [self transformAnimation]) {
        return NO;
    }

    return YES;
}
@end

В классе Макет

- (UICollectionViewLayoutAttributes *)initialLayoutAttributesForAppearingItemAtIndexPath:(NSIndexPath *)itemIndexPath {
    AnimationCollectionViewLayoutAttributes* attributes = (AnimationCollectionViewLayoutAttributes* )[super initialLayoutAttributesForAppearingItemAtIndexPath:itemIndexPath];

    CABasicAnimation *transformAnimation = [CABasicAnimation animationWithKeyPath:@"transform"];
    transformAnimation.duration = 1.0f;
    CGFloat height = [self collectionViewContentSize].height;

    transformAnimation.fromValue = [NSValue valueWithCATransform3D:CATransform3DMakeTranslation(0, 2*height, height)];
    transformAnimation.toValue = [NSValue valueWithCATransform3D:CATransform3DMakeTranslation(0, attributes.bounds.origin.y, 0)];
    transformAnimation.removedOnCompletion = NO;
    transformAnimation.fillMode = kCAFillModeForwards;
    attributes.transformAnimation = transformAnimation;
    return attributes;
}

+ (Class)layoutAttributesClass { 
    return [AnimationCollectionViewLayoutAttributes class]; 
}

затем в UICollectionViewCell примените атрибуты

- (void) applyLayoutAttributes:(AnimationCollectionViewLayoutAttributes *)layoutAttributes
{
    [[self layer] addAnimation:layoutAttributes.transformAnimation forKey:@"transform"];
}
person zyxel    schedule 26.02.2014
comment
Вам также необходимо переопределить +layoutAttributesClass, чтобы вернуть [AnimationCollectionViewLayoutAttributes class] в классе макета. - person Marcelo Fabri; 28.05.2014
comment
добавьте вышеуказанный метод в свой класс CustomFlowLayout.m! - person Hashem Aboonajmi; 28.09.2015

изменить скорость CALayer

@implementation Cell
- (id)initWithFrame:(CGRect)frame
{
self = [super initWithFrame:frame];
if (self) {
    self.layer.speed =0.2;//default speed  is 1
}
return self;
}
person rotoava    schedule 18.04.2014
comment
Это работает отлично, рад сказать. Это намного проще, чем другие обходные пути. Я добавлю, что большее число означает более быструю анимацию. - person sudo; 10.06.2015

Основываясь на ответе @rotava, вы можете временно установить скорость анимации, используя пакетное обновление представления коллекции:

[self.collectionView performBatchUpdates:^{
    [self.collectionView.viewForBaselineLayout.layer setSpeed:0.2];
    [self.collectionView insertItemsAtIndexPaths: insertedIndexPaths];
} completion:^(BOOL finished) {
    [self.collectionView.viewForBaselineLayout.layer setSpeed:1];
}];
person Ashley Mills    schedule 05.02.2016
comment
Мне интересно, важно ли здесь логическое значение finished. При некоторых вызовах (сейчас точно не помню каких) блок completion вызывается более одного раза. Чтобы быть полностью уверенным, что анимации закончились, я бы сделал if ( finished ) { /* ... */ }. Почему здесь это не нужно? Или это так, и вы просто пропустили это? - person Alejandro Iván; 14.07.2016
comment
Если performBatchUpdates имеет шанс быть вызванным во время выполнения предыдущих анимаций, установка скорости слоя обратно на 1 приведет к тому, что предыдущие анимации будут переходить вперед (по мере изменения масштаба времени) даже к конечным позициям. Если вам не нужны никакие другие анимации (кроме performBatchUpdates ), вы можете установить скорость слоя и оставить ее такой. - person Wojciech Nagrodzki; 05.09.2019

После безуспешных попыток [CATransaction setAnimationDuration:] и [UIView setAnimationDuration:] на всех возможных этапах процесса компоновки я придумал несколько хакерский способ изменить продолжительность анимации ячеек, созданных UICollectionView, который не зависит от частных API.

Вы можете использовать свойство speed CALayer, чтобы изменить относительную синхронизацию мультимедиа анимаций, выполняемых на данном слое. Чтобы это работало с UICollectionView, вы можете изменить layer.speed на значение меньше 1 на слое ячейки. Очевидно, что не очень хорошо, чтобы слой ячейки ВСЕГДА имел скорость анимации, отличную от единицы, поэтому один из вариантов — отправить NSNotification при подготовке к анимации ячейки, на которую подписываются ваши ячейки, что изменит скорость слоя, а затем вернет ее обратно. в подходящее время после завершения анимации.

Я не рекомендую использовать этот подход в качестве долгосрочного решения, поскольку он довольно окольный, но он работает. Надеемся, что в будущем Apple предоставит больше возможностей для анимации UICollectionView.

person roperklacks    schedule 12.08.2013

UICollectionView инициирует все анимации внутри, используя какое-то жестко заданное значение. Однако вы всегда можете переопределить это значение, пока анимация не будет зафиксирована. В целом процесс выглядит так:

  • начать анимацию
  • получить все атрибуты макета
  • применять атрибуты к представлениям (UICollectionViewCell)
  • зафиксировать анимацию

применение атрибутов выполняется в каждой ячейке UICollectionViewCell, и вы можете переопределить анимациюDuration в соответствующем методе. Проблема в том, что UICollectionViewCell имеет общедоступный метод applyLayoutAttributes:, НО его реализация по умолчанию пуста!. По сути, у UICollectionViewCell есть другой частный метод, называемый _setLayoutAttributes:, и этот частный метод вызывается UICollectionView, а этот частный метод вызывает applyLayoutAttributes: в конце. Атрибуты макета по умолчанию, такие как кадр, положение, преобразование, применяются с текущим animationDuration до вызова applyLayoutAttributes:. Тем не менее, вы должны переопределить animationDuration в частном методе _setLayoutAttributes:

- (void) _setLayoutAttributes:(PSTCollectionViewLayoutAttributes *)layoutAttributes
{
    [UIView setAnimationDuration:3.0];
    [super _setLayoutAttributes:layoutAttributes];
}

Это, очевидно, не безопасно для AppleStore. Вы можете использовать один из этих хаков во время выполнения, чтобы безопасно переопределить этот закрытый метод.

person gavrix    schedule 28.02.2013

Вы можете установить свойство скорости слоя (например, в ответе Rotoava), чтобы изменить управление скоростью анимации. Проблема в том, что вы используете произвольные значения, потому что не знаете фактическую продолжительность анимации вставки.

Используя это сообщение, вы можете выяснить, что продолжительность анимации по умолчанию.

newAnimationDuration = (1/layer.speed)*originalAnimationDuration
layer.speed = originalAnimationDuration/newAnimationDuration

Если вы хотите сделать анимацию продолжительностью 400 мс, в макете вы должны:

- (UICollectionViewLayoutAttributes *)initialLayoutAttributesForAppearingItemAtIndexPath:(NSIndexPath *)indexPath
{
    UICollectionViewLayoutAttributes* attributes = [super finalLayoutAttributesForDisappearingItemAtIndexPath:indexPath];
    //set attributes here
    UICollectionViewCell *cell = [self.collectionView cellForItemAtIndexPath:indexPath];
    CGFloat originalAnimationDuration = [CATransaction animationDuration];
    CGFloat newAnimationDuration = 0.4f;
    cell.layer.speed = originalAnimationDuration/newAnimationDuration;
    return attributes;
}

В моем случае у меня были ячейки, которые можно было перетаскивать за пределы экрана, и я хотел изменить продолжительность анимации удаления в зависимости от скорости жеста панорамирования.

В распознавателе жестов (который должен быть частью вашего представления коллекции):

- (void)handlePanGesture:(UIPanGestureRecognizer *)sender
{
    CGPoint dragVelocityVector = [sender velocityInView:self.collectionView];
    CGFloat dragVelocity = sqrt(dragVelocityVector.x*dragVelocityVector.x + dragVelocityVector.y*dragVelocityVector.y);
    switch (sender.state) {
    ...
    case UIGestureRecognizerStateChanged:{
        CustomLayoutClass *layout = (CustomLayoutClass *)self.collectionViewLayout;
        layout.dragSpeed = fabs(dragVelocity);
    ...
    }
    ...
}

Затем в вашем customLayout:

- (UICollectionViewLayoutAttributes *)finalLayoutAttributesForDisappearingItemAtIndexPath:(NSIndexPath *)indexPath
{
    UICollectionViewLayoutAttributes* attributes = [super finalLayoutAttributesForDisappearingItemAtIndexPath:indexPath];
    CGFloat animationDistance = sqrt((x2-x1)*(x2-x1)+(y2-y1)*(y2-y1));
    CGFloat originalAnimationDuration = [CATransaction animationDuration];
    CGFloat newAnimationDuration = animationDistance/self.dragSpeed;
    UICollectionViewCell *cell = [self.collectionView cellForItemAtIndexPath:indexPath];
    cell.layer.speed = originalAnimationDuration/newAnimationDuration;
    return attributes;
}
person ndey96    schedule 14.09.2015

Обновление @AshleyMills, поскольку forBaselineLayout устарело.

Это работает

self.collectionView.performBatchUpdates({ () -> Void in
    let indexSet = IndexSet(0...(numberOfSections - 1))
    self.collectionView.insertSections(indexSet)
    self.collectionView.forFirstBaselineLayout.layer.speed = 0.5
}, completion: { (finished) -> Void in
    self.collectionView.forFirstBaselineLayout.layer.speed = 1.0
})
person Ryan Heitner    schedule 09.08.2017

Вы можете изменить свойство UICollectionView layout.speed, которое должно изменить продолжительность анимации вашего макета...

person Skodik.o    schedule 03.02.2015

Без подкласса:

[UIView animateWithDuration:2.0 animations:^{
  [self.collection reloadSections:indexSet];
}];
person bauerMusic    schedule 15.04.2018
comment
Я удивлен, что это нижний ответ. Работал на меня. Я сделал performBatchUpdates в UIView анимированном закрытии вместо reloadSections - person Sandy Chapman; 24.07.2018