对于IOS开发者来讲,在自动布局出现前只能经过计算和设置frame的值来处理,这样设置位置时就会出现不少硬编码,同时在屏幕旋转和不一样屏幕之间适配时须要编码从新调整位置和尺寸,咱们也能够重载视图的layoutSubviews的函数来写代码从新布局。自动布局出现后确实在必定程度上解决了位置和尺寸硬编码的问题,可是经过代码来写自动布局很是的复杂和麻烦,并且代码量会增长不少。在自动布局领域android系统经过提供FrameLayout, LinearLayout, RelativeLayout, AbsoluteLayout等几个类来分别处理各类不一样的布局需求,经过wrap_content,match_parent来自动计算尺寸。android
android系统的FrameLayout类用于进行上下左右居中填充方式的布局,而LinearLayout则是用于进行水平和垂直方向的流式布局,AbsoluteLayout则是硬编码方式的绝对布局。在我前面的2篇文章中分别介绍了MyFrameLayout, MyLinearLayout两种方式的布局,而这章我将继续介绍相对布局MyRelativeLayout.git
所谓相对布局就是指某个视图的位置和尺寸不是固定写死的而是依赖于其余关联的视图,好比一个视图在另一个视图的左边,或者在另一个视图的右下方,或者一个视图的宽度和另一个视图宽度是相等的,或者视图是在父视图的顶部偏移必定的量,或者某一组视图的宽度要平分父视图等等功能。所以咱们分别为子视图定义了以下的扩展属性:github
分别来定义视图的左,上,右,下,水平中心,垂直中心,宽度,高度8个方位和尺寸的相对依赖对象,其中MyRelativePos用来定义相对的依赖位置类。它的定义以下:
@interface MyRelativePos :NSObject
//偏移
-(MyRelativePos* (^)(CGFloat val))offset;
//NSNumber, MyRelativePos对象,若是是centerXPos或者centerYPos则能够传NSArray,数组里面里面也必须是centerXPos,表示指定的视图数组
//在父视图中居中,好比: A.centerXPos.equalTo(@[B.centerXPos.offset(20)].offset(20)
//表示A和B在父视图中居中往下偏移20,B在A的右边,间隔20。
-(MyRelativePos* (^)(id val))equalTo;
@end
这个类中的offset用来指定某个位置的偏移值,而equalTo则用来指定依赖的某个位置和值,好比:
1.A视图的左边等于B视图的右边并偏移30个点: A.leftPos.equalTo(B.rightPos).offset(30)
2.A视图的顶部和父视图的顶部相等:A.topPos.equalTo(A.superView.topPos)
3.A视图的中间在父视图的中间: A.centerXPos.equalTo(A.superView.centerXPos); A.centerYPos.equalTo(A.superView.centerYPos)
4.A视图的左边偏移20: A.topPos.equalTo(@0).offset(20)
5.A,B,C三个视图要总体在布局视图中水平居中:A.centerXPos.equalTo(@[B.centerXPos,C.centerXPos])
而对于相对尺寸则定义了MyRelativeDime类来定义宽度和高度,这个类的定义以下:
@interface MyRelativeDime :NSObject
//乘
-(MyRelativeDime* (^)(CGFloat val))multiply;
//加,用这个和equalTo的数组功能能够实现均分子视图宽度以及间隔的设定。
-(MyRelativeDime* (^)(CGFloat val))add;
//NSNumber, MyRelativeDime以及MyRelativeDime数组,数组的概念就是全部数组里面的子视图的尺寸平分父视图的尺寸。
-(MyRelativeDime* (^)(id val))equalTo;
@end
尺寸定义中equalTo用来指定尺寸的值,能够是NSNumber型指定绝对的尺寸,MyRelativeDime型指定相对的尺寸,NSArray[MyRelativeDime]来指定按比例分配父视图的尺寸,mutiply用来指定在equalTo上指定的值的倍数比例,add指定在equalTo上指定的值的增量值(add方法更可能是用来指定间距)。
1.A视图的宽度等于B视图的宽度,A视图的高度等于B视图的高度的一半: A.widthDime.equalTo(B.widthDime); A.heightDime.equalTo(B.heightDime).multiply(0.5)
2.A视图的高度等于B视图的高度,并增长20: A.heightDime.equalTo(B.heightDime).add(20)
3.A,B,C三个视图平分父视图的宽度: A.widthDime.equalTo(@[B.widthDime, C.widthDime])
4.A视图固定宽度为20, B,C视图按4:6平分剩余的宽度: A.widthDime.equal(@20) B.widthDime.equalTo(@[A.widthDime, C.widthDime.multiply(0.6)]).multiply(0.4)
好了介绍了上述扩展的子视图的扩展属性后,咱们须要的只是创建一个MyRelativeLayout布局视图,而后设置好子视图之间的相对依赖,而后添加进去就OK了。
1、相对布局的子视图的依赖。
上述的布局就是用相对布局实现,代码很简单,请参考以下:
- -(void)loadView
- {
-
- MyRelativeLayout *rl = [MyRelativeLayout new];
- rl.padding = UIEdgeInsetsMake(10, 10, 10, 10);
- rl.backgroundColor = [UIColor grayColor];
- self.view = rl;
-
- UILabel *lb1 = [UILabel new];
- [rl addSubview:lb1];
- lb1.text = @"你好";
- [lb1 sizeToFit];
- lb1.backgroundColor = [UIColor blueColor];
-
-
- lb1.leftPos.equalTo(rl.leftPos);
- lb1.topPos.equalTo(rl.topPos).offset(10);
- lb1.widthDime.equalTo(@60);
-
- UILabel *lb2 = [UILabel new];
- [rl addSubview:lb2];
- lb2.text = @"我好 hello";
- lb2.backgroundColor = [UIColor redColor];
-
- lb2.leftPos.equalTo(lb1.rightPos);
- lb2.topPos.equalTo(lb1.bottomPos);
- lb2.widthDime.equalTo(lb1.widthDime).add(30);
- lb2.heightDime.equalTo(lb1.heightDime).multiply(2).add(-10);
-
- UILabel *lb3 = [UILabel new];
- lb3.text = @"中间";
- lb3.backgroundColor = [UIColor greenColor];
- [rl addSubview:lb3];
-
- lb3.centerXPos.equalTo(rl.centerXPos);
- lb3.centerYPos.equalTo(rl.centerYPos);
- lb3.widthDime.equalTo(rl.widthDime).multiply(0.2);
- lb3.heightDime.equalTo(rl.heightDime).multiply(0.1);
-
- UILabel *lb4 = [UILabel new];
- lb4.text = @"他好";
- [lb4 sizeToFit];
- lb4.backgroundColor = [UIColor orangeColor];
- [rl addSubview:lb4];
-
-
- lb4.leftPos.equalTo(rl.leftPos);
- lb4.rightPos.equalTo(rl.rightPos);
- lb4.topPos.equalTo(@100);
-
- }
这段代码中请注意lb4的设置,咱们会发现当咱们同时指定了左右和上下的依赖视图时,宽度和高度就不须要指定出来,布局会自动算出高度和宽度。注意代码中咱们还为相对布局指定了padding的值,表示里面的全部子视图都会在padding以内。
2、相对布局的子视图的平均分配
有时候咱们的布局的有些子视图但愿能按父视图的尺寸来进行按某些规则进行分配,好比下面的布局:
上面的视图中,第一排的3个子视图平分父视图的宽度。第二排子视图中第一个视图宽度固定,剩余的两个平分。第三排的子视图按0.2 0.3 0.5的比例来平分父视图,代码以下:
- -(void)loadView
- {
- MyRelativeLayout *rl = [MyRelativeLayout new];
- rl.padding = UIEdgeInsetsMake(0, 0, 0, 10);
- rl.backgroundColor = [UIColor grayColor];
- self.view = rl;
-
-
- UIView *v1 = [UIView new];
- v1.backgroundColor = [UIColor redColor];
- v1.heightDime.equalTo(@40);
- [rl addSubview:v1];
-
- UIView *v2 = [UIView new];
- v2.backgroundColor = [UIColor redColor];
- v2.heightDime.equalTo(@40);
- [rl addSubview:v2];
-
- UIView *v3 = [UIView new];
- v3.backgroundColor = [UIColor redColor];
- v3.heightDime.equalTo(@40);
- [rl addSubview:v3];
-
-
- v1.widthDime.equalTo(@[v2.widthDime.add(-10), v3.widthDime.add(-10)]).add(-10);
- v1.leftPos.offset(10);
- v2.leftPos.equalTo(v1.rightPos).offset(10);
- v3.leftPos.equalTo(v2.rightPos).offset(10);
-
-
-
- UIView *v4 = [UIView new];
- v4.backgroundColor = [UIColor greenColor];
- v4.topPos.equalTo(v1.bottomPos).offset(80);
- v4.heightDime.equalTo(@40);
- v4.widthDime.equalTo(@260);
- [rl addSubview:v4];
-
- UIView *v5 = [UIView new];
- v5.backgroundColor = [UIColor greenColor];
- v5.topPos.equalTo(v4.topPos);
- v5.heightDime.equalTo(@40);
- [rl addSubview:v5];
-
- UIView *v6 = [UIView new];
- v6.backgroundColor = [UIColor greenColor];
- v6.topPos.equalTo(v4.topPos);
- v6.heightDime.equalTo(@40);
- [rl addSubview:v6];
-
-
- v5.widthDime.equalTo(@[v4.widthDime.add(-10), v6.widthDime.add(-10)]).add(-10);
- v4.leftPos.offset(10);
- v5.leftPos.equalTo(v4.rightPos).offset(10);
- v6.leftPos.equalTo(v5.rightPos).offset(10);
-
-
-
-
- UIView *v7 = [UIView new];
- v7.backgroundColor = [UIColor blueColor];
- v7.topPos.equalTo(v4.bottomPos).offset(80);
- v7.heightDime.equalTo(@40);
- [rl addSubview:v7];
-
- UIView *v8 = [UIView new];
- v8.backgroundColor = [UIColor blueColor];
- v8.topPos.equalTo(v7.topPos);
- v8.heightDime.equalTo(@40);
- [rl addSubview:v8];
-
- UIView *v9 = [UIView new];
- v9.backgroundColor = [UIColor blueColor];
- v9.topPos.equalTo(v7.topPos);
- v9.heightDime.equalTo(@40);
- [rl addSubview:v9];
-
- v7.widthDime.equalTo(@[v8.widthDime.multiply(0.3).add(-10),v9.widthDime.multiply(0.5).add(-10)]).multiply(0.2).add(-10);
- v7.leftPos.offset(10);
- v8.leftPos.equalTo(v7.rightPos).offset(10);
- v9.leftPos.equalTo(v8.rightPos).offset(10);
-
-
-
- }
看代码咱们发现,在分配视图时指定了视图之间的间距这须要借助offset的调用来指定间距,由于是均分视图咱们又须要为视图的宽度留有间隔,所以咱们须要借助add的方法来将计算出的宽度减去间距的值,而同时咱们为布局视图的padding的值,咱们设置了10的间距来控制最右边的间距为10。
有的时候咱们在均分子视图时,当某个子视图隐藏时其余的剩余的子视图的宽度会进行调整,好比某个子视图设置为隐藏后,右边的子视图向左边靠拢。而有的时候咱们但愿当某个子视图隐藏时,剩余的部分从新填充慢布局视图的某个方位的尺寸,所以咱们能够为布局视图设置开关:
//均分宽度时当有隐藏子视图,是否参与宽度计算,这个属性只有在参与均分视图的子视图隐藏时才有效,默认是NO
@property(nonatomic,assign)BOOL flexOtherViewWidthWhenSubviewHidden;
//均分高度时当有隐藏子视图,是否参与高度计算,这个属性只有在参与均分视图的子视图隐藏时才有效,默认是NO
@property(nonatomic,assign)BOOL flexOtherViewHeightWhenSubviewHidden;
这两个布局视图的属性分别标明当某个子视图数组均分父视图时,而其中某个子视图隐藏时,是否其余视图会从新分配宽度和高度。
3、相对布局的高宽由子视图决定
我在线性布局的文章中有说明能够经过wrapContent来决定是否布局视图的非方向是否由子视图来决定。这时候咱们就不须要手动的指定布局视图的高度和宽度,而是由布局视图里面的子视图来决定布局的尺寸,在android系统中咱们能够设置wrapContent来设置布局视图的尺寸。一样咱们在布局中也分别提供了两个属性:
@property(nonatomic,assign)BOOL wrapContentWidth;
@property(nonatomic,assign)BOOL wrapContentHeight;
从上面的定义能够看出,wrapContentWidth, wrapContentHeight则是指定布局视图的宽度和高度由子视图决定,对于线性布局来讲若是是垂直方向的话wrapContentHeight是默认设置为YES的,而水平方向则wrapContentWidth设置为YES。而对于相对布局来讲二者默认都设置为NO。
咱们先看结果界面:
- -(void)loadView
- {
- [super loadView];
-
- MyRelativeLayout *rl = [[MyRelativeLayout alloc] initWithFrame:CGRectMake(10, 10, 0, 0)];
- rl.padding = UIEdgeInsetsMake(10, 10, 10, 10);
- [self.view addSubview:rl];
- rl.wrapContentWidth = YES;
- rl.wrapContentHeight = YES;
- rl.backgroundColor = [UIColor grayColor];
-
- UILabel *lb1 = [UILabel new];
- lb1.leftPos.equalTo(rl.leftPos).offset(20);
- lb1.text = @"aaaa";
- lb1.backgroundColor = [UIColor redColor];
- [lb1 sizeToFit];
- lb1.rightPos.offset(20);
-
- [rl addSubview:lb1];
-
-
- UILabel *lb3 = [UILabel new];
- lb3.rightPos.equalTo(rl.rightPos).offset(5);
- lb3.topPos.equalTo(rl.topPos).offset(30);
- lb3.bottomPos.offset(10);
- lb3.text = @"ccc";
- lb3.backgroundColor = [UIColor redColor];
- [lb3 sizeToFit];
-
- [rl addSubview:lb3];
-
-
- UILabel *lb2 = [UILabel new];
- lb2.text = @"bbbb";
- lb2.backgroundColor = [UIColor blueColor];
-
- lb2.leftPos.equalTo(lb1.centerXPos);
- lb2.topPos.equalTo(lb1.bottomPos).offset(40);
- lb2.widthDime.equalTo(@50);
- lb2.heightDime.equalTo(@50);
- lb2.bottomPos.offset(40);
-
-
- [rl addSubview:lb2];
-
-
-
- }
上面的代码中咱们能够看到布局视图是没有指定高度和宽度的,而是设置了属性wrapContentWidth = YES, wrapContentHeight = YES
那么布局视图的高度和宽度是怎么计算出来的呢。咱们是经过计算出全部子视图的位置和尺寸的最大高度和宽度来获得布局视图的高度和宽度的,在上面的代码中咱们看到lb3的右边和布局视图的右边相差5,可是布局视图这时候的宽度是没有计算出来的,可是咱们仍是能够这样设置,由于lb1, lb2的尺寸和高度已经把布局视图撑开到足够的高度和宽度了。
同时经过wrapContentXXX的布局视图的属性咱们能够动态的调整布局视图自己的高度和宽度,所以咱们也很适合将布局视图放入到一个UIScrollView中去。
4、一组视图在布局视图中居中
有时候咱们但愿布局中的某些视图总体居中,一个解决的方法是咱们为这些视图创建一个父视图,而后让这个父视图居中,但这个前提是咱们须要新创建一个父视图来包围这批视图。采用相对布局的方法是不须要再新建一个附加的父视图的。咱们先看界面。
界面中咱们看到上面的3个视图总体是在父视图的水平中间的,而下面3个则是在父视图的垂直中间的,这个功能的代码实现很简单:
- -(void)loadView
- {
-
- MyRelativeLayout *rl = [MyRelativeLayout new];
- rl.backgroundColor = [UIColor grayColor];
- self.view = rl;
-
-
- UILabel *lb1 = [UILabel new];
- lb1.text = @"abcdefg";
- [lb1 sizeToFit];
- lb1.backgroundColor = [UIColor redColor];
- lb1.topPos.offset(100);
- [rl addSubview:lb1];
-
- UILabel *lb2 = [UILabel new];
- lb2.text = @"abcdefgfd";
- [lb2 sizeToFit];
- lb2.backgroundColor = [UIColor blueColor];
- lb2.topPos.offset(100);
- [rl addSubview:lb2];
-
-
- UILabel *lb3 = [UILabel new];
- lb3.text = @"abc";
- [lb3 sizeToFit];
- lb3.backgroundColor = [UIColor greenColor];
- lb3.topPos.offset(100);
- [rl addSubview:lb3];
-
-
-
-
- lb1.centerXPos.equalTo(@[lb2.centerXPos.offset(5), lb3.centerXPos.offset(10)]);
-
-
- UILabel *lb4 = [UILabel new];
- lb4.text = @"你好";
- [lb4 sizeToFit];
- lb4.backgroundColor = [UIColor orangeColor];
- [rl addSubview:lb4];
- lb4.leftPos.equalTo(lb1.leftPos);
- lb4.topPos.equalTo(lb2.bottomPos).offset(10);
-
-
-
- UILabel *lb5 = [UILabel new];
- lb5.text = @"abcdefg";
- [lb5 sizeToFit];
- lb5.backgroundColor = [UIColor redColor];
- lb5.centerXPos.equalTo(rl.centerXPos);
- [rl addSubview:lb5];
-
- UILabel *lb6 = [UILabel new];
- lb6.text = @"abcdefgfd";
- [lb6 sizeToFit];
- lb6.backgroundColor = [UIColor blueColor];
- lb6.centerXPos.equalTo(rl.centerXPos);
- [rl addSubview:lb6];
-
-
- UILabel *lb7 = [UILabel new];
- lb7.text = @"abc";
- [lb7 sizeToFit];
- lb7.backgroundColor = [UIColor greenColor];
- lb7.centerXPos.equalTo(rl.centerXPos);
- [rl addSubview:lb7];
-
- lb5.centerYPos.equalTo(@[lb6.centerYPos.offset(5), lb7.centerYPos.offset(10)]);
-
-
- }
代码中咱们能够看到lb1,lb2,lb3是水平居中的,而lb5,lb6,lb7则是垂直居中的。咱们只须要分别为lb1.centerXPos,lb5.centerYPos指定一个关联的居中的数组就能够了,数组的内容就是其余关联的视图的对应的centerXPos或者centerYPos。同时咱们能够指定offset来代表视图之间的偏移的距离。
代码以下:
- -(void)loadView
- {
- MyRelativeLayout *rl = [MyRelativeLayout new];
- self.view = rl;
-
- UILabel *lb1up = [UILabel new];
- lb1up.text = @"左上面";
- lb1up.backgroundColor = [UIColor greenColor];
- lb1up.font = [UIFont systemFontOfSize:17];
- [lb1up sizeToFit];
- [rl addSubview:lb1up];
-
- UILabel *lb1down = [UILabel new];
- lb1down.text = @"我左在下面";
- lb1down.backgroundColor = [UIColor greenColor];
- [lb1down sizeToFit];
- [rl addSubview:lb1down];
-
-
- UILabel *lb2up = [UILabel new];
- lb2up.text = @"我在中间上面";
- lb2up.backgroundColor = [UIColor greenColor];
- lb2up.font = [UIFont systemFontOfSize:12];
- [lb2up sizeToFit];
- [rl addSubview:lb2up];
-
- UILabel *lb2down = [UILabel new];
- lb2down.text = @"中";
- lb2down.backgroundColor = [UIColor greenColor];
- [lb2down sizeToFit];
- [rl addSubview:lb2down];
-
-
- UILabel *lb3up = [UILabel new];
- lb3up.text = @"右上";
- lb3up.backgroundColor = [UIColor greenColor];
- [lb3up sizeToFit];
- [rl addSubview:lb3up];
-
- UILabel *lb3down = [UILabel new];
- lb3down.text = @"右边的下方";
- lb3down.backgroundColor = [UIColor greenColor];
- lb3down.font = [UIFont systemFontOfSize:16];
- [lb3down sizeToFit];
- [rl addSubview:lb3down];
-
-
- lb1up.centerYPos.equalTo(@[lb1down.centerYPos.offset(10)]);
- lb2up.centerYPos.equalTo(@[lb2down.centerYPos.offset(10)]);
- lb3up.centerYPos.equalTo(@[lb3down.centerYPos.offset(10)]);
-
-
- lb1up.centerXPos.equalTo(@[lb2up.centerXPos.offset(60),lb3up.centerXPos.offset(60)]);
-
-
- lb1down.centerXPos.equalTo(lb1up.centerXPos);
- lb2down.centerXPos.equalTo(lb2up.centerXPos);
- lb3down.centerXPos.equalTo(lb3up.centerXPos);
-
-
-
-
- }
经过代码咱们能够看出来虽然是有上下两排视图,可是咱们能够经过centerYPos和centerXPos的值设置数组的方式来实现一组视图的居中显示。
5、总结
好了,相对视图的介绍就布局到这里了,到这里我分别为你介绍了框架布局,线性布局和相对布局方面的东西,经过这三个布局的使用咱们彻底能够摆脱对IOS的自动布局和sizeClass的使用,而用更加简单清晰的方法来布局您的界面,但愿个人库能对您提供很是有利的帮助,若是您须要用个人库来编码,那么就请到: