瀑布流
瀑布流
几条线下来, 没列中类似每行的尺寸都不一样, 应该自己些一个继承自scrollView的瀑布流控件
设置框架, 麻烦点不要仅, 要紧的是健壮.
模仿tableView, 做一个瀑布流控件:
* 数据源和代理的协议要有, 和tableView差不多, 就是只用看说哪个就够了,
* 自己实现的重要的说reload方法, 调用这个方法时, 会重新调用所有必须的数据源和代理方法, 根据返回的数据计算出每个cell的位置展示出来, 一次性计算好所有cell的frame, 返回多少个和多少列后, 就计算好他们的frame保存起来
* 设置私有方法, 判断数据源和代理的方法是否有返回值, 没有的话就返回一个默认值
* 类似算九宫格一样, 先算好几个相对固定的值,
* 注意: 和九宫格不一样的是, 并非说从左到右排列cell, 而是看哪个的高度比较小就往那里排列, 不然如果高度大的都排到了一起, 就很不协调了
* 用一个数组来保存每列的最大y值, 在计算应该在哪列放置cell的时候, 选取y值最小的那列: 用一个C数组来保存每列中的最大y值, 在选列的时候, 拿出数组的y值比较, 选出最小的列, 用最小的列和其最大y值来计算cell的x,y. 在放入cell后, 更新当组的最大y值, 并将算好的frame保存起来
* 最后拿到这一个位置对应的cell即遍历计算到的i, 将frame赋值过去, 并将cell添加到self(瀑布流控件)
需要用时在创建cell
之前创建出来的说一口气就创建列所有的cell, 这样性能很不好, 应该想tableView一样要用到了才去创建:
* 不要将自己的代理设置为自己, 这样占据列代理后别人就不能设置代理或者会覆盖掉自己的代理
* 需要看当前显示的屏幕区域中有没有和算出来的frames有交叠的, 交叠的frame就说要显示出来的.
* scrollView和别的控件有点不同就是在滚动的时候也会调用layoutSubviews方法, 我们可以用这个方法监听滚动
* 在滚动的时候遍历所有的frame.maxY > offset.y && frame.y < offset.y + screenH, 就说要显示的frame,拿到这些位置的, 向数据源拿到对应位置的cell, 将frame赋值给cell
* 这步完成列会有个问题, 在微滚动的时候, 会继续遍历, 继续判断到已经显示的frame符合条件, 拿cell并赋值, 数据源方法会一直调用即使是已经显示的cell, 应该判断是否已经显示出来, 显示了的就不要在向数据源拿了.
* 创建一个字典来保存已经显示的cell : i(位置) : cell. 一个位置i对应一个cell
* 拿出i对应的显示的cell, 遍历frames, 发现交叠显示的时候判断cell=nil?, 没有的正在显示的话向数据源拿, 即为新进入视野的cell, cell=!nil的话就说已经在显示的了, 不用管.也不去数据源拿.
循环利用
以上完成后, 微调不会重复创建显示在屏幕上的cell, 但是显示后就不做处理了, 导致滚完后, 有多少个cell都留在内存里, 应该把不显示的cell扔到缓存池中, 拿来循环利用.
* 创建一个NSMutableSet作为缓存池存放移出屏幕的cell,
* 遍历的时候判断出不再屏幕的时候在判断是否在显示中的cell, 不在屏幕+显示中=在上面那些添加到veiw后又不在屏幕显示中的cell, 说这种移出屏幕的cell的话, 就把它加到缓存池中, 并从view中移除, 也从显示cell字典中移除
* 提供方法给外界, 数据源决定从哪里加载cell(创建OR缓存池), 方法返回根据重用标识的cell(遍历缓存池, 找到这个标识的cell), 在遍历判断显示的时候, 要显示的依然是从数据源中拿, (数据源有可能返回的是缓存池或者新创建的)
* 至此可以在数据源返回cell的时候打印地址, 就能知道是不是循环利用
事件处理, 处理cell的点击
当点击时, 如果点到cell, 应该通知代理
* 在touched方法里监听屏幕的点击,
* 点击中, 拿到当前的点击点, 遍历当前屏幕上存在的cell(即保存显示cell的字典), 当cell.frame contain这个点时, 就返回这个cell的key即索引, 并*stop=yes;
* 在拿到触摸点前判断有没有实现这个代理方法, 没有就不用做这么多事
* 拿到索引后, 看有没有值(点中空白说没有值的), 有值才通知代理
自定义瀑布流后之简单应用
* 应该将自己写好的当作一个框架, 不满意的话就自定义一个, 不要改原来的结构, 增加自己想要的东西
* 向之前使用tableView一样使用这个框架
* 注意瀑布流需要服务器返回图片的宽高, veiw里宽说固定的, 为了比例一样, 需要算出显示出来的高度, 按照原来的宽高比
* 上蜡刷新加载更多---可以用框架, 或者把以前自己做的简单封装起来
* if (UIInterfaceOrientationIsPortrait(self.interfaceOrientation))判断当前控制器的方向
发现: 当操作到子控件时, 会调用到自身的laypoutSubViews方法, 如果在reloadData里没有操作子控件, 要手动调用, 不然reloaddata算出各frame后, 没有判断子控件的 位置来加载, 也是没有反应的.
bug: -[__NSArrayI removeFromSuperview]: unrecognized selector sent to instance 0x10a50f150’
// makeObjectsPerformSelector:@selector(removeFromSuperview) 让数组里的对象执行方法
// performSelector: 自己执行方法