如何使用UICollectionViewTransitionLayout插入自定义的UICollectionViewLayoutAttributes属性
我有两个使用自定义UICollectionViewLayoutAttributes
子类的自定义UICollectionViewLayout
对象。 这些自定义属性添加一个属性tintAlpha
,该属性控制附加到每个集合视图单元的色彩叠加视图的不透明度。
我现在想使用UICollectionViewTransitionLayout
子类在这两个布局之间转换。 如何configuration转换布局子类以在自定义布局属性上插入自定义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属性并将它们caching在prepareLayout
或layoutAttributesForItemAtIndexPath:
。 在UICollectionViewTransitionLayout
上使用一些公共的API将是非常有用的,这样我们就可以从属性对象访问这些对象,就好像我尝试和实现自己的逻辑关于是否使用初始/最终属性vs标准属性与默认实现有一些差异。
有没有更好的方法来插入布局过渡期间这些自定义属性?
更新:
我刚遇到这种情况下的一个额外的问题。 在上面的代码中,直接从当前/下一个布局获取fromAttr
& toAttr
时, collectionView
对于当前布局是nil
(至less在转换的第一个运行循环之外)。 如果布局完全取决于集合视图的边界 – 例如考虑一个简单的封面stream布局 – 那么fromAttr
将是不正确的。
我真的想要一个interpolatedLayoutAttributesFromLayoutAttributes:toLayoutAttributes:progress:
在UICollectionViewTransitionLayout
可以由子类覆盖。
在提出更好的解决scheme之前,我已经实施了以下解决方法…
默认实现从[super prepareLayout]
调用当前和下一个布局,以select并caching需要从/转换到的布局属性。 因为我们无法访问这个caching(我主要抱怨!),所以在转换过程中我们不能直接使用它们。 相反,当缺省实现调用插值布局属性时,我构build了自己的这些属性的caching。 这只能发生在layoutAttributesForElementsInRect:
接近currentLayout.collectionView == nil
的问题),但幸运的是,似乎这个方法是第一次在转换开始时在相同的运行循环中调用,并且在collectionView
属性设置nil
。 这提供了一个机会来build立我们从/到布局属性并在转换期间caching它们。
@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:
的覆盖layoutAttributesForElementsInRect:
只需调用layoutAttributesFor...atIndexPath:
对于super
想要返回属性的每个元素索引path,它会cachingfrom / to属性。 例如, 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; }
这只是留下一个新的方法来做实际的插值,这是你不仅要插入自定义布局属性的属性,而且要重新实现默认插值( 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; }
综上所述…
所以令人沮丧的是这是大量的代码(这里为了简洁起见这里没有显示),大部分代码只是复制或试图复制默认实现正在做的事情。 如果UICollectionViewTransitionLayout
暴露了一个重载的方法,这会导致性能UICollectionViewTransitionLayout
,浪费开发时间。
- (UICollectionViewLayoutAttributes *)interpolatedLayoutAttributesFromLayoutAttributes:(UICollectionViewLayoutAttributes *)fromAttributes toLayoutAttributes:(UICollectionViewLayoutAttributes *)toAttributes progress:(CGFloat)progress { MyLayoutAttributes *attributes = (MyLayoutAttributes *)[super interpolatedLayoutAttributesFromLayoutAttributes:fromAttributes toLayoutAttributes:toAttributes progress:progress]; attributes.customProperty = (1.0f - progress) * fromAttributes.customProperty + progress * toAttributes.customProperty; return attributes; }
这个解决方法的好处在于,您不必重新实现决定哪些布局属性在转换的开始/结束时可见的代码 – 默认实现对我们来说是可行的。 每次布局无效时,我们也不必获取所有内容的属性,然后检查与可见矩形相交的项目。