1. 程式人生 > >iOS collectionViewLayout佈局和自定義

iOS collectionViewLayout佈局和自定義

UICollectionView的結構回顧

首先回顧一下Collection View的構成,我們能看到的有三個部分:

  • Cells
  • Supplementary Views 追加檢視 (類似Header或者Footer)
  • Decoration Views 裝飾檢視 (用作背景展示)

而在表面下,由兩個方面對UICollectionView進行支援。其中之一和tableView一樣,即提供資料的UICollectionViewDataSource以及處理使用者互動的UICollectionViewDelegate。另一方面,對於cell的樣式和組織方式,由於collectionView比tableView要複雜得多,因此沒有按照類似於tableView的style的方式來定義,而是專門使用了一個類來對collectionView的佈局和行為進行描述,這就是UICollectionViewLayout。

這次的筆記將把重點放在UICollectionViewLayout上,因為這不僅是collectionView和tableView的最重要求的區別,也是整個UICollectionView的精髓所在。

如果對UICollectionView的基本構成要素和使用方法還不清楚的話,可以移步到我之前的一篇筆記:Session筆記——205 Introducing Collection Views中進行一些瞭解。

UICollectionViewLayoutAttributes

UICollectionViewLayoutAttributes是一個非常重要的類,先來看看property列表:

  • @property (nonatomic) CGRect frame
  • @property (nonatomic) CGPoint center
  • @property (nonatomic) CGSize size
  • @property (nonatomic) CATransform3D transform3D
  • @property (nonatomic) CGFloat alpha
  • @property (nonatomic) NSInteger zIndex
  • @property (nonatomic, getter=isHidden) BOOL hidden

可以看到,UICollectionViewLayoutAttributes的例項中包含了諸如邊框,中心點,大小,形狀,透明度,層次關係和是否隱藏等資訊。和DataSource的行為十分類似,當UICollectionView在獲取佈局時將針對每一個indexPath的部件(包括cell,追加檢視和裝飾檢視),向其上的UICollectionViewLayout例項詢問該部件的佈局資訊(在這個層面上說的話,實現一個UICollectionViewLayout的時候,其實很像是zap一個delegate,之後的例子中會很明顯地看出),這個佈局資訊,就以UICollectionViewLayoutAttributes的例項的方式給出。

自定義的UICollectionViewLayout

UICollectionViewLayout的功能為向UICollectionView提供佈局資訊,不僅包括cell的佈局資訊,也包括追加檢視和裝飾檢視的佈局資訊。實現一個自定義layout的常規做法是繼承UICollectionViewLayout類,然後過載下列方法:

  • -(CGSize)collectionViewContentSize

    • 返回collectionView的內容的尺寸
  • -(NSArray *)layoutAttributesForElementsInRect:(CGRect)rect

    • 返回rect中的所有的元素的佈局屬性
    • 返回的是包含UICollectionViewLayoutAttributes的NSArray
    • UICollectionViewLayoutAttributes可以是cell,追加檢視或裝飾檢視的資訊,通過不同的UICollectionViewLayoutAttributes初始化方法可以得到不同型別的UICollectionViewLayoutAttributes:

      • layoutAttributesForCellWithIndexPath:
      • layoutAttributesForSupplementaryViewOfKind:withIndexPath:
      • layoutAttributesForDecorationViewOfKind:withIndexPath:
  • -(UICollectionViewLayoutAttributes _)layoutAttributesForItemAtIndexPath:(NSIndexPath _)indexPath

    • 返回對應於indexPath的位置的cell的佈局屬性
  • -(UICollectionViewLayoutAttributes _)layoutAttributesForSupplementaryViewOfKind:(NSString _)kind atIndexPath:(NSIndexPath *)indexPath

    • 返回對應於indexPath的位置的追加檢視的佈局屬性,如果沒有追加檢視可不過載
  • -(UICollectionViewLayoutAttributes * )layoutAttributesForDecorationViewOfKind:(NSString_)decorationViewKind atIndexPath:(NSIndexPath _)indexPath

    • 返回對應於indexPath的位置的裝飾檢視的佈局屬性,如果沒有裝飾檢視可不過載
  • -(BOOL)shouldInvalidateLayoutForBoundsChange:(CGRect)newBounds

    • 當邊界發生改變時,是否應該重新整理佈局。如果YES則在邊界變化(一般是scroll到其他地方)時,將重新計算需要的佈局資訊。

另外需要了解的是,在初始化一個UICollectionViewLayout例項後,會有一系列準備方法被自動呼叫,以保證layout例項的正確。

首先,-(void)prepareLayout將被呼叫,預設下該方法什麼沒做,但是在自己的子類實現中,一般在該方法中設定一些必要的layout的結構和初始需要的引數等。

之後,-(CGSize) collectionViewContentSize將被呼叫,以確定collection應該佔據的尺寸。注意這裡的尺寸不是指可視部分的尺寸,而應該是所有內容所佔的尺寸。collectionView的本質是一個scrollView,因此需要這個尺寸來配置滾動行為。

接下來-(NSArray *)layoutAttributesForElementsInRect:(CGRect)rect被呼叫,這個沒什麼值得多說的。初始的layout的外觀將由該方法返回的UICollectionViewLayoutAttributes來決定。

另外,在需要更新layout時,需要給當前layout傳送 -invalidateLayout,該訊息會立即返回,並且預約在下一個loop的時候重新整理當前layout,這一點和UIView的setNeedsLayout方法十分類似。在-invalidateLayout後的下一個collectionView的重新整理loop中,又會從prepareLayout開始,依次再呼叫-collectionViewContentSize和-layoutAttributesForElementsInRect來生成更新後的佈局。

Demo

說了那麼多,其實還是Demo最能解決問題。Apple官方給了一個flow layout和一個circle layout的例子,都很經典,需要的同學可以從這裡下載

LineLayout——對於個別UICollectionViewLayoutAttributes的調整

先看LineLayout,它繼承了UICollectionViewFlowLayout這個Apple提供的基本的佈局。它主要實現了單行佈局,自動對齊到網格以及當前網格cell放大三個特性。如圖:

先看LineLayout的init方法:

-(id)init
{
    self = [super init];
    if (self) {
        self.itemSize = CGSizeMake(ITEM_SIZE, ITEM_SIZE);
        self.scrollDirection = UICollectionViewScrollDirectionHorizontal;
        self.sectionInset = UIEdgeInsetsMake(200, 0.0, 200, 0.0);
        self.minimumLineSpacing = 50.0;
    }
    return self;
}

self.sectionInset = UIEdgeInsetsMake(200, 0.0, 200, 0.0); 確定了縮排,此處為上方和下方各縮排200個point。由於cell的size已經定義了為200x200,因此螢幕上在縮排後就只有一排item的空間了。

self.minimumLineSpacing = 50.0; 這個定義了每個item在水平方向上的最小間距。

UICollectionViewFlowLayout是Apple為我們準備的開袋即食的現成佈局,因此之前提到的幾個必須過載的方法中需要我們操心的很少,即使完全不過載它們,現在也可以得到一個不錯的線狀一行的gridview了。而我們的LineLayout通過過載父類方法後,可以實現一些新特性,比如這裡的動對齊到網格以及當前網格cell放大。

自動對齊到網格

- (CGPoint)targetContentOffsetForProposedContentOffset: (CGPoint)proposedContentOffset withScrollingVelocity:(CGPoint)velocity
{
    //proposedContentOffset是沒有對齊到網格時本來應該停下的位置
    CGFloat offsetAdjustment = MAXFLOAT;
    CGFloat horizontalCenter = proposedContentOffset.x + (CGRectGetWidth(self.collectionView.bounds) / 2.0);
    CGRect targetRect = CGRectMake(proposedContentOffset.x, 0.0, self.collectionView.bounds.size.width, self.collectionView.bounds.size.height);
    NSArray* array = [super layoutAttributesForElementsInRect:targetRect];

    //對當前螢幕中的UICollectionViewLayoutAttributes逐個與螢幕中心進行比較,找出最接近中心的一個
    for (UICollectionViewLayoutAttributes* layoutAttributes in array) {
        CGFloat itemHorizontalCenter = layoutAttributes.center.x;
        if (ABS(itemHorizontalCenter - horizontalCenter) < ABS(offsetAdjustment)) {
            offsetAdjustment = itemHorizontalCenter - horizontalCenter;
        }
    }
    return CGPointMake(proposedContentOffset.x + offsetAdjustment, proposedContentOffset.y);
}

當前item放大

-(NSArray *)layoutAttributesForElementsInRect:(CGRect)rect
{
    NSArray *array = [super layoutAttributesForElementsInRect:rect];
    CGRect visibleRect;
    visibleRect.origin = self.collectionView.contentOffset;
    visibleRect.size = self.collectionView.bounds.size;

    for (UICollectionViewLayoutAttributes* attributes in array) {
        if (CGRectIntersectsRect(attributes.frame, rect)) {
            CGFloat distance = CGRectGetMidX(visibleRect) - attributes.center.x;
            CGFloat normalizedDistance = distance / ACTIVE_DISTANCE;
            if (ABS(distance) < ACTIVE_DISTANCE) {
                CGFloat zoom = 1 + ZOOM_FACTOR*(1 - ABS(normalizedDistance));
                attributes.transform3D = CATransform3DMakeScale(zoom, zoom, 1.0);
                attributes.zIndex = 1;
            }
        }
    }
    return array;
}

對於個別UICollectionViewLayoutAttributes進行調整,以達到滿足設計需求是UICollectionView使用中的一種思路。在根據位置提供不同layout屬性的時候,需要記得讓-shouldInvalidateLayoutForBoundsChange:返回YES,這樣當邊界改變的時候,-invalidateLayout會自動被髮送,才能讓layout得到重新整理。

CircleLayout——完全自定義的Layout,新增刪除item,以及手勢識別

CircleLayout的例子稍微複雜一些,cell分佈在圓周上,點選cell的話會將其從collectionView中移出,點選空白處會加入一個cell,加入和移出都有動畫效果。

這放在以前的話估計夠寫一陣子了,而得益於UICollectionView,基本只需要100來行程式碼就可以搞定這一切,非常cheap。通過CircleLayout的實現,可以完整地看到自定義的layout的編寫流程,非常具有學習和借鑑的意義。

CircleLayout

首先,佈局準備中定義了一些之後計算所需要用到的引數。

-(void)prepareLayout
{   //和init相似,必須call super的prepareLayout以保證初始化正確
    [super prepareLayout];

    CGSize size = self.collectionView.frame.size;
    _cellCount = [[self collectionView] numberOfItemsInSection:0];
    _center = CGPointMake(size.width / 2.0, size.height / 2.0);
    _radius = MIN(size.width, size.height) / 2.5;
}

其實對於一個size不變的collectionView來說,除了_cellCount之外的中心和半徑的定義也可以扔到init裡去做,但是顯然在prepareLayout裡做的話具有更大的靈活性。因為每次重新給出layout時都會呼叫prepareLayout,這樣在以後如果有collectionView大小變化的需求時也可以自動適應變化。

然後,按照UICollectionViewLayout子類的要求,過載了所需要的方法:

//整個collectionView的內容大小就是collectionView的大小(沒有滾動)
-(CGSize)collectionViewContentSize
{
    return [self collectionView].frame.size;
}

//通過所在的indexPath確定位置。
- (UICollectionViewLayoutAttributes *)layoutAttributesForItemAtIndexPath:(NSIndexPath *)path
{
    UICollectionViewLayoutAttributes* attributes = [UICollectionViewLayoutAttributes layoutAttributesForCellWithIndexPath:path]; //生成空白的attributes物件,其中只記錄了型別是cell以及對應的位置是indexPath
    //配置attributes到圓周上
    attributes.size = CGSizeMake(ITEM_SIZE, ITEM_SIZE);
    attributes.center = CGPointMake(_center.x + _radius * cosf(2 * path.item * M_PI / _cellCount), _center.y + _radius * sinf(2 * path.item * M_PI / _cellCount));
    return attributes;
}

//用來在一開始給出一套UICollectionViewLayoutAttributes
-(NSArray*)layoutAttributesForElementsInRect:(CGRect)rect
{
    NSMutableArray* attributes = [NSMutableArray array];
    for (NSInteger i=0 ; i < self.cellCount; i++) {
        //這裡利用了-layoutAttributesForItemAtIndexPath:來獲取attributes
        NSIndexPath* indexPath = [NSIndexPath indexPathForItem:i inSection:0];
        [attributes addObject:[self layoutAttributesForItemAtIndexPath:indexPath]];
    }    
    return attributes;
}

現在已經得到了一個circle layout。為了實現cell的新增和刪除,需要為collectionView加上手勢識別,這個很簡單,在ViewController中:

UITapGestureRecognizer* tapRecognizer = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(handleTapGesture:)];  
[self.collectionView addGestureRecognizer:tapRecognizer];

對應的處理方法handleTapGesture:為

- (void)handleTapGesture:(UITapGestureRecognizer *)sender {
    if (sender.state == UIGestureRecognizerStateEnded) {
        CGPoint initialPinchPoint = [sender locationInView:self.collectionView];
        NSIndexPath* tappedCellPath = [self.collectionView indexPathForItemAtPoint:initialPinchPoint]; //獲取點選處的cell的indexPath
        if (tappedCellPath!=
            
           

相關推薦

iOS collectionViewLayout佈局定義

UICollectionView的結構回顧 首先回顧一下Collection View的構成,我們能看到的有三個部分: Cells Supplementary Views 追加檢視 (類似Header或者Footer) Decoration Views 裝飾檢視 (用作

iOS系統鍵盤定義鍵盤的切換

// 1. 給UITextView新增一個可點選的UIControl UIControl *control = [[UIControl alloc] initWithFrame:_in

Android中引入佈局定義控制元件

首先是引入佈局: 1.我們自己新建一個layout,就是一個標題欄。 2.然後在我們的mainactivity_layout中使用一個語句就可以實現。 <?xml version="1.0" encoding="utf-8"?> <LinearLayout

IOS百度地圖定義大頭針氣泡

文/煜寒了(簡書作者) 原文連結:http://www.jianshu.com/p/6a334f071c69 著作權歸作者所有,轉載請聯絡作者獲得授權,並標註“簡書作者”。1.首先實現新增多個標註和自定義氣泡 新增自定義標註 [_mapView addAnnotations:array]; arry 中放入標

iOS筆記-(利用EGORefreshTableHeaderView定義上拉載入下拉重新整理)

</pre><p class="p1"></p><pre name="code" class="objc">GIthub下載一個DEMO,在RootviewController中檢視. UIScrollViewDelegate

Android中的通知定義通知佈局

Android中的通知(Notification)是Android中的重要一部分,應用程式通過通知來提醒使用者或者向用戶傳達資訊,下面讓我們來看一下怎麼在我們的程式中使用通知和自定義通知的佈局。 首先我們來看一下怎麼向通知欄中傳送一個通知。由於各個版本的And

iOS整合極光推送 通知 定義訊息

支援的版本 r1.2.5 以後。 功能說明 只有在前端執行的時候才能收到自定義訊息的推送。 從jpush伺服器獲取使用者推送的自定義訊息的內容、標題、附件欄位等。 實現方法 獲取iOS的推送內容需要在delegate類中註冊通知並實現回撥方法。 1、在方法-

ios UITableView定義UITableViewCell

1、自定義cell,FindCell.h#ifndef FindCell_h #define FindCell_h #import <UIKit/UIKit.h> @interface FindCell : UITableViewCell @property

關於AlertDialog.Builder犯的一個錯定義佈局的限制寬高一個知識點

先看錯誤程式碼 AlertDialog.Builder builder=new AlertDialog.Builder(getApplicationContext()); builder.setTitle("溫馨提示")

IOS控制元件系列----使用UITableView實現網格佈局定義顯示列數

先放一引效果圖: 在IOS中達到類似Android中的GridLayout 通常是使用UIConlectionView,這個元件是平果公司已經封裝好的,直接實現相應的介面即可。不知道各位道友是否也曾想過用UItableView來擼一個這個東西,這可能會有一點偏執,但對

ios中tableview的建立定義cell的封裝

#import "HGYwaitServiceViewController.h" #import "HGYWaitingserveCell.h" @interface HGYwaitServic

iOS開發之UITableView定義Header檢視定義Footer檢視

//自定義Header檢視 - (nullable UIView *)tableView:(UITableView *)tableView viewForHeaderInSection:(NSInte

iOS 使用純程式碼定義UITableViewCell實現一個簡單的微博介面佈局

一、實現效果 二、使用純程式碼自定義一個UITableViewCell的步驟 1.新建一個繼承自UITableViewCell的類 2.重寫initWithStyle:reuseIdentifier:方法 新增所有需要顯示的子控制元件(不需要設定子控制元件的資料和fram

1. PMD 使用,編譯定義規則

自定義 pmd 規則 一 PMD簡介PMD是一款代碼靜態檢查工具,可以檢查出很多代碼中潛在的bug以及讓人感到疑惑的代碼,具體大家可以百度下。二 PMD源代碼下載下載地址:https://github.com/pmd/pmd/tree/pmd/5.5.x需要註意的是註意選擇branch,一般選擇最

DOM內容操作定義、樣式改變

abcdefg result 定義 tel class abcde inner 參數 fun 自定義 function 方法名或函數名(參數1,參數2,、、、) { 方法體; return返回值;(可不寫) } function a

iOS NavigationBar 導航欄定義

logs 如果 line navi name tin elf 資料 att 1. 設置導航欄NavigationBar的背景顏色: a) setBarTintColor : 設置NagivationBar的顏色 也可以用 : [[UINavigat

EL函數定義EL函數

asm pex cli servle cti 編寫代碼 lower sed 添加 簡介 EL原本是JSTL1.0中的技術(所以EL和JSTL感情如此好就是自然的了),但是從JSP2.0開始,EL就分離出來納入了JSP的標準了。但是EL函數還是和JSTL技術綁定在一起。下面將

Android零基礎入門第39節:ListActivity定義列表項

arraylist component save 高速 ram 如果 view設置 ren 屬性 相信通過前兩期的學習,以及會開發最簡單的一些列表界面了吧,那麽本期接著來學習更多方法技巧。 一、使用ListActivity 如果程序的窗口僅僅需要

java Collections.sort()實現List排序的默認方法定義方法

public get object 順序 text main 輸出 any 比較 1.java提供的默認list排序方法 主要代碼: List<String> list = new ArrayList();list.add("劉媛媛"); list.add("王

freemarker實現定義指令定義函數

數據 dir variables macro 內置 引擎 eem fig turn 自定義指令: 1.指令在前臺實現   <#macro name param1,param2,param3...paramN>   </#macro> 2.指令在後臺實