ios 自动换行flowLayout

来源:互联网 发布:python 自定义函数 编辑:程序博客网 时间:2024/05/16 04:35

android 自动换行flowLayout


最近产品需要实现自动换行功能,在gitHub看了一下,虽然有不少,但都有那么一点不满足需求的,或者感觉用着不方便的。所以干脆自己写了一份,顺便在有时间时写了一份ios的版本,有兴趣想看android版的的可点击上面链接。


在这里先提供下载地址:https://github.com/lanqi-x/ios_FlowLayout

然后来个图先:



实现概要思路为:继承UIView,复写-(instancetype)initWithFrame:(CGRect)frame(代码创建)、-(instancetype)initWithCoder:(NSCoder *)aDecoder(xib或storyboard)、-(void)addSubview:(UIView *)view和- (void)layoutSubviews。

好,要开始吧啦吧啦了。ios版相对android版来说会比较简单,但相对的坑也比较多,一些get、set方法在此就不说了,代码注释还蛮多的,直接讲addView和layoutSubviews来个方法就好了。

1、首先addview

-(void)addSubview:(UIView *)view{    [self.subViewList addObject:view];    [super addSubview:view];}
从代码就可以理解了,这个很简单,复写只为将view保存到我的数组之中,方便维护。

2、layoutSubviews

layoutSubviews比较复杂,首先oc也没有像java那样方便的内部类(普通类也可以,只是这里使用内部类在一些方法和属性上的调用会方便一些),所以我将每行的view和高度分别放在了两个数组之中。然后ios的View没有像android的view一样有测量方法,于是我把测量和子view的摆放全丢在layoutSubviews这个方法上了,所以我将分为计算和摆放两步来解释下我的实现思路。

(1)计算每行View的个数和每行的高度

这个也不难,就是对在addview存起来的view进行一次for循环,用sumWidth加入view以后所占的高度,即sumWidth+=view的宽度+view之间的间隔。情况一、sumWidth不大于flowLayout的宽,则将view添加到lineList中,比较该view的高度是否大于当前行高度height,如果大于则该行的高度等于view的高度。

情况二、sumWidth大于flowLayout的宽,那么将lineList加到rowList中记为一行,将height加到rowHeightList中记为该行的高度,height等于当前view的高度作为新一行的高度,重新new lineList,将view加到其中作为新的一行。

情况三、view为最后一个,那么不管够不够一行都记为一行,将lineList加到rowList中

关键代码如下:

if (self.subViewList.count>0) {        //清空所有值,重新计算        [self.rowHightList removeAllObjects];        [self.rowList removeAllObjects];                //获取第一个view        NSMutableArray *lineList=[NSMutableArray array];        UIView* firstView=self.subViewList[0];        CGFloat sumWidth=self.horizontalSpace+firstView.frame.size.width;        CGFloat height=firstView.frame.size.height;        CGFloat flowWidth=self.frame.size.width;         [lineList addObject:firstView];                //计算每行放哪些View        for (int i=1; i<self.subViewList.count; i++) {            UIView* view=self.subViewList[i];            sumWidth=sumWidth+view.frame.size.width+self.horizontalSpace;                   if (sumWidth >= flowWidth) {                                //一行放不下了,记录为一行                [self saveLine:lineList lineHeight:height];                                //重新创建一行                lineList=[NSMutableArray array];                [lineList addObject:view];                height=view.frame.size.height;                sumWidth=self.horizontalSpace+view.frame.size.width;                            }else{                //一行还够放                [lineList addObject:view];                if (view.frame.size.height>height) {                    height=view.frame.size.height;                }            }            //如果这是最后一个view了,不管够不够一行,都记为一行            if (i==self.subViewList.count-1) {                [self saveLine:lineList lineHeight:height];            }        }


(2)摆放view

ios的办法不需要像android那样调用view的layout方法,而是改变view的x,y值。这里使用rowList进行双重for循环,这里关键的有五点

(1)每行的左上角坐标及起始y值,这里为行间距加上一行的高度(知道为什么上面计算那里要存每行的高度了吧)。

(2)每个view的x坐标,这里为view之间的间距加上上个view的宽度。

(3)view在该行居中显示,如果view的高度小于行的高度(这里再次用的行高度了),那么该view的y为y+=(lineHeight-view.width)/2(这里只是伪代码哈)

(4)判读是否超过FlowLayout的限定高度或最大行数,如果是,那么剩下的view都不显示(由于ios不像android,android有viewGroup,viewGroup不摆放子view时,子view是直接不显示的,而ios则用原始的frame值进行摆放,所以我这里为了不让他显示在界面上,直接将这些位置超过限制的子view进行了hidden)

(5)记录显示的最后一个view在subViewList的下标是多少。(用于当FlowLayout的高度设置为根据内容时重新计算FlowLayout的最终高度)

 

       //摆放subView        CGFloat y=self.verticalSpace;        //一行一行取出        for (int i=0;i<self.rowList.count;i++) {            lineList=self.rowList[i];            CGFloat x=self.horizontalSpace;            //获取这一行的高度            CGFloat rowHeight=[self.rowHightList[i] floatValue];                        if (i!=0) {                y=y+[self.rowHightList[i-1] floatValue]+self.verticalSpace;            }                        if ((self.fastenHeight && y+[self.rowHightList[i] floatValue]>=self.frame.size.height)                || i>=self.maxLine) {                [self dontShowLine:i];                break;            }                        //摆放每一行的subView            for (UIView *item in lineList){                _lastShowIndex++;                [self setX:x changeView:item];                item.hidden=false;                if (item.frame.size.height<rowHeight) {                    [self setY:y+((rowHeight-item.frame.size.height)/2.0) changeView:item];                }else{                    [self setY:y changeView:item];                }                x=x+item.frame.size.width+self.horizontalSpace;            }                    }


如果设置FlowLayout高度是根据内容决定的,那么重新调整一下FlowLayout的最终高度,首先获取显示的最后一个view的底部Y值(CGRectGetMaxY(lastItem.frame))加上行距作为FlowLayout最终的高度,这里做个两个处理一是将frame的height改为最终的高,二是如果FlowLayout存在NSLayoutAttributeHeight约束,那么就将其移除,添加新的NSLayoutAttributeHeight约束值为FlowLayout最终的高度。

       if(!self.fastenHeight && self.lastShowIndex!=-1){            UIView *lastItem = self.subViewList[self.lastShowIndex];            CGRect rect=self.frame;            //计算flowLayout最终的高度            rect.size.height=CGRectGetMaxY(lastItem.frame) + self.verticalSpace;            self.frame = rect;////            修改约束,保证兄弟或父子控件的约束更新            NSArray* constrains = self.constraints;            for (NSLayoutConstraint* constraint in constrains) {                if (constraint.firstAttribute == NSLayoutAttributeHeight) {                    [self removeConstraint:constraint];                    [self addConstraint:[NSLayoutConstraint constraintWithItem:self attribute:NSLayoutAttributeHeight relatedBy:NSLayoutRelationEqual toItem:nil attribute:NSLayoutAttributeHeight multiplier:1.0 constant:rect.size.height]];                }            }          }


FlowLayout我的实现思路就是这样了,相比android版,这里还剩每行最后剩余空间的分配模式还没实现,以后有时间再补上。最后再提个小坑,就是最终修改FlowLayout的高度那里,如果你的高不是通过NSLayoutAttributeHeight确定,而是通过NSLayoutAttributeTop加NSLayoutAttributeBottom确定的,那么这里就不会更新了,那么如果FlowLayout下方有其他view,界面上就有可能会出现两个view覆盖的情况,因为个人觉得如果需求是想其高度根据内容决定的,那么一般应该是设置NSLayoutAttributeHeight约束的,所以这里不做处理。如果想处理,可以在FlowLayout中定义一个协议,在最后将计算得到的最终高度传给这个协议,提供给外部实时获取到FlowLayout的高度变化。

好,本文结束,如有说的不好的地方请多多包涵,也可在评论中指点一下。



原创粉丝点击