ios 创建圆形布局

创建圆形布局

    圆形布局是一种醒目的排版方式,他会将试图里的内容绕着某个中心区域来排布,这种布局方式很好的演示了如何在创建条目和删除条目的时候,把操作过程以动画形式展现出来。

ios 创建圆形布局_第1张图片

    通过collectionViewContentSize方法把视图内容的尺寸设为固定值。由于它明确的创建了一块固定不变的排版区域,所以集合视图不会再滚动了。代码还会在prepareLayout方法中进行计算,以便进一步向内缩小排版区域。屏幕的高度与屏幕的宽度之中较小的值,决定了圆的半径。无论屏幕方向如何改变,圆的半径总保持不变。

@implementation CircleLayout
{
    NSInteger numberOfItems;
    CGPoint centerPoint;
    CGFloat radius;
    
    NSMutableArray *insertedIndexPaths;
    NSMutableArray * deletedIndexPaths;
}

- (void)prepareLayout
{
    [super prepareLayout];
    CGSize size = self.collectionView.frame.size;
    numberOfItems = [self.collectionView numberOfItemsInSection:0];
    centerPoint = CGPointMake(size.width / 2, size.height / 2);
    radius = MIN(size.width, size.height) / 3;
    insertedIndexPaths = [NSMutableArray array];
    deletedIndexPaths = [NSMutableArray array];
}

- (CGSize)collectionViewContentSize
{
    return self.collectionView.frame.size;
}

- (UICollectionViewLayoutAttributes *)layoutAttributesForItemAtIndexPath:(NSIndexPath *)indexPath
{
    UICollectionViewLayoutAttributes *attributes = [UICollectionViewLayoutAttributes layoutAttributesForCellWithIndexPath:indexPath];
    
    CGFloat progress = (float)indexPath.item / (float)numberOfItems;
    CGFloat theta = 2 * M_PI * progress;
    CGFloat xPosition = centerPoint.x + radius * cos(theta);
    CGFloat yPosition = centerPoint.y + radius * sin(theta);
    attributes.size = [self itemSize];
    attributes.center = CGPointMake(xPosition, yPosition);
    return attributes;
}

- (NSArray *)layoutAttributesForElementsInRect:(CGRect)rect
{
    NSMutableArray *attributes = [NSMutableArray array];
    for (NSInteger index = 0; index < numberOfItems; index ++) {
        NSIndexPath *path = [NSIndexPath indexPathForItem:index inSection:0];
        [attributes addObject:[self layoutAttributesForItemAtIndexPath:path]];
    }
    return attributes;
}

- (void)prepareForCollectionViewUpdates:(NSArray *)updateItems
{
    [super prepareForCollectionViewUpdates:updateItems];
    for (UICollectionViewUpdateItem *updateItem in updateItems) {
        if (updateItem.updateAction == UICollectionUpdateActionInsert) {
            [insertedIndexPaths addObject:updateItem.indexPathAfterUpdate];
        }else if (updateItem.updateAction == UICollectionUpdateActionDelete){
            [deletedIndexPaths addObject:updateItem.indexPathBeforeUpdate];
        }
    }
}

- (UICollectionViewLayoutAttributes *)insertionAttributesForItemAtIndexPath:(NSIndexPath *)itemIndexPath
{
    UICollectionViewLayoutAttributes *attributes = [self layoutAttributesForItemAtIndexPath:itemIndexPath];
    attributes.alpha = 0;
    attributes.center = centerPoint;
    return attributes;
}

- (UICollectionViewLayoutAttributes *)deletionAttributesForItemAtIndexPath:(NSIndexPath *)itemIndexPath
{
    UICollectionViewLayoutAttributes *attributes = [self layoutAttributesForItemAtIndexPath:itemIndexPath];
    attributes.alpha = 0;
    attributes.center = centerPoint;
    attributes.transform3D = CATransform3DMakeScale(0.1, 0.1, 1);
    return attributes;
}

- (UICollectionViewLayoutAttributes *)initialLayoutAttributesForAppearingItemAtIndexPath:(NSIndexPath *)itemIndexPath
{
    return [insertedIndexPaths containsObject:itemIndexPath] ? [self insertionAttributesForItemAtIndexPath:itemIndexPath] : [super initialLayoutAttributesForAppearingItemAtIndexPath:itemIndexPath];
}

- (UICollectionViewLayoutAttributes *)finalLayoutAttributesForDisappearingItemAtIndexPath:(NSIndexPath *)itemIndexPath
{
    return [deletedIndexPaths containsObject:itemIndexPath] ? [self deletionAttributesForItemAtIndexPath:itemIndexPath] : [super finalLayoutAttributesForDisappearingItemAtIndexPath:itemIndexPath];
}
@end

    布局对象会根据每个条目的索引路径来计算他的位置。这个例子采用的布局方式只使用了一个区段,每个条目在该区段内的顺序决定了它在圆周上的位置:

 CGFloat progress = (float)indexPath.item / (float)numberOfItems;
    CGFloat theta = 2 * M_PI * progress;

    上述计算方式也适用于其他图形或其他形式的索引路径,只要各条目在索引路径中的位置都能调整到[0.0,1.0]这个范围内就行。对于圆形来说,此范围可以和从0至2π的弧度对应起来,而对于螺旋形的布局来说,此范围则可以和从0到3π、4π到5π的弧度对应起来。如果想按照贝塞尔曲线来布局,那么开发者需要遍历能够决定曲线形状的各个控制点,并要根据情况在其中插入其他的点。

    

1、实现创建条目与删除条目时的动画效果

    在例子中有几个方法值得关注,他们分别制定了新插入的条目所具备的初始属性,以及刚删除的条目所应具备的最后属性。这些属性使得集合视图在添加新条目及删除现有条目的时候,能够以动画效果来表示该操作执行前后的布局变化过程。

    这个例子的动画效果与苹果公司的原始范例代码一样:新添加的条目一开始会以全透明的形态出现在圆圈正中,然后会移动到它应有的位置上,在移动过程中它将逐渐淡入。而刚删除的条目则会从目前的位置移向圆心,并在此过程中逐渐缩小、淡出。运行范例代码的时候能看到这些效果了。

    开发文档中的initialLayoutAttributesForAppearingItemAtIndexPath及finalLayoutAttributesForDisappearingItemAtIndexPath方法,名字起得很令人困惑,从表面上看,这些方法似乎只会针对刚插入或删除的条目来调用。但实际上,系统会向每一个条目询问它的起始属性和最终属性,而不仅仅向刚添加或删除的条目询问。所以,在添加条目和删除条目的时候,会把所添加条目及所删条目的索引路径分别记录到两个数字里面。这样的话,我们就可以只针对当前要添加或删除的条目来定制其属性了。

该机制所提供的这种方法,能够把视图中全部条目的布局属性都以动画形式表现出来,使得开发者可以按照需要添加额外的动画效果。比方说,如果有新的条目插入第三行,那么该行末尾的条目就应该移动到第四行的开头。在默认的情况下,末尾的条目会按照斜线方向移动到下一行开头,但有了这套方式之后,我们就可以把第三行末尾的单元格向右移出屏幕,然后再将其从第四行左侧移入屏幕。

2、增强圆形布局的实用性

用户可以选定某个条目,然后可以把该条目删掉,也可以把新的条目添加到他的后面:
- (void)delete
{
    if (!count) {
        return;
    }
    count --;
    
    NSArray *selectedItems = [self.collectionView indexPathsForSelectedItems];
    NSInteger itemNumber = selectedItems.count ? ((NSIndexPath *)selectedItems[0]).item : 0;
    NSIndexPath *itemPath = [NSIndexPath indexPathForItem:itemNumber inSection:0];
    self.itemsInSection --;
    [self.collectionView performBatchUpdates:^{
        [self.collectionView deleteItemsAtIndexPaths:@[itemPath]];
        
        
    } completion:^(BOOL finished) {
        if (count) {
            [self.collectionView selectItemAtIndexPath:[NSIndexPath indexPathForItem:MAX(0, itemNumber - 1) inSection:0] animated:NO scrollPosition:UICollectionViewScrollPositionNone];
        }
        
        self.navigationItem.rightBarButtonItem.enabled = (count > 0);
        self.navigationItem.leftBarButtonItem.enabled = (count < 12);
        
    }];
}
- (void)add
{
    NSInteger itemNumber = self.itemsInSection ;
    NSIndexPath *itemPath = [NSIndexPath indexPathForItem:itemNumber inSection:0];
    self.itemsInSection ++;
    count ++;
    [self.collectionView performBatchUpdates:^{
        [self.collectionView insertItemsAtIndexPaths:@[itemPath]];
    } completion:^(BOOL finished) {
        self.navigationItem.rightBarButtonItem.enabled = (count > 0);
        self.navigationItem.leftBarButtonItem.enabled = (count < 12);
        
    }];
}


在真实的应用程序中很少需要添加或删除一些彼此之间没有区别的视图,不过,有的时候却需要添加或删除一些含义各不相同的视图。因此,这个例子所做的改动使得这个范例变得更加实用了。


你可能感兴趣的:(iOS)