iOS基础 - 瀑布流

1、瀑布流简介

瀑布流,又称瀑布流式布局。是比较流行的一种网站页面布局,视觉表现为良莠不齐的多栏布局,随着页面滚动条向下滚动,这种布局还会不断加载数据块并附加至当前尾部。最先采用此布局的网站是Pinterest,逐渐在国内流行开来。国内大多数清新站基本为这类风格数组

2、瀑布流特色

l 琳琅满目:整版以图片为主,大小不一的图片按照必定的规律排列缓存

l 惟美:图片的风格以惟美的图片为主网络

l 操做简单:在浏览网站的时候只须要轻轻滑动一下鼠标滚轮,一切的美妙的图片精彩即可呈如今你面前异步

3、瀑布流应用

瀑布流对于图片的展示,是高效而具备吸引力的,用户一眼扫过的快速阅读模式能够在短期内得到更多的信息量,而瀑布流里懒加载模式又避免了用户鼠标点击的翻页操做布局

瀑布流的主要特性即是错落有致,定宽而不定高的设计让页面区别于传统的矩阵式图片布局模式,巧妙的利用视觉层级,视线的任意流动又缓解了视觉疲劳动画

4、使用瀑布流的网站

l 电商导购,如想去网、蘑菇街和美丽说、好享说,依托于淘宝平台网站

l 兴趣图谱分享,如知美、花瓣等atom

l 细分垂直领域,如针对吃货的零食控、针对家居行业的他部落等spa

5、瀑布流的优缺点

l 优势设计

有效的下降了界面复杂度,节省了空间:咱们再也不须要臃肿复杂的页码导航连接或按钮了

对触屏设备来讲,交互方式更符合直觉:在移动应用的交互环境当中,经过向上滑动进行滚屏的操做已经成为最基本的用户习惯,并且所须要的操做精准程度远远低于点击连接或按钮

更高的参与度:以上两点所带来的交互便捷性可使用户将注意力更多的集中在内容而不是操做上,从而让他们更乐于沉浸在探索与浏览当中

l 缺点

有限的用例:无限滚动的方式只适用于某些特定类型产品当中一部分特定类型的内容

额外的复杂度

l SEO:集中在一页当中动态加载数据,不利于SEO,对于网站而言,存在必定的风险

l 页面的数量:若是网站须要经过更多的内容页面展现更多的相关信息(包括广告)是很重要的策略,那么单页无限滚动的方式就不适用了

6、实现瀑布流的思路

使用多个UITableView,控制它们同时滚动,在复杂的用户操做下,会出现滚动不一样步的状况;若是须要支持设备的多个方向,不利于增长图片列数及方向切换的动画效果,使用一个UIScrollView,参考UITableView的实现方式,开发一个符合需求的可重用的控件

7、瀑布流数据处理思路

每次经过一个GET方法加载一个JSON数据,数据中包含一组数据信息:图像URL、图像标题等

异步加载JSON数据包中的图像,从左至右,从上至下依次显示

滚动至数据末尾时,加载新的数据

数据加载后,从当前最末一张图像开始追加新的图像

滚动至顶部时,下拉刷新新的数据

新数据加载后,从新填充当前视图中的内容

8、开发步骤

使用沙箱数据:自定义UIScrollView,使用沙箱中的图像模拟瀑布流的实现

集成网络:经过网络加载并处理数据

9、自定义瀑布流控件

自定义UIScrollViewWaterFlowView),模拟UITableView

自定义UIViewWaterFlowViewCell),模拟UITableViewCell

单独负责图像内容及文本标签的显示

使用可重用标示符处理视图的重用

自定义UIViewControllerWaterFlowViewController),模拟UITableViewController

处理自定义UIScrollView的数据源及代理方法

10、WaterFlowViewCell的定义

属性

// 可重用标示符

@property (strong, nonatomic) NSString *reuseIdentifier;

// 被选中标记

@property (assign, nonatomic) BOOL selected;

// 图像视图

@property (weak, nonatomic) UIImageView *imageView;

// 文本标签

@property (weak, nonatomic) UILabel *textLabel;

方法:

// 使用可重用标示符实例化视图

- (id)initWithResueIdentifier:(NSString *)reusedIdentifier;

11、WaterFlowViewCell的实现

利用控件的getter方法实现控件懒加载

重写layoutSubviews方法,调整控件布局

注意:此处不须要调用父类的layoutSubViews方法

12、WaterFlowView的定义数据源和代理

l 数据源

单元格数量

l 单元格

视图的列数(默认为1,可选方法)

l 代理

指定单元格的高度

单元格被选中

十3、数据源协议

@protocol WaterFlowViewDataSource <NSObject>

// 单元格数量

- (NSInteger)waterFlowViewNumberOfCells:(WaterFlowView *)waterFlowView;

// 单元格

- (WaterFlowViewCell *)waterFlowView:(WaterFlowView *)waterFlowView cellAtIndexPath:(NSIndexPath *)indexPath;

@optional

// 视图的列数(可选,默认1列)

- (NSInteger)waterFlowViewNumberOfColumns:(WaterFlowView *)waterFlowView;

@end

十4、代理协议

@protocol WaterFlowViewDelegate <UIScrollViewDelegate>

// 指定单元格的高度

- (CGFloat)waterFlowView:(WaterFlowView *)waterFlowView heightForCellAtIndexPath:(NSIndexPath *)indexPath;

// 单元格被选中

- (void)waterFlowView:(WaterFlowView *)waterFlowView didSelectedCellAtIndexPath:(NSIndexPath *)indexPath;

@end

十5、WaterFlowViewController中的实例化视图

- (void)loadView

{

    _waterFlowView = [[WaterFlowView alloc]initWithFrame:CGRectZero];

    [_waterFlowView setDataSource:self];

    [_waterFlowView setDelegate:self];

    // 根据父视图大小调整自身大小

    [_waterFlowView setAutoresizingMask:UIViewAutoresizingFlexibleWidth

     | UIViewAutoresizingFlexibleHeight];

    self.view = _waterFlowView;

}

十6、编写resetView方法实现瀑布流视图的布局

1. 首先初始化根据数据行数indexPaths数组

2. 布局界面

1) 计算每列宽度

2) 使用一个数组,记录每列的当前Y

3) 遍历self.indexPaths生成WaterFlowCellView并计算位置

4) 设置scrollViewcontentSize

十7、修改WaterFlowViewController增长方向支持

#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;

}

十8、引入缓存策略,利用数据字典记录单元格的位置

#pragma mark - 缓存数据

// 单元格位置数组

@property (strong, nonatomic) NSMutableArray *cellFramesArray;

// 单元格缓存集合

@property (strong, nonatomic) NSMutableSet *reusableCellSet;

// 正在显示单元格字典

@property (strong, nonatomic) NSMutableDictionary *screenCellsDict;

编写方法统一辈子成缓存数据

// 创建缓存数据

[self generateCacheData];

十9、生成缓存数据

// 1) 获取当前列数

// 2) 创建列索引数组

// 3) 创建列单元格位置数组

// 4) 创建总体索引数组

// 5. 视图缓存字典

// 6. 正在显示的单元格集合

二10、修改resetView方法,仅记录各个视图位置

// 计算出下一列的数值

NSInteger nextCol = (col + 1) % _numberOfColumns;

// 判断当前的行高是否超过下一列的行高

if (currentY[col] > currentY[nextCol]) {

    col = nextCol;

}

// 在单元格数组中记录全部单元格的位置(位置&大小)

[self.cellFramesArray addObject:[NSValue valueWithCGRect:CGRectMake(x, y, colW, h)]];

二11、判断视图是否在屏幕范围以内

#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;

}

二12、编写dequeueReusableCellWithReuseIdentifier

#pragma mark 使用可重用标示符获取单元格对象

- (WaterFlowCellView *)dequeueReusableCellWithReuseIdentifier:(NSString *)reuseIdentifier

{

    // 须要从一个集合获取可重用单元格

         // 从集合中取出任意对象

      WaterFlowCellView *cell = [self.reusableCellSet anyObject];

    if (cell != nil) {

        // 从集合中删除视图

        [self.reusableCellSet removeObject:cell];

    }

    return cell;

}

二十3、手势处理

- (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];

        }

    }

相关文章
相关标签/搜索