一:瀑布流框架封装的实现思路:此瀑布流框架的封装仿照tableView的底层实现,1:每个cell的frame的设置都是找出每列的最大y值,比较每列的最大y值,将下一个cell放在最大y值最小的那一列,并更新最大y值,继续比较设置frame。2:还涉及了类似于tableView缓存池的处理  瀑布流效果如图:

二:封装代码:

1:瀑布流控件的view封装:

 //  使用瀑布流形式展示内容的控件

 #import <UIKit/UIKit.h>

 typedef enum {
HMWaterflowViewMarginTypeTop,
HMWaterflowViewMarginTypeBottom,
HMWaterflowViewMarginTypeLeft,
HMWaterflowViewMarginTypeRight,
HMWaterflowViewMarginTypeColumn, // 每一列
HMWaterflowViewMarginTypeRow, // 每一行
} HMWaterflowViewMarginType; @class HMWaterflowView, HMWaterflowViewCell; /**
* 数据源方法
*/
@protocol HMWaterflowViewDataSource <NSObject>
@required
/**
* 一共有多少个数据
*/
- (NSUInteger)numberOfCellsInWaterflowView:(HMWaterflowView *)waterflowView;
/**
* 返回index位置对应的cell
*/
- (HMWaterflowViewCell *)waterflowView:(HMWaterflowView *)waterflowView cellAtIndex:(NSUInteger)index; @optional
/**
* 一共有多少列
*/
- (NSUInteger)numberOfColumnsInWaterflowView:(HMWaterflowView *)waterflowView;
@end /**
* 代理方法
*/
@protocol HMWaterflowViewDelegate <UIScrollViewDelegate>
@optional
/**
* 第index位置cell对应的高度
*/
- (CGFloat)waterflowView:(HMWaterflowView *)waterflowView heightAtIndex:(NSUInteger)index;
/**
* 选中第index位置的cell
*/
- (void)waterflowView:(HMWaterflowView *)waterflowView didSelectAtIndex:(NSUInteger)index;
/**
* 返回间距
*/
- (CGFloat)waterflowView:(HMWaterflowView *)waterflowView marginForType:(HMWaterflowViewMarginType)type;
@end /**
* 瀑布流控件
*/
@interface HMWaterflowView : UIScrollView
/**
* 数据源
*/
@property (nonatomic, weak) id<HMWaterflowViewDataSource> dataSource;
/**
* 代理
*/
@property (nonatomic, weak) id<HMWaterflowViewDelegate> delegate; /**
* 刷新数据(只要调用这个方法,会重新向数据源和代理发送请求,请求数据)
*/
- (void)reloadData; /**
* cell的宽度
*/
- (CGFloat)cellWidth; /**
* 根据标识去缓存池查找可循环利用的cell
*/
- (id)dequeueReusableCellWithIdentifier:(NSString *)identifier;
@end

思路分析:1:仿照tableView来设置dataSource 和 delegate 两个代理:waterFlow的dataSource代理方法中仿照tableView的dataSource代理方法,@required 必须实现的是,1:一共有多少个cell,- (NSUInteger)numberOfCellsInWaterflowView:(HMWaterflowView *)waterflowView ,2:根据cell的index返回每一个cell,- (HMWaterflowViewCell *)waterflowView:(HMWaterflowView *)waterflowView cellAtIndex:(NSUInteger)index;其中HMWaterflowViewCell就为每一个单元的cell,自定义HMWaterflowViewCell继承于UIView 3:@optional 方法为,一共返回多少列,如果没有实现此方法则返回默认的列数 2:在waterFlow的delegate方法中,都为@optional,并仿照tableView提供,1:根据index返回每个cell的行高  - (CGFloat)waterflowView:(HMWaterflowView *)waterflowView heightAtIndex:(NSUInteger)index;2:点击index处的cell的方法:- (void)waterflowView:(HMWaterflowView *)waterflowView didSelectAtIndex:(NSUInteger)index; 3:返回整个瀑布流的间距值,若不返回,则会有默认值,上下左右,行间距,列间距,根据上下左右行列不同的类型返回不同的高度,将这几个不同的类型定义成枚举值,将枚举传过去,外界可利用switch根据不同类型,返回不同的间距(类似于按钮回调的枚举tag)

2:以属性设置两个代理:设置delegate时,会有警告,因为瀑布流控件继承的是UIScroll,则继承了UIScroll在.h声明的属性和方法,此delegate会覆盖掉scrollView的滚动视图的代理,所以会有警告,解决办法是,让瀑布流控件的代理协议再遵守UISCrollViewDelegate,则在外部只要遵守了瀑布流的代理,也就遵守了UIScroll的滚动代理,所以以属性设置瀑布流代理后,外部遵守协议,设置代理,则其就可以调用UIscroll的滚动代理方法了

3:仿照tableView并提供刷新表格,和缓存池根据重用标识取cell的方法,- (id)dequeueReusableCellWithIdentifier:(NSString *)identifier;其中id 和 instanceType的区别:1:id 修饰非关联返回类型 ,可做参数,也可以做返回值 而instanceType为关联返回类型,返回该方法所在类的类型,并且只能做返回值,一般以alloc。autorelease,retain开头的方法用id 2:用instanceType相对id来说,能帮助编译器快速确定返回值类型。

2:瀑布流的内部核心封装代码:

 #import "HMWaterflowView.h"
#import "HMWaterflowViewCell.h" #define HMWaterflowViewDefaultCellH 70
#define HMWaterflowViewDefaultMargin 8
#define HMWaterflowViewDefaultNumberOfColumns 3 @interface HMWaterflowView()
/**
* 所有cell的frame数据
*/
@property (nonatomic, strong) NSMutableArray *cellFrames;
/**
* 正在展示的cell
*/
@property (nonatomic, strong) NSMutableDictionary *displayingCells;
/**
* 缓存池(用Set,存放离开屏幕的cell)
*/
@property (nonatomic, strong) NSMutableSet *reusableCells;
@end @implementation HMWaterflowView #pragma mark - 初始化
- (NSMutableArray *)cellFrames
{
if (_cellFrames == nil) {
self.cellFrames = [NSMutableArray array];
}
return _cellFrames;
} - (NSMutableDictionary *)displayingCells
{
if (_displayingCells == nil) {
self.displayingCells = [NSMutableDictionary dictionary];
}
return _displayingCells;
} - (NSMutableSet *)reusableCells
{
if (_reusableCells == nil) {
self.reusableCells = [NSMutableSet set];
}
return _reusableCells;
} - (id)initWithFrame:(CGRect)frame
{
self = [super initWithFrame:frame];
if (self) { }
return self;
} - (void)willMoveToSuperview:(UIView *)newSuperview
{
[self reloadData];
} #pragma mark - 公共接口
/**
* cell的宽度
*/
- (CGFloat)cellWidth
{
// 总列数
int numberOfColumns = [self numberOfColumns];
CGFloat leftM = [self marginForType:HMWaterflowViewMarginTypeLeft];
CGFloat rightM = [self marginForType:HMWaterflowViewMarginTypeRight];
CGFloat columnM = [self marginForType:HMWaterflowViewMarginTypeColumn];
return (self.bounds.size.width - leftM - rightM - (numberOfColumns - ) * columnM) / numberOfColumns;
} /**
* 刷新数据
*/
- (void)reloadData
{
// 清空之前的所有数据
// 移除正在正在显示cell
[self.displayingCells.allValues makeObjectsPerformSelector:@selector(removeFromSuperview)];
[self.displayingCells removeAllObjects];
[self.cellFrames removeAllObjects];
[self.reusableCells removeAllObjects]; // cell的总数
int numberOfCells = [self.dataSource numberOfCellsInWaterflowView:self]; // 总列数
int numberOfColumns = [self numberOfColumns]; // 间距
CGFloat topM = [self marginForType:HMWaterflowViewMarginTypeTop];
CGFloat bottomM = [self marginForType:HMWaterflowViewMarginTypeBottom];
CGFloat leftM = [self marginForType:HMWaterflowViewMarginTypeLeft];
CGFloat columnM = [self marginForType:HMWaterflowViewMarginTypeColumn];
CGFloat rowM = [self marginForType:HMWaterflowViewMarginTypeRow]; // cell的宽度
CGFloat cellW = [self cellWidth]; // 用一个C语言数组存放所有列的最大Y值
CGFloat maxYOfColumns[numberOfColumns];
for (int i = ; i<numberOfColumns; i++) {
maxYOfColumns[i] = 0.0;
} // 计算所有cell的frame
for (int i = ; i<numberOfCells; i++) {
// cell处在第几列(最短的一列)
NSUInteger cellColumn = ;
// cell所处那列的最大Y值(最短那一列的最大Y值)
CGFloat maxYOfCellColumn = maxYOfColumns[cellColumn];
// 求出最短的一列
for (int j = ; j<numberOfColumns; j++) {
if (maxYOfColumns[j] < maxYOfCellColumn) {
cellColumn = j;
maxYOfCellColumn = maxYOfColumns[j];
}
} // 询问代理i位置的高度
CGFloat cellH = [self heightAtIndex:i]; // cell的位置
CGFloat cellX = leftM + cellColumn * (cellW + columnM);
CGFloat cellY = ;
if (maxYOfCellColumn == 0.0) { // 首行
cellY = topM;
} else {
cellY = maxYOfCellColumn + rowM;
} // 添加frame到数组中
CGRect cellFrame = CGRectMake(cellX, cellY, cellW, cellH);
[self.cellFrames addObject:[NSValue valueWithCGRect:cellFrame]]; // 更新最短那一列的最大Y值
maxYOfColumns[cellColumn] = CGRectGetMaxY(cellFrame);
} // 设置contentSize
CGFloat contentH = maxYOfColumns[];
for (int j = ; j<numberOfColumns; j++) {
if (maxYOfColumns[j] > contentH) {
contentH = maxYOfColumns[j];
}
}
contentH += bottomM;
self.contentSize = CGSizeMake(, contentH);
} /**
* 当UIScrollView滚动的时候也会调用这个方法
*/
- (void)layoutSubviews
{
[super layoutSubviews]; // 向数据源索要对应位置的cell
NSUInteger numberOfCells = self.cellFrames.count;
for (int i = ; i<numberOfCells; i++) {
// 取出i位置的frame
CGRect cellFrame = [self.cellFrames[i] CGRectValue]; // 优先从字典中取出i位置的cell
HMWaterflowViewCell *cell = self.displayingCells[@(i)]; // 判断i位置对应的frame在不在屏幕上(能否看见)
if ([self isInScreen:cellFrame]) {// 在屏幕上
if (cell == nil) {
cell = [self.dataSource waterflowView:self cellAtIndex:i];
cell.frame = cellFrame;
[self addSubview:cell]; // 存放到字典中
self.displayingCells[@(i)] = cell;
}
} else { // 不在屏幕上
if (cell) {
// 从scrollView和字典中移除
[cell removeFromSuperview];
[self.displayingCells removeObjectForKey:@(i)]; // 存放进缓存池
[self.reusableCells addObject:cell];
}
}
}
} - (id)dequeueReusableCellWithIdentifier:(NSString *)identifier
{
__block HMWaterflowViewCell *reusableCell = nil; [self.reusableCells enumerateObjectsUsingBlock:^(HMWaterflowViewCell *cell, BOOL *stop) {
if ([cell.identifier isEqualToString:identifier]) {
reusableCell = cell;
*stop = YES;
}
}]; if (reusableCell) { // 从缓存池中移除
[self.reusableCells removeObject:reusableCell];
}
return reusableCell;
} #pragma mark - 私有方法
/**
* 判断一个frame有无显示在屏幕上
*/
- (BOOL)isInScreen:(CGRect)frame
{
return (CGRectGetMaxY(frame) > self.contentOffset.y) &&
(CGRectGetMinY(frame) < self.contentOffset.y + self.bounds.size.height);
} /**
* 间距
*/
- (CGFloat)marginForType:(HMWaterflowViewMarginType)type
{
if ([self.delegate respondsToSelector:@selector(waterflowView:marginForType:)]) {
return [self.delegate waterflowView:self marginForType:type];
} else {
return HMWaterflowViewDefaultMargin;
}
}
/**
* 总列数
*/
- (NSUInteger)numberOfColumns
{
if ([self.dataSource respondsToSelector:@selector(numberOfColumnsInWaterflowView:)]) {
return [self.dataSource numberOfColumnsInWaterflowView:self];
} else {
return HMWaterflowViewDefaultNumberOfColumns;
}
}
/**
* index位置对应的高度
*/
- (CGFloat)heightAtIndex:(NSUInteger)index
{
if ([self.delegate respondsToSelector:@selector(waterflowView:heightAtIndex:)]) {
return [self.delegate waterflowView:self heightAtIndex:index];
} else {
return HMWaterflowViewDefaultCellH;
}
} #pragma mark - 事件处理
- (void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event
{
if (![self.delegate respondsToSelector:@selector(waterflowView:didSelectAtIndex:)]) return; // 获得触摸点
UITouch *touch = [touches anyObject];
// CGPoint point = [touch locationInView:touch.view];
CGPoint point = [touch locationInView:self]; __block NSNumber *selectIndex = nil;
[self.displayingCells enumerateKeysAndObjectsUsingBlock:^(id key, HMWaterflowViewCell *cell, BOOL *stop) {
if (CGRectContainsPoint(cell.frame, point)) {
selectIndex = key;
*stop = YES;
}
}]; if (selectIndex) {
[self.delegate waterflowView:self didSelectAtIndex:selectIndex.unsignedIntegerValue];
}
} @end

思路分析:1:瀑布流的核心代码的封装也是仿照tableView的底层实现:在tableView第一次加载的时候会首先调用一次刷新数据的方法,在下列方法中实现:

- (void)willMoveToSuperview:(UIView *)newSuperview{[self reloadData];},此方法willMoveToSuperview,是子view即将添加到superView时会调用一次。调用[self reloadData],模拟tableView第一次刷新表格 2:基本实现思路是,在reloadData方法中设置每一个cell的frame,存放到大数组中,再在layoutSubView里设置每一个view的frame。

2:在reloadData方法中,1:每执行一次reloadData方法,都要将原来的数据清空,来展示新的cell。其中self.displayingCells为正在显示的cell的缓存字典,key值为每一个index,value为每一个index对应的正在显示的cell(dic.allKeys,dic.allValues,分别获得字典的所有的key值和value值得到一个数组,enum遍历字典的时候,分别可得到字典的key值和value值)。2:在清空数据时,缓存字典,让字典中每一个正在显示的cell从父视图上移除,并清空缓存字典,缓存池,还有装有frame的数组,其中数组里有一个方法是遍历每个元素让每个元素都去执行某个方法[self.displayingCells.allValues makeObjectsPerformSelector:@selector(removeFromSuperview)]; 2:在reloadData中最主要的就是要求出每个cell的xy宽高,其中高度需要询问代理,若实现了代理方法,则根据服务器返回图片的宽高,按比例计算出相应的高度并返回。要想设置每个cell的frame,调用数据源方法,返回总的cell的个数,for循环遍历每个cell,因为设置frame,要从最大y值最小的那列开始设置,所以在for循环遍历每一个cell的时候,首先应找出最小y值的那一列,和最小y值。定义数组存放每一列的最大的y值,因为其为基本数据类型,所以用C语言数组存放,OC数组只能存放对象。先调用代理方法返回列数,若没有实现代理方法,则默认返回的而是3列,定义C语言的数组,定义数组元素个数为numberOfColumns列数的数组,且数组中元素的基本类型都为CGFloat类型。并对数组中的元素初始化,遍历数组,取出每个元素,赋值为0.0,float类型

CGFloat maxYOfColumns[numberOfColumns];

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

maxYOfColumns[i] = 0.0;

}

3:找出最小y值所在的列号,并记录此列下最大的y值。先假设最小的y值的列号是第0列,则最小y值列号下的最大的y值就为从C语言数组中根据列号取出的该列的最大y值,再for循环遍历C语言数组,从列号为1开始遍历,做判断,从列号为1开始从数组中取出的最大y值还比定义的最大y值还小,则此时记录下最小的y值的列号,和最小y值列号下的最大y值。直到找到最小y值的列号,此时,设置frame就从最小y值的列号处开始设置。

4:找到最小y值所在的列号后,开始设置cell的frame,宽度 = 屏幕宽度 - 左右间距 - (总列数 -1)*列间距,都可以求出,屏幕的高度,调用代理根据index返回该index处的cell的高度,(瀑布流图片的宽高都为后台服务器返回),若实现了代理高度的方法,则返回高度时,应该根据服务器返回图片高度的比例计算出。若没实现代理方法则返回默认的高度。 询问代理i位置的高度 CGFloat cellH = [self heightAtIndex:i];X值的计算:计算x值和cell所在的列号有关, CGFloat cellX = leftM + cellColumn * (cellW + columnM);Y值计算:和是否是首行有关,若是在首行,则y值为顶部间距,若不在首行,则y值为该列的最大y值加上一个行间距的值,是否是首行的判断,则判断C语言数组中每一列对应的最大y值,若为0,则在首行,若不为0,则不在首行,此时可以设置cell的frame

5:将设置好的cell的frame(结构体类型,非对象)封装成OC对象,存放到数组中。 CGRect cellFrame = CGRectMake(cellX, cellY, cellW, cellH); [self.cellFrames addObject:[NSValue valueWithCGRect:cellFrame]];

6:还要更新C语言数组中设置frame的所在列的最大y值,以便下次循环计算:maxYOfColumns[cellColumn] = CGRectGetMaxY(cellFrame);

7:在设置完所有的frame后,要根据C语言数组中最大的y值,计算scrollView的contentSize,否则scrollView将不会滚动。如何找出最大的y值?先定义第0列为最大y值,将最大的y值从数组中取出,在for循环遍历C语言数组,j从1开始,将数组中的每一列最大y值取出比较,若比这个最大的还大,则记录下来,最后计算contentSize为最大y值加一个底部间距

CGFloat contentH = maxYOfColumns[0];

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

if (maxYOfColumns[j] > contentH) {

contentH = maxYOfColumns[j];

}

}

contentH += bottomM;

self.contentSize = CGSizeMake(0, contentH);

3:layoutSubView中设置每一个cell的frame:1:遍历存放cell所有frame的数组,取出每一个frame,CGRect cellFrame = [self.cellFrames[i] CGRectValue];首先先判断是否在屏幕上,如果不在屏幕上,则将其cell从父视图上移除,并且从缓存字典中移除,并加入到缓存池,NSMutableSet 所定义的集合中,同数组的用法是相同,存放对象,但数组是有序的,NSMutableSet是无序的。如果在屏幕上显示,则证明已经计算好frame,则优先从缓存字典中根据索引取出cell。判断取出的cell是否存在,不存在,则调用数据源方法,从缓存池中取或是自行创建,返回一个cell,设置frame,添加到scrollView上。并将新创建的cell存入缓存字典中。2:如何判断是否在屏幕上,传递frame,根据frame判断是否在屏幕上

- (BOOL)isInScreen:(CGRect)frame

{

return (CGRectGetMaxY(frame) > self.contentOffset.y) &&

(CGRectGetMinY(frame) < self.contentOffset.y + self.bounds.size.height);

}

4:根据重用标识从缓存池中取数据:遍历集合,得到每一个cell,做条件过滤,判断如果cell的重用标识符与传入的标识符相等,则赋值cell,并停止遍历,在block内部若是想赋值外部变量,则用__block修饰外部的变量,最后再判断cell如果存在从缓存池中删除,若是不删除重用的cell,则其不到重用的作用,内存会被撑爆.

5:cell的点击事件:1:若是想实现点击事件:1:继承UIControl的addTarget  ,touch或是valueChanged 2;添加手势监听器,要开启用户交互权限(如lable,iamgeView)3:实现touchBeagn,touchEnd,touchCancle方法,其中的touchBeagn刚开始点击的时候调用,touchEnd结束点击的时候调用。2:cell的点击采取实现touch方法,实现的是touchEnd,点击结束时的方法。并实现UITouch的方法。3:首先做条件过滤,若没有实现该代理方法,直接返回,若实现了代理方法,先获得触摸点, UITouch *touch = [touches anyObject]; CGPoint point = [touch locationInView:self];得到触摸点,若是第二个参数为cell则触摸点的位置是相对cell来计算的,若是为self,则是针对整个瀑布流控件的frame来计算的。其中touch.view获得的是触摸的view,也就是cell 。目的是求出index,调用代理将index传过去,所以遍历缓存字典,得到key,value值,key为index,value为cell,再判断所得触摸点在不在cell上,调用CGRectContainsPoint(cell.frame, point),如果在,则赋值index,停止遍历,再外部判断index值是否存在,存在则调用代理执行代理的点击方法。@5或是@(3)两个均为对象的表示形式,都为NSNumber类型。

7:自定义cell的封装:

 #import <UIKit/UIKit.h>

 @interface HMWaterflowViewCell : UIView
@property (nonatomic, copy) NSString *identifier;
@end
 #import "HMWaterflowViewCell.h"

 @implementation HMWaterflowViewCell

 - (id)initWithFrame:(CGRect)frame
{
self = [super initWithFrame:frame];
if (self) {
// Initialization code
}
return self;
} /*
// Only override drawRect: if you perform custom drawing.
// An empty implementation adversely affects performance during animation.
- (void)drawRect:(CGRect)rect
{
// Drawing code
}
*/ @end

ios开发瀑布流框架的封装的更多相关文章

  1. ios开发瀑布流框架的应用

    一:瀑布流框架的应用:将封装好的瀑布流框架导入,遵守协议 二:代码: #import "HMShopsViewController.h" #import "HMShopC ...

  2. iOS开发瀑布流的实现

    //自定义布局,继承于UICollectionViewLayout #import <UIKit/UIKit.h> @class WaterFlowLayout; @protocol  W ...

  3. iOS 开发之照片框架详解(2)

    一. 概况 本文接着 iOS 开发之照片框架详解,侧重介绍在前文中简单介绍过的 PhotoKit 及其与 ALAssetLibrary 的差异,以及如何基于 PhotoKit 与 AlAssetLib ...

  4. iOS 开发之照片框架详解之二 —— PhotoKit 详解(下)

    本文链接:http://kayosite.com/ios-development-and-detail-of-photo-framework-part-three.html 这里接着前文<iOS ...

  5. iOS 开发之照片框架详解

    转载自:http://kayosite.com/ios-development-and-detail-of-photo-framework.html 一. 概要 在 iOS 设备中,照片和视频是相当重 ...

  6. iOS 开发之照片框架详解之二 —— PhotoKit 详解(上)

    转载自:http://kayosite.com/ios-development-and-detail-of-photo-framework-part-two.html 一. 概况 本文接着 iOS 开 ...

  7. iOS横向瀑布流的封装

    前段时间, 做一个羡慕, 需要使用到瀑布流! 说道瀑布流, 或许大家都不陌生, 瀑布流的实现也有很多种! 从scrollView 到 tableView 书写的瀑布流, 然后再到2012年iOS6 苹 ...

  8. ReactiveCocoa - iOS开发的新框架

    本文转载至 http://www.infoq.com/cn/articles/reactivecocoa-ios-new-develop-framework ReactiveCocoa(其简称为RAC ...

  9. iOS 开发之照片框架详解(3)

    http://kayosite.com/ios-development-and-detail-of-photo-framework-part-three.html 三. 常用方法的封装 虽然 Phot ...

随机推荐

  1. Unix下后门查找{上}

    本文出自 "李晨光原创技术博客" 博客,请务必保留此出处http://chenguang.blog.51cto.com/350944/683699

  2. php 生成文件txt到指定目录

    // 向文件追加写入内容 $site = PHP_EOL.date("Y-m-d H:i:s")."world"; //PHP_EOL换行 // 使用 FILE ...

  3. Loadrunner经典测试实例

    Loadrunner经典测试实例

  4. C#如何调用非托管的C++Dll

    现在在Windows下的应用程序开发,VS.Net占据了绝大多数的份额.因此很多以前搞VC++开发的人都转向用更强大的VS.Net.在这种情况下,有很多开发人员就面临了如何在C#中使用C++开发好的类 ...

  5. 巧用数据流让 Word 文档在线阅读

            常常写博客或空间日记的朋友,对网络编辑器(如图1,是CSDN的博客编辑器)并不陌生.也比較easy做出非常绚烂的排版.但这次在做一个BS的项目,客户一直在用Office的软件中的Wor ...

  6. [Javascript AST] 1. Continue: Write a simple Babel plugin

    We want to write a Babel Plugin, which move 'const versionRegex = /(/d+)\.(/d+)\.(/d+)/gi' out of fu ...

  7. 删除online日志測试及ora-600 [4194]错误的处理

    今天做了一个关于破坏online日志的恢复測试,主要三个场景: 測试1:正常关闭数据库后删除非当前日志 測试2:正常关库后.删除在线日志文件 測试3:非正常关闭数据库.并删除当前在线日志文件 我的測试 ...

  8. html中的瀑布流是什么

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

  9. WebSocket兼容到低版本浏览器

    就目前而言,WebSocket是最好的Web通信解决方案了.但是IE从10才开始兼容它,对于目前大量IE8存在的市场,原生的WebSocket显然不太实用,我们需要低版本兼容的解决方案.于是我模拟We ...

  10. node版本升级后,原有项目打不开

    node版本升级后,原有项目出现以下问题 gulp[8272]: src\node_contextify.cc:628: Assertion `args[1]->IsString()' fail ...