[iOS] UICollectionView實現圖片水平滾動

来源:http://www.cnblogs.com/rossoneri/archive/2016/01/09/5115335.html
-Advertisement-
Play Games

準備數據 首先先加入一些資源文件:先建立一個xcassets文件,放入圖片:再建立一個plist文件,寫入與圖片對應的內容:在ViewController中讀取plist到詞典中:


先簡單看一下效果:
result
原文地址:http://www.wossoneri.com/2016/01/09/[iOS]%20UICollectionView%E5%AE%9E%E7%8E%B0%E5%9B%BE%E7%89%87%E6%B0%B4%E5%B9%B3%E6%BB%9A%E5%8A%A8/

準備數據

首先先加入一些資源文件:

先建立一個xcassets文件,放入圖片:

xcassets

再建立一個plist文件,寫入與圖片對應的內容:

plist

在ViewController中讀取plist到詞典中:

@property (nonatomic, strong) NSArray *itemTitles;

NSString *path = [[NSBundle mainBundle] pathForResource:@"titles" ofType:@"plist"];
NSDictionary *rootDictionary = [[NSDictionary alloc] initWithContentsOfFile:path];
self.itemTitles = [rootDictionary objectForKey:@"heros"];

可以打log輸出,可以看到plist的內容已經讀取出來,後面就可以用_itemTitle作為數據源了。

添加UICollectionView初步顯示圖片

每個CollectionView都有一個對應的佈局layout,對於預設的的UICollectionViewFlowLayout,效果是類似Android的GridView的佈局。如果要自定義CollectionView的樣式,就要對這個layout進行修改。

建立自己的HorizontalFlowLayout,繼承自UICollectionViewFlowLayout,然後在初始化方法里將滾動方向設置為水平:

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

接下來定製我們的cell的顯示樣式,建立DotaCell,繼承自UICollectionViewCell。由於我們要實現的是圖片和文字的上下佈局,所以增加兩個屬性:

@interface DotaCell : UICollectionViewCell

@property (nonatomic, strong) UIImageView *image;
@property (nonatomic, strong) UILabel *name;

@end

然後設置圖片與文字上下對齊佈局,這裡我使用pod導入Masonry庫來寫自動佈局:


- (instancetype)initWithFrame:(CGRect)frame {
    self = [super initWithFrame:frame];
    if (self) {
        [self initialize];
    }
    return self;
}

- (void)initialize {
    self.layer.doubleSided = NO;
    
    self.image = [[UIImageView alloc] init];
    self.image.backgroundColor = [UIColor clearColor];
    self.image.contentMode = UIViewContentModeCenter;
    self.image.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight;
    
    self.name = [[UILabel alloc] init];
    self.name.font = [UIFont fontWithName:@"Helvetica Neue" size:20];
    self.name.textAlignment = NSTextAlignmentCenter;
    
    [self.contentView addSubview:self.image];
    [self.contentView addSubview:self.name];

    [_image mas_makeConstraints:^(MASConstraintMaker *make) {
        make.left.right.equalTo(self.contentView);
        make.top.equalTo(self.contentView).offset(30);
        make.bottom.equalTo(_name.mas_top).offset(-10);
    }];
    
    [_name mas_makeConstraints:^(MASConstraintMaker *make) {
        make.left.right.equalTo(self.contentView);
        make.top.equalTo(_image.mas_bottom).offset(10);
        make.bottom.equalTo(self.contentView).offset(-20);
    }];
}

寫好layoutcell後就可以用這兩個類來初始化我們的collectionView了:

//add in view did load
    self.layout = [[HorizontalFlowLayout alloc] init];
    
    CGRect rct = self.view.bounds;
    rct.size.height = 150;
    rct.origin.y = [[UIScreen mainScreen] bounds].size.height / 2.0 - rct.size.height;
    
    self.collectionView = [[UICollectionView alloc] initWithFrame:rct collectionViewLayout:_layout];
    self.collectionView.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight;
    self.collectionView.showsHorizontalScrollIndicator = NO;
    self.collectionView.decelerationRate = UIScrollViewDecelerationRateNormal;
    
    [self.collectionView registerClass:[DotaCell class] forCellWithReuseIdentifier:NSStringFromClass([DotaCell class])];
    [self.collectionView setBackgroundColor:[UIColor clearColor]];
    [self.collectionView setDelegate:self];
    [self.collectionView setDataSource:self];

    [self.view addSubview:_collectionView];
    

添加UICollectionViewDataSource的代理方法,使其顯示數據。


- (NSInteger)collectionView:(UICollectionView *)collectionView numberOfItemsInSection:(NSInteger)section {
    return [self.itemTitles count];
}

- (UICollectionViewCell *)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath {
    DotaCell *cell = [collectionView dequeueReusableCellWithReuseIdentifier:NSStringFromClass([DotaCell class]) forIndexPath:indexPath];
    
    cell.image.image = [UIImage imageNamed:[self.itemTitles objectAtIndex:indexPath.row]];
    cell.name.text = [self.itemTitles objectAtIndex:indexPath.row];
    
    return cell;
}

這樣程式就有了我們想要的初步效果:
gif1

圖片水平排放

但...效果的確很差!
下麵要做的就是逐步完善效果,首先我們要讓兩排圖像變成一排去展示。那要怎麼去做?首先,我們在初始化collectionView的地方設置了高度為150,所以圖片就擠在這個150的高度里儘可能的壓縮顯示。由於collectionView的尺寸已經設定,那麼就剩cell的尺寸可以控制了。實現CollectionViewFlowLayoutDelegate的代理方法sizeForItemAtIndexPath


- (CGSize)collectionView:(nonnull UICollectionView *)collectionView layout:(nonnull UICollectionViewLayout *)collectionViewLayout sizeForItemAtIndexPath:(nonnull NSIndexPath *)indexPath {
    return CGSizeMake(64, collectionView.bounds.size.height); 
}

這裡寬度64是圖片的尺寸,高度設置填滿collectionView的高度是為了防止上圖中兩行圖片擠壓的情況,所以直接讓一個cell的高度占滿整個容器。

這時候的效果好了很多,已經有點樣子了:
gif2

頂端圖片滑到中間

但這離我們最終的效果還差很遠,接下來我需要實現讓第一張圖片和最後一張圖片都能滑到屏幕中點的位置,這應該是很常見的效果,實現起來也很簡單。首先我們的一排cell都預設為頂端與collectionView的兩端對齊的,collectionView的左右兩端與viewController.view也是對齊的,所以顯示的效果是,兩端的圖片都與屏幕對齊。知道這個關係就好辦了,直接設置collectionView與其父view的內間距即可。
依舊是實現flowLayout的代理方法:


//Asks the delegate for the margins to apply to content in the specified section.安排初始位置
//使前後項都能居中顯示
- (UIEdgeInsets)collectionView:(nonnull UICollectionView *)collectionView layout:(nonnull UICollectionViewLayout *)collectionViewLayout insetForSectionAtIndex:(NSInteger)section {
    
    NSInteger itemCount = [self collectionView:collectionView numberOfItemsInSection:section];

    NSIndexPath *firstIndexPath = [NSIndexPath indexPathForItem:0 inSection:section];
    CGSize firstSize = [self collectionView:collectionView layout:collectionViewLayout sizeForItemAtIndexPath:firstIndexPath];
    
    NSIndexPath *lastIndexPath = [NSIndexPath indexPathForItem:itemCount - 1 inSection:section];
    CGSize lastSize = [self collectionView:collectionView layout:collectionViewLayout sizeForItemAtIndexPath:lastIndexPath];
    
    return UIEdgeInsetsMake(0, (collectionView.bounds.size.width - firstSize.width) / 2,
                            0, (collectionView.bounds.size.width - lastSize.width) / 2);
    
    
}

效果如圖:
gif3

居中圖片放大顯示

接下來添加一個我們需要的特效,就是中間的圖片放大顯示,其餘的縮小並且增加一層半透明效果。
FlowLayout中有一個名為layoutAttributesForElementsInRect的方法,功能如其名,就是設置範圍內元素的layout屬性。對於這個效果,首先需要設置放大的比例,其次要根據圖片大小和間距來設定一個合適的觸發放大的區域寬度,當圖滑入這個區域就進行縮放。


static CGFloat const ActiveDistance = 80;
static CGFloat const ScaleFactor = 0.2;
//這裡設置放大範圍
- (NSArray *)layoutAttributesForElementsInRect:(CGRect)rect
{
    
    NSArray *array = [super layoutAttributesForElementsInRect:rect];
    
    CGRect visibleRect = (CGRect){self.collectionView.contentOffset, self.collectionView.bounds.size};
    
    for (UICollectionViewLayoutAttributes *attributes in array) {
        //如果cell在屏幕上則進行縮放
        if (CGRectIntersectsRect(attributes.frame, rect)) {
            
            attributes.alpha = 0.5;
   
            CGFloat distance = CGRectGetMidX(visibleRect) - attributes.center.x;//距離中點的距離
            CGFloat normalizedDistance = distance / ActiveDistance;
            
            if (ABS(distance) < ActiveDistance) {
                CGFloat zoom = 1 + ScaleFactor * (1 - ABS(normalizedDistance)); //放大漸變
                attributes.transform3D = CATransform3DMakeScale(zoom, zoom, 1.0);
                attributes.zIndex = 1;
                attributes.alpha = 1.0;
            }
        }
    }
    
    return array;
}

效果如下:
gif4

滑動校正

這時候幾乎完成了,但還差點東西,就是讓其在滾動停止的時候,離屏幕中間最近的cell自動矯正位置到中間。還是在FlowLayout添加該方法,具體說明我都寫到註釋里了:


//scroll 停止對中間位置進行偏移量校正
- (CGPoint)targetContentOffsetForProposedContentOffset:(CGPoint)proposedContentOffset withScrollingVelocity:(CGPoint)velocity {
    CGFloat offsetAdjustment = MAXFLOAT;
    ////  |-------[-------]-------|
    ////  |滑動偏移|可視區域 |剩餘區域|
    //是整個collectionView在滑動偏移後的當前可見區域的中點
    CGFloat centerX = proposedContentOffset.x + (CGRectGetWidth(self.collectionView.bounds) / 2.0);
    //    CGFloat centerX = self.collectionView.center.x; //這個中點始終是屏幕中點
    //所以這裡對collectionView的具體尺寸不太理解,輸出的是屏幕大小,但實際上寬度肯定超出屏幕的
    
    CGRect targetRect = CGRectMake(proposedContentOffset.x, 0.0, self.collectionView.bounds.size.width, self.collectionView.bounds.size.height);
    
    NSArray *array = [super layoutAttributesForElementsInRect:targetRect];
    
    for (UICollectionViewLayoutAttributes *layoutAttr in array) {
        CGFloat itemCenterX = layoutAttr.center.x;
        
        if (ABS(itemCenterX - centerX) < ABS(offsetAdjustment)) { // 找出最小的offset 也就是最中間的item 偏移量
            offsetAdjustment = itemCenterX - centerX;
        }
    }
    
    return CGPointMake(proposedContentOffset.x + offsetAdjustment, proposedContentOffset.y);
}

gif5

增加圖片點擊效果

最後 添加一個點擊cell 將其滾動到中間
viewcontroller添加CollectionViewDelegate的代理方法

- (void)collectionView:(UICollectionView *)collectionView didSelectItemAtIndexPath:(NSIndexPath *)indexPath {
    [self.collectionView selectItemAtIndexPath:indexPath animated:YES scrollPosition:UICollectionViewScrollPositionNone];
    
    //滾動到中間
    [self.collectionView scrollToItemAtIndexPath:indexPath atScrollPosition:UICollectionViewScrollPositionCenteredHorizontally animated:YES];
}

gif6

封裝成控制項

當我們把效果實現之後,就可以考慮將代碼優化一下,合到一個類里,減少書寫常量,增加介面,封裝成一個控制項去使用。比如可以設定文字的顯示與隱藏介面,再比如增加適應各種尺寸的圖片等等。這個代碼就不放了,畢竟不難,有問題給我留言好了。


您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • 五、MySql 中常用子句 1.where子句 我們都知道在查詢數據時,未必會查整個表中的數據,當有條件查詢時,就會用到where子句。其結構: select * from [表名] where [條件]。 2.like子句 like子句就是模糊查詢,有下麵一些通配符: ...
  • 在機器學習和數據挖掘中,經常會聽到兩個名詞:歸一化(Normalization)與標準化(Standardization)。它們具體是什麼?帶來什麼益處?具體怎麼用?本文來具體討論這些問題。
  • 上一篇,我們介紹了Hive的表操作做了簡單的描述和實踐。在實際使用中,可能會存在數據的導入導出,雖然可以使用sqoop等工具進行關係型數據導入導出操作,但有的時候只需要很簡便的方式進行導入導出即可   下麵我們開始介紹hive的數據導入,導出,以及集群的數據遷移進行描述。
  • 一、安裝MySql1.解壓版安裝下載地址:http://dev.mysql.com/downloads/mysql/安裝及配置教程:http://jingyan.baidu.com/article/f3ad7d0ffc061a09c3345bf0.html (百度經驗)2.安裝版安裝下載地址:htt...
  • SQL 語句日期用法及函數--DAY()、MONTH()、YEAR()——返回指定日期的天數、月數、年數;select day(cl_s_time) as '日' from class--返回天select '月'=month(cl_s_time) from class--返回月select '年'...
  • 在OC的UI中,一些常用的控制項如UIImageView,UILabel等預設是沒有交互的,就是在控制項上點擊,雙擊或者滑動等操作是沒有效果的。下麵的方法較為完美的解決了控制項的交互問題:(以UIImageView為例,其他控制項類似)首先,創建一個UIImageView:UIImageView *imag...
  • 不小心在開發過程中,得到了(null)以及的返回值,找了好長時間只找到了一個關於的。由於要根據返回值進行判斷,做出必要反應,因此必須知道返回值所代表的具體字元,在得到(null)後利用isEqual:和@“”,NULL,@“(null)”,nil,Nil比較後均得不到正確結果,弄得不知所措了,但是還...
  • 讓你像使用普通按鈕一樣,只用設置倒計時時長就可以實現倒計時功能
一周排行
    -Advertisement-
    Play Games
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...