瀑布流,又稱瀑布流式佈局。是比較流行的一種網站頁面佈局,視覺表現爲良莠不齊的多欄佈局,隨着頁面滾動條向下滾動,這種佈局還會不斷加載數據塊並附加至當前尾部。最先採用此佈局的網站是Pinterest,逐漸在國內流行開來。國內大多數清新站基本爲這類風格數組
l 琳琅滿目:整版以圖片爲主,大小不一的圖片按照必定的規律排列緩存
l 惟美:圖片的風格以惟美的圖片爲主網絡
l 操做簡單:在瀏覽網站的時候只須要輕輕滑動一下鼠標滾輪,一切的美妙的圖片精彩即可呈如今你面前異步
瀑布流對於圖片的展示,是高效而具備吸引力的,用戶一眼掃過的快速閱讀模式能夠在短期內得到更多的信息量,而瀑布流裏懶加載模式又避免了用戶鼠標點擊的翻頁操做佈局
瀑布流的主要特性即是錯落有致,定寬而不定高的設計讓頁面區別於傳統的矩陣式圖片佈局模式,巧妙的利用視覺層級,視線的任意流動又緩解了視覺疲勞動畫
l 電商導購,如想去網、蘑菇街和美麗說、好享說,依託於淘寶平臺網站
l 興趣圖譜分享,如知美、花瓣等atom
l 細分垂直領域,如針對吃貨的零食控、針對家居行業的他部落等spa
l 優勢設計
有效的下降了界面複雜度,節省了空間:咱們再也不須要臃腫複雜的頁碼導航連接或按鈕了
對觸屏設備來講,交互方式更符合直覺:在移動應用的交互環境當中,經過向上滑動進行滾屏的操做已經成爲最基本的用戶習慣,並且所須要的操做精準程度遠遠低於點擊連接或按鈕
更高的參與度:以上兩點所帶來的交互便捷性可使用戶將注意力更多的集中在內容而不是操做上,從而讓他們更樂於沉浸在探索與瀏覽當中
l 缺點
有限的用例:無限滾動的方式只適用於某些特定類型產品當中一部分特定類型的內容
額外的複雜度
l SEO:集中在一頁當中動態加載數據,不利於SEO,對於網站而言,存在必定的風險
l 頁面的數量:若是網站須要經過更多的內容頁面展現更多的相關信息(包括廣告)是很重要的策略,那麼單頁無限滾動的方式就不適用了
使用多個UITableView,控制它們同時滾動,在複雜的用戶操做下,會出現滾動不一樣步的狀況;若是須要支持設備的多個方向,不利於增長圖片列數及方向切換的動畫效果,使用一個UIScrollView,參考UITableView的實現方式,開發一個符合需求的可重用的控件
每次經過一個GET方法加載一個JSON數據,數據中包含一組數據信息:圖像URL、圖像標題等
異步加載JSON數據包中的圖像,從左至右,從上至下依次顯示
滾動至數據末尾時,加載新的數據
數據加載後,從當前最末一張圖像開始追加新的圖像
滾動至頂部時,下拉刷新新的數據
新數據加載後,從新填充當前視圖中的內容
使用沙箱數據:自定義UIScrollView,使用沙箱中的圖像模擬瀑布流的實現
集成網絡:經過網絡加載並處理數據
自定義UIScrollView(WaterFlowView),模擬UITableView
自定義UIView(WaterFlowViewCell),模擬UITableViewCell
單獨負責圖像內容及文本標籤的顯示
使用可重用標示符處理視圖的重用
自定義UIViewController(WaterFlowViewController),模擬UITableViewController
處理自定義UIScrollView的數據源及代理方法
屬性
// 可重用標示符
@property (strong, nonatomic) NSString *reuseIdentifier;
// 被選中標記
@property (assign, nonatomic) BOOL selected;
// 圖像視圖
@property (weak, nonatomic) UIImageView *imageView;
// 文本標籤
@property (weak, nonatomic) UILabel *textLabel;
方法:
// 使用可重用標示符實例化視圖
- (id)initWithResueIdentifier:(NSString *)reusedIdentifier;
利用控件的getter方法實現控件懶加載
重寫layoutSubviews方法,調整控件佈局
注意:此處不須要調用父類的layoutSubViews方法
l 數據源
單元格數量
l 單元格
視圖的列數(默認爲1,可選方法)
l 代理
指定單元格的高度
單元格被選中
@protocol WaterFlowViewDataSource <NSObject>
// 單元格數量
- (NSInteger)waterFlowViewNumberOfCells:(WaterFlowView *)waterFlowView;
// 單元格
- (WaterFlowViewCell *)waterFlowView:(WaterFlowView *)waterFlowView cellAtIndexPath:(NSIndexPath *)indexPath;
@optional
// 視圖的列數(可選,默認1列)
- (NSInteger)waterFlowViewNumberOfColumns:(WaterFlowView *)waterFlowView;
@end
@protocol WaterFlowViewDelegate <UIScrollViewDelegate>
// 指定單元格的高度
- (CGFloat)waterFlowView:(WaterFlowView *)waterFlowView heightForCellAtIndexPath:(NSIndexPath *)indexPath;
// 單元格被選中
- (void)waterFlowView:(WaterFlowView *)waterFlowView didSelectedCellAtIndexPath:(NSIndexPath *)indexPath;
@end
- (void)loadView
{
_waterFlowView = [[WaterFlowView alloc]initWithFrame:CGRectZero];
[_waterFlowView setDataSource:self];
[_waterFlowView setDelegate:self];
// 根據父視圖大小調整自身大小
[_waterFlowView setAutoresizingMask:UIViewAutoresizingFlexibleWidth
| UIViewAutoresizingFlexibleHeight];
self.view = _waterFlowView;
}
1. 首先初始化根據數據行數indexPaths數組
2. 佈局界面
1) 計算每列寬度
2) 使用一個數組,記錄每列的當前Y值
3) 遍歷self.indexPaths生成WaterFlowCellView並計算位置
4) 設置scrollView的contentSize
#pragma mark 設備旋轉結束
- (void)didRotateFromInterfaceOrientation:(UIInterfaceOrientation)fromInterfaceOrientation
{
[self.waterFlowView reloadData];
}
#pragma mark - 列數
- (NSInteger)numberOfColumnsInWaterFlowView:(WaterFlowView *)waterFlowView
{
// 根據設備方向設定不一樣的瀑布流列數
if ([UIDevice currentDevice].orientation == UIDeviceOrientationLandscapeLeft || [UIDevice currentDevice].orientation == UIDeviceOrientationLandscapeRight) {
self.numberOfColumns = 4;
} else {
self.numberOfColumns = 3;
}
return self.numberOfColumns;
}
#pragma mark - 緩存數據
// 單元格位置數組
@property (strong, nonatomic) NSMutableArray *cellFramesArray;
// 單元格緩存集合
@property (strong, nonatomic) NSMutableSet *reusableCellSet;
// 正在顯示單元格字典
@property (strong, nonatomic) NSMutableDictionary *screenCellsDict;
編寫方法統一輩子成緩存數據
// 創建緩存數據
[self generateCacheData];
// 1) 獲取當前列數
// 2) 創建列索引數組
// 3) 創建列單元格位置數組
// 4) 創建總體索引數組
// 5. 視圖緩存字典
// 6. 正在顯示的單元格集合
// 計算出下一列的數值
NSInteger nextCol = (col + 1) % _numberOfColumns;
// 判斷當前的行高是否超過下一列的行高
if (currentY[col] > currentY[nextCol]) {
col = nextCol;
}
// 在單元格數組中記錄全部單元格的位置(位置&大小)
[self.cellFramesArray addObject:[NSValue valueWithCGRect:CGRectMake(x, y, colW, h)]];
#pragma mark 判斷視圖是否在屏幕範圍以內
- (BOOL)isInScreenWithFrame:(CGRect)frame
{
return (frame.origin.y - self.contentOffset.y) < self.bounds.size.height &&
(frame.origin.y + frame.size.height - self.contentOffset.y) > 0;
}
#pragma mark 使用可重用標示符獲取單元格對象
- (WaterFlowCellView *)dequeueReusableCellWithReuseIdentifier:(NSString *)reuseIdentifier
{
// 須要從一個集合獲取可重用單元格
// 從集合中取出任意對象
WaterFlowCellView *cell = [self.reusableCellSet anyObject];
if (cell != nil) {
// 從集合中刪除視圖
[self.reusableCellSet removeObject:cell];
}
return cell;
}
- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event
{
UITouch *touch = [touches anyObject];
CGPoint location = [touch locationInView:self];
// 遍歷可見視圖集合,查找被點擊的單元格
NSArray *array = [self.displayCellDict allKeys];
for (NSIndexPath *indexPath in array) {
WaterFlowCellView *view = self.displayCellDict[indexPath];
if (CGRectContainsPoint(view.frame, location)) {
[self.delegate waterFlowView:self didSelectRowAtIndexPath:indexPath];
}
}
}