zoukankan      html  css  js  c++  java
  • html中的瀑布流是什么

    html中的瀑布流是什么

    一、总结

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

    2、看效果图

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

    二、瀑布流应用场景

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

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


    享受这些好处,就得考虑若干条前置性条件:
    • 浏览行为缺乏特别明确的目的性,以“逛逛”“扫街”的心态为主
    • 对复杂的信息索引无依赖性
    • 用户以图片为首要检索对象,瀑布流页面的配文只是相当次要的辅助信息
    • 竖图比例较高
    • 图片平均质量较高
    • 图片的风格气质趋于相似(也是Pinterest始终采取邀请制的原因)
    单从以上六点来看,你们不喜欢瀑布流是对的,因为你们根本就不是这些网站的使用者。

    三、html中的瀑布流是什么

    -.什么是瀑布流?

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

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

    上面的数字,就是每个单元格的序号,可以看到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 (nonatomicassignid<WaterfallsViewDataSource> dataSource;

     

    @property (nonatomicassignid<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,copyreadonlyNSString *reuseIdentifier;

     

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

     

    @end

     

    //cell的定义与实现

    //  WaterfallsViewCell.h

    //  Copyright © 2015 penglang. All rights reserved.

    //

     #import <UIKit/UIKit.h>

     @interface WaterfallsViewCell : UIView

     @property (nonatomic,copyreadonlyNSString *reuseIdentifier;

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

     @end

    //  Copyright © 2015 penglang. All rights reserved.

    //

    #import "WaterfallsViewCell.h"

     //static NSUInteger count = 0;

    @interface WaterfallsViewCell ()

     @property (nonatomic,copyreadwriteNSString *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 (nonatomicweakWaterfallsView *waterfallsView;

     

    @end

     

    @implementation ViewController

     

    - (void)viewDidLoad {

        [super viewDidLoad];

        

        //初始化瀑布流

        WaterfallsView *waterfallsView = [[WaterfallsView allocinitWithFrame: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 allocinitWithReuseIdentifier:ID];

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

            

            UILabel *label = [[UILabel allocinitWithFrame:CGRectMake(101010020)];

            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(@"点击了第%lucell",(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 < lastContentOffsetYreturn 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.yreturn NO;

        if (cellFrame.origin.y >= self.contentOffset.y + self.frame.size.heightreturn 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];

            }

        }

    }

    演示图片如下:

    源码下载地址:

    https://github.com/GudTeach/WaterfallsView

  • 相关阅读:
    python函数--isalpha()方法
    python函数--isdigit()方法
    python函数--isalnum()方法
    python函数--range()方法
    python函数--len()方法
    python函数--介绍
    Linux命令总结--awk命令
    Linux命令总结--pwd命令
    Linux命令总结--rm命令
    Linux命令总结--cp命令
  • 原文地址:https://www.cnblogs.com/Renyi-Fan/p/9003572.html
Copyright © 2011-2022 走看看