欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

ios 自动换行flowLayout

程序员文章站 2022-05-31 18:04:41
...

android 自动换行flowLayout


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


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

然后来个图先:

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的高度变化。

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