collectionView: didSelectItemAtIndexPath:
中标记单元格为选中状态,然后调用我的UICollectionView上的reloadData
来实现了非动画效果的大小变化。但是这一切发生得太快了,我不知道如何将大小的变化过渡到动画效果。有什么建议吗?
我已经找到了Animate uicollectionview cells on selection,但答案对我来说不够具体,我还没有弄清楚它是否也适用于我的情况。
collectionView: didSelectItemAtIndexPath:
中标记单元格为选中状态,然后调用我的UICollectionView上的reloadData
来实现了非动画效果的大小变化。在 Chase Roberts 的帮助下,我现在已经找到了解决我的问题的方法。我的代码基本上看起来像这样:
// Prepare for animation
[self.collectionView.collectionViewLayout invalidateLayout];
UICollectionViewCell *__weak cell = [self.collectionView cellForItemAtIndexPath:indexPath]; // Avoid retain cycles
void (^animateChangeWidth)() = ^()
{
CGRect frame = cell.frame;
frame.size = cell.intrinsicContentSize;
cell.frame = frame;
};
// Animate
[UIView transitionWithView:cellToChangeSize duration:0.1f options: UIViewAnimationOptionCurveLinear animations:animateChangeWidth completion:nil];
进一步解释:
其中最重要的一步是在使用的 UICollectionView.collectionViewLayout
上调用 invalidateLayout
方法。如果忘记这一步,很可能会发生单元格超出集合视图边界的情况——这是您最不希望发生的事情之一。同时,请考虑您的代码应该在某个时间点将修改后单元格的新尺寸呈现给布局视图。在我的情况下(我正在使用 UICollectionViewFlowLayout
),我调整了 collectionView:layout:sizeForItemAtIndexPath
方法以反映修改后单元格的新尺寸。如果忘记了这一点,则如果您先调用 invalidateLayout
,然后尝试动画更改大小,则单元格大小根本不会发生任何变化。
最奇怪的(至少对我来说)但也很重要的是,您不能在动画块内部调用 invalidateLayout
方法。如果这样做,动画不会平稳地显示,而是会出现故障和闪烁。
您必须使用单元格作为 transitionWithView
的参数来调用 UIView
的 transitionWithView:duration:options:animations:completion:
方法,而不是整个集合视图,因为您想要动画的是单元格,而不是整个集合。
我使用了我的单元格的 intrinsicContentSize
方法来返回所需的大小——在我的情况下,我根据单元格的选择状态调整其宽度以显示或隐藏按钮。
希望这会对一些人有所帮助。对我来说,花了几个小时才弄清楚如何使该动画正确工作,因此我尝试解放其他人的时间负担。
对于集合视图和表格视图来说,调整单元格大小的动画方式是一样的。如果希望对单元格大小进行动画处理,需要有一个反映单元格状态的数据模型(例如,我只使用标志 (CollectionViewCellExpanded, CollectionViewCellCollapsed) 并相应地返回 CGSize。
当您调用并执行空 performBathUpdates 调用时,视图会自动进行动画处理。
- (void)collectionView:(UICollectionView *)collectionView didSelectItemAtIndexPath:(NSIndexPath *)indexPath
{
[collectionView performBatchUpdates:^{
// append your data model to return a larger size for
// cell at this index path
} completion:^(BOOL finished) {
}];
}
我曾通过使用-setCollectionViewLayout:animated:
创建相同的集合视图布局的新实例成功地实现了动画效果。
例如,如果您正在使用UICollectionViewFlowLayout
,那么:
// Make sure that your datasource/delegate are up-to-date first
// Then animate the layout changes
[collectionView setCollectionViewLayout:[[UICollectionViewFlowLayout alloc] init] animated:YES];
contentOffset
会改变。 - Iulian Onofrei这是我找到的最简单的解决方案-功能非常棒。流畅的动画,不会破坏布局。
Swift
collectionView.performBatchUpdates({}){}
Objective-C
[collectionView performBatchUpdates:^{ } completion:^(BOOL finished) { }];
这些块(在Swift中称为闭包)应该故意保持为空!
reloadData
、moveItemAtIndexPath: toIndexPath:
、reloadItemsAtIndexPaths:
来解决问题,但后来发现应该将块保持为空。
现在真的非常完美。 - zalogatomek[UIView transitionWithView:collectionView
duration:.5
options:UIViewAnimationOptionTransitionCurlUp
animations:^{
//any animatable attribute here.
cell.frame = CGRectMake(3, 14, 100, 100);
} completion:^(BOOL finished) {
//whatever you want to do upon completion
}];
在didselectItemAtIndexPath
方法中玩弄类似这样的东西。
一种良好的实现单元格大小动画效果的方法是在collectionViewCell本身中重写isHighlighted方法。
class CollectionViewCell: UICollectionViewCell {
override var isHighlighted: Bool {
didSet {
if isHighlighted {
UIView.animate(withDuration: 0.2, delay: 0, options: .curveEaseOut, animations: {
self.transform = CGAffineTransform(scaleX: 0.95, y: 0.95)
}, completion: nil)
} else {
UIView.animate(withDuration: 0.2, delay: 0, options: .curveEaseOut, animations: {
self.transform = CGAffineTransform(scaleX: 1, y: 1)
}, completion: nil)
}
}
}
}
使用 performBatchUpdates 不会使单元格内容的布局动画化。相反,您可以使用以下方法:
collectionView.collectionViewLayout.invalidateLayout()
UIView.animate(
withDuration: 0.4,
delay: 0.0,
usingSpringWithDamping: 1.0,
initialSpringVelocity: 0.0,
options: UIViewAnimationOptions(),
animations: {
self.collectionView.layoutIfNeeded()
},
completion: nil
)
这将得到非常平滑的动画。
这与Ignatius Tremor的回答类似,但是过渡动画对我来说无法正常工作。
请参见https://github.com/cnoon/CollectionViewAnimations以获取完整解决方案。
collectionView.performBatchUpdates({ () -> Void in
let ctx = UICollectionViewFlowLayoutInvalidationContext()
ctx.invalidateFlowLayoutDelegateMetrics = true
self.collectionView!.collectionViewLayout.invalidateLayoutWithContext(ctx)
}) { (_: Bool) -> Void in
}
信不信由你,这个可以运行
func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPat`h: IndexPath) {
// Make your item selected in your data source here
UIView.animate(withDuration: 0.5) {
// call reload cells in the animation block and it'll update the selected cell's height with an animation
collectionView.reloadItems(at: [indexPath])
}
}
顺便说一下,我正在使用自动单元格高度,即(collectionView.collectionViewLayout as? UICollectionViewFlowLayout)?.estimatedItemSize = UICollectionViewFlowLayout.automaticSize
class CollectionViewCell: UICollectionViewCell {
override var isHighlighted: Bool {
if isHighlighted {
UIView.animate(withDuration: 0.2, delay: 0, options: .curveEaseInOut, animations: {
self.transform = CGAffineTransform(scaleX: 0.95, y: 0.95)
}) { (bool) in
UIView.animate(withDuration: 0.2, delay: 0, options: .curveEaseInOut, animations: {
self.transform = CGAffineTransform(scaleX: 1, y: 1)
}, completion: nil)
}
}
}
}
__weak
。如果cell
出现在您的块中,则会创建一个强引用,因此您并没有避免任何保留循环。尽管如此,非常感谢您的跟进,非常有帮助! - matehat