我有两个使用自定义UICollectionViewLayout
子类的自定义UICollectionViewLayoutAttributes
对象。这些自定义属性添加了一个属性tintAlpha
,用于控制附加到每个集合视图单元格的色调覆盖视图的不透明度。
我现在想要使用UICollectionViewTransitionLayout
子类在这两个布局之间进行转换。如何配置转换布局子类以在我的自定义布局属性上插入自定义tintAlpha
属性?
我可以这样做:
- (UICollectionViewLayoutAttributes *)layoutAttributesForItemAtIndexPath:(NSIndexPath *)indexPath
{
CustomLayoutAttributes *attr = [super layoutAttributesForItemAtIndexPath:indexPath];
CustomLayoutAttributes *fromAttr = (CustomLayoutAttributes *)[self.currentLayout layoutAttributesForItemAtIndexPath:indexPath];
CustomLayoutAttributes *toAttr = (CustomLayoutAttributes *)[self.nextLayout layoutAttributesForItemAtIndexPath:indexPath];
CGFloat t = self.transitionProgress;
attr.tintAlpha = (1.0f - t) * fromAttr.tintAlpha + t * toAttr.tintAlpha;
return attr;
}
但是,这会忽略应用于initialLayoutAttributesForAppearingItemAtIndexPath:
&中属性的所有更改。当前或下一个布局中的finalLayoutAttributesForDisappearingItemAtIndexPath:
,因此实际上并不正确。据我所知,UICollectionViewTransitionLayout
的默认实现确定了适当的from / to属性,并在prepareLayout
或layoutAttributesForItemAtIndexPath:
中缓存它们。在UICollectionViewTransitionLayout
上设置一些公共API以允许我们从属性对象访问这些对象是非常有用的,就好像我尝试在是否使用初始/最终属性与标准属性上实现自己的逻辑一样默认实现必然存在一些差异。
在布局转换期间是否有更好的方法来插入这些自定义属性?
更新
我刚遇到此方案的其他问题。在上面的代码中,获取fromAttr
&直接来自当前/下一个布局的toAttr
,当前布局的collectionView
为nil
(至少超过转换的第一个运行循环)。如果布局完全取决于集合视图的边界 - 例如考虑一个简单的封面流布局 - 那么fromAttr
将是不正确的。
我真的很想在interpolatedLayoutAttributesFromLayoutAttributes:toLayoutAttributes:progress:
上UICollectionViewTransitionLayout
可以被子类覆盖。
答案 0 :(得分:5)
默认实现调用当前& [super prepareLayout]
的下一个布局选择&缓存需要从/转换到的布局属性。因为我们无法访问此缓存(我的主要抱怨!),所以我们无法在转换过程中直接使用它们。相反,当默认实现调用插值布局属性时,我构造自己的这些属性的缓存。这只能发生在layoutAttributesForElementsInRect:
(接近问题currentLayout.collectionView == nil
),但幸运的是,这个方法似乎首先在与转换开始相同的运行循环中调用,然后在{{1}之前调用} property设置为collectionView
。这样就有机会建立我们的from / to布局属性,并在转换期间缓存它们。
nil
覆盖@interface CustomTransitionLayout ()
@property(nonatomic, strong) NSMutableDictionary *transitionInformation;
@end
@implementation
- (void)prepareLayout
{
[super prepareLayout];
if (!self.transitionInformation) {
self.transitionInformation = [NSMutableDictionary dictionary];
}
}
- (NSArray *)layoutAttributesForElementsInRect:(CGRect)rect
{
// Let the super implementation tell us which attributes are required.
NSArray *defaultLayoutAttributes = [super layoutAttributesForElementsInRect:rect];
NSMutableArray *layoutAttributes = [NSMutableArray arrayWithCapacity:[defaultLayoutAttributes count]];
for (UICollectionViewLayoutAttributes *defaultAttr in defaultLayoutAttributes) {
UICollectionViewLayoutAttributes *attr = defaultAttr;
switch (defaultAttr.representedElementCategory) {
case UICollectionElementCategoryCell:
attr = [self layoutAttributesForItemAtIndexPath:defaultAttr.indexPath];
break;
case UICollectionElementCategorySupplementaryView:
attr = [self layoutAttributesForSupplementaryViewOfKind:defaultAttr.representedElementKind atIndexPath:defaultAttr.indexPath];
break;
case UICollectionElementCategoryDecorationView:
attr = [self layoutAttributesForDecorationViewOfKind:defaultAttr.representedElementKind atIndexPath:defaultAttr.indexPath];
break;
}
[layoutAttributes addObject:attr];
}
return layoutAttributes;
}
只是为layoutAttributesForElementsInRect:
想要返回属性的每个元素索引路径调用layoutAttributesFor...atIndexPath:
,该路径在进行时缓存from / to属性。例如,super
方法看起来像这样:
layoutAttributesForItemAtIndexPath:
这只是留下了一个实现插值的新方法,你不仅要插入自定义布局属性属性,还要重新实现默认插值(- (UICollectionViewLayoutAttributes *)layoutAttributesForItemAtIndexPath:(NSIndexPath *)indexPath
{
NSIndexPath *indexPathKey = [indexPath collectionViewKey];
NSMutableDictionary *info = self.transitionInformation[indexPathKey];
if (!info) {
info = [NSMutableDictionary dictionary];
self.transitionInformation[indexPathKey] = info;
}
// Logic to choose layout attributes to interpolate from.
// (This is not exactly how the default implementation works, but a rough approximation)
MyLayoutAttributes *fromAttributes = info[TransitionInfoFromAttributesKey];
if (!fromAttributes) {
MyLayoutAttributes *standardToAttributes = (MyLayoutAttributes *)[self.nextLayout layoutAttributesForItemAtIndexPath:indexPathKey];
MyLayoutAttributes *initialAttributes = (MyLayoutAttributes *)[self.nextLayout initialLayoutAttributesForAppearingItemAtIndexPath:indexPathkey];
if (initialAttributes && ![initialAttributes isEqual:standardToAttributes]) {
fromAttributes = [initialAttributes copy];
} else {
fromAttributes = [(MyLayoutAttributes *)[self.currentLayout layoutAttributesForItemAtIndexPath:indexPathKey] copy];
}
info[TransitionInfoFromAttributesKey] = fromAttributes;
}
MyLayoutAttributes *toAttributes = info[TransitionInfoToAttributesKey];
if (!toAttributes) {
// ... similar logic as for fromAttributes ...
info[TransitionInfoToAttributesKey] = toAttributes;
}
MyLayoutAttributes *attributes = [self interpolatedLayoutAttributesFromLayoutAttributes:fromAttributes
toLayoutAttributes:toAttributes
progress:self.transitionProgress];
return attributes;
}
/ {{1} } / center
/ size
/ alpha
):
transform
所以令人沮丧的是,这是一个大量的代码(为简洁起见,这里没有显示),而且大多数只是复制或尝试来复制默认实现的内容无论如何。这会导致性能下降,并且如果transform3D
暴露了一个要覆盖的单一方法,那么浪费开发时间就可以真正如此更简单,例如:
- (MyLayoutAttributes *)interpolatedLayoutAttributesFromLayoutAttributes:(MyLayoutAttributes *)fromAttributes
toLayoutAttributes:(MyLayoutAttributes *)toAttributes
progress:(CGFloat)progress
{
MyLayoutAttributes *attributes = [fromAttributes copy];
CGFloat t = progress;
CGFloat f = 1.0f - t;
// Interpolate all the default layout attributes properties.
attributes.center = CGPointMake(f * fromAttributes.x + t * toAttributes.center.x,
f * fromAttributes.y + t * toAttributes.center.y);
// ...
// Interpolate any custom layout attributes properties.
attributes.customProperty = f * fromAttributes.customProperty + t * toAttributes.customProperty;
// ...
return attributes;
}
这个解决方法的好处是你不必重新实现决定哪些布局属性在转换的开始/结束时可见的代码 - 默认实现为我们做了。每次布局无效时,我们也不必获取所有的属性,然后检查与可见矩形相交的项目。