MyLayout (3158Star)相比 SDAutoLayout (5395Star), Masonry(16900Star), PureLayout (7093Star)确实是更强大好用,除了学习成长高,除了属性太多,除了命名混乱外,可以说是相当出色。
源码地址
MyLayou 共有8种布局,大致过了一遍后发现其中流式布局和浮动局的实现很简洁高效,设计思路也很有意思,虽然整体学习成本高,但还是决定深入学习并把所有示例码一遍。
由于布局类型较多,作者建议的使用优先级
浮动布局>流式布局>表格布局>线性布局>框架布局>相对布局>路径布局>栅格布局
但学习暂且从线性布局开始:
8种布局分三部分
线性布局,框架布局,相对布局
流式布局, 表格布局,浮云布局 (栅格布局)
路径布局
线性布局
-
垂直线性布局默认高度由子视图确定
即wrapContentHeight默认设置为YES
-
水平线性布局默认宽度由子视图确定
即wrapContentWidth默认设置为YES
-
默认子视图的左边都跟父视图左对齐,
而上下则依次按加入的顺序排列,
myCenterX表示子视图的水平中心点在父视图的水平中心点上的偏移,myCenterY的设置没有意义。 -
默认子视图的上边都跟父视图上对齐,
而左右则依次按加入的顺序排列。
myCenterY表示子视图的垂直中心点在父视图的垂直中心点上的偏移,myCenterX的设置没有意义
框架布局
框架布局相对比较简单,它的子控件位置尺寸都是相对于父视图的,所以适用于作为根视图产,布局一些位置固定不变的视图时候使用,。
相对布局
AutoLayout 其本身就是一个相对布局,视图间相对有依赖关系。
使用跟 Masonry使用非常相似,但功能更加丰富。
上下左右距离均是指跟父视图的边距,均分父视图可以有 Equal 数组来实现。
//v1,v2,v3平分父视图的宽度。
//因为每个子视图之间都有10的间距,因此平分时要减去这个间距值。这里的宽度通过设置等于数组来完成均分。
v1.widthSize.equalTo(@[v2.widthSize.add(-10), v3.widthSize.add(-10)]).add(-10);
//v4宽度固定,v5,v6按一定的比例来平分父视图的宽度,这里同样也是因为每个子视图之间有间距,因此都要减10
v4.widthSize.equalTo(@160); //第一个视图宽度固定
v5.widthSize.equalTo(@[v4.widthSize.add(-10), v6.widthSize.add(-10)]).add(-10);
//v7,v8,v9按照2:3:5的比例均分父视图。
v7.widthSize.equalTo(@[v8.widthSize.multiply(0.3).add(-10),v9.widthSize.multiply(0.5).add(-10)]).multiply(0.2).add(-10);
//V10,V11实现了高度均分
v10.heightSize.equalTo(@[v11.heightSize.add(-20)]).add(-10);
//注意这里最后一个偏移-20,也能达到和底部边距的效果。
v12.heightSize.equalTo(@[v13.heightSize.add(-10),v14.heightSize.add(-20)]).add(-10);
//均分三个布局的高度。
layout1.heightSize.equalTo(@[layout2.heightSize.add(-10), layout3.heightSize]).add(-10);
//通过为centerXPos等于一个数组值,表示v1和v2在父布局视图之内整体水平居中,这里的20表示v1和v2之间还有20的间隔。
v1.centerXPos.equalTo(@[v2.centerXPos.offset(20)]);
//通过为centerYPos等于一个数组值,表示v1和v2在父布局视图之内整体垂直居中,这里的20表示v1和v2之间还有20的间隔。
v1.centerYPos.equalTo(@[v2.centerYPos.offset(10)]);
表格布局
- 表格布局中myLeft,myTop,myRight,myBottom,myLeading,myTrailingr代表的含义与线性布局相同。
- 表格布局分行设置,添加子视图时先添加行
//第一行固定高度固定宽度,
[tableLayout addRow:50
colSize:70];
//第二行固定高度,均分宽度
[tableLayout addRow:40
colSize:MyLayoutSize.average];
//第三行固定高度,子视图自己决定宽度。
[tableLayout addRow:30
colSize:MyLayoutSize.wrap];
//第四行固定高度,子视图自己决定宽度。
[tableLayout addRow:30
colSize:MyLayoutSize.fill];
//第五行高度均分,表示剩余高度再均分,宽度均分,
[tableLayout addRow:MyLayoutSize.average
colSize:MyLayoutSize.average];
//第六行高度固定为30, 列数固定为4。
//这里只添加了3列,可见列宽是固定的。
//这样每列将会平分行的宽度或者高度
[tableLayout addRow:30
colCount:4];
//第七行高度由子视图决定,均分宽度
[tableLayout addRow:MyLayoutSize.wrap
colSize:MyLayoutSize.average];
流式布局
所谓流就是向某个方向依次的排列,而当到达某个设定的边界或者设定的数量时则另起一行并回到原先的起点重新开始继续按某个方向依次排列。
特点:
- 总是优先沿着一个固定的方向排列,其中沿着的方向一共有两种: 从先左到右,然后从上到下---(垂直流);或者先从上到下,然后从左到右---(横向流)。
- 当流沿着某个特定方向满足了某个特定的要求后才会进行换行重新开始排列,而这个特定的要求有两种:一种是容器空间不足以容纳要排列的内容,一种是内容到达了容器空间的某个特定方向的数量限制。
前者的一个具体的实例就是WEB页面中CSS中所定义的float布局,或者一些标签流;而后者的一个具体实例就是微信或者支付宝里面的钱包功能菜单列表
//设置好流式布局,只要设置子视图的宽高即自动折行展示
MyFlowLayout *actionLayout
= [MyFlowLayout flowLayoutWithOrientation:MyOrientation_Vert
arrangedCount:2];//列数
actionLayout.wrapContentHeight = YES;
actionLayout.myTop = 30;
//所有子视图水平填充,也就是所有子视图的宽度相等
//如果不设置这个属性则需要设置每个子控件的宽
actionLayout.gravity = MyGravity_Horz_Fill;
actionLayout.padding = UIEdgeInsetsMake(5, 5, 5, 5);
actionLayout.subviewHSpace = 5;
actionLayout.subviewVSpace = 5;
[rootLayout addSubview:actionLayout];
for (int i = 0; i< 10; i++) {
UIButton *button = [UIButton buttonWithType:UIButtonTypeSystem];
[button setTitle:title forState:UIControlStateNormal];
button.titleLabel.font = [CFTool font:14];
[button addTarget:self action:action forControlEvents:UIControlEventTouchUpInside];
button.myHeight = 30;
button.layer.borderColor = [UIColor lightGrayColor].CGColor;
button.layer.borderWidth = 0.5;
[actionLayout addSubview:button];
}
注意:
- 可以单独设置某行的对齐状态
MyLinearLayout *rootLayout = [MyLinearLayout linearLayoutWithOrientation:MyOrientation_Vert];
rootLayout.myHorzMargin = 0;
rootLayout.wrapContentHeight = YES;
//子视图之间的间距设置为10
rootLayout.subviewVSpace = 10;
//所有子视图的宽度都和自己相等,这样子视图就不再需要设置宽度了。
rootLayout.gravity = MyGravity_Horz_Fill;
[scrollView addSubview:rootLayout];
MyFlowLayout *flowLayout = [MyFlowLayout
flowLayoutWithOrientation:MyOrientation_Vert arrangedCount:2];
flowLayout.backgroundColor = [UIColor whiteColor];
//高度由子视图决定。
flowLayout.wrapContentHeight = YES;
#pragma--mark
//所有子视图整体水平居中
flowLayout.gravity = MyGravity_Horz_Center;
//每行子视图垂直居中对齐
flowLayout.arrangedGravity = MyGravity_Vert_Center;
flowLayout.padding = UIEdgeInsetsMake(20, 20, 20, 20); //四周内边距设置为20
[rootLayout addSubview:flowLayout];
//只有一行图片
UIImageView *headerImageView = [[UIImageView alloc] initWithImage:[UIImage imageNamed:@"minions1"]];
headerImageView.myTop = 20;
headerImageView.myBottom = 20;
[flowLayout addSubview:headerImageView];
//因为流式布局这里面每行两列,所以这里建立一个宽高为0的占位视图。我们可以在流式布局中通过使用占位视图来充满行的数量。
UIView *placeholderView1 = [UIView new];
[flowLayout addSubview:placeholderView1];
- 可以设置arrangedCount 为0
对一个垂直数量约束流式布局举例来说:假设布局视图的宽度是100,A子视图占据了20的固定宽度,B子视图的weight设置为0.4,C子视图的weight设置为0.6 那么:
A子视图的宽度 = 20
B子视图的宽度 = (100-20)0.4/(0.4+0.6) = 32
C子视图的宽度 = (100-20)0.6/(0.4+0.6) = 48
对一个垂直内容约束流式布局举例来说:假设布局视图的宽度是100,A子视图占据了20的固定宽度,B子视图的weight设置为0.4,C子视图的weight设置为0.6 那么:
A子视图的宽度 = 20
B子视图的宽度 = (100-20)0.4 = 32
C子视图的宽度 = (100-20-32)0.6 = 28.8
MyFlowLayout *flowLayout = [MyFlowLayout flowLayoutWithOrientation:MyOrientation_Vert
arrangedCount:0];
[rootLayout addSubview:flowLayout];
flowLayout.wrapContentHeight = YES;
flowLayout.subviewSpace = 10;
flowLayout.padding = UIEdgeInsetsMake(10, 10, 10, 10);
//第一行占据全部
UILabel *v1 = [UILabel new];
v1.text = @"100%";
v1.textAlignment = NSTextAlignmentCenter;
v1.adjustsFontSizeToFitWidth = YES;
v1.backgroundColor = [CFTool color:5];
v1.weight = 1;
v1.myHeight = 50;
[flowLayout addSubview:v1];
//第二行第一个固定,剩余的占据全部
UILabel *v2 = [UILabel new];
v2.text = @"50";
v2.textAlignment = NSTextAlignmentCenter;
v2.adjustsFontSizeToFitWidth = YES;
v2.backgroundColor = [CFTool color:6];
v2.myWidth = 50;
v2.myHeight = 50;
[flowLayout addSubview:v2];
UILabel *v3 = [UILabel new];
v3.text = @"100%";
v3.textAlignment = NSTextAlignmentCenter;
v3.adjustsFontSizeToFitWidth = YES;
v3.backgroundColor = [CFTool color:7];
v3.weight = 1;
v3.myHeight = 50;
[flowLayout addSubview:v3];
//weight
//当流式布局是垂直流式布局时这个属性用来设置子视图宽度占用父流式布局当前行的剩余宽度的比重,
//这样子视图就不需要明确的设定宽度值。
// 当流式布局是水平流式布局时这个属性用来设置子视图高度占用父流式布局当前列的剩余高度的比重,
//这样子视图就不需要明确的设定高度值。
//第三行,三个子视图均分。
UILabel *v4 = [UILabel new];
v4.text = @"1/3";
v4.textAlignment = NSTextAlignmentCenter;
v4.adjustsFontSizeToFitWidth = YES;
v4.backgroundColor = [CFTool color:5];
//因为要均分为3部分,而我们设置了水平间距subviewHSpace为10.所以我们这里要减去20。也就是减去2个间隔
//注意这里是先-20,再1/3
v4.weight = 1/3.0;
v4.widthSize.add(-20);
v4.myHeight = 50;
[flowLayout addSubview:v4];
UILabel *v5 = [UILabel new];
v5.text = @"1/2";
v5.textAlignment = NSTextAlignmentCenter;
v5.adjustsFontSizeToFitWidth = YES;
v5.backgroundColor = [CFTool color:6];
v5.weight = 1/2.0;
v5.widthSize.add(-10); //因为剩下的要均分为2部分,而我们设置了水平间距subviewHSpace为10.所以我们这里要减去10。也就是减去1个间隔。
v5.myHeight = 50;
[flowLayout addSubview:v5];
UILabel *v6 = [UILabel new];
v6.text = @"1/1";
v6.textAlignment = NSTextAlignmentCenter;
v6.adjustsFontSizeToFitWidth = YES;
v6.backgroundColor = [CFTool color:7];
v6.weight = 1/1.0; //最后一个占用剩余的所有空间。这里没有间距了,所以不需要再减。
v6.myHeight = 50;
[flowLayout addSubview:v6];
浮动布局
- 浮动布局也分垂直和水平布局两种
MyFloatLayout *actionLayout = [MyFloatLayout floatLayoutWithOrientation:MyOrientation_Vert];
actionLayout.padding = UIEdgeInsetsMake(5, 5, 5, 5);
actionLayout.subviewHSpace = 5;
actionLayout.wrapContentHeight = YES;
actionLayout.bottomBorderline = [[MyBorderline alloc] initWithColor:[UIColor blackColor]];
NSArray *actions = @[NSLocalizedString(@"flexed width, fixed space", @""),
NSLocalizedString(@"fixed width, flexed space", @"")];
for (NSInteger i = 0; i < actions.count; i++){
UIButton *button = [UIButton new];
[button setTitle:actions[i] forState:UIControlStateNormal];
[button setTitleColor:[CFTool color:7] forState:UIControlStateNormal];
[button setTitleColor:[CFTool color:2] forState:UIControlStateSelected];
button.titleLabel.font = [CFTool font:14];
button.layer.cornerRadius = 5;
button.layer.borderColor = [UIColor lightGrayColor].CGColor;
button.layer.borderWidth = 0.5;
button.myHeight = 44;
//宽度均分,这里减去2.5是因为有视图之间的间距为5
button.widthSize.equalTo(actionLayout.widthSize).multiply(1.0/actions.count).add(-2.5);
[actionLayout addSubview:button];
}
-
在常规情况下
左右浮动布局时,必须有明确的宽度,即不能用wrapContentWidth;
上下浮动布局时,必须有明确的高度,即不能用wrapContentHeight。
设置明确宽度或者高度的原因是浮动布局需要根据这些宽度或者高度的约束自动换行浮动。但在实践中浮动方向上没有尺寸约束限制,而是人为的来控制子视图的换行,并且还要布局视图的宽度和高度具有包裹属性,那就需要设置noBoundaryLimit为YES时,同时设置包裹属性。
MyFloatLayout *contentLayout = [MyFloatLayout floatLayoutWithOrientation:MyOrientation_Horz];
contentLayout.backgroundColor = [UIColor whiteColor];
contentLayout.noBoundaryLimit = YES;
//对于上下浮动布局来说,如果只想向上浮动,而高度又希望是由子视图决定,
//则必须要设置noBoundaryLimit的值为YES。
contentLayout.wrapContentHeight = YES;
contentLayout.myHorzMargin = 0;
contentLayout.padding = UIEdgeInsetsMake(5, 5, 5, 5);
contentLayout.subviewHSpace = 5;
contentLayout.subviewVSpace = 5;
[rootLayout addSubview:contentLayout];
UIImageView *headImageView = [[UIImageView alloc] initWithImage:[UIImage imageNamed:@"head1"]];
//头像部分固定尺寸
headImageView.mySize = CGSizeMake(40, 40);
[contentLayout addSubview:headImageView];
UILabel *nameLabel = [UILabel new];
nameLabel.text = @"欧阳大哥";
nameLabel.font = [CFTool font:17];
nameLabel.textColor = [CFTool color:4];
nameLabel.clearFloat = YES; //注意这里要另外起一行。
nameLabel.backgroundColor =[UIColor redColor];
nameLabel.widthSize.equalTo(contentLayout.widthSize).add(-45);
//40的头像宽度外加5的左右间距。
[nameLabel sizeToFit];
[contentLayout addSubview:nameLabel];
UILabel *nickNameLabel = [UILabel new];
nickNameLabel.text = @"醉里挑灯看键";
nickNameLabel.font = [CFTool font:15];
nickNameLabel.textColor = [CFTool color:3];
[nickNameLabel sizeToFit];
[contentLayout addSubview:nickNameLabel];
UILabel *addressLabel = [UILabel new];
addressLabel.text = @"联系地址:中华人民共和国北京市朝阳区盈科中心B座2楼,其他的我就不会再告诉你了。";
addressLabel.font = [CFTool font:15];
addressLabel.textColor = [CFTool color:4];
addressLabel.wrapContentHeight = YES;addressLabel.widthSize.equalTo(contentLayout.widthSize).add(-45); //40的头像宽度外加5的左右间距。
[addressLabel sizeToFit];
[contentLayout addSubview:addressLabel];
//浮动布局的一个缺点是居中对齐难以实现,
//所以这里需要对子视图做一些特殊处理.
//注意这里weight的使用。
MyFloatLayout *contentLayout = [MyFloatLayout floatLayoutWithOrientation:MyOrientation_Vert];
contentLayout.backgroundColor = [UIColor whiteColor];
contentLayout.wrapContentHeight = YES;
contentLayout.myHorzMargin = 0;
contentLayout.padding = UIEdgeInsetsMake(5, 5, 5, 5);
[rootLayout addSubview:contentLayout];
UIImageView *headImageView = [[UIImageView alloc] initWithImage:[UIImage imageNamed:@"minions4"]];
headImageView.contentMode = UIViewContentModeCenter;
headImageView.weight = 1; //占据全部宽度。
headImageView.heightSize.equalTo(@80);
[contentLayout addSubview:headImageView];
UILabel *nameLabel = [UILabel new];
nameLabel.text = @"欧阳大哥";
nameLabel.font = [CFTool font:17];
nameLabel.textColor = [CFTool color:4];
nameLabel.textAlignment = NSTextAlignmentCenter;
nameLabel.weight = 1;
nameLabel.myTop = 10;
[nameLabel sizeToFit];
[contentLayout addSubview:nameLabel];
UILabel *nickNameLabel = [UILabel new];
nickNameLabel.text = @"醉里挑灯看键";
nickNameLabel.font = [CFTool font:15];
nickNameLabel.textColor = [CFTool color:3];
nickNameLabel.textAlignment = NSTextAlignmentCenter;
nickNameLabel.weight = 1;
nickNameLabel.myTop = 5;
nickNameLabel.myBottom = 15;
[nickNameLabel sizeToFit];
[contentLayout addSubview:nickNameLabel];
NSArray *images = @[@"section1",@"section2", @"section3"];
NSArray *menus = @[@"Followers",@"Starred", @"Following"];
NSArray *values = @[@"140",@"5",@"0"];
//三个小图标均分宽度。
for (int i = 0; i < 3; i++)
{
UIImageView *imageView = [[UIImageView alloc] initWithImage:[UIImage imageNamed:images[i]]];
imageView.contentMode = UIViewContentModeCenter;
imageView.heightSize.equalTo(@20);
imageView.weight = 1.0/ (3 - i); //这里三个,第一个占用全部的1/3,第二个占用剩余的1/2,第三个占用剩余的1/1。这样就实现了均分宽度的效果。
[contentLayout addSubview:imageView];
}
//文本均分宽度
for (int i = 0; i < 3; i++)
{
UILabel *menuLabel = [UILabel new];
menuLabel.text = menus[i];
menuLabel.textColor = [CFTool color:2];
menuLabel.font = [CFTool font:14];
menuLabel.textAlignment = NSTextAlignmentCenter;
menuLabel.adjustsFontSizeToFitWidth = YES;
menuLabel.weight = 1.0/ (3 - i);
menuLabel.myTop = 10;
[menuLabel sizeToFit];
[contentLayout addSubview:menuLabel];
}
- 对浮动的行进行对齐处理,并且对齐是以当前行(列)内最高(宽)的子视图为参考 进行的
在垂直浮动布局里面的子视图的行内对齐只能设置MyGravity_Vert_Top, MyGravity_Vert_Center, MyGravity_Vert_Bottom, MyGravity_Vert_Fill这几种对齐方式。
在水平浮动布局里面的子视图的列内对齐只能设置MyGravity_Horz_Left, MyGravity_Horz_Center, MyGravity_Horz_Right, MyGravity_Horz_Fill这几种对齐方式。
MyFloatLayout *floatLayout = [MyFloatLayout floatLayoutWithOrientation:MyOrientation_Vert];
UIImageView *logoImageView = [[UIImageView alloc] initWithImage:[UIImage imageNamed:@"p1-12"]];
logoImageView.layer.borderColor = [CFTool color:4].CGColor;
logoImageView.layer.borderWidth = 1;
logoImageView.myAlignment = MyGravity_Vert_Center; //在浮动的一行内垂直居中对齐。
logoImageView.myMargin = 10; //四周的边距都设置为10.
logoImageView.mySize = CGSizeMake(80, 36);
[floatLayout addSubview:logoImageView];
UILabel *brandLabel = [UILabel new];
brandLabel.text = @"千奈美官方旗舰店";
[brandLabel sizeToFit];
brandLabel.myAlignment = MyGravity_Vert_Center; //在浮动的一行内垂直居中对齐。
brandLabel.myVertMargin = 10;
[floatLayout addSubview:brandLabel];
UIButton *attentionButton = [UIButton buttonWithType:UIButtonTypeSystem];
[attentionButton setTitle:@"关注" forState:UIControlStateNormal];
[attentionButton sizeToFit];
attentionButton.reverseFloat = YES; //关注放在右边,所以浮动到右边。
attentionButton.myMargin = 10;
attentionButton.myAlignment = MyGravity_Vert_Center; //在浮动的一行内垂直居中对齐。
[floatLayout addSubview:attentionButton];
网友评论