我现在想使用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可以让我们从/到属性对象访问这个方法是非常有用的,就好像我尝试实现我自己的逻辑关于是使用初始/最终属性还是绑定的标准属性与默认实现有一些差异.
更新:
我刚刚遇到了一个额外的问题,这种情况.在上面的代码中,当从Attt& toAttr直接从当前/下一个布局,collectionView对于当前布局为零(至少在第一个运行循环之外).如果布局完全取决于集合视图的边界 – 例如,可以考虑一个简单的封面流布局,那么,从安装程序将不正确.
我真的在为一个interpolatedLayoutAttributesFromLayoutAttributes:toLayoutAttributes:progress:在UICollectionViewTransitionLayout上可以被子类覆盖.
解决方法
默认实现调用到当前&下一个布局从[super prepareLayout]选择&缓存需要从/转换到的布局属性.因为我们没有访问这个缓存(我的主要抱怨!),我们不能直接在转换期间使用它.相反,当默认实现通过插值的布局属性调用时,我构造自己的这些属性缓存.这只能在layoutAttributesForElementsInRect中发生:(靠近currentLayout.collectionView == nil的问题),但幸运的是,似乎这个方法首先在与转换开始相同的运行循环中调用,并且在将CollectionView属性设置为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:对于超级想返回属性的每个元素索引路径,缓存从/到属性的属性.例如,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暴露了一个单独的方法来覆盖,那么这样做会导致更差的性能,并浪费开发时间,如果真的要简单得多,比如:
- (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; }
这个解决方法的好处是,您不必重新实现代码,从而决定在转换的开始/结束时可以看到哪些布局属性 – 默认实现对我们来说是这样.每当布局无效时,我们也不必得到所有的属性,然后检查与可见直线相交的项目.