html中的瀑布流是什么

一、总结

1、瀑布流: 从左往右排列,哪一列现在的总高度最小,就优先排序把item(单元格)放在这一列.这样排完所有的单元格后,可以保证每一列的总高度都相差不大

2、看效果图html中的瀑布流是什么-LMLPHP

3、瀑布流使用前置性条件:瀑布流很多有特定的使用前置性条件,不要乱用

二、瀑布流应用场景

首先,我们得明确一点,瀑布流肯定是有好处的,不然pinterest不可能那么火,蘑菇街在加入瀑布流元素之后PV暴涨。

我觉得瀑布流的好处大致有:提高发现好图的效率以及图片列表页极强的视觉感染力。

享受这些好处,就得考虑若干条前置性条件:

  • 浏览行为缺乏特别明确的目的性,以“逛逛”“扫街”的心态为主
  • 对复杂的信息索引无依赖性
  • 用户以图片为首要检索对象,瀑布流页面的配文只是相当次要的辅助信息
  • 竖图比例较高
  • 图片平均质量较高
  • 图片的风格气质趋于相似(也是Pinterest始终采取邀请制的原因)

单从以上六点来看,你们不喜欢瀑布流是对的,因为你们根本就不是这些网站的使用者。

三、html中的瀑布流是什么

-.什么是瀑布流?

瀑布流视图与UITableView类似,但是相对复杂一点.UITableView只有一列,可以有多个小节(section),每一个小节(section)可以有多行(row).

瀑布流呢,可以有多列,每一个item(单元格)的高度可以不相同,但是宽度必须一样.排列的方式是,从左往右排列,哪一列现在的总高度最小,就优先排序把item(单元格)放在这一列.这样排完所有的单元格后,可以保证每一列的总高度都相差不大,不至于,有的列很矮,有的列很高.这样就很难看了.

html中的瀑布流是什么-LMLPHP

上面的数字,就是每个单元格的序号,可以看到item的排列顺序是个什么情况.

二.怎么实现一个瀑布流呢?

仿照UITableView的设计,我们要知道有多少个单元格,我们得问我们的数据源.有几列,问数据源.在某一个序号上是怎样的cell,问数据源.

某一个序号单元格的高度,问代理.单元格的列边距,行边距,整体的瀑布流视图的上下左右距离瀑布流视图所在的父视图的边距.这些,都问代理.

同时,我们也要在接口处对外提供方法,reloadData,当瀑布流视图要更新的时候可以调用.我们还要对外提供方法cellWidth,让外界可以直接知道每个单元格的高度是怎样的.同时,我们也要提供一个类似于UITableView的用来从缓存池取cell的方法.不然的话,屏幕每滑动到新的单元格地方,就要重新新建一个cell,这样当瀑布流总单元格多了之后,有多少单元格需要显示就创建多少次,这样是相当消耗性能的.所以要有缓存池,让外界在取cell时优先从缓存池取,缓存池取不到了,再来新建一个cell也不迟.UITableView就是这么做的.我们也要这么做.所以对外提供一个方法 -(WaterfallsViewCell *)dequeueReusableCellWithIdentifier:(NSString *)identifier;

仿照UITableView每个单元格是一种UITableViewCell,我们也可以定义一个瀑布流cell,继承于UIView即可,后期调用时可以随意定制cell的内容.

因为要实现的瀑布流,需要上下滚动,实际上是一个UIScrollView.所以,直接继承于UIScrollView.

所以有如下的接口定义

1.这个是瀑布流视图 WaterfallsView

//  Copyright © 2015年 penglang. All rights reserved.

//

#import <UIKit/UIKit.h>

@class WaterfallsView,WaterfallsViewCell;

typedef enum {

WaterfallsViewMarginTypeTop,

WaterfallsViewMarginTypeLeft,

WaterfallsViewMarginTypeBottom,

WaterfallsViewMarginTypeRight,

WaterfallsViewMarginTypeColumn,

WaterfallsViewMarginTypeRow

}WaterfallsViewMarginType;

@protocol WaterfallsViewDataSource <NSObject>

@required

//- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section;

/**

*  有多少个cell

*/

-(NSUInteger)numberOfCells;

/**

*  在某一个序号的cell

*/

-(WaterfallsViewCell *)waterfallsView:(WaterfallsView *)waterfallsView cellAtIndex:(NSUInteger)index;

@optional

/**

*  有多少列

*/

-(NSUInteger)numberOfColumns;

@end

@protocol WaterfallsViewDelegate <UIScrollViewDelegate>

@optional

/**

*  某一个序号的单元格的高度

*/

-(CGFloat)waterfallsView:(WaterfallsView *)waterfallsView heightAtIndex:(NSUInteger)index;

/**

*  单元格与瀑布流视图的边界

*/

-(CGFloat)waterfallsView:(WaterfallsView *)waterfallsView margins:(WaterfallsViewMarginType)marginType;

/**

*  点击了某一个序号的单元格,怎么处理

*/

-(void)waterfallsView:(WaterfallsView *)waterfallsView didSelectCellAtIndex:(NSUInteger)index;

@end

@interface WaterfallsView : UIScrollView

@property (nonatomic, assign) id<WaterfallsViewDataSource> dataSource;

@property (nonatomic, assign) id<WaterfallsViewDelegate> delegate;

-(void)reloadData;

-(WaterfallsViewCell *)dequeueReusableCellWithIdentifier:(NSString *)identifier;

-(CGFloat)cellWidth;

@end

//这个是瀑布流视图的单元格视图  WaterfallsViewCell

//  Copyright © 2015年 penglang. All rights reserved.

//

#import <UIKit/UIKit.h>

@interface WaterfallsViewCell : UIView

@property (nonatomic,copy, readonly) NSString *reuseIdentifier;

-(instancetype)initWithReuseIdentifier:(NSString *)reuseIdentifier;

@end

//cell的定义与实现

//  WaterfallsViewCell.h

//  Copyright © 2015年 penglang. All rights reserved.

//

#import <UIKit/UIKit.h>

@interface WaterfallsViewCell : UIView

@property (nonatomic,copy, readonly) NSString *reuseIdentifier;

-(instancetype)initWithReuseIdentifier:(NSString *)reuseIdentifier;

@end

//  Copyright © 2015年 penglang. All rights reserved.

//

#import "WaterfallsViewCell.h"

//static NSUInteger count = 0;

@interface WaterfallsViewCell ()

@property (nonatomic,copy, readwrite) NSString *reuseIdentifier;

@end

//  WaterfallsViewCell.m

@implementation WaterfallsViewCell

-(instancetype)initWithReuseIdentifier:(NSString *)reuseIdentifier{

if (self = [super init]) {

self.reuseIdentifier = reuseIdentifier;

}

//NSLog(@"创建cell%lu",(unsigned long)++count);

return self;

}

@end

然后,我们在控制器中就可以看该怎么使用定义的数据源方法

#import "ViewController.h"

#import "WaterfallsView.h"

#import "WaterfallsViewCell.h"

#define MyColor(r,g,b) [UIColor colorWithRed:(r)/255.0 green:(g)/255.0 blue:(b)/255.0 alpha:1.0]

#define MyColorA(r,g,b,a) [UIColor colorWithRed:(r)/255.0 green:(g)/255.0 blue:(b)/255.0 alpha:a]

@interface ViewController ()<WaterfallsViewDataSource,WaterfallsViewDelegate>

@property (nonatomic, weak) WaterfallsView *waterfallsView;

@end

@implementation ViewController

- (void)viewDidLoad {

[super viewDidLoad];

//初始化瀑布流

WaterfallsView *waterfallsView = [[WaterfallsView alloc] initWithFrame:self.view.bounds];

waterfallsView.dataSource = self;

waterfallsView.delegate = self;

[self.view addSubview:waterfallsView];

_waterfallsView = waterfallsView;

}

#pragma mark - WaterfallsViewDataSource 数据源方法实现

-(NSUInteger)numberOfCells{

return 16;

}

-(NSUInteger)numberOfColumns{

return 3;

}

-(WaterfallsViewCell *)waterfallsView:(WaterfallsView *)waterfallsView cellAtIndex:(NSUInteger)index{

static NSString *ID = @"waterfallsCell";

WaterfallsViewCell *cell = [waterfallsView dequeueReusableCellWithIdentifier:ID];

if (cell == nil) {

cell = [[WaterfallsViewCell alloc] initWithReuseIdentifier:ID];

cell.backgroundColor = MyColor(arc4random_uniform(256), arc4random_uniform(256), arc4random_uniform(256));

UILabel *label = [[UILabel alloc] initWithFrame:CGRectMake(10, 10, 100, 20)];

label.textColor = [UIColor whiteColor];

label.tag = 10;

[cell addSubview:label];

}

UILabel *label = (UILabel *)[cell viewWithTag:10];

label.text = [NSString stringWithFormat:@"%lu",(unsigned long)index];

return cell;

}

#pragma mark - WaterfallsViewDelegate 代理方法实现

-(CGFloat)waterfallsView:(WaterfallsView *)waterfallsView heightAtIndex:(NSUInteger)index{

switch (index%3) {

case 0:

return 70.0;

break;

case 1:

return 90.0;

break;

case 2:

return 120.0;

break;

default:

return 150.0;

break;

}

}

-(CGFloat)waterfallsView:(WaterfallsView *)waterfallsView margins:(WaterfallsViewMarginType)marginType{

switch (marginType) {

case WaterfallsViewMarginTypeTop:

return 30;

case WaterfallsViewMarginTypeLeft:

case WaterfallsViewMarginTypeBottom:

case WaterfallsViewMarginTypeRight:

return 10;

break;

default:

return 5;

break;

}

}

-(void)waterfallsView:(WaterfallsView *)waterfallsView didSelectCellAtIndex:(NSUInteger)index{

NSLog(@"点击了第%lu个cell",(unsigned long)index);

}

@end

在控制器中这样用当然写代码很舒服了.可是我们只是对外提供了这些方法,很好用,而我们并没有实现它.

现在就来实现它的方法.

核心需要实现的方法其实就是

-(void)reloadData

在调用reloadData之时,需要重新将cell在瀑布流上显示出来,我们要确定每个单元格的位置,在相应的位置显示相应的我们设置好的单元格.

所以,我们要知道瀑布流视图的上\左\下\右边距各是多少,这些可以问数据源方法,我们可以在瀑布流视图实现里面给一个默认的间距.如果控制器没有实现边距数据源方法,就用我们默认设置的边距.

所以写一个辅助的方法,供内部调用

-(CGFloat)marginForType:(WaterfallsViewMarginType)type{

CGFloat margin = 0;

if ([self.delegate respondsToSelector:@selector(waterfallsView:margins:)]) {

margin = [self.delegate waterfallsView:self margins:type];

}else{

margin = WaterfallsViewDefaultMargin;

}

return margin;

}

这样就可以知道各种间距了.

同时,要知道总共有多少个cell,问数据源方法的实现者

有几列,也可以问数据源实现者,如果外界没实现,可以预先设置一个默认的列数.

所以有如下一些方法供内部调用,还有各种高度,等等.

-(CGFloat)marginForType:(WaterfallsViewMarginType)type{

CGFloat margin = 0;

if ([self.delegate respondsToSelector:@selector(waterfallsView:margins:)]) {

margin = [self.delegate waterfallsView:self margins:type];

}else{

margin = WaterfallsViewDefaultMargin;

}

return margin;

}

-(NSUInteger)columnsCount{

if ([self.dataSource respondsToSelector:@selector(numberOfColumns)]) return [self.dataSource numberOfColumns];

return WaterfallsViewDefaultColumnCount;

}

-(CGFloat)cellHeightAtIndex:(NSUInteger)index{

if ([self.delegate respondsToSelector:@selector(waterfallsView:heightAtIndex:)]) return [self.delegatewaterfallsView:self heightAtIndex:index];

return WaterfallsViewDefaultCellHeight;

}

reloadData方法我就直接放出来了

-(void)reloadData{

[self.cellFrames removeAllObjects];

[self.displayingCells removeAllObjects];

CGFloat topM = [self marginForType:WaterfallsViewMarginTypeTop];

CGFloat leftM = [self marginForType:WaterfallsViewMarginTypeLeft];

CGFloat bottomM = [self marginForType:WaterfallsViewMarginTypeBottom];

CGFloat rowM = [self marginForType:WaterfallsViewMarginTypeRow];

CGFloat columnM = [self marginForType:WaterfallsViewMarginTypeColumn];

NSUInteger totalCellCount = [self.dataSource numberOfCells];

NSUInteger totalColumnCount = [self columnsCount];

CGFloat cellW = [self cellWidth];

//这个数组用来存放每一列的最大的高度

CGFloat maxYOfColumn[totalColumnCount];

for (int i = 0; i < totalColumnCount; i++) {

maxYOfColumn[i] = 0;

}

int cellColumn;

for (int i = 0; i < totalCellCount; i++) {

CGFloat cellH = [self cellHeightAtIndex:i];

cellColumn = 0;

for (int j = 1; j < totalColumnCount; j++) {

if (maxYOfColumn[j] < maxYOfColumn[cellColumn]) {

cellColumn = j;

}

}

CGFloat cellX = leftM + (cellW + columnM) * cellColumn;

CGFloat cellY;

if (maxYOfColumn[cellColumn] == 0) {

cellY = topM;

}else{

cellY = maxYOfColumn[cellColumn] + rowM;

}

CGRect cellFrame = CGRectMake(cellX, cellY, cellW, cellH);

[self.cellFrames addObject:[NSValue valueWithCGRect:cellFrame]];

maxYOfColumn[cellColumn] = CGRectGetMaxY(cellFrame);

}

CGFloat maxYOfWaterfallsView = 0;

for (int i = 0; i < totalColumnCount; i++) {

if (maxYOfColumn[i] > maxYOfWaterfallsView) maxYOfWaterfallsView = maxYOfColumn[i];

}

maxYOfWaterfallsView += bottomM;

self.contentSize = CGSizeMake(0, maxYOfWaterfallsView);

}

以上只是把所有cell的frame求出来了,用数组保存,这样就可以知道每一个cell放在瀑布流上的哪个地方.最终得到最大的cell的高度后,就可以设置瀑布流视图的contentSize.不然无法滚动.但是,这还不够的,

我们要将cell在瀑布流视图上显示出来.

这个操作,是在layoutSubviews方法中实现

-(void)layoutSubviews{

[super layoutSubviews];

//回滚,从后往前遍历cell

if (self.scrollDirection == WaterfallsViewScrollDirectionRollback) {

for (int i = (int)[self.dataSource numberOfCells] - 1; i >=0 ; i--) {

[self handleCellWithIndex:i];

}

}else{ //往前滑动更多的cell,一般情况,从前往后遍历cell

for (int i = 0; i < [self.dataSource numberOfCells]; i++) {

[self handleCellWithIndex:i];

}

}

lastContentOffsetY = self.contentOffset.y;

NSLog(@"displaying Cells Count :%lu",(unsigned long)self.displayingCells.count);

}

//因为向前滚,序号小的cell要先消失,在后面的序号大的cell要新显示出来.所以,从序号小的遍历起.不在屏幕上的cell可以先回收到缓存池中,后面要显示的的cell就可以从缓存池中去拿了.

//同理,回滚的话,序号大的cell要先消失,在前面的序号小的cell要新显示出来,所以,从序号大的遍历起,不在屏幕上的cell先回收,前面新显示的cell就可以从缓存池中去拿了.

滚动方向的枚举定义,以及怎么获取滚动方向,如下

//滚动方向的枚举定义

typedef enum{

WaterfallsViewScrollDirectionForward,

WaterfallsViewScrollDirectionRollback

}WaterfallsViewScrollDirection;

//获得当前的滚动方向

-(WaterfallsViewScrollDirection)scrollDirection{

if (self.contentOffset.y < lastContentOffsetY) return WaterfallsViewScrollDirectionRollback;

return WaterfallsViewScrollDirectionForward;

}

//处理某一个序号的cell,从保存的cellFrames数组中获得这个序号的cell的frame,先尝试看当前cell有没有在显示在屏幕上,在displayingCells字典中能不能拿到

//如果当前的cell有在屏幕上显示,如果cell在displayingCells字典中没有拿到,问数据源方法要.然后给cell设置我们事先就算好的frame,把它加入到displayingCells字典中,

//同时加入到瀑布流视图上.

//如若不在屏幕上,并且displayingCells字典中能够取到,说明刚刚它在屏幕上显示着呢,现在要从屏幕上离开了,那就要把它从displayingCells字典中移除,同时从瀑布流视图移除,

//可以加入缓存池中

-(void)handleCellWithIndex:(NSUInteger)index{

CGRect cellFrame = [self.cellFrames[index] CGRectValue];

WaterfallsViewCell *cell = self.displayingCells[@(index)];

if ([self isOnScreen:cellFrame] == YES) {

if (cell == nil) {

cell = [self.dataSource waterfallsView:self cellAtIndex:index];

cell.frame = cellFrame;

self.displayingCells[@(index)] = cell;

[self addSubview:cell];

}

}else{

if (cell != nil) {

[self.displayingCells removeObjectForKey:@(index)];

[cell removeFromSuperview];

[self.reusableCells addObject:cell];

}

}

}

//是否在屏幕上,如果cell的y值最大处,比瀑布流视图的contentOffset.y小,说明在显示区域的上部分.如果cell的y值最小处,比瀑布流视图显示的最大y值的地方还大,说明说明在显示区域的下部分.

//这两种都不在屏幕上呢.其他情况,都是在屏幕上的

-(BOOL)isOnScreen:(CGRect)cellFrame{

if (CGRectGetMaxY(cellFrame) <= self.contentOffset.y) return NO;

if (cellFrame.origin.y >= self.contentOffset.y + self.frame.size.height) return NO;

return YES;

}

/**

*  供外界调用取可重复利用cell

*/

//外界调用,当控制器中实现数据源方法

-(WaterfallsViewCell *)waterfallsView:(WaterfallsView *)waterfallsView cellAtIndex:(NSUInteger)index;

时,先调用这个方法,从缓存池中取cell,不同结构的cell可以用不同的identifier以示区别.从缓存池中取cell,也是根据cell的identifier来取.

当缓存池中取到cell了之后,要将cell从缓存池中移除,表示这个cell已经被利用了,取不到cell,说明这个identifier标示的cell已经被用完了.外面需要自己新建cell.这个就是cell的根据标识重复利用cell的原理.

/**

*  在某一个序号的cell

*/

-(WaterfallsViewCell *)dequeueReusableCellWithIdentifier:(NSString *)identifier{

__block WaterfallsViewCell *cell = nil;

[self.reusableCells enumerateObjectsUsingBlock:^(WaterfallsViewCell *reusableCell, BOOL *stop) {

if ([reusableCell.reuseIdentifier isEqualToString:identifier]) {

cell = reusableCell;

*stop = YES;

}

}];

if (cell != nil) {

[self.reusableCells removeObject:cell];

}

//    NSLog(@"缓存池剩余的cell个数:%ld",self.reusableCells.count);

return cell;

}

//当点击了某个cell之后,需要有所响应.代理方法中拿到了点击的cell之后,即可做相应的处理.

所以这里实现了touchesBegan: withEvent:方法

//通过遍历displayingCells中的所有cell,如果触摸发生的地方正好在其中的某一个cell中,把cell的序号通过代理方法传出去,外界就知道了某个cell被点击了,自己实现相应的方法,即可做出相应的反应.

-(void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event{

UITouch *touch = [touches anyObject];

CGPoint pointInView = [touch locationInView:self];

__block NSInteger selectedIndex = -1;

[self.displayingCells enumerateKeysAndObjectsUsingBlock:^(NSNumber *index, WaterfallsViewCell *cell, BOOL * stop) {

if (CGRectContainsPoint(cell.frame, pointInView) == YES) {

selectedIndex = [index unsignedIntegerValue];

*stop = YES;

}

}];

if (selectedIndex >= 0) {

if ([self.delegate respondsToSelector:@selector(waterfallsView:didSelectCellAtIndex:)]) {

[self.delegate waterfallsView:self didSelectCellAtIndex:selectedIndex];

}

}

}

演示图片如下:

html中的瀑布流是什么-LMLPHP

源码下载地址:

https://github.com/GudTeach/WaterfallsView

05-11 19:29