【问题标题】:Custom layout 1 large 2 small cells UICollectionView自定义布局 1 大 2 小单元 UICollectionView
【发布时间】:2021-06-19 22:39:57
【问题描述】:

我想创建一个如下图所示的布局,但无法理解如何使用流布局来实现这种布局

基本上我想要的是一张大图而不是两张小图, 然后从索引 3,4,5... 等等将是一个 3 图像网格。只有前 3 个单元格会有不同的布局。有谁知道我们如何在 collectionView 中实现这种布局?

为 collectionView 更新代码

- (NSInteger)collectionView:(UICollectionView *)collectionView numberOfItemsInSection:(NSInteger)section
{
    return 100;
}

- (UICollectionViewCell *)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath
{
    AppDelegate* delegate = [AppDelegate applicationDelegate];
    FriendsListCell *cell= (FriendsListCell*)[collectionView dequeueReusableCellWithReuseIdentifier:kFriendsListCellIdentifier forIndexPath:indexPath];
   
    cell.backgroundColor = [UIColor redColor];
    return cell;
}



- (CGSize)collectionView:(UICollectionView *)collectionView layout:(UICollectionViewLayout *)collectionViewLayout sizeForItemAtIndexPath:(NSIndexPath *)indexPath
{
    CGSize newSize = CGSizeZero;
    newSize.height = 100;  // use your prefered size
    newSize.width = 100;   // use your prefered size

    if(indexPath.item == 0) // set custom size for the first item
    {
        newSize.width = 200;  // use your prefered size
        newSize.height = 200; // use your prefered size
    }
    return newSize;
}

【问题讨论】:

  • 您无法使用默认 FlowLayout 实现该行为。你需要一个定制的。一些解释:stackoverflow.com/questions/43186246/…
  • @Larme 你能解释一下逻辑吗?我想要上面的前 3 个单元格,然后其余的单元格将连续 3 个单元格
  • 您需要创建自己的自定义流布局。它应该知道每个项目的框架(而不仅仅是大小)。
  • @Larme 你在 GitHub 上有一个示例代码来回答另一个问题吗?

标签: ios objective-c uicollectionview autolayout uicollectionviewcell


【解决方案1】:

您必须根据索引自定义collectionview项目的widthheight

如下:

func collectionView(collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAtIndexPath indexPath: NSIndexPath) -> CGSize {
        let width : CGFloat
        let height : CGFloat
        if indexPath.item == 0 { // set custom size for the first item
            width = 200 // use your prefered size
            height = 200 // use your prefered size
        } else {
            width = 100 // use your prefered size
            height = 100 // use your prefered size
        }
        return CGSizeMake(width, height)
}

别忘了conformUICollectionViewDelegateFlowLayout

对于Objective-c

- (CGSize)collectionView:(UICollectionView *)collectionView layout:(UICollectionViewLayout *)collectionViewLayout sizeForItemAtIndexPath:(NSIndexPath *)indexPath
{
    CGSize newSize = CGSizeZero;
    newSize.height = 100;  // use your prefered size
    newSize.width = 100;   // use your prefered size

    if(indexPath.item == 0) // set custom size for the first item
    {
        newSize.width = 200;  // use your prefered size
        newSize.height = 200; // use your prefered size
    }
    return newSize;
}

更新截图

【讨论】:

  • 我只是放了示例代码,你可以怎么做。
  • 布局看起来很简单,但并不是像传递宽度和高度那样简单。如果我传递宽度和高度,那么第二个项目将放在哪里?
  • 查看代码并思考。当第一个项目的宽度和高度为 200 时。意味着您的第二个项目将在那之后放置。
  • 我假设你的手机屏幕是 347。如果第一个项目的宽度为 200,那么第二个项目应该在第一个项目之后出现,第二个项目是 100。
  • 我认为你搞砸了 collectionview 配置(大小、方向等)和项目大小。
【解决方案2】:

在 Objective-C 中:

@interface MyFlowLayoutObjC()

@property (nonatomic, strong) NSMutableDictionary *cellsLayout;
@property (nonatomic, assign) CGSize computedContentSize;
@end

@implementation MyFlowLayoutObjC

-(id)init {
    self = [super init];
    if (self) {
        _cellsLayout = [[NSMutableDictionary alloc] init];
    }
    return  self;
}
-(void)prepareLayout {
    [super prepareLayout];

    [self setComputedContentSize:CGSizeZero];

    [[self cellsLayout] removeAllObjects];
    CGSize fullFrameSize = [[self collectionView] frame].size;
    NSUInteger numberOfitems = [[self collectionView] numberOfItemsInSection:0];
    CGSize unitSize = CGSizeMake(fullFrameSize.width / 3.0, fullFrameSize.width / 3.0);
    for (NSUInteger anIndex = 0; anIndex < numberOfitems; anIndex ++) {
        NSIndexPath *indexPath = [NSIndexPath indexPathForItem:anIndex inSection:0];
        UICollectionViewLayoutAttributes *attributes = [UICollectionViewLayoutAttributes layoutAttributesForCellWithIndexPath:indexPath];
        switch (anIndex) {
            case 0:
                [attributes setFrame:CGRectMake(0, 0, unitSize.width * 2, unitSize.height * 2)];
                break;
            case 1:
                [attributes setFrame:CGRectMake(unitSize.width * 2, 0, unitSize.width, unitSize.height)];
                break;
            case 2:
                [attributes setFrame:CGRectMake(unitSize.width * 2, unitSize.height, unitSize.width, unitSize.height)];
                break;
            default:
                [attributes setFrame:CGRectMake(unitSize.width  * (anIndex % 3), unitSize.height * (CGFloat)((anIndex / 3) + 1), unitSize.width, unitSize.height)];
                break;
        }
        [[self cellsLayout] setObject:attributes forKey:indexPath];
        [self setComputedContentSize:CGSizeMake(MAX([attributes frame].origin.x + [attributes frame].size.width, [self computedContentSize].width),
                                                MAX([attributes frame].origin.y + [attributes frame].size.height, [self computedContentSize].height))];
    }
}

-(NSArray<__kindof UICollectionViewLayoutAttributes *> *)layoutAttributesForElementsInRect:(CGRect)rect {
    NSArray *layouts = [[self cellsLayout] allValues];
    NSPredicate *predicate = [NSPredicate predicateWithBlock:^BOOL(UICollectionViewLayoutAttributes * _Nullable aLayout, NSDictionary<NSString *,id> * _Nullable bindings) {
        return CGRectIntersectsRect([aLayout frame], rect);
    }];
    return [layouts filteredArrayUsingPredicate:predicate];
}

-(UICollectionViewLayoutAttributes *)layoutAttributesForItemAtIndexPath:(NSIndexPath *)indexPath {
    return [self cellsLayout][indexPath];
}

- (CGSize)collectionViewContentSize {
    return [self computedContentSize];
}

@end

在 Swift 中:

class MyFlowLayout: UICollectionViewLayout {

    private var cellsLayout: [IndexPath: UICollectionViewLayoutAttributes] = [:]
    
    override func prepare() {
        cellsLayout.removeAll()

        guard let fullFrameSizeWidth = collectionView?.frame.size else { return }
        guard let numberOfItems = collectionView?.numberOfItems(inSection: 0) else { return }
        let unitSize = CGSize(width: fullFrameSizeWidth.width / 3.0, height: fullFrameSizeWidth.width / 3.0)
        for anIndex in 0..<numberOfItems {
            let indexPath = IndexPath(item: anIndex, section: 0)
            let attributes = UICollectionViewLayoutAttributes(forCellWith: indexPath)
            switch anIndex {
            case 0:
                attributes.frame = CGRect(origin: .zero, size: CGSize(width: unitSize.width * 2, height: unitSize.height * 2))
            case 1:
                attributes.frame = CGRect(origin: CGPoint(x: unitSize.width * 2, y: 0), size: unitSize)
            case 2:
                attributes.frame = CGRect(origin: CGPoint(x: unitSize.width * 2, y: unitSize.height), size: unitSize)
            default:
                attributes.frame = CGRect(origin: CGPoint(x: unitSize.width  * CGFloat(anIndex % 3),
                                                          y: unitSize.height * CGFloat(Int(anIndex / 3) + 1)),
                                          size: unitSize)
            }
            cellsLayout[indexPath] = attributes
        }

    }

    override func layoutAttributesForElements(in rect: CGRect) -> [UICollectionViewLayoutAttributes]? {
        return cellsLayout.compactMap { $0.value.frame.intersects(rect) ? $0.value : nil }
    }

    override func layoutAttributesForItem(at indexPath: IndexPath) -> UICollectionViewLayoutAttributes? {
        return cellsLayout[indexPath]
    }

    override var collectionViewContentSize: CGSize {
        return CGSize(width: cellsLayout.max(by: { $0.1.frame.maxX < $1.1.frame.maxX })?.value.frame.maxX ?? 0,
                      height: cellsLayout.max(by: { $0.1.frame.maxY < $1.1.frame.maxY })?.value.frame.maxY ?? 0)
    }
}

这应该可以解决问题。

现在,让我们解释一下,您需要一个自定义的UICollectionViewFlowLayout。因为仅仅覆盖-collectionView:layout:sizeForItemAtIndexPath:,你不能这样做。它将获得第一个项目的大小,将其放入框架中。然后,获取第二个项目的框架,如果合适,则将其放在其右侧,否则放在其下方,依此类推。 设置好所有内容后,它将以每条线的 y 轴为中心。这就是你有这种行为的原因。

那么需要什么?

让我们覆盖-layoutAttributesForElementsInRect: 以返回与矩形(可见矩形)相交的每个单元格的布局。 为了做到这一点,我们需要知道这些框架,所以我在-prepareLayout进行了所需的计算。

【讨论】:

  • 谢谢拉姆。我还有一个问题可以给你发一封电子邮件吗?
  • 如果您有开发问题,最好使用 StackOverflow,这样每个人都可以互动、回复,还可以学习。
  • 是的,你是对的,但我被困在某个地方,我想尽快知道。
  • 而且我什么都不知道,其他人可能会很快互动,有更好的答案。当我有解决方案并且我感兴趣时,我会回答。
  • 是的,我已经在这里发布了 - stackoverflow.com/questions/66766177/…
猜你喜欢
  • 2013-03-05
  • 2017-05-07
  • 2014-09-02
  • 1970-01-01
  • 2018-05-29
  • 1970-01-01
  • 1970-01-01
  • 2018-08-02
相关资源
最近更新 更多