最近產品需要實現自動換行功能,在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];
}
}
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的高度變化。
好,本文結束,如有說的不好的地方請多多包涵,也可在評論中指點一下。