繁体   English   中英

重新排序UICollectionView的单元格

[英]Reorder cells of UICollectionView

考虑具有流布局和水平方向的UICollectionView 默认情况下,单元格从上到下,从左到右排序。 像这样:

1 4 7 10 13 16
2 5 8 11 14 17
3 6 9 12 15 18

在我的例子中,集合视图被分页并且它被设计为使得特定数量的单元格适合每个页面。 因此,更自然的顺序是:

1 2 3   10 11 12
4 5 6 - 13 14 15
7 8 9   16 17 18

如果没有实现我自己的自定义布局,最简单的方法是什么? 特别是,我不想放弃使用UICollectionViewFlowLayout免费提供的任何功能(例如插入/删除动画)。

或者一般来说,如何在流布局上实现重新排序函数f(n) 例如,这可以适用于从右到左的排序。

到目前为止我的方法

我的第一种方法是子类UICollectionViewFlowLayout并覆盖layoutAttributesForItemAtIndexPath: ::

- (UICollectionViewLayoutAttributes *)layoutAttributesForItemAtIndexPath:(NSIndexPath *)indexPath
{
    NSIndexPath *reorderedIndexPath = [self reorderedIndexPathOfIndexPath:indexPath];
    UICollectionViewLayoutAttributes *layout = [super layoutAttributesForItemAtIndexPath:reorderedIndexPath];
    layout.indexPath = indexPath;
    return layout;
}

其中reorderedIndexPathOfIndexPath:f(n) 通过调用super ,我不必手动计算每个元素的布局。

另外,我不得不重写layoutAttributesForElementsInRect:这是布局用来选择要显示的元素的方法。

- (NSArray *)layoutAttributesForElementsInRect:(CGRect)rect
{
    NSMutableArray *result = [NSMutableArray array];
    NSInteger sectionCount = 1;
    if ([self.collectionView.dataSource respondsToSelector:@selector(numberOfSectionsInCollectionView:)])
    {
        sectionCount = [self.collectionView.dataSource numberOfSectionsInCollectionView:self.collectionView];
    }
    for (int s = 0; s < sectionCount; s++)
    {
        NSInteger itemCount = [self.collectionView.dataSource collectionView:self.collectionView numberOfItemsInSection:s];
        for (int i = 0; i < itemCount; i++)
        {
            NSIndexPath *indexPath = [NSIndexPath indexPathForItem:i inSection:s];
            UICollectionViewLayoutAttributes *layout = [self layoutAttributesForItemAtIndexPath:indexPath];
            if (CGRectIntersectsRect(rect, layout.frame))
            {
                [result addObject:layout];
            }
        }
    }
    return result;
}

在这里,我只是尝试每个元素,如果它在给定的rect ,我返回它。

如果采用这种方法,我有以下更具体的问题:

  • 有什么办法可以简化layoutAttributesForElementsInRect:覆盖,还是让它更高效?
  • 我错过了什么吗? 至少交换不同页面的单元格会产生奇怪的结果。 我怀疑它与initialLayoutAttributesForAppearingItemAtIndexPath:finalLayoutAttributesForDisappearingItemAtIndexPath: ,但我无法确切地指出问题究竟是什么。
  • 在我的例子中, f(n)取决于每页的列数和行数。 有没有办法从UICollectionViewFlowLayout中提取这些信息,而不是自己UICollectionViewFlowLayout硬编码? 我想到了查询layoutAttributesForElementsInRect:使用集合视图的边界,并从那里推断layoutAttributesForElementsInRect:和列,但这也感觉效率低下。

我已经考虑了很多关于你的问题,并考虑到以下几点:

对FlowLayout进行子类化似乎是重新排序单元格和使用流布局动画的最有效和最有效的方法。 除了两件重要的事情外,你的方法是有效的:

  1. 假设您有一个只有2个单元格的集合视图,并且您已经设计了页面,以便它可以包含9个单元格。 第一个单元格将位于视图的左上角,就像原始流程布局一样。 但是,您的第二个单元格应位于视图的顶部,并且它具有索引路径[0,1]。 重新排序的索引路径将是[0,3](原始流布局单元的索引路径将在其位置上)。 在你的layoutAttributesForItemAtIndexPath覆盖中你会发送像[super layoutAttributesForItemAtIndexPath:[0, 3]]这样的消息,你会得到一个nil对象,因为只有2个单元:[0,0]和[0,1]。 这将是你最后一页的问题。
  2. 即使您可以通过覆盖targetContentOffsetForProposedContentOffset:withScrollingVelocity:并手动设置itemSizeminimumLineSpacingminimumInteritemSpacing等属性来实现分页行为,但是要使项目对称,定义分页边界等等,还有很多工作要做。

我thnik,子类化流程布局正在为您准备很多实现,因为您想要的不再是流布局。 但让我们一起思考它。 关于你的问题:

  • 你的layoutAttributesForElementsInRect: override正是原始apple实现的方式,所以没有办法简化它。 但是,对于您的情况,您可以考虑以下内容:如果每页有3行项目,并且第一行中的项目框架与矩形框架相交,则(如果所有项目具有相同大小)第二和第三行项目的框架相交这个矩形。
  • 对不起,我不明白你的第二个问题
  • 在我的例子中,重新排序函数如下所示:(a是每页上行/列的整数 ,行=列)

f(n)=(n%a²)+(a - 1)(col - row)+a²(n /a²); col =(n%a²)%a; row =(n%a²)/ a;

回答这个问题,流程布局不知道每列中有多少行,因为这个数字可能因列而异,具体取决于每个项目的大小。 它也可以说每页上的列数,因为它取决于滚动位置,也可以变化。 因此,没有比查询layoutAttributesForElementsInRect更好的方法,但这也包括只能部分可见的单元格。 由于您的单元格大小相等,理论上您可以通过水平滚动方向找出集合视图中有多少行:通过开始迭代计算它们的每个单元格并断开它们的frame.origin.x更改。

所以,我认为,你有两个选择来实现你的目的:

  1. 子类UICollectionViewLayout 实现所有这些方法似乎很多工作,但这是唯一有效的方法。 例如,您可以使用itemSizeitemsInOneRow等属性。 然后,你可以很容易地找到一个公式来计算基于它的每个项目的帧的号码(最好的办法是做在prepareLayout和所有帧存储阵列,这样你卡恩访问你所需要的帧layoutAttributesForItemAtIndexPath )。 实现layoutAttributesForItemAtIndexPathlayoutAttributesForItemsInRectcollectionViewContentSize也会非常简单。 initialLayoutAttributesForAppearingItemAtIndexPathfinalLayoutAttributesForDisappearingItemAtIndexPath您可以将alpha属性设置为0.0。 这就是标准流布局动画的工作原理。 通过重写targetContentOffsetForProposedContentOffset:withScrollingVelocity:您可以实现“分页行为”。

  2. 考虑使用流布局, pagingEnabled = YES ,水平滚动方向和项目大小等于屏幕大小来创建集合视图。 每个屏幕尺寸一个项目。 对于每个单元格,您可以将新的集合视图设置为具有垂直流布局的子视图,以及与其他集合视图相同的数据源,但具有偏移量。 它非常有效,因为您可以重用包含9个(或其他)单元格块的整个集合视图,而不是使用标准方法重用每个单元格。 所有动画都应该正常工作。

在这里,您可以使用布局子类化方法下载示例项目。 (#2)

使用标准UICollectionViewFlowLayout获得2个集合视图不是一个简单的解决方案吗?

甚至更好:拥有一个带水平滚动的页面视图控制器,每个页面都是一个具有正常流程布局的集合视图。

这个想法如下:在你的UICollectionViewController -init method你创建了一个第二个集合视图,其右边的帧偏移是原始集合视图宽度。 然后将其作为子视图添加到原始集合视图中。 要在集合视图之间切换,只需添加滑动识别器即可。 要计算偏移值,您可以将集合视图的原始帧存储在ivar cVFrame 要标识您的集合视图,您可以使用标记。

init方法的示例:

CGRect cVFrame = self.collectionView.frame;
UICollectionView *secondView = [[UICollectionView alloc] 
              initWithFrame:CGRectMake(cVFrame.origin.x + cVFrame.size.width, 0, 
                             cVFrame.size.width, cVFrame.size.height) 
       collectionViewLayout:[UICollectionViewFlowLayout new]];
    [secondView setBackgroundColor:[UIColor greenColor]];
    [secondView setTag:1];
    [secondView setDelegate:self];
    [secondView setDataSource:self];
    [self.collectionView addSubview:secondView];

UISwipeGestureRecognizer *swipeRight = [[UISwipeGestureRecognizer alloc]
                      initWithTarget:self action:@selector(swipedRight)];
    [swipeRight setDirection:UISwipeGestureRecognizerDirectionRight];

UISwipeGestureRecognizer *swipeLeft = [[UISwipeGestureRecognizer alloc] 
                       initWithTarget:self action:@selector(swipedLeft)];
    [swipeLeft setDirection:UISwipeGestureRecognizerDirectionLeft];

    [self.collectionView addGestureRecognizer:swipeRight];
    [self.collectionView addGestureRecognizer:swipeLeft];

swipeRightswipeLeft方法的示例:

-(void)swipedRight {
    // Switch to left collection view
    [self.collectionView setContentOffset:CGPointMake(0, 0) animated:YES];
}

-(void)swipedLeft {
    // Switch to right collection view
    [self.collectionView setContentOffset:CGPointMake(cVFrame.size.width, 0) 
                                 animated:YES];
}

然后,实现DataSource方法并不是一个大问题(在您的情况下,您希望每页上有9个项目):

-(NSInteger)collectionView:(UICollectionView *)collectionView 
    numberOfItemsInSection:(NSInteger)section {
    if (collectionView.tag == 1) {
         // Second collection view
         return self.dataArray.count % 9;
    } else {
         // Original collection view
         return 9; // Or whatever
}

在方法-collectionView:cellForRowAtIndexPath ,如果是第二个集合视图,则需要从模型中获取带偏移量的数据。

另外,不要忘记为第二个集合视图注册可重用单元格的类。 您还可以只创建一个手势识别器,并识别左侧和右侧的滑动。 由你决定。

我想,现在它应该工作,试试看:-)

您有一个实现UICollectionViewDataSource协议的对象。 collectionView:cellForItemAtIndexPath:只返回您想要返回的正确项目。 我不明白哪里会有问题。

编辑:好的,我看到了问题。 以下是解决方案: http//www.skeuo.com/uicollectionview-custom-layout-tutorial ,具体步骤17至25.这不是一项大量工作,可以很容易地重复使用。

这是我的解决方案,我没有使用动画进行测试,但我认为它会很好地进行一些改动,希望它有用

CELLS_PER_ROW = 4;
CELLS_PER_COLUMN = 5;
CELLS_PER_PAGE = CELLS_PER_ROW * CELLS_PER_COLUMN;

UICollectionViewFlowLayout* flowLayout = (UICollectionViewFlowLayout*)collectionView.collectionViewLayout;
flowLayout.scrollDirection = UICollectionViewScrollDirectionHorizontal;
flowLayout.itemSize = new CGSize (size.width / CELLS_PER_ROW - delta, size.height / CELLS_PER_COLUMN - delta);
flowLayout.minimumInteritemSpacing = ..;
flowLayout.minimumLineSpacing = ..;
..

- (UICollectionViewCell *)collectionView:(UICollectionView *)collectionView
                  cellForItemAtIndexPath:(NSIndexPath *)indexPath
{
    NSInteger index = indexPath.row;
    NSInteger page = index / CELLS_PER_PAGE;
    NSInteger indexInPage = index - page * CELLS_PER_PAGE;
    NSInteger row = indexInPage % CELLS_PER_COLUMN;
    NSInteger column = indexInPage / CELLS_PER_COLUMN;

    NSInteger dataIndex = row * CELLS_PER_ROW + column + page * CELLS_PER_PAGE;

    id cellData = _data[dataIndex];
    ...
}

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM