布局体系的概览
在我前面的几篇文章中分别介绍了视图从一个方向依次排列的线性布局(MyLinearLayout)、视图层叠且停靠于父布局视图某个位置的框架布局(MyFrameLayout)、视图之间通过约束和依赖实现布局的相对布局(MyRelativeLayout)、以及多列多行排列的表格布局(MyTableLayout)、以及将要介绍的流式布局(MyFlowLayout)这5种布局体系。这些视图布局的方式都有一些统一的特征,都要求必须将子视图放入到一个特殊的视图中去,我们称这些特殊的视图为布局视图(Layout View)。这些布局视图都有一个共同的基类:基础布局视图(MyBaseLayout)。同时我们还为视图建立了很多扩展的属性来进行位置和尺寸的设置,以及我们还专门建立了服务某些布局视图的视图扩展属性。在这些扩展属性中:用于定位视图位置的类是MyLayoutPos类,这个类可以用来决定视图的上、下、左、右、水平居中、垂直居中六个方位的具体值;而用于决定视图尺寸的类是MyLayoutDime类,这个类可以用来决定视图的高度和宽度的具体值;用于决定视图排列布局方向的是枚举MyLayoutViewOrientation类型,方位类型定义了垂直和水平两个方位;用于决定视图停靠区域的MyMarginGravity枚举类型,枚举类型定义了14种停靠的区域类型,这里要分清楚的是MyMarginGravity和MyLayoutPos的区别,前者是用来描述某个具体的方位,而后者则是用来某个方位的具体位置;用于描述子视图和布局视图四周内边距的padding属性,这个属性只用于布局视图;用于描述布局视图的尺寸大小由子视图整体包裹的wrapContentWidth,wrapContentHeight的属性;用于描述苹果各种屏幕尺寸适配的MySizeClass定义,以及具体的实现类MyLayoutSizeClass类。这些属性和类共同构建了出了一套完整的iOS界面布局系统。下面是这个套界面布局体系的类结构图:
介绍完布局的整个体系结构后,我们先来理解流的概念,所谓流就是向某个方向依次的排列,而当到达某个设定的边界或者设定的数量时则另起一行并回到原先的起点重新开始继续按某个方向依次排列。一个最简单的例子就是假设我们在写文章时,假定每行的文字规定了80个字则我们首先在第一行书写文字,而当要书写的文字超过80个字时我们就会自动另起一行重新开始。因此我们可以看出流布局有如下的特点:
1.总是优先沿着一个固定的方向排列,其中沿着的方向一共有两种: 从先左到右,然后从上到下;或者先从上到下,然后从左到右。我们称先从左到右然后从上到下的流为垂直流,也称为纵向流;而先从上到下然后从左到右的流为水平流,也称为横向流。
2.当流沿着某个特定方向满足了某个特定的要求后才会进行换行重新开始排列,而这个特定的要求有两种:一种是容器空间不足以容纳要排列的内容,一种是内容到达了容器空间的某个特定方向的数量限制。前者的一个具体的实例就是WEB页面中CSS中所定义的float布局,或者一些标签流;而后者的一个具体实例就是微信或者支付宝里面的钱包功能菜单列表
下面我们就列出一些典型的流式布局:
MyFlowLayout就是一个用来实现上述流功能的布局视图类,他是从MyBaseLayout类派生。支持分别从垂直和水平两个方向的进行布局,同时支持子视图按内容填充约束或者填充数量约束两种换行或者换列策略的四种布局:
1.垂直内容填充约束布局。
这种流式布局的布局机制是,里面的子视图按添加的顺序每行依次从左排列到右,而当布局视图的剩余宽度容纳不下一个要插入的新的子视图的宽度时则会新起一行,重新从左到右继续排列,如果遇到某个子视图的宽度甚至比布局视图还要宽时则总时会压缩子视图的宽度和布局视图的宽度保持一致,这样最终形成的结果是子视图将按从左到右,从上到下的顺序依次排列,且每行的数量不固定。下面就是这种流式布局的效果图:
2.垂直数量约束布局。
这种流式布局的布局机制是,里面的子视图按添加的顺序每行依次从左排列到右,当一行内的子视图的数量到达布局视图约定的数量值时则会新起一行,重新从左到右继续排列,这样最终形成的结果是子视图将按从左到右,从上到下的顺序依次排列,且每行的数量是固定的。下面就是这种流式布局的效果图:
3.水平内容填充约束布局。
这种流式布局的布局机制是,里面的子视图按添加的顺序每列依次从上排列到下,而当布局视图的剩余高度容纳不下一个要插入的新的子视图的高度时则会新起一列,重新从上到下继续排列,如果遇到某个子视图的高度甚至比布局视图还要高时则总时会压缩子视图的高度和布局视图的高度保持一致,这样最终形成的结果是子视图将按从上到下,从左到右的顺序依次排列,且每列的数量不固定。下面就是这种流式布局的效果图:
4.水平数量约束布局。
这种流式布局的布局机制是,里面的子视图按添加的顺序每列依次从上排列到下,当一列内的子视图的数量到达布局视图约定的数量值时则会新起一列,重新从上到下继续排列,这样最终形成的结果是子视图将按从上到下,从左到右的顺序依次排列,且每列的数量是固定的。下面就是这种流式布局的效果图:
MyFlowLayout流式布局类
一、流式布局的建立以及类型的指定
MyFlowLayout要实现上面四种不同的布局可以通过初始化方法:
-(id)initWithOrientation:(MyLayoutViewOrientation)orientation arrangedCount:(NSInteger)arrangedCount;
+(id)flowLayoutWithOrientation:(MyLayoutViewOrientation)orientation arrangedCount:(NSInteger)arrangedCount;
中的orientation参数来指定流式布局的方向,当设置为
MyLayoutViewOrientation_Vert时表示垂直流式布局,而当设置为
MyLayoutViewOrientation_Horz时则表示为水平流式布局;而其中的arrangedCount参数则是指定布局方向排列的子视图的数量约束值,当设置为0时则表示建立的不是数量约束布局而是内容填充约束布局。如果我们调用init方法来初始化一个流式布局的话则默认建立的是一个垂直内容填充约束布局。另外我们也可以通过类的两个属性:
@property(nonatomic,assign)MyLayoutViewOrientation orientation;
@property(nonatomic,assign) NSInteger arrangedCount;
来随时调整流式布局的方向和数量的,也就是说流式布局的种类是随时都可以变换的。
二、流式布局对包裹特性的支持
因为MyFlowLayout是继承自MyBaseLayout因此MyFlowLayout类的高度和宽度尺寸也支持由包裹的子视图来决定,也就是支持wrapContentWidth和wrapContentHeight两个属性设置为YES的情况,但不是4种流式布局都支持包裹属性,对于数量约束布局来说不管是水平的还是垂直都支持包裹属性,而对于内容填充约束布局来说则当是垂直布局时只支持wrapContentHeight为YES的情况,因为每行能填充的子视图的数量是依赖于布局视图的宽度决定的,因此是不能支持wrapContentWidth为YES的场景的;同样的道理对于水平内容约束布局来说只支持wrapContentWidth为YES则不支持wrapContentHeight为YES的场景的。
三、流式布局内子视图的尺寸位置和间距
对于流式布局来说,虽然我们总是按约定的规则来排列定位其中的每个子视图的位置,但是我们依然在某种情况下需要设置每个子视图之间的间距,以及子视图本身的高度和宽度尺寸。我们可以通过设置子视图的frame值来指定每个子视图的高度或者宽度,我们也可以通过视图扩展的属性widthDime,heightDime,myWidth,myHeight,mySize来设置子视图的布局尺寸。同样我们也可以通过设置子视图的扩展属性leftPos,rightPos,topPos,bottomPos,myLeftMargin,myRightMargin,myTopMargin,myBottomMargin来设置每个子视图的外边距值,对于流式布局来说外边距值的设置具有特别的意义,就拿myLeftMargin和myTopMargin的设置为例子,子视图所在的位置不同其所代表的意义是不同的。在一个垂直布局的情况下,如果子视图是第一行一列则myLeftMargin,myTopMargin的值是这个子视图离父布局视图的边距值;而当子视图是第二行一列时则myLeftMargin是指定的离父布局视图的左边距值,而myTopMargin则是离第一行整体子视图的顶部边距值;而当子视图是第一行二列是则myLeftMargin是指定离前一个子视图的左边距值,而myTopMargin则是离父布局视图的顶部边距值;而当子视图是二行二列时则myLeftMargin和myTopMargin则分别是前一个子视图的左边距值和第一行整体子视图的顶部边距值。下面的图形可以很清楚的描述出这些设置的意义:
上面的图表显示了布局视图的内边距padding设置,以及每个子视图的外边距设置值,以及可以很清楚的看到流式布局的每一行是如何确定出来的,以及当另起一行时处于新行的子视图的垂直位置是如何计算出来的。同时我们在图中还看到了两个间距:subviewHorzMargin和subviewVertMargin的设置。这两是流式布局的两个属性:
@property(nonatomic ,assign)CGFloat subviewVertMargin;
@property(nonatomic,assign) CGFloat subviewHorzMargin;
@property(nonatomic,assign) CGFloat subviewMargin;
其中的subviewMargin是上面两个的整体设置值,这三个属性的意义是设置所有视图之间的行间距和列间距,其中subviewVertMargin用于设置行间距,而subviewHorzMargin则是用于设置列间距,这两个属性的默认值都是0。有时候我们不想为每个子视图都设置四周的外边距值,而希望所有的子视图之间的行间距和列间距都是某个固定的值,这时候我们就可以通过直接设置这两个属性的值来进行所有子视图之间的间距的设置,而不用分别为每个子视图都去设置四周的边距值。
对于数量约束流式布局来说,因为我们限制了一个方向上的子视图的数量,有时候我们希望这个方向上的所有子视图的尺寸都是一样且平分这个方向上的布局视图的尺寸,而不需要我们依次为所有子视图指定尺寸,就像通过subviewVertMargin和subviewHorzMargin设置视图的行间距和列间距一样,流式布局也提供了一个属性:
@property(nonatomic,assign)BOOL averageArrange;
属性averageArrange的意义是表示流式布局视图里面的所有子视图的尺寸都相等并且值是等于布局视图的尺寸除以布局视图指定的数量值,这个属性值默认是NO。而且这个属性的设置只会在数量约束布局里面才生效,在内容填充约束布局里面是无意义的。
如果流式布局的方向是MyLayoutViewOrientation_Vert则表示每行的子视图的宽度会被均分,这样子视图不需要指定宽度,但是布局视图必须要指定一个明确的宽度值,如果设置为YES则wrapContentWidth的设置不会起作用。
如果流式布局的方向是MyLayoutViewOrientation_Horz则表示每列的子视图的高度会被均分,这样子视图不需要指定高度,但是布局视图必须要指定一个明确的高度值,如果设置为YES则wrapContentHeight的设置不会起作用。下面是属性averageArrange设置为YES和为NO时的两种布局效果图:
在一些场景中我们可以通过设置averageArrange为YES,并且同时设置
subviewVertMargin和subviewHorzMargin的值来减少分别为每个子视图设置位置和尺寸的工作量。另外在一些布局场景中我们还可以做如下的设置:
1.在垂直内容填充约束布局中,我们可以设置某个子视图的宽度和布局视图的宽度建立约束关系,以及让某个子视图的高度同子视图的宽度建立约束关系,也就是说可以设置子视图.widthDime.equalTo(flowLayout.widthDime),以及子视图.heightDime.equalTo(子视图.widthDime)
2.在水平内容填充约束布局中,我们可以设置某个子视图的高度和布局视图的高度建立约束关系,以及让某个子视图的宽度同子视图的高度建立约束关系,也就是说可以设置子视图.heightDime.equalTo(flowLayout.heightDime),以及子视图.widthDime.equalTo(子视图.heightDime)
3.在垂直数量约束布局中,我们可以设置某个子视图的高度同子视图的宽度建立约束关系,也就是说可以设置子视图.heightDime.equalTo(子视图.widthDime)
4.在水平数量约束布局中,我们可以设置某个子视图的宽度同子视图的高度建立约束关系,也就是说可以设置子视图.widthDime.equalTo(子视图.heightDime)
四、流式布局内子视图的停靠设置
在线性布局中我们可以让所有的子视图整体的停靠在布局视图的一个特定的区域,这个可以通过线性布局的gravity属性来设置。同样在流式布局中我们也可以通过gravity属性来设置流式布局中的所有子视图都整体停靠在布局视图的某个特定的区域。在有的时候我们的布局视图设置有明确的高度和宽度值,同时我们又希望布局视图里面的所有子视图整体的停靠在布局视图的某个区域,我们可以设置布局视图的属性:
@property(nonatomic,assign)MyMarginGravity gravity;
属性的默认值是
MyMarginGravity_None表示不进行停靠处理,也就是默认的从左上角开始进行子视图的整体布局。
如果布局视图方向为
MyLayoutViewOrientation_Vert
时且
averageArrange
为
YES
时则水平方向的停靠失效,否则可以为垂直布局视图设置整体水平方向上的左,中,右整体停靠;而总是可以设置整体垂直方向上的上、中、下整体停靠。
如果布局视图方向为MyLayoutViewOrientation_Horz时且averageArrange为YES时则垂直方向的停靠失效,否则可以为水平布局视图设置整体垂直方向上的上、中、下整体停靠;而总是可以设置整体水平方向上的左、中、右整体停靠。
gravity属性是用来设置所有子视图的整体停靠特性的,而在实际的应用场景中我们还想进一步设置一行内或者一列内的视图之间的停靠对齐方式。对于垂直布局来说,在一行内的视图之间的高度是可以不经相同的。在一行之内的视图总是会存在有一个高度最高的子视图,因此我们也希望这行内的其他子视图能以这个子视图为基础来进行垂直方向的对齐停靠设置(水平布局则是水平方向的对齐停靠设置)。因此我们可以通过属性:
@property(nonatomic,assign)MyMarginGravity arrangedGravity;
属性来进行设置行内或者列内的视图之间的对齐停靠方式,这个属性默认设置为
MyMarginGravity_None,表示不处理行内停靠,也就是总是按左边或者顶部对齐方式来布局行内的子视图。这个属性的设置是依赖于布局的方向的。
如果方向是
MyLayoutViewOrientation_Vert
则只用于表示每行子视图的上中下停靠对齐位置,这个属性只支持
MyMarginGravity_Vert_Top
,
MyMarginGravity_Vert_Center,MyMarginGravity_Vert_Bottom
,
MyMarginGravity_Vert_Fill四种停靠对齐方式
这里的对齐基础是以每行中的最高的子视图为基准;
如果方向是
MyLayoutViewOrientation_Horz
则只用于表示每列子视图的左中右停靠对齐位置,这个属性只支持
MyMarginGravity_Horz_Left
,
MyMarginGravity_Horz_Center,MyMarginGravity_Horz_Right
,
MyMarginGravity_Horz_Fill四种停靠对齐方式
这里的对齐基础是以每列中的最宽的子视图为基准。
这里需要注意的是arrangedGravity描述的所有的行内或者列内的停靠对齐方式,而不是只针对于某个一行或者一列,而gravity则用来描述所有子视图整体的停靠位置。您可以通过流式布局库的DEMO例子来调整具体的值来查看设置的结果。
五、流式布局和表格布局以及UICollectionView的区别以及应用
在前面的文章中我们介绍了 表格布局MyTalbeLayout,表格布局也可以用来建立多行多列布局的应用场景。但在实际使用中还是有一些差别的。表格布局需要明确的指定建立一个新的行操作,同时又要明确的指定建立列的操作,同时表格布局的行和列的指定都是可以单独指定的,而流失布局则没有明确的行和列的概念,流失布局总是按一个方向进行排列,只要在遇到数量的约束和内容的空间的约束时就是自动的进行换行处理,因此流时布局在建立子视图时简单而且方便。而针对UICollectionView来说也跟表格布局一样需要明确的指定一共有多少行,每行有多少列,并且所有设置都是通过委托的形式来完成的,代码量多而且操作起来麻烦。在实际的应用中流式布局更加适合于用来建立那些标签流、九宫格菜单功能、枚举功能等方面的布局。下面的图片展示了流式布局的几个DEMO效果:
六、总结
关于流式布局的功能就介绍到这了,流式布局是MyLayout布局系统里面的5大布局视图之一,主要用于建立那些有规律排列和对齐的视图的应用场景,而且通过使用流式布局来建立界面布局使用的代码量是最少而且最灵活的,视图之间的排列顺序的调整只需要调整其布局视图中的顺序就可以完成了。通过流式布局可以很简单的来建立一套瀑布流风格展示的界面,以及文字标签等功能。如果您想了解更多的关于流式布局的功能请您访问我的github站点来了解更多:
https://github.com/youngsoft/MyLinearLayout