ios 自动换行flowLayout

android 自动换行flowLayout


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


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

然后来个图先:

ios 自动换行flowLayout_第1张图片


实现概要思路为:继承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= 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.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


如果设置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的高度变化。

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



你可能感兴趣的:(ios)