UICollectionView 和 CustomFlowLayout 如何将滚动限制为每次滚动仅一页?
UICollectionView with CustomFlowLayout How to restricts scroll to only one page per scroll?
我已经在我的 iOS 应用程序中实现了 customFlowLayout
。我已经将 targetContentOffsetForProposedContentOffset:withScrollingVelocity
子类化为
子类化 UICollectionViewFlowLayout
。现在我的问题是当用户滚动 collectionview
它必须滚动到下一个索引。现在它随机滚动。
所以任何人都知道如何将滚动限制为每个滚动只有一个项目。
以下是我的代码。
#pragma mark - UICollectionViewLayout (UISubclassingHooks)
- (CGPoint)targetContentOffsetForProposedContentOffset:(CGPoint)proposedContentOffset withScrollingVelocity:(CGPoint)velocity {
CGSize collectionViewSize = self.collectionView.bounds.size;
CGFloat proposedContentOffsetCenterX = proposedContentOffset.x + collectionViewSize.width / 2;
CGRect proposedRect = CGRectMake(proposedContentOffset.x, 0, collectionViewSize.width, collectionViewSize.height);
UICollectionViewLayoutAttributes *candidateAttributes;
for (UICollectionViewLayoutAttributes *attributes in [self layoutAttributesForElementsInRect:proposedRect]) {
if (attributes.representedElementCategory != UICollectionElementCategoryCell) continue;
if (!candidateAttributes) {
candidateAttributes = attributes;
continue;
}
if (fabs(attributes.center.x - proposedContentOffsetCenterX) < fabs(candidateAttributes.center.x - proposedContentOffsetCenterX)) {
candidateAttributes = attributes;
}
}
proposedContentOffset.x = candidateAttributes.center.x - self.collectionView.bounds.size.width / 2;
CGFloat offset = proposedContentOffset.x - self.collectionView.contentOffset.x;
if ((velocity.x < 0 && offset > 0) || (velocity.x > 0 && offset < 0)) {
CGFloat pageWidth = self.itemSize.width + self.minimumLineSpacing;
proposedContentOffset.x += velocity.x > 0 ? pageWidth : -pageWidth;
}
return proposedContentOffset;
}
- (BOOL)shouldInvalidateLayoutForBoundsChange:(CGRect)newBounds {
return YES;
}
- (NSArray *)layoutAttributesForElementsInRect:(CGRect)rect {
if (!self.scaleItems) return [super layoutAttributesForElementsInRect:rect];
NSArray *attributesArray = [[NSArray alloc] initWithArray:[super layoutAttributesForElementsInRect:rect] copyItems:YES];
CGRect visibleRect = (CGRect){self.collectionView.contentOffset, self.collectionView.bounds.size};
CGFloat visibleCenterX = CGRectGetMidX(visibleRect);
[attributesArray enumerateObjectsUsingBlock:^(UICollectionViewLayoutAttributes *attributes, NSUInteger idx, BOOL *stop) {
CGFloat distanceFromCenter = visibleCenterX - attributes.center.x;
CGFloat absDistanceFromCenter = MIN(ABS(distanceFromCenter), self.scalingOffset);
CGFloat scale = absDistanceFromCenter * (self.minimumScaleFactor - 1) / self.scalingOffset + 1;
attributes.transform3D = CATransform3DScale(CATransform3DIdentity, scale, scale, 1);
}];
return attributesArray;
}
您的代码看起来应该根据用户请求很好地滚动。也就是说,如果他们快速滚动,它将跳过一些项目并很好地落在后面的项目上,如果他们滚动缓慢,它将继续到下一个或 return 到前一个项目,这取决于滚动距离。但是,这不是你说的那样。
当用户尝试快速滚动时,您想要的可能不太好用...
无论如何,要得到你想要的,你基本上只想使用 proposedContentOffset
来确定滚动方向(它是大于还是小于当前内容偏移量)。
现在,一旦您有了它,您就可以在下一页或上一页获取项目的布局属性(而不是当前代码,后者可能会在许多页面之外获取属性)。这是当前偏移量 + 或 - 视图宽度。
您的其余代码将保持不变。忽略滚动方向是这样的:
- (CGPoint)targetContentOffsetForProposedContentOffset:(CGPoint)proposedContentOffset withScrollingVelocity:(CGPoint)velocity
{
CGSize collectionViewSize = self.collectionView.bounds.size;
CGFloat width = collectionViewSize.width;
CGFloat halfWidth = width * 0.5;
CGFloat direction = (proposedContentOffset.x > self.collectionView.contentOffset.x ? 1 : 0);
CGFloat pageOffsetX = 250.0 * floor(self.collectionView.contentOffset.x / 250.0);
CGFloat proposedContentOffsetCenterX = pageOffsetX + (width * direction);
CGRect proposedRect = CGRectMake(proposedContentOffsetCenterX, 0, collectionViewSize.width, collectionViewSize.height);
UICollectionViewLayoutAttributes *candidateAttributes;
for (UICollectionViewLayoutAttributes *attributes in [self layoutAttributesForElementsInRect:proposedRect]) {
if (attributes.representedElementCategory != UICollectionElementCategoryCell) continue;
candidateAttributes = attributes;
break;
}
proposedContentOffset.x = candidateAttributes.center.x - halfWidth;
// CGFloat offset = proposedContentOffset.x - self.collectionView.contentOffset.x;
//
// if ((velocity.x < 0 && offset > 0) || (velocity.x > 0 && offset < 0)) {
// CGFloat pageWidth = self.itemSize.width + self.minimumLineSpacing;
// proposedContentOffset.x += velocity.x > 0 ? pageWidth : -pageWidth;
// }
return proposedContentOffset;
}
我已经注释掉了底部的部分,因为初始版本不需要它。先用一个简单的版本测试一下,然后再详细说明你是否需要在边缘情况下进行更多控制。
我已经在我的 iOS 应用程序中实现了 customFlowLayout
。我已经将 targetContentOffsetForProposedContentOffset:withScrollingVelocity
子类化为
子类化 UICollectionViewFlowLayout
。现在我的问题是当用户滚动 collectionview
它必须滚动到下一个索引。现在它随机滚动。
所以任何人都知道如何将滚动限制为每个滚动只有一个项目。
以下是我的代码。
#pragma mark - UICollectionViewLayout (UISubclassingHooks)
- (CGPoint)targetContentOffsetForProposedContentOffset:(CGPoint)proposedContentOffset withScrollingVelocity:(CGPoint)velocity {
CGSize collectionViewSize = self.collectionView.bounds.size;
CGFloat proposedContentOffsetCenterX = proposedContentOffset.x + collectionViewSize.width / 2;
CGRect proposedRect = CGRectMake(proposedContentOffset.x, 0, collectionViewSize.width, collectionViewSize.height);
UICollectionViewLayoutAttributes *candidateAttributes;
for (UICollectionViewLayoutAttributes *attributes in [self layoutAttributesForElementsInRect:proposedRect]) {
if (attributes.representedElementCategory != UICollectionElementCategoryCell) continue;
if (!candidateAttributes) {
candidateAttributes = attributes;
continue;
}
if (fabs(attributes.center.x - proposedContentOffsetCenterX) < fabs(candidateAttributes.center.x - proposedContentOffsetCenterX)) {
candidateAttributes = attributes;
}
}
proposedContentOffset.x = candidateAttributes.center.x - self.collectionView.bounds.size.width / 2;
CGFloat offset = proposedContentOffset.x - self.collectionView.contentOffset.x;
if ((velocity.x < 0 && offset > 0) || (velocity.x > 0 && offset < 0)) {
CGFloat pageWidth = self.itemSize.width + self.minimumLineSpacing;
proposedContentOffset.x += velocity.x > 0 ? pageWidth : -pageWidth;
}
return proposedContentOffset;
}
- (BOOL)shouldInvalidateLayoutForBoundsChange:(CGRect)newBounds {
return YES;
}
- (NSArray *)layoutAttributesForElementsInRect:(CGRect)rect {
if (!self.scaleItems) return [super layoutAttributesForElementsInRect:rect];
NSArray *attributesArray = [[NSArray alloc] initWithArray:[super layoutAttributesForElementsInRect:rect] copyItems:YES];
CGRect visibleRect = (CGRect){self.collectionView.contentOffset, self.collectionView.bounds.size};
CGFloat visibleCenterX = CGRectGetMidX(visibleRect);
[attributesArray enumerateObjectsUsingBlock:^(UICollectionViewLayoutAttributes *attributes, NSUInteger idx, BOOL *stop) {
CGFloat distanceFromCenter = visibleCenterX - attributes.center.x;
CGFloat absDistanceFromCenter = MIN(ABS(distanceFromCenter), self.scalingOffset);
CGFloat scale = absDistanceFromCenter * (self.minimumScaleFactor - 1) / self.scalingOffset + 1;
attributes.transform3D = CATransform3DScale(CATransform3DIdentity, scale, scale, 1);
}];
return attributesArray;
}
您的代码看起来应该根据用户请求很好地滚动。也就是说,如果他们快速滚动,它将跳过一些项目并很好地落在后面的项目上,如果他们滚动缓慢,它将继续到下一个或 return 到前一个项目,这取决于滚动距离。但是,这不是你说的那样。
当用户尝试快速滚动时,您想要的可能不太好用...
无论如何,要得到你想要的,你基本上只想使用 proposedContentOffset
来确定滚动方向(它是大于还是小于当前内容偏移量)。
现在,一旦您有了它,您就可以在下一页或上一页获取项目的布局属性(而不是当前代码,后者可能会在许多页面之外获取属性)。这是当前偏移量 + 或 - 视图宽度。
您的其余代码将保持不变。忽略滚动方向是这样的:
- (CGPoint)targetContentOffsetForProposedContentOffset:(CGPoint)proposedContentOffset withScrollingVelocity:(CGPoint)velocity
{
CGSize collectionViewSize = self.collectionView.bounds.size;
CGFloat width = collectionViewSize.width;
CGFloat halfWidth = width * 0.5;
CGFloat direction = (proposedContentOffset.x > self.collectionView.contentOffset.x ? 1 : 0);
CGFloat pageOffsetX = 250.0 * floor(self.collectionView.contentOffset.x / 250.0);
CGFloat proposedContentOffsetCenterX = pageOffsetX + (width * direction);
CGRect proposedRect = CGRectMake(proposedContentOffsetCenterX, 0, collectionViewSize.width, collectionViewSize.height);
UICollectionViewLayoutAttributes *candidateAttributes;
for (UICollectionViewLayoutAttributes *attributes in [self layoutAttributesForElementsInRect:proposedRect]) {
if (attributes.representedElementCategory != UICollectionElementCategoryCell) continue;
candidateAttributes = attributes;
break;
}
proposedContentOffset.x = candidateAttributes.center.x - halfWidth;
// CGFloat offset = proposedContentOffset.x - self.collectionView.contentOffset.x;
//
// if ((velocity.x < 0 && offset > 0) || (velocity.x > 0 && offset < 0)) {
// CGFloat pageWidth = self.itemSize.width + self.minimumLineSpacing;
// proposedContentOffset.x += velocity.x > 0 ? pageWidth : -pageWidth;
// }
return proposedContentOffset;
}
我已经注释掉了底部的部分,因为初始版本不需要它。先用一个简单的版本测试一下,然后再详细说明你是否需要在边缘情况下进行更多控制。