Подтвердить что ты не робот

Как установить продолжительность анимации 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;
}
4b9b3361

Ответ 1

Чтобы решить проблему без взлома, которая была предложена в ответе gavrix, вы можете создать подкласс UICollectionViewLayoutAttributes с новым свойством CABasicAnimation *transformAnimation, чем создать настраиваемое преобразование с подходящей продолжительностью и назначить его атрибутам в initialLayoutAttributesForAppearingItemAtIndexPath, затем в UICollectionViewSell, как в 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"];
}

Ответ 2

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

@implementation Cell
- (id)initWithFrame:(CGRect)frame
{
self = [super initWithFrame:frame];
if (self) {
    self.layer.speed =0.2;//default speed  is 1
}
return self;
}

Ответ 3

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

[self.collectionView performBatchUpdates:^{
    [self.collectionView.viewForBaselineLayout.layer setSpeed:0.2];
    [self.collectionView insertItemsAtIndexPaths: insertedIndexPaths];
} completion:^(BOOL finished) {
    [self.collectionView.viewForBaselineLayout.layer setSpeed:1];
}];

Ответ 4

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

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

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

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

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

Ответ 5

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

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

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

Ответ 6

Вы можете установить свойство скорости слоя (как в ответе 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;
}

Ответ 7

Обновление @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
})

Ответ 8

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

[UIView animateWithDuration:2.0 animations:^{
  [self.collection reloadSections:indexSet];
}];

Ответ 9

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