MyLinearLayout基本功能介绍
MyLinearLayout的实现充分参考了Android中的LinearLayout布局,但是却比LinearLayout更为强大,他几乎可以实现AutoLayout的所有功能甚至其不具备的功能。MyLinearLayout是一个基于流式布局的容器视图,我们只需要把子视图添加到MyLinearLayout中,并设置一些简单的约束参数那么就可以完成各种布局的要求了,而且后续中只要子视图的位置和大小进行变化都会触发容器视图里面的子视图进行重新布局。
我先简单的对MyLinearLayout里面的属性和函数进行介绍,然后我们再实现一些布局的场景的代码的实现。MyLinearLayout既可以用于编码实现有可以用在XIB中使用,但是在XIB中使用时请把AutoLayout的支持去掉,因为MyLinearLayout不是基于自动布局的。
在介绍MyLinearLayout之前,我们先要对视图扩展出一些属性,这些属性只用于MyLinearLayout中。我会在后面一一介绍这些属性以及用法。
@interface UIView(LayoutExt)
//下面4个属性用于指定视图跟他相关视图之间的间距,如果为0则是没有间距,如果>0 <1则是相对间距,是按父视图的比例算的,比如父视图是100,而左间距是0.1则值是10。如果大于等于1则是绝对间距
//一般当使用相对间距时主要用图是子视图的宽度和高度是固定的,只是边距随父视图的大小而调整。
@property(nonatomic,assign)CGFloat topMargin;
@property(nonatomic,assign)CGFloat leftMargin;
@property(nonatomic,assign)CGFloat bottomMargin;
@property(nonatomic,assign)CGFloat rightMargin;
@property(nonatomic,assign)UIEdgeInsets margin;//上面四个边距
//用于指定边距的停靠位置,也就是在父视图中的停靠策略,如果设置为MGRAVITY_NONE则不使用停靠策略而是使用frame中x,y来定位视图的位置。
@property(nonatomic,assign)MarignGravity marginGravity;
//视图的相对尺寸,如果为0则视图使用绝对的高度或宽度。值的范围是0-1表示自身的高度或者宽度是父视图高度和宽度的百分比,如果是1则表示和父视图是一样的高度和宽度
//如果为负数则表示子视图离父视图的两边的边距。
@property(nonatomic,assign)CGFloat matchParentWidth;
@property(nonatomic,assign)CGFloat matchParentHeight;
//设定视图的高度在宽度是固定的情况下根据内容的大小而浮动,如果内容无法容纳的话则自动拉升视图的高度,如果原始高度高于内容则会缩小视图的高度。默认为NO,这个属性主要用UILabel,UITextView的多行的情况。
@property(nonatomic,assign,getter=isFlexedHeight)BOOL flexedHeight;
@end
@interface UIView(LinearLayoutExtra)
//比重,指定自定的高度或者宽度在父视图的比重。取值为>=0 <=1,这个特性用于平均分配高宽度或者按比例分配高宽度
@property(nonatomic,assign)CGFloat weight;
@end
这些属性只用在MyLinearLayout中和MyFrameLayout(框架布局,请浏览我的另外一篇文章)中才有意义。如果我们在XIB中进行布局的话我们可以在自定义数据设置界面指定这些属性。
接下来我们在介绍MyLinearLayout的定义,对于流式布局来说简单点就是从上到下或者从左到右,因此我们定义了垂直布局和水平布局两种样式。
//布局排列的方向
typedefenum : NSUInteger {
LVORIENTATION_VERT,
LVORIENTATION_HORZ,
} LineViewOrientation;
以及MyLinearLayout的属性:
@property(nonatomic,assign)LineViewOrientation orientation;
orientation = LVORIENTATION_VERT orientation = LVORIENTATION_HORZ
因为垂直布局和水平布局的实现都是一样的,下面的例子我都将以垂直布局进行举例,同时在没有特殊说明的情况下我会把MyLinearLayout的背景设置为灰色。
一、子视图间距设置以及自动调整大小的属性
要实现上面的布局需要键入下面的代码:
//默认高宽为200,200
MyLinearLayout *ll = [[MyLinearLayout alloc] initWithFrame:CGRectMake(0, 0, 200,200)];
ll.orientation = LVORIENTATION_VERT;
ll.backgroundColor = [UIColor grayColor];
//不再需要指定y的偏移值了。
UIView *v1 = [[UIView alloc] initWithFrame:CGRectMake(20, 0, 120, 40)];
v1.backgroundColor = [UIColor redColor];
v1.topMargin = 4;
[ll addSubview:v1];
UIView *v2 = [[UIView alloc] initWithFrame:CGRectMake(40, 0, 80, 60)];
v2.backgroundColor = [UIColor greenColor];
v2.topMargin = 6;
[ll addSubview:v2];
UIView *v3 = [[UIView alloc] initWithFrame:CGRectMake(30, 0, 150, 30)];
v3.backgroundColor = [UIColor blueColor];
v3.topMargin = 3;
v3.bottomMargin = 4;
[ll addSubview:v3];
[self.view addSubview:ll];
上面的代码中实现了垂直布局的代码,在这段代码中我们发现v1,v2,v3的frame中x,y值部分不需要指定和计算了,都默认设置为0,而改用topMargin和bottomMargin,leftMargin和rightMargin来指定视图之间的间距,这样一个好处是当某个子视图的高度变化时布局会自动重新进行子视图位置的排列,而不要手动进行调整。 同时可以发现虽然MyLinearLayout的高度设置为200,但实际高度确是147,这是怎么回事呢? 这是因为MyLinearLayout中有一个属性:
@property(nonatomic,assign,getter =isAutoAdjustSize)BOOL autoAdjustSize;
这个属性的默认值是YES表示布局的高度会根据里面子视图的整体高度而调整,如果所有子视图的高度大于线性布局的高度时则会扩充布局的高度,而如果小于布局的高度时则布局的高度会缩小,如果autoAdjustSize设置为NO时则布局的高度是会保持不变的,也就是不会随着子视图的整体高度而调整。既然线性布局的高度会调整,那么这个高度的调整时我们还可以指出高度调整时线性布局本身的位置是否需要移动,因此我们还可以指定另外一个属性:
@property(nonatomic,assign)LineViewAutoAdjustDir autoAdjustDir;
这个属性是指定当线性布局的高度调整时位置伸缩的方向,这个值可以有如下的值:
//调整大小时伸缩的方向
typedefenum : NSUInteger {
LVAUTOADJUSTDIR_TAIL, //头部固定尾部伸缩
LVAUTOADJUSTDIR_CENTER,//中间固定头尾伸缩
LVAUTOADJUSTDIR_HEAD, //尾部固定头部伸缩
}LineViewAutoAdjustDir;
下面的截图分别说明了autoAdjustSize为NO时,以及为YES时的autoAdjustDir的不同值的结果:
代码如下:
-(UIView*)createView:(BOOL)autoAdjustSize autoAdjustDir:(LineViewAutoAdjustDir)autoAdjustDir
{
MyLinearLayout *ll = [[MyLinearLayout alloc] initWithFrame:CGRectMake(0, 0, 100,200)];
ll.leftMargin = 10;
ll.orientation = LVORIENTATION_VERT;
ll.autoAdjustSize = autoAdjustSize;
ll.autoAdjustDir = autoAdjustDir;
ll.backgroundColor = [UIColor grayColor];
//不再需要指定y的偏移值了。
UIView *v1 = [[UIView alloc] initWithFrame:CGRectMake(0, 0, 60, 40)];
v1.backgroundColor = [UIColor redColor];
v1.topMargin = 4;
v1.leftMargin = 10;
[ll addSubview:v1];
UIView *v2 = [[UIView alloc] initWithFrame:CGRectMake(0, 0, 40, 60)];
v2.backgroundColor = [UIColor greenColor];
v2.topMargin = 6;
v2.leftMargin = 20;
[ll addSubview:v2];
UIView *v3 = [[UIView alloc] initWithFrame:CGRectMake(0, 0, 75, 30)];
v3.backgroundColor = [UIColor blueColor];
v3.topMargin = 3;
v3.bottomMargin = 4;
v3.leftMargin = 15;
[ll addSubview:v3];
return ll;
}
-(void)loadView
{
self.view = [MyFrameLayout new];
MyLinearLayout *test1ll = [MyLinearLayout new];
test1ll.orientation = LVORIENTATION_HORZ; //水平布局
test1ll.marginGravity = MGRAVITY_CENTER; //本视图在父视图中居中
test1ll.gravity = MGRAVITY_HORZ_CENTER; //本视图里面的所有子视图整体水平居中停靠
test1ll.wrapContent = YES; //本视图的高度由子视图中最高的决定。
[self.view addSubview:test1ll];
//标尺视图
UIView *v = [[UIView alloc] initWithFrame:CGRectMake(0, 0, 10, 200)];
v.backgroundColor = [UIColor blackColor];
v.leftMargin = 10;
[test1ll addSubview:v];
[test1ll addSubview:[self createView:NO autoAdjustDir:LVAUTOADJUSTDIR_TAIL]];
[test1ll addSubview:[self createView:YES autoAdjustDir:LVAUTOADJUSTDIR_TAIL]];
[test1ll addSubview:[self createView:YES autoAdjustDir:LVAUTOADJUSTDIR_CENTER]];
[test1ll addSubview:[self createView:YES autoAdjustDir:LVAUTOADJUSTDIR_HEAD]];
}
二、布局里面子视图的隐藏显示以及对UIScrollView的支持。
有时候有一些场景中,当某个或者某几个视图隐藏时,我们希望下面的视图能够自动往上移动以便填补空白,而当某个视图再次显示时下面的视图又再次往下移动,很幸运! MyLinearLayout是支持这种情况的(视图必须是MyLinearLayout的直接子视图才可以),具体例子请大家自行实验。
另外因为我们有autoAdjustSize属性,因此我们可以把线性布局放入到一个ScrollView中(这又有点像android中的scrollview的方式)。并且线性布局提供一个属性:
@property(nonatomic,assign,getter = isAdjustScrollViewContentSize)BOOL adjustScrollViewContentSize;
这个属性是指当某个线性布局放入到ScrollView中时,可以指定是否当自己的高度调整时,是否也会自动调整父视图ScrollView的contentSize值,这个属性的值默认是NO,如果设置为YES时也只有父视图是ScrollView才有效。
这段代码如下:
-(void)loadView
{
UIScrollView *scrollView = [UIScrollView new];
self.view = scrollView;
MyLinearLayout *ll = [MyLinearLayout new];
ll.backgroundColor = [UIColor grayColor];
ll.padding = UIEdgeInsetsMake(10, 10, 10, 10);
ll.matchParent; //宽度和UIScrollView保持一致
ll.adjustScrollViewContentSize = YES;
UILabel *label = [UILabel new];
label.matchParent;
label.flexed这是一段可以隐藏的字符串,点击下面的按钮就可以实现文本的显示和隐藏,同时可以支持根据文字内容动态调整高度,这需要把flexedHeight设置为YES";
[ll addSubview:label];
UIButton *btn = [[UIButton alloc] initWithFrame:CGRectMake(0, 0, 0, 60)];
btn.matchParent;
[btn setTitle:@"点击按钮显示隐藏文本" forState:UIControlStateNormal];
[btn addTarget:self action:@selector(handleLabelShow:) forControlEvents:UIControlEventTouchUpInside];
[ll addSubview:btn];
UIView *bottomView = [[UIView alloc] initWithFrame:CGRectMake(0, 0, 0, 400)];
bottomView.backgroundColor = [UIColor greenColor];
bottomView.matchParent;
[ll addSubview:bottomView];
[self.view addSubview:ll];
}
-(void)handleLabelShow:(UIButton*)sender
{
UIView *supv = sender.superview;
NSArray *arr = supv.subviews;
UILabel *lab = [arr objectAtIndex:0];
if (lab.isHidden)
lab.hidden = NO;
else
lab.hidden = YES;
}
从代码中我们可以看出当我们要隐藏和显示某个子视图时直接设置子视图隐藏和取消隐藏而不需要再编码来调整整个视图的高度,不需要编码来移动下面兄弟视图的位置,不需要编码来调整父UIScrollView的contentSize来调整高度,我们还可以看到UIScrollView下只需要添加一个布局视图,同时我们还指定了布局视图的宽度和UIScrollView是保持一致的。同时我们还看到了UILabel中使用了扩展属性flexedHeight,这个属性设置为YES时,系统会在布局时自动根据指定的宽度来调整自己的高度,从而多行显示完所有的内容,这个属性非常的强大。
三、布局内视图位置的停靠以及布局的内部边距设定以及子视图大小的指定
上面的例子中所有子视图的frame的x都是指定的一个常量值,也就是在垂直布局中子视图的左右位置是可以自己定义的,但有时候我们希望布局里面的所有子视图的位置都是固定的,比如所有子视图左对齐,或者居中对齐,或者居右对齐。这时候我们就需要用到布局中的如下属性了:
@property(nonatomic,assign)MarignGravity gravity;
这个属性用于指定布局中的所有子视图的停靠位置,一共有如下的定义:
MGRAVITY_NONE //不使用停靠而是靠frame.origin.x,y的值来进行定位
MGRAVITY_HORZ_LEFT //水平居左停靠
MGRAVITY_HORZ_CENTER // 水平居中停靠
MGRAVITY_HORZ_RIGHT //水平居右停靠
MGRAVITY_HORZ_FILL //水平填充
MGRAVITY_VERT_TOP //垂直居顶停靠
MGRAVITY_VERT_CENTER //垂直居中停靠
MGRAVITY_VERT_BOTTOM //垂直居低停靠
MGRAVITY_VERT_FILL //垂直填充
MGRAVITY_CENTER //居中
MGRAVITY_FILL //填充
这些定义中当gravity为MGRAVITY_NONE时则表明不使用停靠策略,也就是按子视图中的marginGravity属性中指定的停靠策略或者用frame的orign.x的值来决定(或者orign.y)。子视图中的marginGravity用来指定子视图自身的停靠策略,视图的取值也是MarginGravity,比如有一些场景中我们希望所有的子视图都左边对齐,那么我们可以设置布局视图的gravity的值为:MGRAVITY_HORZ_LEFT。而有时候我们又希望布局中的部分子视图左边对齐,部分右边对齐则我们不需要设置布局视图的gravity值而是分别对每个子视图的marginGravity值进行设定。
有时候我们希望布局里面的所有子视图都跟布局保持一定的间距,这时候我们就可以用如下的属性:
@property(nonatomic,assign)UIEdgeInsets padding; //用来描述里面的子视图的离自己的边距,默认上下左右都是0
//这个是上面属性的简化设置版本。
@property(nonatomic,assign)CGFloat topPadding;
@property(nonatomic,assign)CGFloat leftPadding;
@property(nonatomic,assign)CGFloat bottomPadding;
@property(nonatomic,assign)CGFloat rightPadding;
padding属性用来描述里面的所有子视图跟自己保持的边界距离。
下面代码显示了垂直布局中的左中右三种停靠方式,以及四周的边距都设置为5,看代码你会发现frame中的x的值已经不起作用了,这样是不是进一步的简化了编码?
-(UIView*)createView:(MarignGravity)gravity padding:(UIEdgeInsets)padding
{
MyLinearLayout *ll = [[MyLinearLayout alloc] initWithFrame:CGRectMake(0, 0, 100,200)];
ll.leftMargin = 10;
ll.orientation = LVORIENTATION_VERT;
ll.gravity = gravity;
ll.padding = padding;
ll.backgroundColor = [UIColor grayColor];
//不再需要指定y的偏移值了。
UIView *v1 = [[UIView alloc] initWithFrame:CGRectMake(0, 0, 60, 40)];
v1.backgroundColor = [UIColor redColor];
v1.topMargin = 4;
[ll addSubview:v1];
UIView *v2 = [[UIView alloc] initWithFrame:CGRectMake(0, 0, 40, 60)];
v2.backgroundColor = [UIColor greenColor];
v2.topMargin = 6;
[ll addSubview:v2];
UIView *v3 = [[UIView alloc] initWithFrame:CGRectMake(0, 0, 75, 30)];
v3.backgroundColor = [UIColor blueColor];
v3.topMargin = 3;
v3.bottomMargin = 4;
[ll addSubview:v3];
return ll;
}
-(void)loadView
{
self.view = [MyFrameLayout new];
MyLinearLayout *test1ll = [MyLinearLayout new];
test1ll.orientation = LVORIENTATION_HORZ; //水平布局
test1ll.marginGravity = MGRAVITY_CENTER; //本视图在父视图中居中
test1ll.gravity = MGRAVITY_HORZ_CENTER; //本视图里面的所有子视图整体水平居中停靠
test1ll.wrapContent = YES; //本视图的高度由子视图中最高的决定。
[self.view addSubview:test1ll];
[test1ll addSubview:[self createView:MGRAVITY_HORZ_LEFT padding:UIEdgeInsetsMake(5, 5, 5, 5)]];
[test1ll addSubview:[self createView:MGRAVITY_HORZ_CENTER padding:UIEdgeInsetsMake(5, 5, 5, 5)]];
[test1ll addSubview:[self createView:MGRAVITY_HORZ_RIGHT padding:UIEdgeInsetsMake(5, 5, 5, 5)]];
}
上面的视图中我们可以通过为布局视图设置gravity的值来调整里面所有子视图的左右方向的停靠位置,同样我们也可以通过设置布局视图的gravity的值来调整所有子视图上下方向的停靠位置,见下面的图片:
代码如下:
-(UIView*)createView:(MarignGravity)gravity
{
MyLinearLayout *ll = [[MyLinearLayout alloc] initWithFrame:CGRectMake(0, 0, 100,200)];
ll.leftMargin = 10;
ll.orientation = LVORIENTATION_VERT;
ll.gravity = gravity;
ll.backgroundColor = [UIColor grayColor];
UIView *v1 = [[UIView alloc] initWithFrame:CGRectMake(0, 0, 0, 40)];
v1.backgroundColor = [UIColor redColor];
v1.topMargin = 4;
v1.matchParent.0;
[ll addSubview:v1];
UIView *v2 = [[UIView alloc] initWithFrame:CGRectMake(0, 0, 40, 60)];
v2.backgroundColor = [UIColor greenColor];
v2.topMargin = 6;
[ll addSubview:v2];
UIView *v3 = [[UIView alloc] initWithFrame:CGRectMake(0, 0, 75, 30)];
v3.backgroundColor = [UIColor blueColor];
v3.topMargin = 3;
v3.bottomMargin = 4;
[ll addSubview:v3];
return ll;
}
-(void)loadView
{
self.view = [MyFrameLayout new];
MyLinearLayout *test1ll = [MyLinearLayout new];
test1ll.orientation = LVORIENTATION_HORZ; //水平布局
test1ll.marginGravity = MGRAVITY_CENTER; //本视图在父视图中居中
test1ll.gravity = MGRAVITY_HORZ_CENTER; //本视图里面的所有子视图整体水平居中停靠
test1ll.wrapContent = YES; //本视图的高度由子视图中最高的决定。
[self.view addSubview:test1ll];
[test1ll addSubview:[self createView:MGRAVITY_VERT_TOP]];
[test1ll addSubview:[self createView:MGRAVITY_VERT_CENTER]];
[test1ll addSubview:[self createView:MGRAVITY_VERT_BOTTOM]];
}
从上面两段代码中我们可以通过gravity的值来设置布局视图内的所有视图的停靠方式,但是有的时候我们不希望由布局视图来统一控制停靠的方向(对于垂直布局的话是指左右方向,对于水平布局的话是指上下方向),而是希望由子视图自生来控制停靠的位置,这时候我们就需要使用子视图的扩展属性marginGravity以及xxxMargin这几个扩展属性来进行处理了,使用子视图自己设定的停靠位置,要求布局视图的gravity的在某个方向上的停靠值未被设定,也就是说如果子视图想自己调整左右位置则布局视图的gravity的水平方向的停靠必须设置为MGRAVITY_NONE.
代码如下:
-(UIView*)createView
{
MyLinearLayout *ll = [[MyLinearLayout alloc] initWithFrame:CGRectMake(0, 0, 100,200)];
ll.leftMargin = 10;
ll.orientation = LVORIENTATION_VERT;
ll.backgroundColor = [UIColor grayColor];
//不再需要指定y的偏移值了。
UIView *v1 = [[UIView alloc] initWithFrame:CGRectMake(0, 0, 60, 40)];
v1.backgroundColor = [UIColor redColor];
v1.topMargin = 4;
v1.marginGravity = MGRAVITY_HORZ_LEFT;
[ll addSubview:v1];
UIView *v2 = [[UIView alloc] initWithFrame:CGRectMake(0, 0, 40, 60)];
v2.backgroundColor = [UIColor greenColor];
v2.topMargin = 6;
v2.marginGravity = MGRAVITY_HORZ_CENTER;
[ll addSubview:v2];
UIView *v3 = [[UIView alloc] initWithFrame:CGRectMake(0, 0, 75, 30)];
v3.backgroundColor = [UIColor blueColor];
v3.topMargin = 3;
v3.marginGravity = MGRAVITY_HORZ_RIGHT;
[ll addSubview:v3];
UIView *v4 = [[UIView alloc] initWithFrame:CGRectMake(0, 0, 75, 30)];
v4.backgroundColor = [UIColor yellowColor];
v4.topMargin = 3;
v4.bottomMargin = 4;
v4.marginGravity = MGRAVITY_HORZ_RIGHT;
v4.rightMargin = 10;
[ll addSubview:v4];
return ll;
}
-(void)loadView
{
self.view = [MyFrameLayout new];
MyLinearLayout *test1ll = [MyLinearLayout new];
test1ll.orientation = LVORIENTATION_HORZ; //水平布局
test1ll.marginGravity = MGRAVITY_CENTER; //本视图在父视图中居中
test1ll.gravity = MGRAVITY_HORZ_CENTER; //本视图里面的所有子视图整体水平居中停靠
test1ll.wrapContent = YES; //本视图的高度由子视图中最高的决定。
[self.view addSubview:test1ll];
[test1ll addSubview:[self createView]];
}
上面的子视图通过自身的marginGravity值来确定自己在父视图中的停靠位置,注意一下v3,v4都是右边停靠,但是v4的右边多出了一个rightMargin值。
在布局时,有时候子视图的宽度(如果是水平布局时则是高度)是由布局视图决定的,比如某个子视图的宽度和布局视图一样宽,或者某个子视图是布局视图宽度的80%,子视图于父视图的左右间距设置为20,这时候就需要引入子视图的一个扩展属性了(见上面的子视图的扩展属性):
//视图的相对尺寸,如果为0则视图使用绝对的高度或宽度。值的范围是0-1表示自身的高度或者宽度是父视图高度和宽度的百分比,如果是1则表示和父视图是一样的高度和宽度
//如果为负数则表示子视图离父视图的两边的边距。
@property(nonatomic,assign)CGFloat matchParentWidth;
@property(nonatomic,assign)CGFloat matchParentHeight;
这两个属性既可以用于布局里面的子视图,也可以用于布局视图本身,用于布局视图本身的情况下我们可以让布局的宽度和高度和布局的父视图保持一致。而不需要手动指定布局的宽度和高度,如果是为布局视图设置这个参数的话,请在将布局视图加入非布局视图之前设置。
下面例子中,我们把红色子视图的宽度分别设置为跟布局一样宽,80%,以及两边边距为20的例子(需要注意的是如果有padding则是会扣除padding后的值),代码中红色子视图部分的frame的width的值将被设置为0,取而代之的是matchParentWidth的设置。
代码如下:
-(UIView*)createView
{
MyLinearLayout *ll = [[MyLinearLayout alloc] initWithFrame:CGRectMake(0, 0, 100,200)];
ll.orientation = LVORIENTATION_VERT;
ll.backgroundColor = [UIColor grayColor];
UIView *v1 = [[UIView alloc] initWithFrame:CGRectMake(0, 0, 0, 40)];
v1.backgroundColor = [UIColor redColor];
v1.topMargin = 4;
v1.matchParent.0;
[ll addSubview:v1];
UIView *v2 = [[UIView alloc] initWithFrame:CGRectMake(0, 0, 0, 60)];
v2.backgroundColor = [UIColor greenColor];
v2.topMargin = 6;
v2.matchParent.8;
[ll addSubview:v2];
UIView *v3 = [[UIView alloc] initWithFrame:CGRectMake(0, 0, 0, 30)];
v3.backgroundColor = [UIColor blueColor];
v3.topMargin = 3;
v3.matchParenthttps://img.111com.net/get_pic/php/upload/image/20150708/1436322290134857.png" title="1436322290134857.png" alt="01.png"/>
-(void)loadView
{
MyLinearLayout *ll = [MyLinearLayout new];
//保证容器和视图控制的视图的大小进行伸缩调整。
ll.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight;
ll.orientation = LVORIENTATION_VERT;
ll.padding = UIEdgeInsetsMake(20, 20, 20, 20);
ll.backgroundColor = [UIColor grayColor];
MyLinearLayout *topll = MyLinearLayout.new;
topll.orientation = LVORIENTATION_HORZ;
topll.weight = 0.5;
topll.matchParent;
UIView *topLeft = UIView.new;
topLeft.backgroundColor = [UIColor redColor];
topLeft.weight = 0.5;
topLeft.matchParent.0;
[topll addSubview:topLeft];
UIView *topRight = UIView.new;
topRight.backgroundColor = [UIColor greenColor];
topRight.weight = 0.5;
topRight.matchParent.0;
topRight.leftMargin = 20;
[topll addSubview:topRight];
[ll addSubview:topll];
UIView *bottom = UIView.new;
bottom.backgroundColor = [UIColor blueColor];
bottom.weight = 0.5;
bottom.matchParent.0;
bottom.topMargin = 20;
[ll addSubview:bottom];
self.view = ll;
}
从上面的代码可以看出,其中没有使用到任何绝对的位置和大小的数字,都是相对值,为了支持复杂的布局我们使用了MyLinearLayout的嵌套的方式来解决问题。
通过为子视图的weight的指定我们可以很灵活的对布局里面的子视图的高度进行设置,一个布局中我们可以设置某些子视图的绝对高度,也可以设置另外一些子视图的weight。比如:
1.某个线性布局有3个子视图,并且顶部和底部的视图的高度都是固定的,而中间的视图则占用布局的剩余高度则可以如下设置:
v1.frame = CGRectMake(x,0,x, 30)
v2.frame = CGRectZero
v2.weight = 1.0
v3.frame = CGRectMake(x,0,x,50)
2.某个线性布局有3个子视图,顶部的视图高度固定的,而底部两个视图则按剩下的高度的4:6来进行分配则可以设置如下:
v1.frame = CGRectMake(x,0,x, 30)
v2.frame = CGRectZero
v2.weight = 0.4
v3.frame = CGRectZero
v3.weight = 0.6
五、终极武器2:布局视图的高度和宽度完全由子视图来控制。
在垂直布局中,我们知道布局的高度可以由所有子视图动态调整,那么宽度是否也可以由子视图来决定呢?这是可以了!!当布局中某个子视图的宽度是确定的,我么可以选择由子视图里面最宽的那个视图来决定布局视图的宽度。 视图的一个扩展属性matchParentXXX的概念就类似于android中的match_parent的值,而布局中的属性wrapContent则类似于android的wrap_content的值:
@property(nonatomic,assign)BOOL wrapContent;
这个属性的的意义是用于指定布局的非布局方向部分的值是否由子视图里面最大的值来决定,当是垂直布局时则布局的宽度由子视图中最宽的子视图的宽度决定,而当是水平布局时则布局的高度由子视图中最高的子视图的高度决定。这个属性默认的值是NO, wrapContent设置为YES则场景更多应用在布局里面的子视图是UILabel的时候,当label设置了文字的内容后调用sizeToFit后系统会自动计算出其宽度,从而出发重新布局。重新调整布局的高度和宽度。
-(void)loadView
{
[super loadView];
//布局视图中不需要指定宽度,而是由最大子视图决定宽度
MyLinearLayout *ll = [[MyLinearLayout alloc] initWithFrame:CGRectMake(100, 100, 0,0)];
ll.orientation = LVORIENTATION_VERT;
ll.wrapContent = YES;
ll.backgroundColor = [UIColor grayColor];
UIView *v1 = [[UIView alloc] initWithFrame:CGRectMake(0, 0, 90, 40)]; //这个子视图最宽
v1.backgroundColor = [UIColor redColor];
v1.marginGravity = MGRAVITY_HORZ_LEFT; //如果不设置这个属性则左右margin无效
v1.leftMargin = 10;
v1.rightMargin = 20; //父视图宽度会是90+10+20
v1.topMargin = 4;
[ll addSubview:v1];
UIView *v2 = [[UIView alloc] initWithFrame:CGRectMake(0, 0, 40, 60)];
v2.backgroundColor = [UIColor greenColor];
v2.topMargin = 6;
[ll addSubview:v2];
UIView *v3 = [[UIView alloc] initWithFrame:CGRectMake(0, 0, 75, 30)];
v3.backgroundColor = [UIColor blueColor];
v3.topMargin = 3;
v3.topMargin = 4;
[ll addSubview:v3];
[self.view addSubview:ll];
}
上面的视图中可以看到布局视图的宽度是第一个视图的宽度外加上leftMargin,rightMargin的总值
六、终极武器3:视图之间的间距也可以是相对值。
上面的所有布局中,我们可以让布局视图随着子视图的尺寸进行大小的调整,也可以让子视图随着布局视图的尺寸进行大小的调整,也可以使用weight进行子视图的按比例尺寸分配,也可以使用gravity进行子视图的位置和尺寸的控制。但是前面的代码中所有的间距指定的都是固定值,正是因为间距部分是固定值,因此我们还是无法好好的适配不同尺寸的屏幕,比如有时候我们希望子视图的尺寸是固定的,但是视图之间的间距是随着屏幕尺寸的大小而调整。在比如下面的登录对话框
我们要求底部版权部分固定在底部,并且有固定的底部边距,而中间的图标和账号输入框的间距之间则需要根据布局的大小的调整而进行缩放。这时候因为子视图的高度是固定的,而间距是浮动的,因此解决的方法就是我们不设置固定的间距,而是设置浮动的间距,将间距按一定比例进行指定。通过采用间距使用比例的方法我们可以很容易的实现则不同屏幕尺寸上以及横向和纵向屏幕上进行完美的适配,这样的话我们是不是不再需要size class了。 上面的子视图扩展属性中我们已经看到了四个边距值是可以设置相对边距的,当我们把边距设置为>0而小于1的话,则表明是按比例来设置间距。我们可以看看如下代码是如何实现上述功能的:
-(void)loadView
{
MyLinearLayout *ll = [MyLinearLayout new];
ll.backgroundColor = [UIColor grayColor];
ll.autoAdjustSize = NO;
ll.gravity = MGRAVITY_HORZ_CENTER;
//头像
UIImageView *imgView = [UIImageView new];
imgView.image = [UIImage imageNamed:@"user"];
imgView.backgroundColor = [UIColor whiteColor];
[imgView sizeToFit];
imgView.topMargin = 0.45;
[ll addSubview:imgView];
//输入框
UITextField *txtField = [[UITextField alloc] initWithFrame:CGRectMake(0, 0, 200, 40)];
txtField.borderStyle = UITextBorderStyleLine;
txtField.placeholder = @"请输入用户名称";
txtField.backgroundColor = [UIColor whiteColor];
txtField.topMargin = 0.1;
txtField.bottomMargin = 0.45;
[ll addSubview:txtField];
UILabel *lab = [[UILabel alloc] initWithFrame:CGRectMake(0, 0, 0, 0)];
lab.bottomMargin = 20;
lab.text = @"版权所有 XXX 公司";
[lab sizeToFit];
[ll addSubview:lab];
self.view = ll;
}
上面的代码中我们可以看到底部子视图的bottomMargin使用的是固定的间距也就是保证在底部,而头像和布局,头像和账号,以及账号和底部则采用的是相对的margin值,这样就是实现了上述的功能了,这样是不是很简单。
通过相对间距和子视图的weight属性,我们还能实现很多强大的功能,比如:
1.我们想让某个子视图跟父视图的边距始终保持在整体宽度的20%左右,那么我们只需要为子视图的leftMargin = 0.2就可以了,然后设置子视图的marginGravity = MGRAVITY_HORZ_LEFT. 就可以了。
2.我们在一个垂直布局中有3个子视图,并且要求这三个子视图的高度保持一致,同时间距也和高度保持一致,也就是平均分布三个子视图。
代码如下:
-(void)loadView
{
MyLinearLayout *ll = [MyLinearLayout new];
ll.backgroundColor = [UIColor grayColor];
ll.autoAdjustSize = NO;
ll.gravity = MGRAVITY_HORZ_CENTER;
ll.leftPadding = 10;
ll.rightPadding = 10;
UIView *v1 = [[UIView alloc] initWithFrame:CGRectMake(0, 0, 0, 100)];
v1.backgroundColor = [UIColor redColor];
v1.matchParent;
[ll addSubview:v1];
UIView *v2 = [[UIView alloc] initWithFrame:CGRectMake(0, 0, 0, 50)];
v2.backgroundColor = [UIColor greenColor];
v2.matchParent;
[ll addSubview:v2];
UIView *v3 = [[UIView alloc] initWithFrame:CGRectMake(0, 0, 0, 70)];
v3.backgroundColor = [UIColor blueColor];
v3.matchParent;
[ll addSubview:v3];
//每个视图的高度保持原始值,剩余的部分平分间距
//[ll averageMargin:YES];
//会把视图和间距都平分,即使设置了高度也无效。
[ll averageSubviews:YES];
self.view = ll;
}
三个子视图和间距的高度都是平均的。看如下函数:
-(void)averageSubviews:(BOOL)centered;
这个函数用来指定将子视图和间距平均分配,centered表示是否整体居中,也就是是否保留顶部和底部的边距。
-(void)averageMargin:(BOOL)centered;
这个函数要求每个子视图都具有固定的高度或者宽度,而是把所有剩余的间距全部平分,同样centered也表示视图是否居中。
需要注意的是上面两个函数只对之前添加的视图有效,后续添加的视图是无效的。
七、终极武器4:UITableView的替代品。
实践中我们经常使用UITableView来布局一些静态的CELL,这种方式在某些场合确实很方面,既可以重用又可以很方便的使用滚动视图的功能,但是静态CELL一个最致命的问题是,有时候我们的某个CELL的高度是要求动态变化的,而且有时候我们的CELL里面有UILabel,里面的内容假如很长的话需要换行显示,从而调整CELL的高度,于是我们就只能在:
- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath;
动态计算CELL的高度,然后又在CELL中进行各种麻烦的布局,而且这个问题AutoLayout是无法解决的。我象形很多人都会在这个问题上写很多代码。还有一个场景是我们的CELL之间是可以设置分割线的,但是有时候有些需求是我们要求顶部没有线,底部没有线等等一些奇怪的问题,于是乎我们就需要在CELL中插入背景视图并且