iOS之流布局UICollectionView全系列教程

一虫溜、簡介

UICollectionView是iOS6之后引入的一個(gè)新的UI控件,它和UITableView有著諸多的相似之處,其中許多代理方法都十分類似香缺。簡單來說,UICollectionView是比UITbleView更加強(qiáng)大的一個(gè)UI控件歇僧,有如下幾個(gè)方面:

1图张、支持水平和垂直兩種方向的布局

2锋拖、通過layout配置方式進(jìn)行布局

3、類似于TableView中的cell特性外祸轮,CollectionView中的Item大小和位置可以自由定義

4兽埃、通過layout布局回調(diào)的代理方法,可以動(dòng)態(tài)的定制每個(gè)item的大小和collection的大體布局屬性

5适袜、更加強(qiáng)大一點(diǎn)柄错,完全自定義一套layout布局方案,可以實(shí)現(xiàn)意想不到的效果

這篇博客苦酱,我們主要討論CollectionView使用原生layout的方法和相關(guān)屬性售貌,其他特點(diǎn)和更強(qiáng)的制定化,會(huì)在后面的博客中介紹

二疫萤、先來實(shí)現(xiàn)一個(gè)最簡單的九宮格類布局

在了解UICollectionView的更多屬性前颂跨,我們先來使用其進(jìn)行一個(gè)最簡單的流布局試試看,在controller的viewDidLoad中添加如下代碼:

//創(chuàng)建一個(gè)layout布局類
UICollectionViewFlowLayout * layout = [[UICollectionViewFlowLayout alloc]init];
//設(shè)置布局方向?yàn)榇怪绷鞑季?layout.scrollDirection = UICollectionViewScrollDirectionVertical;
//設(shè)置每個(gè)item的大小為100*100
layout.itemSize = CGSizeMake(100, 100);
//創(chuàng)建collectionView 通過一個(gè)布局策略layout來創(chuàng)建
UICollectionView * collect = [[UICollectionView alloc]initWithFrame:self.view.frame collectionViewLayout:layout];
//代理設(shè)置
collect.delegate=self;
collect.dataSource=self;
//注冊item類型 這里使用系統(tǒng)的類型
[collect registerClass:[UICollectionViewCell class] forCellWithReuseIdentifier:@"cellid"];
   
[self.view addSubview:collect];

這里有一點(diǎn)需要注意扯饶,collectionView在完成代理回調(diào)前恒削,必須注冊一個(gè)cell聚请,類似如下:

[collect registerClass:[UICollectionViewCell class] forCellWithReuseIdentifier:@"cellid"];

這和tableView有些類似抬纸,又有些不同亏娜,因?yàn)閠ableView除了注冊cell的方法外恒傻,還可以通過臨時(shí)創(chuàng)建來做:


//tableView在從復(fù)用池中取cell的時(shí)候捷绒,有如下兩種方法
//使用這種方式如果復(fù)用池中無帮非,是可以返回nil的欧啤,我們在臨時(shí)創(chuàng)建即可
- (nullable __kindof UITableViewCell *)dequeueReusableCellWithIdentifier:(NSString *)identifier;
//6.0后使用如下的方法直接從注冊的cell類獲取創(chuàng)建鼓择,如果沒有注冊 會(huì)崩潰
- (__kindof UITableViewCell *)dequeueReusableCellWithIdentifier:(NSString *)identifier forIndexPath:(NSIndexPath *)indexPath NS_AVAILABLE_IOS(6_0);

我們可以分析:因?yàn)閁ICollectionView是iOS6.0之前的新類脯爪,因此這里統(tǒng)一了從復(fù)用池中獲取cell的方法则北,沒有再提供可以返回nil的方式,并且在UICollectionView的回調(diào)代理中痕慢,只能使用從復(fù)用池中獲取cell的方式進(jìn)行cell的返回尚揣,其他方式會(huì)崩潰,例如:


//這是正確的方法
-(UICollectionViewCell *)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath{
    UICollectionViewCell * cell  = [collectionView dequeueReusableCellWithReuseIdentifier:@"cellid" forIndexPath:indexPath];
    cell.backgroundColor = [UIColor colorWithRed:arc4random()%255/255.0 green:arc4random()%255/255.0 blue:arc4random()%255/255.0 alpha:1];
    return cell;
}
 
//這樣做會(huì)崩潰
-(UICollectionViewCell *)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath{
//    UICollectionViewCell * cell  = [collectionView dequeueReusableCellWithReuseIdentifier:@"cellid" forIndexPath:indexPath];
//    cell.backgroundColor = [UIColor colorWithRed:arc4random()%255/255.0 green:arc4random()%255/255.0 blue:arc4random()%255/255.0 alpha:1];
    UICollectionViewCell * cell = [[UICollectionViewCell alloc]init];
    return cell;
}

上面錯(cuò)誤的方式會(huì)崩潰掖举,信息如下快骗,讓我們使用從復(fù)用池中取cell的方式:


上面的設(shè)置完成后,我們來實(shí)現(xiàn)如下幾個(gè)代理方法:

這里與TableView的回調(diào)方式十分類似


//返回分區(qū)個(gè)數(shù)
-(NSInteger)numberOfSectionsInCollectionView:(UICollectionView *)collectionView{
    return 1;
}
//返回每個(gè)分區(qū)的item個(gè)數(shù)
-(NSInteger)collectionView:(UICollectionView *)collectionView numberOfItemsInSection:(NSInteger)section{
    return 10;
}
//返回每個(gè)item
-(UICollectionViewCell *)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath{
    UICollectionViewCell * cell  = [collectionView dequeueReusableCellWithReuseIdentifier:@"cellid" forIndexPath:indexPath];
    cell.backgroundColor = [UIColor colorWithRed:arc4random()%255/255.0 green:arc4random()%255/255.0 blue:arc4random()%255/255.0 alpha:1];
    return cell;
}

效果如下:

同樣塔次,如果內(nèi)容的大小超出一屏方篮,和tableView類似是可以進(jìn)行視圖滑動(dòng)的。

還有一點(diǎn)細(xì)節(jié)励负,我們在上面設(shè)置布局方式的時(shí)候設(shè)置了垂直布局:

layout.scrollDirection = UICollectionViewScrollDirectionVertical;
//這個(gè)是水平布局
//layout.scrollDirection = UICollectionViewScrollDirectionHorizontal;

這樣系統(tǒng)會(huì)在一行充滿后進(jìn)行第二行的排列藕溅,如果設(shè)置為水平布局,則會(huì)在一列充滿后继榆,進(jìn)行第二列的布局巾表,這種方式也被稱為流式布局

三汁掠、UICollectionView中的常用方法和屬性

//通過一個(gè)布局策略初識(shí)化CollectionView
- (instancetype)initWithFrame:(CGRect)frame collectionViewLayout:(UICollectionViewLayout *)layout;
 
//獲取和設(shè)置collection的layout
@property (nonatomic, strong) UICollectionViewLayout *collectionViewLayout;
 
//數(shù)據(jù)源和代理
@property (nonatomic, weak, nullable) id <UICollectionViewDelegate> delegate;
@property (nonatomic, weak, nullable) id <UICollectionViewDataSource> dataSource;
 
//從一個(gè)class或者xib文件進(jìn)行cell(item)的注冊
- (void)registerClass:(nullable Class)cellClass forCellWithReuseIdentifier:(NSString *)identifier;
- (void)registerNib:(nullable UINib *)nib forCellWithReuseIdentifier:(NSString *)identifier;
 
//下面兩個(gè)方法與上面相似,這里注冊的是頭視圖或者尾視圖的類
//其中第二個(gè)參數(shù)是設(shè)置 頭視圖或者尾視圖 系統(tǒng)為我們定義好了這兩個(gè)字符串
//UIKIT_EXTERN NSString *const UICollectionElementKindSectionHeader NS_AVAILABLE_IOS(6_0);
//UIKIT_EXTERN NSString *const UICollectionElementKindSectionFooter NS_AVAILABLE_IOS(6_0);
- (void)registerClass:(nullable Class)viewClass forSupplementaryViewOfKind:(NSString *)elementKind withReuseIdentifier:(NSString *)identifier;
- (void)registerNib:(nullable UINib *)nib forSupplementaryViewOfKind:(NSString *)kind withReuseIdentifier:(NSString *)identifier;
 
//這兩個(gè)方法是從復(fù)用池中取出cell或者頭尾視圖
- (__kindof UICollectionViewCell *)dequeueReusableCellWithReuseIdentifier:(NSString *)identifier forIndexPath:(NSIndexPath *)indexPath;
- (__kindof UICollectionReusableView *)dequeueReusableSupplementaryViewOfKind:(NSString *)elementKind withReuseIdentifier:(NSString *)identifier forIndexPath:(NSIndexPath *)indexPath;
 
//設(shè)置是否允許選中 默認(rèn)yes
@property (nonatomic) BOOL allowsSelection;
 
//設(shè)置是否允許多選 默認(rèn)no
@property (nonatomic) BOOL allowsMultipleSelection;
 
//獲取所有選中的item的位置信息
- (nullable NSArray<NSIndexPath *> *)indexPathsForSelectedItems; 
 
//設(shè)置選中某一item集币,并使視圖滑動(dòng)到相應(yīng)位置考阱,scrollPosition是滑動(dòng)位置的相關(guān)參數(shù),如下:
/*
typedef NS_OPTIONS(NSUInteger, UICollectionViewScrollPosition) {
    //無
    UICollectionViewScrollPositionNone                 = 0,
    //垂直布局時(shí)使用的 對(duì)應(yīng)上中下
    UICollectionViewScrollPositionTop                  = 1 << 0,
    UICollectionViewScrollPositionCenteredVertically   = 1 << 1,
    UICollectionViewScrollPositionBottom               = 1 << 2,
    //水平布局時(shí)使用的  對(duì)應(yīng)左中右
    UICollectionViewScrollPositionLeft                 = 1 << 3,
    UICollectionViewScrollPositionCenteredHorizontally = 1 << 4,
    UICollectionViewScrollPositionRight                = 1 << 5
};
*/
- (void)selectItemAtIndexPath:(nullable NSIndexPath *)indexPath animated:(BOOL)animated scrollPosition:(UICollectionViewScrollPosition)scrollPosition;
 
//將某一item取消選中
- (void)deselectItemAtIndexPath:(NSIndexPath *)indexPath animated:(BOOL)animated;
 
//重新加載數(shù)據(jù)
- (void)reloadData;
 
//下面這兩個(gè)方法鞠苟,可以重新設(shè)置collection的布局乞榨,后面的方法多了一個(gè)布局完成后的回調(diào),iOS7后可以用
//使用這兩個(gè)方法可以產(chǎn)生非常炫酷的動(dòng)畫效果
- (void)setCollectionViewLayout:(UICollectionViewLayout *)layout animated:(BOOL)animated;
- (void)setCollectionViewLayout:(UICollectionViewLayout *)layout animated:(BOOL)animated completion:(void (^ __nullable)(BOOL finished))completion NS_AVAILABLE_IOS(7_0);
 
//下面這些方法更加強(qiáng)大偶妖,我們可以對(duì)布局更改后的動(dòng)畫進(jìn)行設(shè)置
//這個(gè)方法傳入一個(gè)布局策略layout姜凄,系統(tǒng)會(huì)開始進(jìn)行布局渲染政溃,返回一個(gè)UICollectionViewTransitionLayout對(duì)象
//這個(gè)UICollectionViewTransitionLayout對(duì)象管理動(dòng)畫的相關(guān)屬性趾访,我們可以進(jìn)行設(shè)置
- (UICollectionViewTransitionLayout *)startInteractiveTransitionToCollectionViewLayout:(UICollectionViewLayout *)layout completion:(nullable UICollectionViewLayoutInteractiveTransitionCompletion)completion NS_AVAILABLE_IOS(7_0);
//準(zhǔn)備好動(dòng)畫設(shè)置后,我們需要調(diào)用下面的方法進(jìn)行布局動(dòng)畫的展示董虱,之后會(huì)調(diào)用上面方法的block回調(diào)
- (void)finishInteractiveTransition NS_AVAILABLE_IOS(7_0);
//調(diào)用這個(gè)方法取消上面的布局動(dòng)畫設(shè)置扼鞋,之后也會(huì)進(jìn)行上面方法的block回調(diào)
- (void)cancelInteractiveTransition NS_AVAILABLE_IOS(7_0);
 
//獲取分區(qū)數(shù)
- (NSInteger)numberOfSections;
 
//獲取某一分區(qū)的item數(shù)
- (NSInteger)numberOfItemsInSection:(NSInteger)section;
 
//下面兩個(gè)方法獲取item或者頭尾視圖的layout屬性,這個(gè)UICollectionViewLayoutAttributes對(duì)象
//存放著布局的相關(guān)數(shù)據(jù)愤诱,可以用來做完全自定義布局云头,后面博客會(huì)介紹
- (nullable UICollectionViewLayoutAttributes *)layoutAttributesForItemAtIndexPath:(NSIndexPath *)indexPath;
- (nullable UICollectionViewLayoutAttributes *)layoutAttributesForSupplementaryElementOfKind:(NSString *)kind atIndexPath:(NSIndexPath *)indexPath;
 
//獲取某一點(diǎn)所在的indexpath位置
- (nullable NSIndexPath *)indexPathForItemAtPoint:(CGPoint)point;
 
//獲取某個(gè)cell所在的indexPath
- (nullable NSIndexPath *)indexPathForCell:(UICollectionViewCell *)cell;
 
//根據(jù)indexPath獲取cell
- (nullable UICollectionViewCell *)cellForItemAtIndexPath:(NSIndexPath *)indexPath;
 
//獲取所有可見cell的數(shù)組
- (NSArray<__kindof UICollectionViewCell *> *)visibleCells;
 
//獲取所有可見cell的位置數(shù)組
- (NSArray<NSIndexPath *> *)indexPathsForVisibleItems;
 
//下面三個(gè)方法是iOS9中新添加的方法,用于獲取頭尾視圖
- (UICollectionReusableView *)supplementaryViewForElementKind:(NSString *)elementKind atIndexPath:(NSIndexPath *)indexPath NS_AVAILABLE_IOS(9_0);
- (NSArray<UICollectionReusableView *> *)visibleSupplementaryViewsOfKind:(NSString *)elementKind NS_AVAILABLE_IOS(9_0);
- (NSArray<NSIndexPath *> *)indexPathsForVisibleSupplementaryElementsOfKind:(NSString *)elementKind NS_AVAILABLE_IOS(9_0);
 
//使視圖滑動(dòng)到某一位置淫半,可以帶動(dòng)畫效果
- (void)scrollToItemAtIndexPath:(NSIndexPath *)indexPath atScrollPosition:(UICollectionViewScrollPosition)scrollPosition animated:(BOOL)animated;
 
//下面這些方法用于動(dòng)態(tài)添加溃槐,刪除,移動(dòng)某些分區(qū)獲取items
- (void)insertSections:(NSIndexSet *)sections;
- (void)deleteSections:(NSIndexSet *)sections;
- (void)reloadSections:(NSIndexSet *)sections;
- (void)moveSection:(NSInteger)section toSection:(NSInteger)newSection;
 
- (void)insertItemsAtIndexPaths:(NSArray<NSIndexPath *> *)indexPaths;
- (void)deleteItemsAtIndexPaths:(NSArray<NSIndexPath *> *)indexPaths;
- (void)reloadItemsAtIndexPaths:(NSArray<NSIndexPath *> *)indexPaths;
- (void)moveItemAtIndexPath:(NSIndexPath *)indexPath toIndexPath:(NSIndexPath *)newIndexPath;

iOS流布局UICollectionView系列二——UICollectionView的代理方法

一科吭、引言

在上一篇博客中昏滴,介紹了最基本的UICollectionView的使用和其中我們常用的屬性和方法,也介紹了瀑布流布局的過程與思路对人,這篇博客來討論關(guān)于UICollectionView的代理方法的使用谣殊。

二、UICollectionViewDataSource協(xié)議

這個(gè)協(xié)議主要用于collectionView相關(guān)數(shù)據(jù)的處理牺弄,包含方法如下:

首先姻几,有兩個(gè)方法是我們必須實(shí)現(xiàn)的:

設(shè)置每個(gè)分區(qū)的Item個(gè)數(shù)

  • (NSInteger)collectionView:(UICollectionView *)collectionView numberOfItemsInSection:(NSInteger)section;

設(shè)置返回每個(gè)item的屬性

  • (UICollectionViewCell *)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath;

下面的方法是可選實(shí)現(xiàn)的:

雖然這個(gè)方法是可選的,一般我們都會(huì)去實(shí)現(xiàn)势告,設(shè)置分區(qū)數(shù)

  • (NSInteger)numberOfSectionsInCollectionView:(UICollectionView *)collectionView;

對(duì)頭視圖或者尾視圖進(jìn)行設(shè)置

  • (UICollectionReusableView *)collectionView:(UICollectionView *)collectionView viewForSupplementaryElementOfKind:(NSString *)kind atIndexPath:(NSIndexPath *)indexPath;

設(shè)置某個(gè)item是否可以被移動(dòng)蛇捌,返回NO則不能移動(dòng)

  • (BOOL)collectionView:(UICollectionView *)collectionView canMoveItemAtIndexPath:(NSIndexPath *)indexPath NS_AVAILABLE_IOS(9_0);

移動(dòng)item的時(shí)候,會(huì)調(diào)用這個(gè)方法

  • (void)collectionView:(UICollectionView *)collectionView moveItemAtIndexPath:(NSIndexPath )sourceIndexPath toIndexPath:(NSIndexPath)destinationIndexPath咱台;

三络拌、UICollectionViewDelegate協(xié)議

這個(gè)協(xié)議用來設(shè)置和處理collectionView的功能和一些邏輯,所有方法都是可選實(shí)現(xiàn):

是否允許某個(gè)Item的高亮吵护,返回NO盒音,則不能進(jìn)入高亮狀態(tài)

  • (BOOL)collectionView:(UICollectionView *)collectionView shouldHighlightItemAtIndexPath:(NSIndexPath *)indexPath;

當(dāng)item高亮?xí)r觸發(fā)的方法

  • (void)collectionView:(UICollectionView *)collectionView didHighlightItemAtIndexPath:(NSIndexPath *)indexPath;

結(jié)束高亮狀態(tài)時(shí)觸發(fā)的方法

  • (void)collectionView:(UICollectionView *)collectionView didUnhighlightItemAtIndexPath:(NSIndexPath *)indexPath;

是否可以選中某個(gè)Item表鳍,返回NO,則不能選中

  • (BOOL)collectionView:(UICollectionView *)collectionView shouldSelectItemAtIndexPath:(NSIndexPath *)indexPath;

是否可以取消選中某個(gè)Item

  • (BOOL)collectionView:(UICollectionView *)collectionView shouldDeselectItemAtIndexPath:(NSIndexPath *)indexPath;

已經(jīng)選中某個(gè)item時(shí)觸發(fā)的方法

  • (void)collectionView:(UICollectionView *)collectionView didSelectItemAtIndexPath:(NSIndexPath *)indexPath;

取消選中某個(gè)Item時(shí)觸發(fā)的方法

  • (void)collectionView:(UICollectionView *)collectionView didDeselectItemAtIndexPath:(NSIndexPath *)indexPath;

將要加載某個(gè)Item時(shí)調(diào)用的方法

  • (void)collectionView:(UICollectionView *)collectionView willDisplayCell:(UICollectionViewCell *)cell forItemAtIndexPath:(NSIndexPath *)indexPath NS_AVAILABLE_IOS(8_0);

將要加載頭尾視圖時(shí)調(diào)用的方法

  • (void)collectionView:(UICollectionView *)collectionView willDisplaySupplementaryView:(UICollectionReusableView *)view forElementKind:(NSString *)elementKind atIndexPath:(NSIndexPath *)indexPath NS_AVAILABLE_IOS(8_0);

已經(jīng)展示某個(gè)Item時(shí)觸發(fā)的方法

  • (void)collectionView:(UICollectionView *)collectionView didEndDisplayingCell:(UICollectionViewCell *)cell forItemAtIndexPath:(NSIndexPath *)indexPath;

已經(jīng)展示某個(gè)頭尾視圖時(shí)觸發(fā)的方法

  • (void)collectionView:(UICollectionView *)collectionView didEndDisplayingSupplementaryView:(UICollectionReusableView *)view forElementOfKind:(NSString *)elementKind atIndexPath:(NSIndexPath *)indexPath;

這個(gè)方法設(shè)置是否展示長按菜單

  • (BOOL)collectionView:(UICollectionView *)collectionView shouldShowMenuForItemAtIndexPath:(NSIndexPath *)indexPath;

長按菜單中可以觸發(fā)一下類復(fù)制粘貼的方法祥诽,效果如下:

這個(gè)方法用于設(shè)置要展示的菜單選項(xiàng)

  • (BOOL)collectionView:(UICollectionView *)collectionView canPerformAction:(SEL)action forItemAtIndexPath:(NSIndexPath *)indexPath withSender:(nullable id)sender;

這個(gè)方法用于實(shí)現(xiàn)點(diǎn)擊菜單按鈕后的觸發(fā)方法,通過測試譬圣,只有copy,cut和paste三個(gè)方法可以使用

  • (void)collectionView:(UICollectionView *)collectionView performAction:(SEL)action forItemAtIndexPath:(NSIndexPath *)indexPath withSender:(nullable id)sender;

通過下面的方式可以將點(diǎn)擊按鈕的方法名打印出來:

-(void)collectionView:(UICollectionView *)collectionView performAction:(SEL)action forItemAtIndexPath:(NSIndexPath *)indexPath withSender:(id)sender{    
    NSLog(@"%@",NSStringFromSelector(action));
}

collectionView進(jìn)行重新布局時(shí)調(diào)用的方法

  • (nonnull UICollectionViewTransitionLayout *)collectionView:(UICollectionView *)collectionView transitionLayoutForOldLayout:(UICollectionViewLayout *)fromLayout newLayout:(UICollectionViewLayout *)toLayout;

iOS流布局UICollectionView系列三——使用FlowLayout進(jìn)行更靈活布局

一雄坪、引言

前面的博客介紹了UICollectionView的相關(guān)方法和其協(xié)議中的方法厘熟,但對(duì)布局的管理類UICollectionViewFlowLayout沒有著重探討,這篇博客介紹關(guān)于布局的相關(guān)設(shè)置和屬性方法维哈。通過layout的設(shè)置绳姨,我們可以編寫更加靈活的布局效果。

二阔挠、將九宮格式的布局進(jìn)行升級(jí)

在第一篇博客中飘庄,通過UICollectionView,我們很輕松的完成了一個(gè)九宮格的布局购撼,但是如此中規(guī)中矩的布局方式跪削,有時(shí)候并不能滿足我們的需求,有時(shí)我們需要每一個(gè)Item展示不同的大小迂求,代碼如下:

- (void)viewDidLoad {
    [super viewDidLoad];
    // Do any additional setup after loading the view, typically from a nib.
    UICollectionViewFlowLayout *layout = [[UICollectionViewFlowLayout alloc]init];
    layout.scrollDirection = UICollectionViewScrollDirectionVertical;
    UICollectionView *collect = [[UICollectionView alloc]initWithFrame:CGRectMake(0, 0, 320, 400) collectionViewLayout:layout];
    collect.delegate=self;
    collect.dataSource=self;
    
    [collect registerClass:[UICollectionViewCell class] forCellWithReuseIdentifier:@"cellid"];
  ;
    [self.view addSubview:collect];
    
    
}
//設(shè)置每個(gè)item的大小碾盐,雙數(shù)的為50*50 單數(shù)的為100*100
-(CGSize)collectionView:(UICollectionView *)collectionView layout:(UICollectionViewLayout *)collectionViewLayout sizeForItemAtIndexPath:(NSIndexPath *)indexPath{
    if (indexPath.row%2==0) {
        return CGSizeMake(50, 50);
    }else{
        return CGSizeMake(100, 100);
    }
}
 
//代理相應(yīng)方法
-(NSInteger)numberOfSectionsInCollectionView:(UICollectionView *)collectionView{
    return 1;
}
-(NSInteger)collectionView:(UICollectionView *)collectionView numberOfItemsInSection:(NSInteger)section{
    return 100;
}
-(UICollectionViewCell *)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath{
    UICollectionViewCell * cell  = [collectionView dequeueReusableCellWithReuseIdentifier:@"cellid" forIndexPath:indexPath];
    cell.backgroundColor = [UIColor colorWithRed:arc4random()%255/255.0 green:arc4random()%255/255.0 blue:arc4random()%255/255.0 alpha:1];
    return cell;
}

效果如下:

現(xiàn)在的布局效果是不是炫酷了許多。

三揩局、UICollectionViewFlowLayout相關(guān)屬性方法

UICollectionViewFlowLayout是系統(tǒng)提供給我們一個(gè)封裝好的流布局設(shè)置類毫玖,其中有一些布局屬性我們可以進(jìn)行設(shè)置:

設(shè)置行與行之間的間距最小距離

@property (nonatomic) CGFloat minimumLineSpacing;

設(shè)置列與列之間的間距最小距離

@property (nonatomic) CGFloat minimumInteritemSpacing;

設(shè)置每個(gè)item的大小

@property (nonatomic) CGSize itemSize;

設(shè)置每個(gè)Item的估計(jì)大小,一般不需要設(shè)置

@property (nonatomic) CGSize estimatedItemSize NS_AVAILABLE_IOS(8_0);

設(shè)置布局方向

@property (nonatomic) UICollectionViewScrollDirection scrollDirection;

這個(gè)UICollectionViewScrollDirection的枚舉如下:

typedef NS_ENUM(NSInteger, UICollectionViewScrollDirection) {    
    UICollectionViewScrollDirectionVertical,//水平布局    
    UICollectionViewScrollDirectionHorizontal//垂直布局
};

設(shè)置頭視圖尺寸大小

@property (nonatomic) CGSize headerReferenceSize;

設(shè)置尾視圖尺寸大小

@property (nonatomic) CGSize footerReferenceSize;

設(shè)置分區(qū)的EdgeInset

@property (nonatomic) UIEdgeInsets sectionInset;

這個(gè)屬性可以設(shè)置分區(qū)的偏移量凌盯,例如我們在剛才的例子中添加如下設(shè)置:

 layout.sectionInset = UIEdgeInsetsMake(20, 20, 20, 20);

效果如下付枫,會(huì)看到分區(qū)的邊界閃出了20像素

下面這兩個(gè)方法設(shè)置分區(qū)的頭視圖和尾視圖是否始終固定在屏幕上邊和下邊

@property (nonatomic) BOOL sectionHeadersPinToVisibleBounds NS_AVAILABLE_IOS(9_0);

@property (nonatomic) BOOL sectionFootersPinToVisibleBounds NS_AVAILABLE_IOS(9_0);

四、動(dòng)態(tài)的配置layout的相關(guān)屬性UICollectionViewDelegateFlowLayout

上面的方法在創(chuàng)建FlowLayout時(shí)靜態(tài)的進(jìn)行設(shè)置十气,如果我們需要?jiǎng)討B(tài)的設(shè)置這些屬性励背,就像我們例子中的,每個(gè)item的大小會(huì)有差異砸西,我們可以通過代理來實(shí)現(xiàn)叶眉。

UICollectionViewDelegateFlowLayout是UICollectionViewDelegate的子協(xié)議,其中常用方法如下芹枷,我們只需要實(shí)現(xiàn)我們需要的即可:

動(dòng)態(tài)設(shè)置每個(gè)Item的尺寸大小

  • (CGSize)collectionView:(UICollectionView )collectionView layout:(UICollectionViewLayout)collectionViewLayout sizeForItemAtIndexPath:(NSIndexPath *)indexPath;

動(dòng)態(tài)設(shè)置每個(gè)分區(qū)的EdgeInsets

  • (UIEdgeInsets)collectionView:(UICollectionView )collectionView layout:(UICollectionViewLayout)collectionViewLayout insetForSectionAtIndex:(NSInteger)section;

動(dòng)態(tài)設(shè)置每行的間距大小

  • (CGFloat)collectionView:(UICollectionView )collectionView layout:(UICollectionViewLayout)collectionViewLayout minimumLineSpacingForSectionAtIndex:(NSInteger)section;

動(dòng)態(tài)設(shè)置每列的間距大小

  • (CGFloat)collectionView:(UICollectionView )collectionView layout:(UICollectionViewLayout)collectionViewLayout minimumInteritemSpacingForSectionAtIndex:(NSInteger)section;

動(dòng)態(tài)設(shè)置某個(gè)分區(qū)頭視圖大小

  • (CGSize)collectionView:(UICollectionView )collectionView layout:(UICollectionViewLayout)collectionViewLayout referenceSizeForHeaderInSection:(NSInteger)section;

動(dòng)態(tài)設(shè)置某個(gè)分區(qū)尾視圖大小

  • (CGSize)collectionView:(UICollectionView )collectionView layout:(UICollectionViewLayout)collectionViewLayout referenceSizeForFooterInSection:(NSInteger)section;

iOS流布局UICollectionView系列四——自定義FlowLayout進(jìn)行瀑布流布局

一衅疙、引言

前幾篇博客從UICollectionView的基礎(chǔ)應(yīng)用到設(shè)置UICollectionViewFlowLayout更加靈活的進(jìn)行布局,但都限制在系統(tǒng)為我們準(zhǔn)備好的布局框架中鸳慈,還是有一些局限性饱溢,例如,如果我要進(jìn)行瀑布流似的不定高布局走芋,前面的方法就很難滿足我們的需求了绩郎,如下:

這種布局無疑在app的應(yīng)用中更加廣泛潘鲫,商品的展示,書架書目的展示肋杖,都會(huì)傾向于采用這樣的布局方式状植,當(dāng)然,通過自定義FlowLayout津畸,我們也很容易實(shí)現(xiàn)。

二后频、進(jìn)行自定義瀑布流布局

首先,我們新建一個(gè)文件繼承于UICollectionViewFlowLayout:

@interface MyLayout : UICollectionViewFlowLayout

為了演示的方便帝簇,這里我不做更多的封裝丧肴,只添加一個(gè)屬性芋浮,直接讓外界將item個(gè)數(shù)傳遞進(jìn)來壳快,我們把重心方法重寫布局的方法上:

@interface MyLayout : UICollectionViewFlowLayout
@property(nonatomic,assign)int itemCount;
@end

前面說過眶痰,UICollectionViewFlowLayout是一個(gè)專門用來管理collectionView布局的類竖伯,因此七婴,collectionView在進(jìn)行UI布局前打厘,會(huì)通過這個(gè)類的對(duì)象獲取相關(guān)的布局信息户盯,F(xiàn)lowLayout類將這些布局信息全部存放在了一個(gè)數(shù)組中嵌施,數(shù)組中是UICollectionViewLayoutAttributes類滓侍,這個(gè)類是對(duì)item布局的具體設(shè)置撩笆,以后咱們在討論這個(gè)類夕冲〈跤悖總之弥姻,F(xiàn)lowLayout類將每個(gè)item的位置等布局信息放在一個(gè)數(shù)組中庭敦,在collectionView布局時(shí)秧廉,會(huì)調(diào)用FlowLayout類layoutAttributesForElementsInRect:方法來獲取這個(gè)布局配置數(shù)組。因此蔽豺,我們需要重寫這個(gè)方法修陡,返回我們自定義的配置數(shù)組濒析,另外号杏,F(xiàn)lowLayout類在進(jìn)行布局之前主经,會(huì)調(diào)用prepareLayout方法罩驻,所以我們可以重寫這個(gè)方法惠遏,在里面對(duì)我們的自定義配置數(shù)據(jù)進(jìn)行一些設(shè)置节吮。

簡單來說,自定義一個(gè)FlowLayout布局類就是兩個(gè)步驟:

1帚豪、設(shè)計(jì)好我們的布局配置數(shù)據(jù) prepareLayout方法中

2狸臣、返回我們的配置數(shù)組 layoutAttributesForElementsInRect方法中

示例代碼如下:

@implementation MyLayout
{
    //這個(gè)數(shù)組就是我們自定義的布局配置數(shù)組
    NSMutableArray * _attributeAttay;
}
//數(shù)組的相關(guān)設(shè)置在這個(gè)方法中
//布局前的準(zhǔn)備會(huì)調(diào)用這個(gè)方法
-(void)prepareLayout{
    _attributeAttay = [[NSMutableArray alloc]init];
    [super prepareLayout];
    //演示方便 我們設(shè)置為靜態(tài)的2列
    //計(jì)算每一個(gè)item的寬度
    float WIDTH = ([UIScreen mainScreen].bounds.size.width-self.sectionInset.left-self.sectionInset.right-self.minimumInteritemSpacing)/2;
    //定義數(shù)組保存每一列的高度
    //這個(gè)數(shù)組的主要作用是保存每一列的總高度,這樣在布局時(shí),我們可以始終將下一個(gè)Item放在最短的列下面
    CGFloat colHight[2]={self.sectionInset.top,self.sectionInset.bottom};
    //itemCount是外界傳進(jìn)來的item的個(gè)數(shù) 遍歷來設(shè)置每一個(gè)item的布局
    for (int i=0; i<_itemCount; i++) {
        //設(shè)置每個(gè)item的位置等相關(guān)屬性
        NSIndexPath *index = [NSIndexPath indexPathForItem:i inSection:0];
        //創(chuàng)建一個(gè)布局屬性類委粉,通過indexPath來創(chuàng)建
        UICollectionViewLayoutAttributes * attris = [UICollectionViewLayoutAttributes layoutAttributesForCellWithIndexPath:index];
        //隨機(jī)一個(gè)高度 在40——190之間
        CGFloat hight = arc4random()%150+40;
        //哪一列高度小 則放到那一列下面
        //標(biāo)記最短的列
        int width=0;
        if (colHight[0]<colHight[1]) {
            //將新的item高度加入到短的一列
            colHight[0] = colHight[0]+hight+self.minimumLineSpacing;
            width=0;
        }else{
            colHight[1] = colHight[1]+hight+self.minimumLineSpacing;
            width=1;
        }
        
        //設(shè)置item的位置
        attris.frame = CGRectMake(self.sectionInset.left+(self.minimumInteritemSpacing+WIDTH)*width, colHight[width]-hight-self.minimumLineSpacing, WIDTH, hight);
        [_attributeAttay addObject:attris];
    }
    
    //設(shè)置itemSize來確保滑動(dòng)范圍的正確 這里是通過將所有的item高度平均化栗涂,計(jì)算出來的(以最高的列位標(biāo)準(zhǔn))
    if (colHight[0]>colHight[1]) {
        self.itemSize = CGSizeMake(WIDTH, (colHight[0]-self.sectionInset.top)*2/_itemCount-self.minimumLineSpacing);
    }else{
          self.itemSize = CGSizeMake(WIDTH, (colHight[1]-self.sectionInset.top)*2/_itemCount-self.minimumLineSpacing);
    }
    
}
//這個(gè)方法中返回我們的布局?jǐn)?shù)組
-(NSArray<UICollectionViewLayoutAttributes *> *)layoutAttributesForElementsInRect:(CGRect)rect{
    return _attributeAttay;
}
 
 
@end

自定義完成FlowLayout后,我們在ViewController中進(jìn)行使用:

- (void)viewDidLoad {
    [super viewDidLoad];
    // Do any additional setup after loading the view, typically from a nib.
    MyLayout * layout = [[MyLayout alloc]init];
    layout.scrollDirection = UICollectionViewScrollDirectionVertical;
    layout.itemCount=100;
     UICollectionView * collect  = [[UICollectionView alloc]initWithFrame:CGRectMake(0, 0, 320, 400) collectionViewLayout:layout];
    collect.delegate=self;
    collect.dataSource=self;
    
    [collect registerClass:[UICollectionViewCell class] forCellWithReuseIdentifier:@"cellid"];
  
    [self.view addSubview:collect];
    
    
}
 
 
 
-(NSInteger)numberOfSectionsInCollectionView:(UICollectionView *)collectionView{
    return 1;
}
-(NSInteger)collectionView:(UICollectionView *)collectionView numberOfItemsInSection:(NSInteger)section{
    return 100;
}
-(UICollectionViewCell *)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath{
    UICollectionViewCell * cell  = [collectionView dequeueReusableCellWithReuseIdentifier:@"cellid" forIndexPath:indexPath];
    cell.backgroundColor = [UIColor colorWithRed:arc4random()%255/255.0 green:arc4random()%255/255.0 blue:arc4random()%255/255.0 alpha:1];
    return cell;
}

運(yùn)行效果就是我們引言中的截圖忿墅。

三亿柑、UICollectionViewLayoutAttributes類中我們可以配置的屬性

通過上面的例子望薄,我們可以了解,collectionView的item布局其實(shí)是LayoutAttributes類具體配置的采转,這個(gè)類可以配置的布局屬性不止是frame這么簡單,其中還有許多屬性:

//配置item的布局位置
@property (nonatomic) CGRect frame;
//配置item的中心
@property (nonatomic) CGPoint center;
//配置item的尺寸
@property (nonatomic) CGSize size;
//配置item的3D效果
@property (nonatomic) CATransform3D transform3D;
//配置item的bounds
@property (nonatomic) CGRect bounds NS_AVAILABLE_IOS(7_0);
//配置item的旋轉(zhuǎn)
@property (nonatomic) CGAffineTransform transform NS_AVAILABLE_IOS(7_0);
//配置item的alpha
@property (nonatomic) CGFloat alpha;
//配置item的z坐標(biāo)
@property (nonatomic) NSInteger zIndex; // default is 0
//配置item的隱藏
@property (nonatomic, getter=isHidden) BOOL hidden; 
//item的indexpath
@property (nonatomic, strong) NSIndexPath *indexPath;
//獲取item的類型
@property (nonatomic, readonly) UICollectionElementCategory representedElementCategory;
@property (nonatomic, readonly, nullable) NSString *representedElementKind; 
 
//一些創(chuàng)建方法
+ (instancetype)layoutAttributesForCellWithIndexPath:(NSIndexPath *)indexPath;
+ (instancetype)layoutAttributesForSupplementaryViewOfKind:(NSString *)elementKind withIndexPath:(NSIndexPath *)indexPath;
+ (instancetype)layoutAttributesForDecorationViewOfKind:(NSString *)decorationViewKind withIndexPath:(NSIndexPath *)indexPath;

通過上面的屬性,可以布局出各式各樣的炫酷效果拆撼,正如一句話:沒有做不到闸度,只有想不到莺禁。

iOS流布局UICollectionView系列五——圓環(huán)布局的實(shí)現(xiàn)

一、引言

前邊的幾篇博客浩峡,我們了解了UICollectionView的基本用法以及一些擴(kuò)展翰灾,在不定高的瀑布流布局中预侯,我們發(fā)現(xiàn)双戳,可以通過設(shè)置具體的布局屬性類UICollectionViewLayoutAttributes來設(shè)置設(shè)置每個(gè)item的具體位置飒货,我們可以再擴(kuò)展一下塘辅,如果位置我們可以自由控制扣墩,那個(gè)布局我們也可以更加靈活,就比如創(chuàng)建一個(gè)如下的circleLayout:

這種布局方式在apple的官方文檔中也有介紹亚脆,是UICollectionView的一個(gè)應(yīng)用示例。

二柑营、設(shè)計(jì)一個(gè)圓環(huán)布局

先自定義一個(gè)layout類由境,這個(gè)類繼承于UICollectionViewLayout讥蟆,UICollectionLayout是一個(gè)布局抽象基類瘸彤,我們要使用自定義的布局方式,必須將其子類化玻靡,可能你還記得,我們在進(jìn)行瀑布流布局的時(shí)候使用過UICollectionViewFlowLayout類蝎土,這個(gè)類就是繼承于UICollectionViewLayout類,系統(tǒng)為我們實(shí)現(xiàn)好的一個(gè)布局方案暴构。

@interface MyLayout : UICollectionViewLayout
//這個(gè)int值存儲(chǔ)有多少個(gè)item
@property(nonatomic,assign)int itemCount;
@end

我們需要重寫這個(gè)類的三個(gè)方法取逾,來進(jìn)行圓環(huán)布局的設(shè)置,首先是prepareLayout琉用,為布局做一些準(zhǔn)備工作邑时,使用collectionViewContentSize來設(shè)置內(nèi)容的區(qū)域大小,最后使用layoutAttributesForElementsInRect方法來返回我們的布局信息字典浅浮,這個(gè)和前面瀑布流布局的思路是一樣的:

@implementation MyLayout
{
    NSMutableArray * _attributeAttay;
}
-(void)prepareLayout{
    [super prepareLayout];
    //獲取item的個(gè)數(shù)
    _itemCount = (int)[self.collectionView numberOfItemsInSection:0];
    _attributeAttay = [[NSMutableArray alloc]init];
    //先設(shè)定大圓的半徑 取長和寬最短的
    CGFloat radius = MIN(self.collectionView.frame.size.width, self.collectionView.frame.size.height)/2;
    //計(jì)算圓心位置
    CGPoint center = CGPointMake(self.collectionView.frame.size.width/2, self.collectionView.frame.size.height/2);
    //設(shè)置每個(gè)item的大小為50*50 則半徑為25
    for (int i=0; i<_itemCount; i++) {
        UICollectionViewLayoutAttributes * attris = [UICollectionViewLayoutAttributes layoutAttributesForCellWithIndexPath:[NSIndexPath indexPathForItem:i inSection:0]];
        //設(shè)置item大小
        attris.size = CGSizeMake(50, 50);
        //計(jì)算每個(gè)item的圓心位置
        /*
         .
         . .
         .   . r
         .     .
         .........
         */
        //計(jì)算每個(gè)item中心的坐標(biāo)
        //算出的x y值還要減去item自身的半徑大小
        float x = center.x+cosf(2*M_PI/_itemCount*i)*(radius-25);
        float y = center.y+sinf(2*M_PI/_itemCount*i)*(radius-25);
     
        attris.center = CGPointMake(x, y);
        [_attributeAttay addObject:attris];
    }
    
   
    
}
//設(shè)置內(nèi)容區(qū)域的大小
-(CGSize)collectionViewContentSize{
    return self.collectionView.frame.size;
}
//返回設(shè)置數(shù)組
-(NSArray<UICollectionViewLayoutAttributes *> *)layoutAttributesForElementsInRect:(CGRect)rect{
    return _attributeAttay;
}

在viewController中代碼如下:

- (void)viewDidLoad {
    [super viewDidLoad];
    // Do any additional setup after loading the view, typically from a nib.
    MyLayout * layout = [[MyLayout alloc]init];
     UICollectionView * collect  = [[UICollectionView alloc]initWithFrame:CGRectMake(0, 0, 320, 400) collectionViewLayout:layout];
    collect.delegate=self;
    collect.dataSource=self;
    
    [collect registerClass:[UICollectionViewCell class] forCellWithReuseIdentifier:@"cellid"];
    [self.view addSubview:collect];
}
 
-(NSInteger)numberOfSectionsInCollectionView:(UICollectionView *)collectionView{
    return 1;
}
-(NSInteger)collectionView:(UICollectionView *)collectionView numberOfItemsInSection:(NSInteger)section{
    return 10;
}
-(UICollectionViewCell *)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath{
    UICollectionViewCell * cell  = [collectionView dequeueReusableCellWithReuseIdentifier:@"cellid" forIndexPath:indexPath];
    cell.layer.masksToBounds = YES;
    cell.layer.cornerRadius = 25;
    cell.backgroundColor = [UIColor colorWithRed:arc4random()%255/255.0 green:arc4random()%255/255.0 blue:arc4random()%255/255.0 alpha:1];
    return cell;
}

如上非常簡單的一些邏輯控制淮捆,我們就實(shí)現(xiàn)哦圓環(huán)布局桐腌,隨著item的多少案站,布局會(huì)自動(dòng)調(diào)整,如果不是UICollectionView的功勞,實(shí)現(xiàn)這樣的功能誊稚,我們可能要寫上一陣子了_疾瓮。

iOS流布局UICollectionView系列六——將布局從平面應(yīng)用到空間

一肩碟、引言

前面,我們將布局由線性的瀑布流布局?jǐn)U展到了圓環(huán)布局吨拍,這使我們使用UICollectionView的布局思路大大邁進(jìn)了一步,這次,我們玩的更加炫一些,想辦法將布局應(yīng)用到空間戏锹。之前在管理布局的item的具體屬性的類UICollectionViewLayoutAttributrs類中,有transform3D這個(gè)屬性,通過這個(gè)屬性的設(shè)置宏粤,我們真的可以在空間的坐標(biāo)系中進(jìn)行布局設(shè)計(jì)。iOS系統(tǒng)的控件中赶袄,也并非沒有這樣的先例,UIPickerView就是很好的一個(gè)實(shí)例,這篇博客,我們就通過使用UICollectionView實(shí)現(xiàn)一個(gè)類似系統(tǒng)的UIPickerView的布局視圖逆粹,來體會(huì)UICollectionView在3D控件布局的魅力。系統(tǒng)的pickerView效果如下:

二蟋字、先來實(shí)現(xiàn)一個(gè)炫酷的滾輪空間布局

萬丈的高樓也是由一磚一瓦堆砌而成忠聚,在我們完全模擬系統(tǒng)pickerView前赂毯,我們應(yīng)該先將視圖的布局?jǐn)[放這一問題解決膛堤。我們依然來創(chuàng)建一個(gè)類朝群,繼承于UICollectionViewLayout:

@interface MyLayout : UICollectionViewLayout 

@end

對(duì)于.m文件的內(nèi)容,前幾篇博客中我們都是在prepareLayout中進(jìn)行布局的靜態(tài)設(shè)置怯晕,那是因?yàn)槲覀兦皫灼┛椭械牟季侄际庆o態(tài)的潜圃,布局并不會(huì)隨著我們的手勢操作而發(fā)生太大的變化舟茶,因此我們?nèi)吭趐repareLayout中一次配置完了。而我們這次要討論的布局則不同堵第,pickerView會(huì)隨著我們手指的拖動(dòng)而進(jìn)行滾動(dòng)吧凉,因此UICollectionView中的每一個(gè)item的布局是在不斷變化的,所以這次踏志,我們采用動(dòng)態(tài)配置的方式阀捅,在layoutAttributesForItemAtIndexPath方法中進(jìn)行每個(gè)item的布局屬性設(shè)置。

至于layoutAttributesForItemAtIndexPath方法针余,它也是UICollectionViewLayout類中的方法饲鄙,用于我們自定義時(shí)進(jìn)行重寫,至于為什么動(dòng)態(tài)布局要在這里面配置item的布局屬性圆雁,后面我們會(huì)了解到忍级。

在編寫我們的布局類之前,先做好準(zhǔn)備工作伪朽,在viewController中轴咱,實(shí)現(xiàn)如下代碼:


- (void)viewDidLoad {
    [super viewDidLoad];
    // Do any additional setup after loading the view, typically from a nib.
    MyLayout * layout = [[MyLayout alloc]init];
     UICollectionView * collect  = [[UICollectionView alloc]initWithFrame:CGRectMake(0, 0, 320, 400) collectionViewLayout:layout];
    collect.delegate=self;
    collect.dataSource=self;
    [collect registerClass:[UICollectionViewCell class] forCellWithReuseIdentifier:@"cellid"];
    [self.view addSubview:collect];
}
 
-(NSInteger)numberOfSectionsInCollectionView:(UICollectionView *)collectionView{
    return 1;
}
-(NSInteger)collectionView:(UICollectionView *)collectionView numberOfItemsInSection:(NSInteger)section{
    return 10;
}
-(UICollectionViewCell *)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath{
    UICollectionViewCell * cell  = [collectionView dequeueReusableCellWithReuseIdentifier:@"cellid" forIndexPath:indexPath];
    cell.backgroundColor = [UIColor colorWithRed:arc4random()%255/255.0 green:arc4random()%255/255.0 blue:arc4random()%255/255.0 alpha:1];
    UILabel * label = [[UILabel alloc]initWithFrame:CGRectMake(0, 0, 250, 80)];
    label.text = [NSString stringWithFormat:@"我是第%ld行",(long)indexPath.row];
    [cell.contentView addSubview:label];
    return cell;
}

上面我創(chuàng)建了10個(gè)Item,并且在每個(gè)Item上添加了一個(gè)標(biāo)簽烈涮,標(biāo)寫是第幾行朴肺。

在我們自定義的布局類中重寫layoutAttributesForElementsInRect,在其中返回我們的布局?jǐn)?shù)組:

-(NSArray<UICollectionViewLayoutAttributes *> *)layoutAttributesForElementsInRect:(CGRect)rect{
    NSMutableArray * attributes = [[NSMutableArray alloc]init];
    //遍歷設(shè)置每個(gè)item的布局屬性
    for (int i=0; i<[self.collectionView numberOfItemsInSection:0]; i++) {
        [attributes addObject:[self layoutAttributesForItemAtIndexPath:[NSIndexPath indexPathForItem:i inSection:0]]];
    }
    return attributes;
}

之后坚洽,在我們布局類中重寫layoutAttributesForItemAtIndexPath方法:

-(UICollectionViewLayoutAttributes *)layoutAttributesForItemAtIndexPath:(NSIndexPath *)indexPath{
    //創(chuàng)建一個(gè)item布局屬性類
    UICollectionViewLayoutAttributes * atti = [UICollectionViewLayoutAttributes layoutAttributesForCellWithIndexPath:indexPath];
    //獲取item的個(gè)數(shù)
    int itemCounts = (int)[self.collectionView numberOfItemsInSection:0];
    //設(shè)置每個(gè)item的大小為260*100
    atti.size = CGSizeMake(260, 100);  
   /*
   后邊介紹的代碼添加在這里
   
   */ 
    return atti;
}

上面的代碼中戈稿,我們什么都沒有做,下面我們一步步來實(shí)現(xiàn)3D的滾輪效果讶舰。

首先鞍盗,我們先將所有的item的位置都設(shè)置為collectionView的中心:

atti.center = CGPointMake(self.collectionView.frame.size.width/2, self.collectionView.frame.size.height/2);

這時(shí),如果我們運(yùn)行程序的話绘雁,所有item都將一層層貼在屏幕的中央橡疼,如下:

很丑對(duì)吧,之后我們來設(shè)置每個(gè)item的3D效果,在上面的布局方法中添加如下代碼:

    //創(chuàng)建一個(gè)transform3D類
    //CATransform3D是一個(gè)類似矩陣的結(jié)構(gòu)體
    //CATransform3DIdentity創(chuàng)建空得矩陣
    CATransform3D trans3D = CATransform3DIdentity;
    //這個(gè)值設(shè)置的是透視度庐舟,影響視覺離投影平面的距離
    trans3D.m34 = -1/900.0;
    //下面這些屬性 后面會(huì)具體介紹
    //這個(gè)是3D滾輪的半徑
    CGFloat radius = 50/tanf(M_PI*2/itemCounts/2);
    //計(jì)算每個(gè)item應(yīng)該旋轉(zhuǎn)的角度
    CGFloat angle = (float)(indexPath.row)/itemCounts*M_PI*2;
    //這個(gè)方法返回一個(gè)新的CATransform3D對(duì)象欣除,在原來的基礎(chǔ)上進(jìn)行旋轉(zhuǎn)效果的追加
    //第一個(gè)參數(shù)為旋轉(zhuǎn)的弧度,后三個(gè)分別對(duì)應(yīng)x挪略,y历帚,z軸滔岳,我們需要以x軸進(jìn)行旋轉(zhuǎn)
    trans3D = CATransform3DRotate(trans3D, angle, 1.0, 0, 0);
    //進(jìn)行設(shè)置
    atti.transform3D = trans3D;

對(duì)于上面的radius屬性,運(yùn)用了一些簡單的幾何和三角函數(shù)的知識(shí)挽牢。如果我們將系統(tǒng)的pickerView沿著y軸旋轉(zhuǎn)90°谱煤,你會(huì)發(fā)現(xiàn)側(cè)面的它是一個(gè)規(guī)則的正多邊形,這里的radius就是這個(gè)多邊形中心到其邊的垂直距離禽拔,也是內(nèi)切圓的半徑刘离,所有的item拼成了一個(gè)正多邊形,示例如下:

通過簡單的數(shù)學(xué)知識(shí)睹栖,h/2弦對(duì)應(yīng)的角的弧度為2*pi/(邊數(shù))/2硫惕,在根據(jù)三角函數(shù)相關(guān)知識(shí)可知,這個(gè)角的正切值為h/2/radius野来,這就是我們r(jià)adius的由來恼除。

對(duì)于angle屬性,它是每一個(gè)item的x軸旋轉(zhuǎn)度數(shù)曼氛,如果我們將所有item的中心都放在一點(diǎn)豁辉,通過旋轉(zhuǎn)讓它們散開如下圖所示:

每個(gè)item旋轉(zhuǎn)的弧度就是其索引/(2*pi)。

通過上面的設(shè)置舀患,我們再運(yùn)行代碼徽级,效果如下:

仔細(xì)觀察我們可以發(fā)現(xiàn),item以x中軸線進(jìn)行了旋轉(zhuǎn)平均布局构舟,側(cè)面的效果就是我們上面的簡筆畫那樣灰追,下面要進(jìn)行我們的第三步了,將這個(gè)item狗超,全部沿著其Z軸向前拉弹澎,就可以成為我們滾輪的效果,示例圖如下:

我們繼續(xù)在剛才的代碼后面添加這行代碼:

 //這個(gè)方法也返回一個(gè)transform3D對(duì)象努咐,追加平移效果苦蒿,后面三個(gè)參數(shù),對(duì)應(yīng)平移的x渗稍,y佩迟,z軸,我們沿z軸平移 
 trans3D = CATransform3DTranslate(trans3D, 0, 0, radius);

再次運(yùn)行竿屹,效果如下:

布局的效果我們已經(jīng)完成了报强,離成功很近了對(duì)吧,只是現(xiàn)在的布局是靜態(tài)的拱燃,我們不能滑動(dòng)這個(gè)滾輪秉溉,我們還需要用動(dòng)態(tài)滑動(dòng)做一些處理。

三、讓滾輪滑動(dòng)起來

通過上面的努力召嘶,我們已經(jīng)靜態(tài)布局出了一個(gè)類似pickerView的滾輪父晶,現(xiàn)在我們再來添加滑動(dòng)滾動(dòng)的效果

首先,我們需要給collectionView一個(gè)滑動(dòng)的范圍弄跌,我們以一屏collectionView的滑動(dòng)距離來當(dāng)做滾輪滾動(dòng)一下的參照甲喝,我們在布局類中的如下方法中返回滑動(dòng)區(qū)域:

-(CGSize)collectionViewContentSize{
    return CGSizeMake(self.collectionView.frame.size.width, self.collectionView.frame.size.height*[self.collectionView numberOfItemsInSection:0]);
}

這時(shí)我們的collectionView已經(jīng)可以進(jìn)行滑動(dòng),但是并不是我們想要的效果铛只,滾輪并沒有滾動(dòng)埠胖,而是隨著滑動(dòng)出了屏幕,因此格仲,我們需要在滑動(dòng)的時(shí)候不停的動(dòng)態(tài)布局押袍,將滾輪始終固定在collectionView的中心,先需要在布局類中實(shí)現(xiàn)如下方法:

//返回yes凯肋,則一有變化就會(huì)刷新布局
-(BOOL)shouldInvalidateLayoutForBoundsChange:(CGRect)newBounds{
    return YES;
}

將上面的布局的中心點(diǎn)設(shè)置加上一個(gè)動(dòng)態(tài)的偏移量:

 atti.center = CGPointMake(self.collectionView.frame.size.width/2, self.collectionView.frame.size.height/2+self.collectionView.contentOffset.y);

現(xiàn)在在運(yùn)行,會(huì)發(fā)現(xiàn)滾輪會(huì)隨著滑動(dòng)始終固定在中間汽馋,但是還是不如人意侮东,滾輪并沒有轉(zhuǎn)動(dòng)起來,我們還需要?jiǎng)討B(tài)的設(shè)置每個(gè)item的旋轉(zhuǎn)角度豹芯,這樣連續(xù)看起來悄雅,滾輪就轉(zhuǎn)了起來,在上面設(shè)置布局的方法中铁蹈,我們在添加一些處理:

//獲取當(dāng)前的偏移量
float offset = self.collectionView.contentOffset.y;
//在角度設(shè)置上宽闲,添加一個(gè)偏移角度
float angleOffset = offset/self.collectionView.frame.size.height;
CGFloat angle = (float)(indexPath.row+angleOffset)/itemCounts*M_PI*2;

再看看效果,沒錯(cuò)握牧,就是這么簡單容诬,滾輪已經(jīng)轉(zhuǎn)了起來。

四沿腰、讓其循環(huán)滾動(dòng)的邏輯

我們再進(jìn)一步览徒,如果滾動(dòng)可以循環(huán),這個(gè)控件將更加炫酷颂龙,添加這樣的邏輯也很簡單习蓬,通過監(jiān)測scrollView的偏移量,我們可以對(duì)齊進(jìn)行處理措嵌,因?yàn)閏ollectionView繼承于scrollView躲叼,我們可以直接在ViewController中實(shí)現(xiàn)其代理方法秽澳,如下:

-(void)scrollViewDidScroll:(UIScrollView *)scrollView{
    //小于半屏 則放到最后一屏多半屏
    if (scrollView.contentOffset.y<200) {
        scrollView.contentOffset = CGPointMake(0, scrollView.contentOffset.y+10*400);
    //大于最后一屏多一屏 放回第一屏
    }else if(scrollView.contentOffset.y>11*400){
        scrollView.contentOffset = CGPointMake(0, scrollView.contentOffset.y-10*400);
    }
}

因?yàn)樵蹅兊沫h(huán)狀布局赘被,上面的邏輯剛好可以無縫對(duì)接锹雏,但是會(huì)有新的問題脸狸,一開始運(yùn)行躬窜,滾輪就是出現(xiàn)在最后一個(gè)item的位置,而不是第一個(gè)速妖,并且有些相關(guān)的地方妹蔽,我們也需要一些適配:

在viewController中:

//一開始將collectionView的偏移量設(shè)置為1屏的偏移量
collect.contentOffset = CGPointMake(0, 400);

在layout類中:

//將滾動(dòng)范圍設(shè)置為(item總數(shù)+2)*每屏高度 
-(CGSize)collectionViewContentSize{
    return CGSizeMake(self.collectionView.frame.size.width, self.collectionView.frame.size.height*([self.collectionView numberOfItemsInSection:0]+2));
}
//將計(jì)算的具體item角度向前遞推一個(gè)
CGFloat angle = (float)(indexPath.row+angleOffset-1)/itemCounts*M_PI*2;

OK,我們終于大功告成了神帅,可以發(fā)現(xiàn)再姑,實(shí)現(xiàn)這樣一個(gè)布局效果炫酷的控件,代碼其實(shí)并沒有多少找御,相比元镀,數(shù)學(xué)邏輯要比編寫代碼本身困難,這十分類似數(shù)學(xué)中的幾何問題霎桅,如果你弄清了邏輯栖疑,解決是分分鐘的事,我們可以通過這樣的一個(gè)思路滔驶,設(shè)計(jì)更多3D或者平面特效的布局方案遇革,抽獎(jiǎng)的轉(zhuǎn)動(dòng)圓盤,書本的翻頁揭糕,甚至立體的標(biāo)簽云萝快,UICollectionView都可以實(shí)現(xiàn),這篇博客中的代碼在下面的連接中著角,疏漏之處揪漩,歡迎指正!

http://pan.baidu.com/s/1jGCmbKM

iOS流布局UICollectionView系列七——三維中的球型布局

一吏口、引言

通過6篇的博客奄容,從平面上最簡單的規(guī)則擺放的布局,到不規(guī)則的瀑布流布局产徊,再到平面中的圓環(huán)布局昂勒,我們突破了線性布局的局限,在后面囚痴,我們將布局?jǐn)U展到了空間叁怪,在Z軸上進(jìn)行了平移,我們實(shí)現(xiàn)了一個(gè)類似UIPickerView的布局模型深滚,其實(shí)我們還可以再進(jìn)一步奕谭,類比于平面布局,picKerView只是線性排列布局在空間上的旋轉(zhuǎn)與平移痴荐,這次血柳,我們更加充分了利用一下空間的尺寸,來設(shè)計(jì)一個(gè)圓球的布局模型生兆。

二难捌、將布局?jǐn)U展為空間球型

在viewController中先實(shí)現(xiàn)一些準(zhǔn)備代碼:

- (void)viewDidLoad {
    [super viewDidLoad];
    // Do any additional setup after loading the view, typically from a nib.
    MyLayout * layout = [[MyLayout alloc]init];
     UICollectionView * collect  = [[UICollectionView alloc]initWithFrame:CGRectMake(0, 0, 320, 400) collectionViewLayout:layout];
    collect.delegate=self;
    collect.dataSource=self;
    //這里設(shè)置的偏移量是為了無縫進(jìn)行循環(huán)的滾動(dòng)膝宁,具體在上一篇博客中有解釋
    collect.contentOffset = CGPointMake(320, 400);
    [collect registerClass:[UICollectionViewCell class] forCellWithReuseIdentifier:@"cellid"];
    [self.view addSubview:collect];
}
 
-(NSInteger)numberOfSectionsInCollectionView:(UICollectionView *)collectionView{
    return 1;
}
//我們返回30的標(biāo)簽
-(NSInteger)collectionView:(UICollectionView *)collectionView numberOfItemsInSection:(NSInteger)section{
    return 30;
}
-(UICollectionViewCell *)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath{
    UICollectionViewCell * cell  = [collectionView dequeueReusableCellWithReuseIdentifier:@"cellid" forIndexPath:indexPath];
    cell.backgroundColor = [UIColor colorWithRed:arc4random()%255/255.0 green:arc4random()%255/255.0 blue:arc4random()%255/255.0 alpha:1];
    UILabel * label = [[UILabel alloc]initWithFrame:CGRectMake(0, 0, 30, 30)];
    label.text = [NSString stringWithFormat:@"%ld",(long)indexPath.row];
    [cell.contentView addSubview:label];
    return cell;
}
 
- (void)didReceiveMemoryWarning {
    [super didReceiveMemoryWarning];
    // Dispose of any resources that can be recreated.
}
//這里對(duì)滑動(dòng)的contentOffset進(jìn)行監(jiān)控,實(shí)現(xiàn)循環(huán)滾動(dòng)
-(void)scrollViewDidScroll:(UIScrollView *)scrollView{
    if (scrollView.contentOffset.y<200) {
        scrollView.contentOffset = CGPointMake(scrollView.contentOffset.x, scrollView.contentOffset.y+10*400);
    }else if(scrollView.contentOffset.y>11*400){
        scrollView.contentOffset = CGPointMake(scrollView.contentOffset.x, scrollView.contentOffset.y-10*400);
    }
    if (scrollView.contentOffset.x<160) {
        scrollView.contentOffset = CGPointMake(scrollView.contentOffset.x+10*320,scrollView.contentOffset.y);
    }else if(scrollView.contentOffset.x>11*320){
        scrollView.contentOffset = CGPointMake(scrollView.contentOffset.x-10*320,scrollView.contentOffset.y);
    }
}

這里面的代碼比較上一篇博客中的并沒有什么大的改動(dòng)根吁,只是做了橫坐標(biāo)的兼容员淫。

在我們的layout類中,將代碼修改成如下:

-(void)prepareLayout{
    [super prepareLayout];
    
}
//返回的滾動(dòng)范圍增加了對(duì)x軸的兼容
-(CGSize)collectionViewContentSize{
    return CGSizeMake( self.collectionView.frame.size.width*([self.collectionView numberOfItemsInSection:0]+2), self.collectionView.frame.size.height*([self.collectionView numberOfItemsInSection:0]+2));
}
-(BOOL)shouldInvalidateLayoutForBoundsChange:(CGRect)newBounds{
    return YES;
}
 
-(UICollectionViewLayoutAttributes *)layoutAttributesForItemAtIndexPath:(NSIndexPath *)indexPath{
    UICollectionViewLayoutAttributes * atti = [UICollectionViewLayoutAttributes layoutAttributesForCellWithIndexPath:indexPath];
    //獲取item的個(gè)數(shù)
    int itemCounts = (int)[self.collectionView numberOfItemsInSection:0];
    atti.center = CGPointMake(self.collectionView.frame.size.width/2+self.collectionView.contentOffset.x, self.collectionView.frame.size.height/2+self.collectionView.contentOffset.y);
    atti.size = CGSizeMake(30, 30);
    
    CATransform3D trans3D = CATransform3DIdentity;
    trans3D.m34 = -1/900.0;
    
    CGFloat radius = 15/tanf(M_PI*2/itemCounts/2);
    //根據(jù)偏移量 改變角度
    //添加了一個(gè)x的偏移量
    float offsety = self.collectionView.contentOffset.y;
    float offsetx = self.collectionView.contentOffset.x;
    //分別計(jì)算偏移的角度
    float angleOffsety = offsety/self.collectionView.frame.size.height;
    float angleOffsetx = offsetx/self.collectionView.frame.size.width;
    CGFloat angle1 = (float)(indexPath.row+angleOffsety-1)/itemCounts*M_PI*2;
    //x击敌,y的默認(rèn)方向相反
    CGFloat angle2 = (float)(indexPath.row-angleOffsetx-1)/itemCounts*M_PI*2;
    //這里我們進(jìn)行四個(gè)方向的排列
   if (indexPath.row%4==1) {
        trans3D = CATransform3DRotate(trans3D, angle1, 1.0,0, 0);
    }else if(indexPath.row%4==2){
        trans3D = CATransform3DRotate(trans3D, angle2, 0, 1, 0);
    }else if(indexPath.row%4==3){
        trans3D = CATransform3DRotate(trans3D, angle1, 0.5,0.5, 0);
    }else{
        trans3D = CATransform3DRotate(trans3D, angle1, 0.5,-0.5,0);
    }
    
    trans3D = CATransform3DTranslate(trans3D, 0, 0, radius);
    
    atti.transform3D = trans3D;
    return atti;
}
 
 
-(NSArray<UICollectionViewLayoutAttributes *> *)layoutAttributesForElementsInRect:(CGRect)rect{
    NSMutableArray * attributes = [[NSMutableArray alloc]init];
    //遍歷設(shè)置每個(gè)item的布局屬性
    for (int i=0; i<[self.collectionView numberOfItemsInSection:0]; i++) {
        [attributes addObject:[self layoutAttributesForItemAtIndexPath:[NSIndexPath indexPathForItem:i inSection:0]]];
    }
    return attributes;
}

布局效果如下:

滑動(dòng)屏幕介返,這個(gè)圓球是可以進(jìn)行滾動(dòng)的。

TIP:這里我們只平均分配了四個(gè)方向上的布局沃斤,如果item更加小也更加多圣蝎,我們可以分配到更多的方向上,使球體更加充實(shí)衡瓶。

轉(zhuǎn)自https://blog.csdn.net/LVXIANGAN/article/details/73826108?utm_source=blogkpcl6

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末徘公,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子哮针,更是在濱河造成了極大的恐慌关面,老刑警劉巖,帶你破解...
    沈念sama閱讀 218,941評(píng)論 6 508
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件十厢,死亡現(xiàn)場離奇詭異缭裆,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)寿烟,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,397評(píng)論 3 395
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來辛燥,“玉大人筛武,你說我怎么就攤上這事】嫠” “怎么了徘六?”我有些...
    開封第一講書人閱讀 165,345評(píng)論 0 356
  • 文/不壞的土叔 我叫張陵,是天一觀的道長榴都。 經(jīng)常有香客問我待锈,道長,這世上最難降的妖魔是什么嘴高? 我笑而不...
    開封第一講書人閱讀 58,851評(píng)論 1 295
  • 正文 為了忘掉前任竿音,我火速辦了婚禮,結(jié)果婚禮上拴驮,老公的妹妹穿的比我還像新娘春瞬。我一直安慰自己,他們只是感情好套啤,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,868評(píng)論 6 392
  • 文/花漫 我一把揭開白布宽气。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪萄涯。 梳的紋絲不亂的頭發(fā)上绪氛,一...
    開封第一講書人閱讀 51,688評(píng)論 1 305
  • 那天,我揣著相機(jī)與錄音涝影,去河邊找鬼枣察。 笑死,一個(gè)胖子當(dāng)著我的面吹牛袄琳,可吹牛的內(nèi)容都是我干的询件。 我是一名探鬼主播,決...
    沈念sama閱讀 40,414評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼唆樊,長吁一口氣:“原來是場噩夢啊……” “哼宛琅!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起逗旁,我...
    開封第一講書人閱讀 39,319評(píng)論 0 276
  • 序言:老撾萬榮一對(duì)情侶失蹤嘿辟,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后片效,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體红伦,經(jīng)...
    沈念sama閱讀 45,775評(píng)論 1 315
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,945評(píng)論 3 336
  • 正文 我和宋清朗相戀三年淀衣,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了昙读。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 40,096評(píng)論 1 350
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡膨桥,死狀恐怖蛮浑,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情只嚣,我是刑警寧澤沮稚,帶...
    沈念sama閱讀 35,789評(píng)論 5 346
  • 正文 年R本政府宣布,位于F島的核電站册舞,受9級(jí)特大地震影響蕴掏,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜调鲸,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,437評(píng)論 3 331
  • 文/蒙蒙 一盛杰、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧线得,春花似錦饶唤、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,993評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽办素。三九已至,卻和暖如春祸穷,著一層夾襖步出監(jiān)牢的瞬間性穿,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 33,107評(píng)論 1 271
  • 我被黑心中介騙來泰國打工雷滚, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留需曾,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 48,308評(píng)論 3 372
  • 正文 我出身青樓祈远,卻偏偏與公主長得像呆万,于是被迫代替她去往敵國和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子车份,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,037評(píng)論 2 355

推薦閱讀更多精彩內(nèi)容

  • 時(shí)間從來不是良藥谋减,更妄論解藥。它是麻沸散嗎丁杜冷丁扫沼,是濃霧是灌木是摘下眼鏡的視界出爹。時(shí)間不會(huì)給你結(jié)果,它只負(fù)...
    FantasyGallery閱讀 189評(píng)論 0 4
  • 文/Haijun 人機(jī)交互是研究人缎除、計(jì)算機(jī)以及他們之間相互關(guān)系的技術(shù)严就。以人為中心,自然器罐、親切和生動(dòng)的交互成為發(fā)展新...
    Blue_Ocean閱讀 2,017評(píng)論 2 2
  • 現(xiàn)在的我梢为,作什么都不認(rèn)真,對(duì)什么都充滿好奇心轰坊,就是沉不下心來抖誉,好動(dòng),好玩衰倦,難道這就是青春期,好奇是好的旁理,對(duì)新鮮事物...
    遠(yuǎn)方的行人閱讀 153評(píng)論 0 0
  • 對(duì)于風(fēng)孽文,我有著別樣的情緒驻襟。它可以帶走我一切的煩惱,它可以帶我回到童真芋哭,它亦可以讓我的心靈純凈沉衣。 風(fēng)猶...
    梅自香濃閱讀 182評(píng)論 0 0