How to create a centered UICollectionView like in Spotify's Player
我很难在Spotify的播放器中创建这样的uiCollectionView:
我的问题是双重的。
1)如何将单元格居中,以便您可以看到中间单元格以及左右单元格之一。
- 如果创建正方形的单元格并在每个单元格之间添加间距,则单元格将正确显示,但不会居中。
2)pagingabled=yes时,CollectionView会正确地从一个页面滑动到另一个页面。但是,如果单元格不居中,则只需将集合视图移到屏幕宽度的页面上。所以问题是你如何使页面移动,从而得到上面的效果。
3)当单元格移动时,如何设置其大小的动画
- 我不想为此担心太多。如果我能做到这一点,那就太好了,但更困难的问题是1和2。
我目前拥有的代码是一个简单的uicollectionview,具有正常的委托设置和自定义的uicollectionview单元格,这些单元格是正方形的。也许我需要子类uicollectionviewflowlayout?或者我需要将paginnabled设置为no,然后使用自定义滑动事件?希望有任何帮助!
为了创建水平传送带布局,您必须将
下面的swift 4.1-iOS 11完整代码展示了如何实现它们。
集合视图控制器.swift
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 | import UIKit class CollectionViewController: UICollectionViewController { let collectionDataSource = CollectionDataSource() let flowLayout = ZoomAndSnapFlowLayout() override func viewDidLoad() { super.viewDidLoad() title ="Zoomed & snapped cells" guard let collectionView = collectionView else { fatalError() } //collectionView.decelerationRate = .fast // uncomment if necessary collectionView.dataSource = collectionDataSource collectionView.collectionViewLayout = flowLayout collectionView.register(CollectionViewCell.self, forCellWithReuseIdentifier:"Cell") } } |
缩放和快照流布局.swift
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 | import UIKit class ZoomAndSnapFlowLayout: UICollectionViewFlowLayout { let activeDistance: CGFloat = 200 let zoomFactor: CGFloat = 0.3 override init() { super.init() scrollDirection = .horizontal minimumLineSpacing = 40 itemSize = CGSize(width: 150, height: 150) } required init?(coder aDecoder: NSCoder) { fatalError("init(coder:) has not been implemented") } override func prepare() { guard let collectionView = collectionView else { fatalError() } let verticalInsets = (collectionView.frame.height - collectionView.adjustedContentInset.top - collectionView.adjustedContentInset.bottom - itemSize.height) / 2 let horizontalInsets = (collectionView.frame.width - collectionView.adjustedContentInset.right - collectionView.adjustedContentInset.left - itemSize.width) / 2 sectionInset = UIEdgeInsets(top: verticalInsets, left: horizontalInsets, bottom: verticalInsets, right: horizontalInsets) super.prepare() } override func layoutAttributesForElements(in rect: CGRect) -> [UICollectionViewLayoutAttributes]? { guard let collectionView = collectionView else { return nil } let rectAttributes = super.layoutAttributesForElements(in: rect)!.map { $0.copy() as! UICollectionViewLayoutAttributes } let visibleRect = CGRect(origin: collectionView.contentOffset, size: collectionView.frame.size) // Make the cells be zoomed when they reach the center of the screen for attributes in rectAttributes where attributes.frame.intersects(visibleRect) { let distance = visibleRect.midX - attributes.center.x let normalizedDistance = distance / activeDistance if distance.magnitude < activeDistance { let zoom = 1 + zoomFactor * (1 - normalizedDistance.magnitude) attributes.transform3D = CATransform3DMakeScale(zoom, zoom, 1) attributes.zIndex = Int(zoom.rounded()) } } return rectAttributes } override func targetContentOffset(forProposedContentOffset proposedContentOffset: CGPoint, withScrollingVelocity velocity: CGPoint) -> CGPoint { guard let collectionView = collectionView else { return .zero } // Add some snapping behaviour so that the zoomed cell is always centered let targetRect = CGRect(x: proposedContentOffset.x, y: 0, width: collectionView.frame.width, height: collectionView.frame.height) guard let rectAttributes = super.layoutAttributesForElements(in: targetRect) else { return .zero } var offsetAdjustment = CGFloat.greatestFiniteMagnitude let horizontalCenter = proposedContentOffset.x + collectionView.frame.width / 2 for layoutAttributes in rectAttributes { let itemHorizontalCenter = layoutAttributes.center.x if (itemHorizontalCenter - horizontalCenter).magnitude < offsetAdjustment.magnitude { offsetAdjustment = itemHorizontalCenter - horizontalCenter } } return CGPoint(x: proposedContentOffset.x + offsetAdjustment, y: proposedContentOffset.y) } override func shouldInvalidateLayout(forBoundsChange newBounds: CGRect) -> Bool { // Invalidate layout so that every cell get a chance to be zoomed when it reaches the center of the screen return true } override func invalidationContext(forBoundsChange newBounds: CGRect) -> UICollectionViewLayoutInvalidationContext { let context = super.invalidationContext(forBoundsChange: newBounds) as! UICollectionViewFlowLayoutInvalidationContext context.invalidateFlowLayoutDelegateMetrics = newBounds.size != collectionView?.bounds.size return context } } |
集合数据源.swift
1 2 3 4 5 6 7 8 9 10 11 12 13 14 | import UIKit class CollectionDataSource: NSObject, UICollectionViewDataSource { func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int { return 9 } func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell { let cell = collectionView.dequeueReusableCell(withReuseIdentifier:"Cell", for: indexPath) as! CollectionViewCell return cell } } |
集合视图单元格.swift
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | import UIKit class CollectionViewCell: UICollectionViewCell { override init(frame: CGRect) { super.init(frame: frame) contentView.backgroundColor = .green } required init?(coder aDecoder: NSCoder) { fatalError("init(coder:) has not been implemented") } } |
预期结果:
来源:
- WWDC 2012会话219-"高级集合视图和构建自定义布局"
昨天,我让uiCollectionView像这样移动。
我可以与您共享我的代码:)
这是我的故事板
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 | @interface FavoriteViewController () <UICollectionViewDelegate, UICollectionViewDataSource, UICollectionViewDelegateFlowLayout> { NSMutableArray * mList; CGSize cellSize; } @property (weak, nonatomic) IBOutlet UICollectionView *cv; @end @implementation FavoriteViewController - (void) viewWillAppear:(BOOL)animated { [super viewWillAppear:animated]; // to get a size. [self.view setNeedsLayout]; [self.view layoutIfNeeded]; CGRect screenFrame = [[UIScreen mainScreen] bounds]; CGFloat width = screenFrame.size.width*self.cv.frame.size.height/screenFrame.size.height; cellSize = CGSizeMake(width, self.cv.frame.size.height); // if cell's height is exactly same with collection view's height, you get an warning message. cellSize.height -= 1; [self.cv reloadData]; // setAlpha is for hiding looking-weird at first load [self.cv setAlpha:0]; } - (void) viewDidAppear:(BOOL)animated { [super viewDidAppear:animated]; [self scrollViewDidScroll:self.cv]; [self.cv setAlpha:1]; } #pragma mark - scrollview delegate - (void) scrollViewDidScroll:(UIScrollView *)scrollView { if(mList.count > 0) { const CGFloat centerX = self.cv.center.x; for(UICollectionViewCell * cell in [self.cv visibleCells]) { CGPoint pos = [cell convertPoint:CGPointZero toView:self.view]; pos.x += cellSize.width/2.0f; CGFloat distance = fabs(centerX - pos.x); // If you want to make side-cell's scale bigger or smaller, // change the value of '0.1f' CGFloat scale = 1.0f - (distance/centerX)*0.1f; [cell setTransform:CGAffineTransformMakeScale(scale, scale)]; } } } - (void)scrollViewWillEndDragging:(UIScrollView *)scrollView withVelocity:(CGPoint)velocity targetContentOffset:(inout CGPoint *)targetContentOffset { // for custom paging CGFloat movingX = velocity.x * scrollView.frame.size.width; CGFloat newOffsetX = scrollView.contentOffset.x + movingX; if(newOffsetX < 0) { newOffsetX = 0; } else if(newOffsetX > cellSize.width * (mList.count-1)) { newOffsetX = cellSize.width * (mList.count-1); } else { NSUInteger newPage = newOffsetX/cellSize.width + ((int)newOffsetX%(int)cellSize.width > cellSize.width/2.0f ? 1 : 0); newOffsetX = newPage*cellSize.width; } targetContentOffset->x = newOffsetX; } #pragma mark - collectionview delegate - (NSInteger)collectionView:(UICollectionView *)collectionView numberOfItemsInSection:(NSInteger)section { return mList.count; } - (UICollectionViewCell *)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath { UICollectionViewCell * cell = [collectionView dequeueReusableCellWithReuseIdentifier:@"list" forIndexPath:indexPath]; NSDictionary * dic = mList[indexPath.row]; UIImageView * iv = (UIImageView *)[cell.contentView viewWithTag:1]; UIImage * img = [UIImage imageWithData:[dic objectForKey:kKeyImg]]; [iv setImage:img]; return cell; } - (CGSize)collectionView:(UICollectionView *)collectionView layout:(UICollectionViewLayout*)collectionViewLayout sizeForItemAtIndexPath:(NSIndexPath *)indexPath { return cellSize; } - (UIEdgeInsets)collectionView:(UICollectionView *)collectionView layout:(UICollectionViewLayout*)collectionViewLayout insetForSectionAtIndex:(NSInteger)section { CGFloat gap = (self.cv.frame.size.width - cellSize.width)/2.0f; return UIEdgeInsetsMake(0, gap, 0, gap); } - (CGFloat)collectionView:(UICollectionView *)collectionView layout:(UICollectionViewLayout*)collectionViewLayout minimumLineSpacingForSectionAtIndex:(NSInteger)section { return 0; } - (CGFloat)collectionView:(UICollectionView *)collectionView layout:(UICollectionViewLayout*)collectionViewLayout minimumInteritemSpacingForSectionAtIndex:(NSInteger)section { return 0; } |
使单元格居中的关键代码是
滚动视图将结束拖动
插入分数索引
动画的关键代码大小是
我希望这能帮助你
附笔。如果要像上载的图像一样更改alpha,请在ScrollViewDidScroll中添加[cell setalpha]
不久前,我就想有类似的行为,在@mike_m的帮助下,我终于明白了。尽管有很多种方法可以做到这一点,但这个特定的实现是创建一个自定义的uiCollectionViewLayout。
下面的代码(可以在这里找到gist:https://gist.github.com/mmick66/9812223)
现在重要的是要设置以下内容:
这应该包括第1部分和第2部分。现在,对于第3部分,您可以通过不断地失效和更新,将其合并到自定义集合视图中,但是如果您问我,这有点麻烦。所以另一种方法是在
您可以使用
集合视图的中心可以用这个漂亮的方法找到:
现在设置一个规则,如果单元格的中心距X远,则单元格的大小为"正常大小",称为1。它越靠近中心,就越接近正常尺寸2的两倍。
然后您可以使用以下if/else思想:
1 2 3 4 5 6 7 | if (distance > x) { cell.transform = CGAffineTransformMakeScale(1.0f, 1.0f); } else if (distance <= x) { float scale = MIN(distance/x) * 2.0f; cell.transform = CGAffineTransformMakeScale(scale, scale); } |
会发生的是,手机的大小会完全按照你的触摸。如果你还有什么问题的话,请告诉我,因为我已经把大部分问题都写出来了。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 | - (CGPoint)targetContentOffsetForProposedContentOffset:(CGPoint)offset withScrollingVelocity:(CGPoint)velocity { CGRect cvBounds = self.collectionView.bounds; CGFloat halfWidth = cvBounds.size.width * 0.5f; CGFloat proposedContentOffsetCenterX = offset.x + halfWidth; NSArray* attributesArray = [self layoutAttributesForElementsInRect:cvBounds]; UICollectionViewLayoutAttributes* candidateAttributes; for (UICollectionViewLayoutAttributes* attributes in attributesArray) { // == Skip comparison with non-cell items (headers and footers) == // if (attributes.representedElementCategory != UICollectionElementCategoryCell) { continue; } // == First time in the loop == // if(!candidateAttributes) { candidateAttributes = attributes; continue; } if (fabsf(attributes.center.x - proposedContentOffsetCenterX) < fabsf(candidateAttributes.center.x - proposedContentOffsetCenterX)) { candidateAttributes = attributes; } } return CGPointMake(candidateAttributes.center.x - halfWidth, offset.y); } |
正如您在评论中所说,您希望在目标C代码中,有一个非常著名的库,叫做i卡洛塞尔,它可以帮助您完成您的要求。链接:https://github.com/nicklockwood/i卡洛塞尔
您可以使用"旋转"或"线性"或其他一些几乎没有修改或没有修改的样式来实现自定义视图。
要实现它,您只实现了它的一些委托方法,并且它适用于ex:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 | //specify the type you want to use in viewDidLoad _carousel.type = iCarouselTypeRotary; //Set the following delegate methods - (NSInteger)numberOfItemsInCarousel:(iCarousel *)carousel { //return the total number of items in the carousel return [_items count]; } - (UIView *)carousel:(iCarousel *)carousel viewForItemAtIndex:(NSInteger)index reusingView:(UIView *)view { UILabel *label = nil; //create new view if no view is available for recycling if (view == nil) { //don't do anything specific to the index within //this `if (view == nil) {...}` statement because the view will be //recycled and used with other index values later view = [[UIImageView alloc] initWithFrame:CGRectMake(0, 0, 200.0f, 200.0f)]; ((UIImageView *)view).image = [UIImage imageNamed:@"page.png"]; view.contentMode = UIViewContentModeCenter; label = [[UILabel alloc] initWithFrame:view.bounds]; label.backgroundColor = [UIColor clearColor]; label.textAlignment = NSTextAlignmentCenter; label.font = [label.font fontWithSize:50]; label.tag = 1; [view addSubview:label]; } else { //get a reference to the label in the recycled view label = (UILabel *)[view viewWithTag:1]; } //set item label label.text = [_items[index] stringValue]; return view; } - (CGFloat)carousel:(iCarousel *)carousel valueForOption:(iCarouselOption)option withDefault:(CGFloat)value { if (option == iCarouselOptionSpacing) { return value * 1.1; } return value; } |
您可以从Github存储库链接中包含的"示例/基本iOS示例"中查看完整的工作演示。
由于它是老的和流行的,您可以找到一些相关的教程,它也将比自定义代码实现稳定得多。
不应启用
单元大小的动画可以通过子类化uicollectionviewflowlayout和重写
希望这有帮助。