Как установить продолжительность анимации 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;
}
Ответы
Ответ 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, которое должно изменить продолжительность анимации вашего макета...