标签:布局 ios autolayout 流式布局 size class
下载地址:
https://github.com/youngsoft/MyLinearLayout
众所周知,对于IOS开发者来说,随着手机屏幕的尺寸在增多,不可避免的也需要考虑适配的问题了。这个问题在IOS6以前我们可以通过autoresizingMask和frame进行组合来解决视图伸缩、旋转的适配,但是这个方案不彻底还是需要编写很多的代码来完成;而在IOS6以后推出了AutoLayout的解决方案,这个方案的实现和可操作性太过于复杂和繁琐不仅编写的代码特别多,就是在XIB上进行布局约束也很麻烦和难以管理。于是乎有大牛就对AutoLayout进行了改造和精简推出了一些简化自动布局的框架比如:
Masonry库:
https://github.com/Masonry/Masonry
CocoaUI库:
https://github.com/ideawu/CocoaUIDemo
我有幸的拜读和简单使用了一下这些库,发现确实有一些好处,可以简化视图布局的处理。但使用起来还是有一些麻烦,这些库都是对IOS的AutoLayout进行封装而已。因此本身如果不对自动布局不很了解的话也很容易绕道复杂的约束冲突中去;IOS8后因为有了4.7寸,5.5寸的屏幕后推出了sizeClass来进行布局,从这个解决方案的提出就可以看出苹果其实对AutoLayout的支持只不过是一个过渡的方案而已(本质上来说还是AutoLayout的方案过于复杂)。
那么有了这些后为什么我们还是不能满足??还要提出MyLinearLayout解决方案?
1.我们的应用还要支持到6.0以下。
2.我是一个老程序员不想学习新的布局语言。
3.AutoLayout,size Class语法过于晦涩而且不好控制,我的frame甚至无法使用了,我的动画特效也不好处理了。
4.MyLinearLayout的方法和使用逻辑简单,易用而且功能强大。
5.支持国产原创的库发展。
如果上述理由您没有一条同意的话那么可以关闭掉这篇文章了。
对于视图的布局来说,android系统的解决方案相对来说还是比较好的,对于android系统来说我们基本上不需要设置视图摆放的具体位置,因此一般也不需要设置视图的绝对位置值和绝对大小值,而是通过wrap_content,match_parent来指定视图本身的相对高度和宽度,同时通过LinearLayout, RelativeLayout,FrameLayout等布局来决定里面子视图的排列和摆放的位置,通过weight,padding,margin,grivity等属性来设置视图尺寸比例和排列对齐以及间距方面的东西, 而对于IOS来说我们以前编码时对于视图的布局总是通过setFrame来实现,用这种方法进行编程的最大的问题是我们必须在代码中写死很多位置的常量,而且还要自己进行计算高度和宽度以及边距等等,一般屏幕尺寸不同还需要对不同的尺寸进行处理,同样对于AutoLayout来说我们需要在代码里面编写大量的约束,造成的结果就是代码复杂和难以理解,对于维护来说更加是个灾难,而对于布局的微调更加是一个灾难。
基于上面的各种因素以及参考,MyLinearLayout横空出世了:https://github.com/youngsoft/MyLinearLayout
MyLinearLayout的实现充分参考了Android中的LinearLayout布局,但是却比LinearLayout更为强大,他几乎可以实现AutoLayout的所有功能甚至其不具备的功能。MyLinearLayout是一个基于流式布局的容器视图,我们只需要把子视图添加到MyLinearLayout中,并设置一些简单的约束参数那么就可以完成各种布局的要求了,而且后续中只要子视图的位置和大小进行变化都会触发容器视图里面的子视图进行重新布局,当我们在使用MyLinearLayout时里面的子视图的大小和位置也不是那么重要了(这个不是绝对的,我们还是可以指定frame的值)。
我先简单的对MyLinearLayout里面的属性和函数进行介绍,然后我们再实现一些布局的场景的代码的实现。MyLinearLayout既可以用于编码实现有可以用在XIB中使用,但是在XIB中使用时请把AutoLayout的支持去掉,因为MyLinearLayout不是基于自动布局的。
在介绍MyLinearLayout之前,我们先要对视图扩展出一些属性,这些属性只用于MyLinearLayout中。我会在后面一一介绍这些属性以及用法。
@interface UIView(LinearLayoutExtra)
//距离前面兄弟视图的距离,如果前面没有兄弟视图则是距离MyLinearLayout头部的距离
@property(nonatomic,assign)CGFloat headMargin;
//距离后面兄弟视图的距离,如果后面没有兄弟视图则是距离MyLinearLayout尾部的距离
@property(nonatomic,assign)CGFloat tailMargin;
//比重,指定自定的高度或者宽度在父视图的比重。取值为>=0 <=1,这个特性用于平均分配高宽度或者按比例分配高宽度
@property(nonatomic,assign)CGFloat weight;
//相对尺寸如果为0则视图使用绝对的高度或宽度;值的范围是0-1表示自身的高度或者宽度是linearlayout的高度和宽度的百分比,如果是垂直布局则是宽度,如果是水平布局则是高度,如果是1则表示和linearlayout是一样的高度和宽度;如果为负数则表示本视图离linearlayout的两边的边距是多少。
@property(nonatomic,assign)CGFloat matchParent;
@end
这些属性只用在MyLinearLayout中才有意义。如果我们在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.headMargin = 4; [ll addSubview:v1]; UIView *v2 = [[UIView alloc] initWithFrame:CGRectMake(40, 0, 80, 60)]; v2.backgroundColor = [UIColor greenColor]; v2.headMargin = 6; [ll addSubview:v2]; UIView *v3 = [[UIView alloc] initWithFrame:CGRectMake(30, 0, 150, 30)]; v3.backgroundColor = [UIColor blueColor]; v3.headMargin = 3; v3.tailMargin = 4; [ll addSubview:v3]; [self.view addSubview:ll];
上面的代码中实现了垂直布局的代码,在这段代码中我们发现v1,v2,v3的frame中y值部分不需要指定和计算了,都默认设置为0,而改用headMargin和tailMargin来指定视图之间的间距,这样一个好处是当某个子视图的高度变化时布局会自动重新进行子视图位置的排列,而不要手动进行调整。 同时可以发现虽然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;
代码如下:
-(void)test1:(BOOL)autoAdjustSize autoAdjustDir:(LineViewAutoAdjustDir)autoAdjustDir x:(CGFloat)x { MyLinearLayout *ll = [[MyLinearLayout alloc] initWithFrame:CGRectMake(x, 100, 100,200)]; ll.orientation = LVORIENTATION_VERT; ll.autoAdjustSize = autoAdjustSize; ll.autoAdjustDir = autoAdjustDir; ll.backgroundColor = [UIColor grayColor]; //不再需要指定y的偏移值了。 UIView *v1 = [[UIView alloc] initWithFrame:CGRectMake(10, 0, 60, 40)]; v1.backgroundColor = [UIColor redColor]; v1.headMargin = 4; [ll addSubview:v1]; UIView *v2 = [[UIView alloc] initWithFrame:CGRectMake(20, 0, 40, 60)]; v2.backgroundColor = [UIColor greenColor]; v2.headMargin = 6; [ll addSubview:v2]; UIView *v3 = [[UIView alloc] initWithFrame:CGRectMake(15, 0, 75, 30)]; v3.backgroundColor = [UIColor blueColor]; v3.headMargin = 3; v3.tailMargin = 4; [ll addSubview:v3]; [self.view addSubview:ll]; } -(void)loadView { [super loadView]; UIView *v = [[UIView alloc] initWithFrame:CGRectMake(10, 100, 10, 200)]; v.backgroundColor = [UIColor blackColor]; [self.view addSubview:v]; [self test1:NO autoAdjustDir:LVAUTOADJUSTDIR_TAIL x:60]; [self test1:YES autoAdjustDir:LVAUTOADJUSTDIR_TAIL x:60 + 100 + 20]; [self test1:YES autoAdjustDir:LVAUTOADJUSTDIR_CENTER x:60 + (100 + 20)*2]; [self test1:YES autoAdjustDir:LVAUTOADJUSTDIR_HEAD x:60 + (100 + 20)*3]; }
有时候有一些场景中,当某个或者某几个视图隐藏时,我们希望下面的视图能够自动往上移动以便填补空白,而当某个视图再次显示时下面的视图又再次往下移动,很幸运! MyLinearLayout是支持这种情况的(视图必须是MyLinearLayout的直接子视图才可以),具体例子请大家自行实验。
另外因为我们有autoAdjustSize属性,因此我们可以把线性布局放入到一个ScrollView中(这又有点像android中的scrollview的方式)。并且线性布局提供一个属性:
@property(nonatomic,assign,getter = isAdjustScrollViewContentSize)BOOL adjustScrollViewContentSize;
三、非布局方向的对齐以及布局的内部边距设定以及子视图大小的指定
上面的例子中所有子视图的frame的x都是指定的一个常量值,也就是在垂直布局中子视图的左右位置是可以自己定义的,但有时候我们希望布局里面的所有子视图的位置都是固定的,比如所有子视图左对齐,或者居中对齐,或者居右对齐。这时候我们就需要用到布局中的如下属性了:
@property(nonatomic,assign)LineViewAlign align;
这个属性用于指定布局里面非布局方向的子视图的对齐方式,也就是说当是垂直布局时则是指所有子视图的水平对齐方式,而水平布局时则是所有子视图的垂直对齐方式,这个属性的值可以如下定义:
typedefenum : NSUInteger{
LVALIGN_DEFAULT =0, //默认,不改变对齐方式
LVALIGN_LEFT =1, //用于垂直布局,左对齐
LVALIGN_RIGHT =2, //用于垂直布局,右对齐
LVALIGN_TOP =1, //用于水平布局,顶对齐
LVALIGN_BOTTOM =2, //用于水平布局,底部对齐
LVALIGN_CENTER =3, //居中对齐
}LineViewAlign;
有时候我们希望布局里面的所有子视图都跟布局保持一定的间距,这时候我们就可以用如下的属性:
@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的值已经不起作用了,这样是不是进一步的简化了编码?
-(void)test2:(LineViewAlign)align padding:(UIEdgeInsets)padding x:(CGFloat)x { MyLinearLayout *ll = [[MyLinearLayout alloc] initWithFrame:CGRectMake(x, 100, 100,200)]; ll.orientation = LVORIENTATION_VERT; ll.align = align; ll.padding = padding; ll.backgroundColor = [UIColor grayColor]; //不再需要指定x和y的偏移值了 UIView *v1 = [[UIView alloc] initWithFrame:CGRectMake(0, 0, 60, 40)]; v1.backgroundColor = [UIColor redColor]; v1.headMargin = 4; [ll addSubview:v1]; UIView *v2 = [[UIView alloc] initWithFrame:CGRectMake(0, 0, 40, 60)]; v2.backgroundColor = [UIColor greenColor]; v2.headMargin = 6; [ll addSubview:v2]; UIView *v3 = [[UIView alloc] initWithFrame:CGRectMake(0, 0,75, 30)]; v3.backgroundColor = [UIColor blueColor]; v3.headMargin = 3; v3.tailMargin = 4; [ll addSubview:v3]; [self.view addSubview:ll]; } -(void)loadView { [super loadView]; [self test2:LVALIGN_LEFT padding:UIEdgeInsetsMake(5, 5, 5, 5) x:60]; [self test2:LVALIGN_CENTER padding:UIEdgeInsetsMake(5, 5, 5, 5) x:60 + 100 + 20]; [self test2:LVALIGN_RIGHT padding:UIEdgeInsetsMake(5, 5, 5, 5) x:60 + (100 + 20)*2]; }
//相对尺寸如果为0则视图使用绝对的高度或宽度;值的范围是0-1表示自身的高度或者宽度是linearlayout的高度和宽度的百分比,如果是垂直布局则是宽度,如果是水平布局则是高度,如果是1则表示和linearlayout是一样的高度和宽度;如果为负数则表示本视图离linearlayout的两边的边距是多少。
@property(nonatomic,assign)CGFloat matchParent;
下面例子中,我们把红色子视图的宽度分别设置为跟布局一样宽,80%,以及两边边距为20的例子(需要注意的是如果有padding则是会扣除padding后的值),代码中红色子视图部分的frame的width的值将被设置为0,取而代之的是matchParent的值的设置。
-(void)test3:(CGFloat)matchParent x:(CGFloat)x { MyLinearLayout *ll = [[MyLinearLayout alloc] initWithFrame:CGRectMake(x, 100, 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.headMargin = 4; v1.matchParent = matchParent; [ll addSubview:v1]; UIView *v2 = [[UIView alloc] initWithFrame:CGRectMake(0, 0, 40, 60)]; v2.backgroundColor = [UIColor greenColor]; v2.headMargin = 6; [ll addSubview:v2]; UIView *v3 = [[UIView alloc] initWithFrame:CGRectMake(0, 0, 75, 30)]; v3.backgroundColor = [UIColor blueColor]; v3.headMargin = 3; v3.tailMargin = 4; [ll addSubview:v3]; [self.view addSubview:ll]; } -(void)loadView { [super loadView]; [self test3:1.0 x:60]; [self test3:0.8 x:60 + 100 + 20]; [self test3:-20 x:60 + (100 + 20)*2]; }
四、布局方向的子视图的对齐方式
有时候我们的布局视图的大小是固定的,在垂直布局中,垂直布局的高度是要求固定不变的,要求里面的子视图按顶部或者中间或者底部的方式布局起来,这样我们需要使用布局的一个属性:
@property(nonatomic,assign)LineViewAlign gravity;
代码为:
-(void)test4:(LineViewAlign)gravity x:(CGFloat)x { MyLinearLayout *ll = [[MyLinearLayout alloc] initWithFrame:CGRectMake(x, 100, 100,200)]; ll.orientation = LVORIENTATION_VERT; ll.gravity = gravity; ll.backgroundColor = [UIColor grayColor]; //不再需要指定y的偏移值了。 UIView *v1 = [[UIView alloc] initWithFrame:CGRectMake(0, 0, 0, 40)]; v1.backgroundColor = [UIColor redColor]; v1.headMargin = 4; v1.matchParent = 1.0; [ll addSubview:v1]; UIView *v2 = [[UIView alloc] initWithFrame:CGRectMake(0, 0, 40, 60)]; v2.backgroundColor = [UIColor greenColor]; v2.headMargin = 6; [ll addSubview:v2]; UIView *v3 = [[UIView alloc] initWithFrame:CGRectMake(0, 0, 75, 30)]; v3.backgroundColor = [UIColor blueColor]; v3.headMargin = 3; v3.tailMargin = 4; [ll addSubview:v3]; [self.view addSubview:ll]; } -(void)loadView { [super loadView]; [self test4:LVALIGN_TOP x:60]; [self test4:LVALIGN_CENTER x:60 + 100 + 20]; [self test4:LVALIGN_BOTTOM x:60 + (100 + 20)*2]; }
五、终极武器1:子视图的相对尺寸,以及布局视图的尺寸,复杂布局的处理。
在某些时候,我们知道了布局视图的高度的情况下,想平分里面所有子视图的高度,或者里面的子视图的高度我们只需要指定相对值而不需要指定绝对值,如果我们想在布局视图里面增加2个视图,其中一个视图占用布局视图的40%,而另外一个视图占用60%。因为如果支持子视图的相对高度的话,那么当布局视图进行缩放或者进行旋转时里面的子视图都会按照指定比例进行缩放,这时候我们需要用到上面视图的一个强大的扩展属性:weight了:
@property(nonatomic,assign)CGFloat weight;
这个视图的扩展属性的设置的范围是0到1表示子视图本身在布局视图中所占用的高度或者宽度的比例,下面我们来实现一个自动布局里面一个最经典的需求:
每个视图的间距都为20,分为上下2个部分,各占用50%,上面的2个视图各占用50%。
MyLinearLayout *ll = [[MyLinearLayout alloc] initWithFrame:self.view.bounds]; //保证容器和视图控制的视图的大小进行伸缩调整。 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 = 1; UIView *topLeft = UIView.new; topLeft.backgroundColor = [UIColor redColor]; topLeft.weight = 0.5; topLeft.matchParent = 1.0; [topll addSubview:topLeft]; UIView *topRight = UIView.new; topRight.backgroundColor = [UIColor greenColor]; topRight.weight = 0.5; topRight.matchParent = 1.0; topRight.headMargin = 20; [topll addSubview:topRight]; [ll addSubview:topll]; UIView *bottom = UIView.new; bottom.backgroundColor = [UIColor blueColor]; bottom.weight = 0.5; bottom.matchParent = 1.0; bottom.headMargin = 20; [ll addSubview:bottom]; [self.view addSubview: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:布局视图的高度和宽度完全由子视图来控制。
在垂直布局中,我们知道布局的高度可以由所有子视图动态调整,那么宽度是否也可以由子视图来决定呢?这是可以了!!当布局中某个子视图的宽度是确定的,我么可以选择由子视图里面最宽的那个视图来决定布局视图的宽度。 视图的一个扩展属性matchParent的概念就类似于android中的match_parent的值,而布局中的属性wrapContent则类似于android的wrap_content的值:
@property(nonatomic,assign)BOOL wrapContent;
-(void)test6 { //布局的高度和宽度不需要指定。 MyLinearLayout *ll = [[MyLinearLayout alloc] initWithFrame:CGRectMake(100, 100, 0,0)]; ll.orientation = LVORIENTATION_VERT;
ll.autoAdjustSize = YES; ll.wrapContent = YES; ll.backgroundColor = [UIColor grayColor]; //这里最宽为90 UIView *v1 = [[UIView alloc] initWithFrame:CGRectMake(0, 0, 90, 40)]; v1.backgroundColor = [UIColor redColor]; v1.headMargin = 4; [ll addSubview:v1]; UIView *v2 = [[UIView alloc] initWithFrame:CGRectMake(0, 0, 40, 60)]; v2.backgroundColor = [UIColor greenColor]; v2.headMargin = 6; [ll addSubview:v2]; UIView *v3 = [[UIView alloc] initWithFrame:CGRectMake(0, 0, 75, 30)]; v3.backgroundColor = [UIColor blueColor]; v3.headMargin = 3; v3.tailMargin = 4; [ll addSubview:v3]; [self.view addSubview:ll]; } -(void)loadView { [super loadView]; [self test6]; }
七、总结
MyLinearLayout的功能基本就介绍完成了,最后需要总结的是: MyLinearLayout可以完全胜任屏幕的旋转,各种尺寸的完美适配。各种版本操作系统的完美适配,开发简单易用而且功能强大,而且他不是基于AutoLayout的也不是基于Size Class,没有版本限制,也不需要学习新的布局知识。我们只要将MyLinearLayout和布局自己的autoresizingMask结合起来就可以开发强大的界面布局。
最后欢迎大家到我的github中去下载库和DEMO。
https://github.com/youngsoft/MyLinearLayout
IOS不用AutoLayout也能实现自动布局的类----MyLinearLayout横空出世
标签:布局 ios autolayout 流式布局 size class
原文地址:http://blog.csdn.net/yangtiang/article/details/46483999