iOS:UICollectionView流式布局及其在該布局上的擴展的線式布局


UICollectionViewFlowLayout是蘋果公司做好的一種單元格布局方式,它約束item的排列規則是:從左到右依次排列,如果右邊不夠放下,就換一行重復上面的方式排放,,,,,
 
常用的流式布局UICollectionViewFlowLayout的屬性

@property (nonatomic) CGFloat minimumLineSpacing;       //每一個item之間最小的行間距

@property (nonatomic) CGFloat minimumInteritemSpacing;//每一個item之間最小的列間距

@property (nonatomic) CGSize itemSize;              //每一個item的大小

@property (nonatomic) CGSize estimatedItemSize; //每一個item的預測大小 

@property (nonatomic) UICollectionViewScrollDirection scrollDirection; // 集合視圖的滾動方向,默認垂直

@property (nonatomic) CGSize headerReferenceSize; //表頭視圖大小

@property (nonatomic) CGSize footerReferenceSize; //表尾視圖大小

@property (nonatomic) UIEdgeInsets sectionInset;    //和集合視圖上下左右四邊的間距

 

使用流式布局很簡單,不需要我們做任何的操作,只需要創建它的實例,然后把它放入創建的集合視圖中即可。然而,流式布局看起來比較的單一,沒有很炫酷的效果。基於此,我們可以在流式布局的基礎上進行一些布局的擴展,比如線式布局等。。。

 

 

 

 

下面就做一個流式布局和線式布局的切換,點擊時,可以自由切換,使集合視圖的item排列呈現出不同的效果,舉例如下:

1、首先創建一個自定義的單元格類,並附帶創建一個.xib文件,在.xib文件中設置一個UIImageView,將它IBOutLet到對應的類中

  

 

2、准備一些圖片素材

3、在ImagesCell類中

.h

#import <UIKit/UIKit.h>

@interface ImagesCell : UICollectionViewCell
@property (strong,nonatomic)UIImage *image;
@end

.m

#import "ImagesCell.h"

@interface ImagesCell() 
@property (weak, nonatomic) IBOutlet UIImageView *imageView;
@end

@implementation ImagesCell


- (void)awakeFromNib {
    
    //設置圖像視圖圖層的屬性
    self.imageView.layer.borderWidth = 3;
    self.imageView.layer.borderColor = [[UIColor redColor]CGColor];
    self.imageView.layer.cornerRadius = 5;
    self.imageView.clipsToBounds = YES; //切割邊角
    
}


-(void)setImage:(UIImage *)image
{
    _image = image;
    
    //顯示圖片
    [_imageView setImage:_image];
}
@end

3.在控制器類ViewController類中

#import "ViewController.h"
#import "ImagesCell.h"
#import "CustomLineLayout.h"

@interface ViewController ()<UICollectionViewDataSource,UICollectionViewDelegate>
@property (strong,nonatomic)UICollectionView *collectionView;
@property (strong,nonatomic)NSMutableArray *images;
@end

@implementation ViewController

static NSString *const reuseIndentifier = @"image";

//懶加載
-(NSMutableArray *)images
{
    if (!_images)
    {
        _images = [NSMutableArray array];
        for (int i=1; i<=25; i++)
        {
            [_images addObject:[NSString stringWithFormat:@"clothes%d",i]];
        }
    }
    return _images;
}

- (void)viewDidLoad {
    [super viewDidLoad];
   
    //創建集合視圖
    CGFloat width = self.view.frame.size.width;
    CGRect rect = CGRectMake(0, 100, width, 200);
    self.collectionView = [[UICollectionView alloc]initWithFrame:rect collectionViewLayout:[[CustomLineLayout alloc]init]];

    
    //設置數據源和代理
    self.collectionView.dataSource  = self;
    self.collectionView.delegate  = self;
    
    //注冊單元格
    [self.collectionView registerNib:[UINib nibWithNibName:@"ImagesCell" bundle:nil] forCellWithReuseIdentifier:reuseIndentifier];
    
    //添加視圖
   [self.view addSubview:self.collectionView];
    
    
    //UICollectionViewLayout:最根本的布局,自定義布局時,完全需要自己重新去寫需要的布局
    //UICollectionViewFlowLayout :流水布局,自定義布局時,有時可以在它的布局基礎上再進行擴展布局
}


//切換布局方式
-(void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event
{
    if ([self.collectionView.collectionViewLayout isKindOfClass:[CustomLineLayout class]])
    {
        [self.collectionView setCollectionViewLayout:[[UICollectionViewFlowLayout alloc]init] animated:YES];
    }
    else
    {
        [self.collectionView setCollectionViewLayout:[[CustomLineLayout alloc]init] animated:YES];
    }
}

#pragma mark - <UICollectionDataSourceDelegate>
//返回組數
-(NSInteger)numberOfSectionsInCollectionView:(UICollectionView *)collectionView
{
    return 1;
}
//返回個數
-(NSInteger)collectionView:(UICollectionView *)collectionView numberOfItemsInSection:(NSInteger)section
{
    return self.images.count;
}
//顯示conllectionView的單元格
-(ImagesCell*)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath
{
    //設置重用單元格
    
    ImagesCell *cell = [collectionView dequeueReusableCellWithReuseIdentifier:reuseIndentifier forIndexPath:indexPath];

    //設置cell的內容
    cell.image = [UIImage imageNamed:[self.images objectAtIndex:indexPath.item]];
    return cell;
}

//選中item時刪除它
-(void)collectionView:(UICollectionView *)collectionView didSelectItemAtIndexPath:(NSIndexPath *)indexPath
{
    //1.先刪除掉對應的模型數據
    [self.images removeObjectAtIndex:indexPath.item];
    
    //2.刪除item(刷新UI)
    [self.collectionView deleteItemsAtIndexPaths:@[indexPath]];
}
@end

4、自定義線式布局,它繼承於流式布局,即

在.m文件中設置每一個item的布局屬性如下:

#import "CustomLineLayout.h"

//設置item的固定的寬和高
static const CGFloat itemWH = 100;

//設置縮放時的有效距離
static const CGFloat activeDistance = 150;

//設置縮放因數,值越大,縮放效果越明顯
static const CGFloat scaleFactor = 0.6;

@implementation CustomLineLayout


//UICollectionViewLayoutAttributes:很重要,布局屬性設置
//每一個cell(item)都有自己的UICollectionViewLayoutAttributes
//每一個indexPath都有自己的UICollectionViewLayoutAttributes

-(instancetype)init{
    if (self = [super init]){
        
    }
    return self;
}

//每一次重新布局前,都會准備布局(蘋果官方推薦使用該方法進行一些初始化)
-(void)prepareLayout
{
    [super prepareLayout];
    
    //初始化,設置默認的item屬性
    self.itemSize = CGSizeMake(itemWH, itemWH);
    self.scrollDirection = UICollectionViewScrollDirectionHorizontal;
    self.minimumLineSpacing = itemWH * scaleFactor;
    
    //將第一個和最后一個item始終顯示在中間,即分別將它們設置到組頭和組尾的距離
    CGFloat inset = (self.collectionView.frame.size.width - itemWH) / 2;
    self.sectionInset = UIEdgeInsetsMake(0, inset, 0, inset);
}

//是否要重新刷新布局(只要顯示的item邊界發生改變就重新布局)
//只要每一次重新布局內部就會調用下面的layoutAttributesForElementsInRect:獲取所有cell(item)的屬性
-(BOOL)shouldInvalidateLayoutForBoundsChange:(CGRect)newBounds
{
    return YES;
}


//用來設置colectionView停止滾動時的那一刻位置,內部會自動調用
#pragma targetContentOffset : 原本colectionView停止滾動時的那一刻位置
#pragma velocity : 滾動的速率,根據正負可以判斷滾動方向是向左還是向右

-(CGPoint)targetContentOffsetForProposedContentOffset:(CGPoint)proposedContentOffset withScrollingVelocity:(CGPoint)velocity
{
    
    //1.計算colectionView最終停留的位置
    CGRect lastRect;
    lastRect.origin = proposedContentOffset;
    lastRect.size = self.collectionView.frame.size;
    
    //2.取出這個范圍內的所有item的屬性
    NSArray *array = [self layoutAttributesForElementsInRect:lastRect];
    
    
    //3.計算最終屏幕的中心x
    CGFloat centerX = proposedContentOffset.x+ self.collectionView.frame.size.width/2;
    
    
    //4.遍歷所有的屬性,通過計算item與最終屏幕中心的最小距離,然后將item移動屏幕的中心位置
    CGFloat adjustOffsetX = MAXFLOAT;
    for (UICollectionViewLayoutAttributes *attris in array)
    {
        if (ABS(attris.center.x - centerX) < ABS(adjustOffsetX)) {
            
            adjustOffsetX = attris.center.x - centerX;
        }
    }
 
    //5.返回要移動到中心的item的位置
    return CGPointMake(proposedContentOffset.x + adjustOffsetX , proposedContentOffset.y);
}

//返回需要重新布局的所有item屬性
-(NSArray *)layoutAttributesForElementsInRect:(CGRect)rect
{
    //0.計算可見的矩形框屬性
    CGRect visiableRect;
    visiableRect.size = self.collectionView.frame.size;
    visiableRect.origin = self.collectionView.contentOffset;
    
    
    //1.取出默認的cell的UICollectionViewLayoutAttributes
    NSArray *array = [super layoutAttributesForElementsInRect:rect];
    
    
    //計算屏幕最中心的x(滾出去的所有的item的偏移 + collectionView寬度的一半)
    CGFloat centerX = self.collectionView.contentOffset.x + self.collectionView.frame.size.width/2;
    
    
    //2.遍歷所有的布局屬性
    for(UICollectionViewLayoutAttributes *attrs in array)
    {
        //如果遍歷的item和可見的矩形框的frame不相交,即不e是可見的,就直接跳過,只對可見的item進行放縮
        if (!CGRectIntersectsRect(visiableRect, attrs.frame)) continue;
        
        //每一個item的中心x
        CGFloat itemCenterX =  attrs.center.x;
        
        
        //差距越大,縮放比例越小
        //計算每一個item的中心x和屏幕最中心x的絕對值距離,然后可以計算出縮放比例,即
        
        //<1>計算間距/屏幕一半時的比例,得出的數一定<1
        //CGFloat ratio = ABS(itemCenterX - centerX) / (self.collectionView.frame.size.width/2);
        //CGFloat ratio = ABS(itemCenterX - centerX) / 150;
        //<2>實現放大
        //CGFloat scale = 1 +  (1 - ratio);
        //attrs.transform3D = CATransform3DMakeScale(scale, scale, 1.0);
        //attrs.transform = CGAffineTransformMakeScale(scale, scale);
        
        
        //當item的中心x距離屏幕的中心x距離在有效距離150以內時,item才開始放大
        if (ABS(itemCenterX - centerX) <= activeDistance)
        {
            //CGFloat ratio = ABS(itemCenterX - centerX) / (self.collectionView.frame.size.width/2);
            CGFloat ratio = ABS(itemCenterX - centerX) / activeDistance;
            
            //<2>實現放大
            CGFloat scale = 1 +  scaleFactor*(1 - ratio);
            attrs.transform3D = CATransform3DMakeScale(scale, scale, 1.0);
            //attrs.transform = CGAffineTransformMakeScale(scale, scale);
        }
        else
        {
            attrs.transform = CGAffineTransformMakeScale(1, 1);
        }
    }
    return array;
}
@end

演示結果如下:

 流式布局:                                                切換為線式布局:

 

 

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM