前言
在MyLayout的6大布局中,每种布局都有不同的应用场景。且每种布局的子视图的约束机制不一样:线性布局MyLinearLayout、表格布局MyTableLayout、流式布局MyFlowLayout、浮动布局MyFloatLayout这四种布局的子视图之间的约束是通过添加到父布局的先后顺序来决定的;框架布局MyFrameLayout中的子视图则只跟父布局视图有关,而跟添加的先后顺序无关;相对布局中MyRelativeLayout的子视图则是通过设置视图之间的依赖关系来建立约束的,而跟添加的先后顺序无关。即便如此,同一种界面功能在一些情况下都可以用任何一种布局来实现。在这些布局中相对布局因为是通过设定视图之间的依赖来建立一种布局约束,因此我们可以用他来构造一些复杂且无规律的界面布局,但其缺点则是太过于依赖约束,导致当界面调整时需要重新设定视图之间的依赖关系(iOS的AutoLayout其实就是一种相对布局的实现,布局时需要设置太多的约束,这也是我一直诟病AutoLayout的原因)。那么是否可以有一种方法不设置视图之间的依赖而来实现一些复杂的界面布局呢? 这也就是我们推出浮动布局MyFloatLayout的原因。
浮动
我们的UI界面中总是有一种场景是:某个容器视图后续添加的子视图的左边总是紧跟着前面添加的子视图的右边,而上边则跟前面视图的上边保持一致进行停靠显示,而当容器视图剩余的宽度空间不够容纳新加入的子视图时则新加入的子视图自动的往下移动且在不覆盖已经排列好的视图的前提下寻找出一个可以容纳其宽度的最合适的位置进行停靠。我们称这种机制为浮动。比如下面的场景:
我们的容器视图的尺寸为500x300,当添加视图A时,因为视图A的宽度是80,宽度能够被容器视图容纳,所以我们将视图A浮动到容器视图的左上角位置。而当添加视图B时,因为视图B的宽度是100,仍然能够被容器视图的宽度容纳(容器视图剩余宽度为420),所以将视图B浮动到视图A的右边并且上边对齐。我们也可以按同样的方式来处理视图C的浮动。这样容器视图剩余的宽度变为了170(500-80-100-150)。假如这时候我们想再放入一个尺寸为200x100的视图D的时候,因为这时要添加的视图D的宽度为200,而容器视图的剩余宽度只有170了,这时候视图D将不能浮动到视图C的右边了,我们必须要找一个合适的位置来放置视图D。根据浮动的原则我们算出最合适的浮动位置是A视图的右边,且为了保持前面视图不被覆盖,因此视图D放置的最合适的坐标位置是:(80,130)。下面就是视图D放入后的结果:
根据浮动的规则假如视图D的宽度不是200而是400的话,那么视图D将不能浮动到视图A的右边(视图A的右边的剩余的宽度为320,无法容纳400的宽度),那么根据浮动的规则,视图D将再次往下移动,并浮动到容器视图的最左边的(0,180)的位置上。我们继续来加入一个新的视图E,视图E的尺寸为100x50。那么视图E应该是浮动到视图C的右边还是视图D的右边呢? 答案是D的右边,虽然C右边的空间也可以容纳100的宽度,但是却不符合浮动的规则。我们上面说的浮动的规则是在可以容纳新加入视图宽度的情况下新加入的视图的上边和前一个加入的视图的上边对齐,而且新加入的视图的左边必须放置在前一个加入视图的右边。因此视图E加入到容器视图后的结果如下:
最后,我们再来考察新视图F的加入。假如视图F的尺寸为300x50。那么根据浮动的规则视图将无法浮动到E的右边,同时也无法浮动到D的右边了,这时候只能继续往下移动,而最终的左边是浮动到容器视图的最左边,而上边的位置则是视图D的下方。最终的布局结果如下:
通过上面的4张图片中,我们就可以总结出浮动的规则,而且上面的浮动规则只是我们的浮动布局MyFloatLayout中的一种浮动方向:优先向左浮动,再向下浮动。我们可以通过浮动布局这种浮动机制,不再设置明确设定每个子视图之间的依赖关系,而只是根据视图加入到布局视图的顺序就能够实现一种不规则的视图排列的布局场景。下面是我们总结的优先向左浮动,再向下浮动的浮动规则:
1.加入布局视图的第一个子视图总是浮动到布局视图的左上角。
2.如果新加入的子视图的宽度能够被放入到前一个加入的视图右边到布局视图右边的剩余宽度空间中的话,则新加入的视图的左边位置是等于前一个加入视图的右边位置,且新加入视图的上边位置和前一个加入的视图的上边位置保持一致。
3.如果新加入的子视图的宽度不能被放入到前一个加入的视图的右边到布局视图右边的剩余宽度空间中时,则新加入的视图将继续往左往下寻找到一个能容纳其宽度的最小空间,并且不能遮挡掉前面加入的所有子视图的最佳的位置进行放置。
4.如果某个子视图的宽度大于等于布局视图的宽度,则总是浮动到布局视图的最左边,且上边的位置是前面所有子视图的最下边的位置进行放置。
5.总是确保任意的子视图之间是不能被重叠覆盖。
上面的5条规则就是浮动布局中的一种浮动规则的定义,而浮动布局的规则定义其实是从HTML中的CSS中的一种定位:浮动定位的规则而来的。CSS中的浮动定位最初的设计初衷是为实现图文混排的效果而设置的一种浮动定位技术, 在CSS中我们可以为某个元素指定float这个属性,而这个属性的值可以设定为left或者right或者none,分别表示元素是向左浮动还是向右浮动还是不浮动。同时我们还可以为元素指定clear这个属性来清除浮动,clear这个属性可以设置left, right,both,none这四个值。若果您想了解CSS中关于浮动定位的信息可以访问:CSS浮动定位和CSS浮动知识分享这两篇文章进行详细了解。
前面说到,在CSS中我们可以通过float属性来指定浮动的方向,以及clear属性来指定清除浮动的操作。而我们的浮动布局MyFloatLayout也是支持浮动方向,以及清除浮动这些特性的设定的。向右浮动我们在后续会介绍,这里先介绍清除浮动的作用和意义。
清除浮动
上面的几个场景中我们发现,不管新加入视图的宽度如何,只要容器视图中剩余宽度能够容纳新加入的子视图,则子视图总是会浮动到前面一个视图的右边。但在实际的应用场景中,我们又希望某个视图不遵守这种默认的浮动规则,而是让新加入的子视图的左边总是和容器视图的左边对齐,且子视图的上边则是放入到前面加入的占用最高空间的视图的下方。比如下面的情况:
我们的子视图C,虽然宽度为150,并且能浮动到视图B的右边,但是实际中我们则想让视图C浮动到A的下边并且左边和容器视图对齐,这时候我们就需要用到清除浮动的概念了。 我们可以为视图C设置一个清除浮动的属性,这样视图C就达到了我们想要的效果。因此我们可以看出,所谓清除浮动就是使得视图的默认的浮动规则失效,而总是让视图的左边和容器视图的左边对齐,而让视图的上边则设置为前面加入的所有同一个方向浮动的视图的最高高度的下方。因此我们可以得出浮动布局的第6条规则:
6.如果子视图设定了清除浮动属性,则视图在布局时的左边界总是和容器视图的左边界相等,而上边界则是在所有前面加入的同一个方向浮动视图的最高的高度的下方显示。
通过视图的清除浮动属性,我们可以设置让某些视图不进行浮动,而是达到另起一行的效果。
比重
我们再来考察一种场景,就上面的例子来说,我们先加入了视图A和B,现在我们想加入一个视图C,并且想让视图C浮动在视图B的右边。而且宽度则是已经填充的A和B剩余的宽度320(500-80-100)。一个办法就是我们手动的设定视图C的宽度为320,这样就能达到想要的效果,但是在实际的应用中,A和B的宽度可能是不确定的,并且容器视图的宽度也是不确定的,而不管何种情况我们又总想让视图C的宽度总是占用剩余的宽度,就像如下的效果图一样:
在上面的场景中,我们希望不需要明确的设置视图C的宽度,而是通过一种比重的特性来设置视图C总是占用容器视图的剩余宽度的某个比例值。这里的比重的设置,是在整体布局视图的浮动的方向的设定上的,就是说当整体的布局视图里面的视图是支持左边和右边浮动时则这个比重指定的是视图的宽度的相对比例值,而当布局视图支持的是上边和下边浮动时则这个比重指的是视图的高度的相对比例值。而且我们约定比重值的设定必须大于0且小于等于1。通过比重值的设定,我们可以不需要对某个新加入的视图设定具体的宽度或者高度,而只需要指定一个相对的值,而由浮动布局来根据当前的浮动情况来自动计算出应该有的宽度或者宽度。其中的具体的计算公式为:
某个设置了比重值的视图的宽度或者高度 = (布局视图的宽度或者高度 - 前一个视图的右边或者下边的边界值)* 视图的比重值。
就以上面的左右浮动的例子来说,假设我们设定视图C的比重为1。根据公式的定义,布局视图的宽度是500,而前面一个视图B的右边边界值是180(100+80)。因此最终视图C的宽度就是:320 = (500 - 180)*1, 而假如设定视图C的比重是0.5的话,则最终视图C的宽度就是160了。又如果我们再增加一个视图D的比重设置为1的话。因为前面的视图C的宽度已经算出是160,他的右边距值是340(180+160), 因此最终视图D的宽度就是160 (500 - 340)*1了。其中的效果图如下:
浮动布局中的子视图可以通过设定比重来得到剩余的宽度或者高度,因此浮动布局中针对比重属性定义新的规则如下:
7.当某个子视图设定了比重属性时,这个视图的宽度或者高度将根据布局视图的浮动方向设定,以及比重的值的设定自动进行计算,比重的设置必须大于0小于等于1,而通过比重计算出来的宽度或高度的公式为:布局视图的宽度或者高度 - 前一个视图的右边或则下边的边界值)* 视图的比重值
左右浮动
上面我们的介绍的浮动的例子中,都总是默认是向左浮动,然后再从上到下的进行布局。但前面也有说到CSS中的元素的浮动定位是同时支持向左或向右浮动的。而我们的浮动布局也是支持某个子视图向左或者向右浮动的。当某个子视图在加入到布局视图时,可以设定为向左还是向右浮动,这里的向左和向右是不能同时支持的,视图要么向左要么向右。对于视图向右浮动来说,他的机制是和向左浮动是一样的。我们可以看如下的视图:
可以看出,当A,B,C,D,E,F这几个视图向右浮动时,除了方向外,其他的规则是跟视图向左浮动的规则是一样的。一个布局视图里面的子视图是可以设置为向左或者向右浮动的,而前面的例子里的所有子视图要么都向左,要么都向右。但是实际场景中我们是可以设置某些视图向左浮动,而某些视图向右浮动的。比如下面的例子:
上面的例子中我们把子视图添加到布局视图的顺序分别是A,B,C,D,E,F这个顺序,且设定C,D,E这三个子视图是向左浮动的,而A,B,F这三个子视图是向右浮动的。在前面的所有向左浮动的例子中,我们的剩余宽度的比较总是以布局视图的右边界为标准的,而前面所有向右浮动的例子中我们的剩余宽度的比较总是以布局视图的左边界为标准的。那么当我们的布局视图里面的子视图又有向左浮动的且又有向右浮动的情况时,我们的宽度边界又是如何考虑的呢?
我们来分析一下上面的左右浮动的例子,因为我们总是按添加的先后顺序来进行浮动布局的,所以上面的例子中A,B这两个子视图都向右浮动这个很容易理解,而视图C向左浮动也比较容易理解。我们来考察当D视图向左浮动要插入到容器视图时,我们发现如果视图D浮动到视图C的右边并且上边和视图C保持一致时,视图D的布局宽度将会覆盖掉视图B的部分空间,如果出现了覆盖则是不符合浮动布局规则5中的定义的,因此视图D必须要往下移动,直到到达视图B的底部后才不会出现覆盖现象,因此视图D的上边位置就变为了100,而左边的位置则仍然等于视图A的右边的位置了。视图E也是向左浮动,这里我们是要求E和最后一个加入的D必须要保持上边对齐,但是如果保持上边对齐的话就无法容纳E的宽度而将产生覆盖,因此必须要将视图E往下移动,直到移到视图A的下面才能满足宽度的填充,因此视图E的上边位置就设置为视图A的下边,而左边位置则设置为D的右边。最后我们再来考察F的情况,虽然前面最后一个向右浮动的视图是B,但是根据浮动规则2的约定,视图F的上边位置必须要和最后一个加入的视图E的上边位置保持一致,但是如果和E的上边位置保持一致的话,F的长度将会覆盖掉E的位置,因此视图F必须要往下移动到视图E的下面,并且右边要和布局视图的右边界保持一致,这样才能容纳视图F的显示。通过上面的例子我们可以看出当一个布局视图中同时存在着向左浮动和向右浮动的子视图时,我们就有浮动布局的将新增规范8的定义如下:
8.当浮动布局中同时存在着向左和向右浮动的子视图时,向左浮动的视图剩余宽度的右边界是在不覆盖掉右边视图的情况下的最小向右浮动的视图的左边界,而向右浮动的视图的剩余宽度的左边界是在不覆盖掉左边视图的情况下的最大向左浮动的视图的右边界。
上下浮动
前面我们介绍向左和向右浮动的布局视图的一些场景。在CSS中也只定义了向左和向右浮动的功能,向左向右浮动的布局视图的原则是按视图添加的顺序,以及设定的浮动方向优先按左或者按右浮动,然后再整体的从上到下进行布局展示。但是在实际的情况中我们会要求有某个子视图按向上或者向下浮动的来进行布局,并且布局的顺序是按添加的子视图的顺序优先按向上或者按向下进行浮动,然后再整体的从左到右进行布局展示,这种浮动布局我们称之为上下浮动布局。上下浮动布局里面的子视图,进行浮动的依据是根据子视图本身的高度,以及布局视图的高度来决定的(而左右浮动布局则是根据宽度来决定的)。其中的浮动规范除了方向上不同外,其他的机制都是跟左右浮动是一样的。我们这里就不再进行赘述了,下面我们通过一张布局来了解一下上下浮动布局的界面:
上图可以看出上下浮动除了方向上和左右浮动不一样外,其他的规则都是一致的,上下浮动布局是依然支持清除浮动的,只不过清除浮动时方向是变为了向右移动。同时上下浮动布局也是支持子视图的比重设置的,只不过这里的比重是指子视图的高度。
MyFloatLayout的方法和属性的介绍
说了这么多浮动布局的实现原理以及布局的机制,那我们怎么来使用和定义浮动布局呢?要实现和使用浮动布局,我们必须要使用浮动布局MyFloatLayout这个类,这个类的定义如下:
@interface MyFloatLayout : MyBaseLayout
-(id)initWithOrientation:(MyLayoutViewOrientation)orientation;
+(id)floatLayoutWithOrientation:(MyLayoutViewOrientation)orientation;
@property(nonatomic,assign) MyLayoutViewOrientation orientation;
@property(nonatomic,assign) IBInspectable MyGravity gravity;
@end
从类的初始化方法中我们可以看出,在创建一个浮动布局时必须要指定一个方向,这个方向指的是最终子视图的布局走向,因为左右浮动布局我们是先按左右浮动最终是一个从上到下的排列过程,而上下浮动布局则是先按上下浮动最终则是从左到右排列,因此当我们指定orientation的值为MyLayoutViewOrientation_Vert表示的是创建一个左右浮动的浮动布局,而当值设定为MyLayoutViewOrientation_Horz时则表示建立的是一个上下浮动的浮动布局,系统默认建立的是左右浮动的浮动布局。而且后续还可以通过orientation属性来进行动态的修改浮动的方向。当浮动布局的浮动方向指定后,接下来我们就要为某个要添加到浮动布局的子视图指定浮动方向属性、清除浮动属性、以及比重了,这些则可以通过视图的扩展分类:
@interface UIView(MyFloatLayoutExt)
@property(nonatomic,assign,getter=isReverseFloat) IBInspectable BOOL reverseFloat;
@property(nonatomic,assign) IBInspectable BOOL clearFloat;
@property(nonatomic, assign) IBInspectable CGFloat weight;
@end
来设置。 在默认情况下当我们建立的是一个左右浮动布局时,我们添加到布局里面的所有子视图默认都是向左浮动的,而当建立的是一个上下浮动布局时,我们添加到布局里面的所有子视图默认都是向上浮动的,因此当需要改动子视图浮动的方向则可以设置属性reverseFloat来实现,这个属性是一个BOOL类型的值,当设置为YES时表示按默认方向相反的方向浮动,也就是在左右浮动布局中,如果设置某个子视图的reverseFloat为YES的话则表示子视图是向右浮动,而对于上下浮动布局来数则表示是向下浮动。视图的扩展属性clearFloat也是一个BOOL类型,表示是否清除浮动,默认值是NO表示不清除浮动,当某个子视图需要有清除浮动的效果时,请将这个属性设置为YES。最后一个视图的扩展属性weight表示视图的宽度或者高度的比重,这个值默认值是0,表示不是按比重来指定宽度,这时候你在添加子视图时必须明确的指定宽度或者高度,而当设置为非0时则不需要为子视图指定宽度和高度,而由布局系统来自动帮你计算。这里的weight的设置范围是:0<=weight <=1.
上面分别的介绍了浮动布局的建立,以及子视图的扩展的属性设置来实现视图在浮动布局中的浮动方式、是否清除浮动、以及比重的设置方法。另外对于浮动布局来说,因为是从MyBaseLayout中派生的,因此浮动布局同样支持wrapContentWidth以及wrapContentHeight属性的设置的,也就是浮动布局的宽高可以由子视图来决定的,需要明确的是一般情况下我们对于左右浮动布局来说,只需要设置wrapContentHeight。当然你也可以设置wrapContentWidth(设置这个属性的前提是布局视图里面有一个子视图特别的宽);同样对于上下浮动布局来说,只需要设置wrapContentWidth。当然你也可以设置wrapContentHeight(设置这个属性的前提是布局视图里面有一个子视图特别的高)。
最后,我们看到浮动布局视图里面还有一个gravity属性,这个属性在左右浮动布局视图中可以用来设置所有子视图的整体的上,中,下三种停靠模式,而在上下浮动布局视图中则可以用来设置所有子视图的整体的左,中,右三种停靠模式。
下面的效果图就是我们使用浮动布局来实现的仿天猫和ZAKER界面布局的效果图:
上面的gif图片中有三个DEMO分别介绍浮动布局的,你可以通过DEMO中的FOLTest1ViewController来研究和学习浮动布局的各种属性的设置以及效果,而FOLTest2ViewController则是介绍的天猫首页的布局、FOLTest3ViewController介绍的则是ZAKER的卡片式布局的实现。通过DEMO我们可以看出,当我们要实现一些不规则的界面布局时,我们并不需要使用相对布局来实现,而只需设定正确的子视图的添加顺序,以及浮动属性的设置就能达到我们想要的效果,而且采用浮动布局的优点时不需要再去考虑视图之间的依赖关系的设置了。
智能边界线
为了说明智能边界线我们先来看这两个界面:
上面的两个界面是仿淘宝和天猫首页以及ZAKER新闻的界面,我们来观察其中的每个区块之间的边界线。我们发现处在边缘部分是没有显示边界线的,而边界线只会显示在区块交界的地方显示一条边界线。在一般情况下,不规则边界线的显示我们有可能需要UI人员提供图片来完成,或者不提供图片我们在编程时也需要进行条件的判断以便决定是否需要在特定的位置绘制边界线,显然这样做将会增加我们的代码量。因此为了解决这个问题,我们的布局系统提供了边界线以及智能边界线的功能。如果您用了MyBaseLayout派生的6大布局的话,我们是可以通过基类提供的四个属性:
@property(nonatomic, strong) MyBorderline *leftBorderline;
@property(nonatomic, strong) MyBorderline *rightBorderline;
@property(nonatomic, strong) MyBorderline *topBorderline;
@property(nonatomic, strong) MyBorderline *bottomBorderline;
来为布局视图指定要显示的四边的边界线,我们可以支持设置边界线的颜色,粗细,缩进,以及点线等功能,这样我们就不再需要单独的提供边界线的切图了。要想看边界线的例子,可以查看LLTest4ViewController和AllTest3ViewController这两个DEMO的介绍。即便如此,对于上面的特殊情况,我们还需要进行编程以及条件判断来完成边界线的指定,因此为了解决这个问题,我们在布局中新增加了一个智能边界线的属性:
@property(nonatomic, strong) MyBorderline *intelligentBorderline;
@property(nonatomic, assign) BOOL notUseIntelligentBorderline;
如果为某个布局视图设置了智能边界线的值,那么这个布局视图里面的子布局视图将会根据视图之间的关系而自动智能的生成边界线。这里需要强调的是只有布局视图里面的子布局视图才会生成智能的边界线,对于布局视图里面的非布局子视图是不会生成边界线的。而如果我们的某个布局视图里面的子布局视图不想使用智能的边界线,而是仍想自己手动设定,那么只需要将自己的notUseIntelligentBorderline设置为YES即可,他表示不使用父布局提供的智能边界线功能。在当前的布局库版本中,我们只有线性布局、浮动布局、表格布局、流式布局支持智能边界线的设定,而框架布局、相对布局则是不支持的。正是因为布局系统里面提供的智能边界线的功能,就使得我们在设定布局视图之间的边界线时非常的简单,只需要一句话就能搞定。
浮动布局的实践
上面就是我们要介绍的关于浮动布局的全部的东西,接下来我们将借着DEMO中的代码来具体的介绍我们如何使用浮动布局来实现上面的功能的。在介绍之前,我们这里说明一下,我们仍然是可以用子视图的扩展属性myLeft,myRight,myTop,myBottom这4个属性来指定视图之间的间距的。同时我们还支持子视图的宽度扩展属性widthSize的值可以设置为一个具体值,也可以等于布局视图的宽度,以及前面已经布好局的子视图的宽度,甚至还可以等于子视图的高度。
因为所有的关于浮动布局的代码我们都能在DEMO中找到,因为我们只介绍几个例子,其他的大家可以自己去研究,我们看下面的图:
我们看到上面的界面左上角的区块的高度为180,而其余的区块都是90,并且每个区块的宽度都是屏幕的一半。为了容纳上面的界面我们需要先建立左右浮动的浮动布局:
CGFloat itemHeight = 90;
//品牌特卖
MyFloatLayout *layout1 = [MyFloatLayout floatLayoutWithOrientation:MyLayoutViewOrientation_Vert];
layout1.backgroundColor = [UIColor whiteColor];
layout1.wrapContentHeight = YES;
layout1.intelligentBorderline = [[MyBorderline alloc] initWithColor:[UIColor lightGrayColor]];
我们设定的layout1的高度由子视图决定,并且设置了智能边界线。接下来我们只需要每个区块按顺序依次添加进来即可。且从上面的区域中我们可以看出一共有3种不同的类型的区块分别是A,B, C三种区块,这三种区块其实是用MyFloatLayout来实现的。
A区块我们也可以用一个浮动布局来实现,我们只需要建立一个上下浮动布局,标题,小图都默认往上浮动。剩下的大图宽度和父布局宽度相等,并且设置weight=1就可以了,这部分代码的具体实现就在FOLTest2ViewController中的createItemLayout1_1的方法中实现。
B区块我们也可以用浮动布局来实现,我们只需要建立一个左右浮动布局,大图片优先向右浮动,高度和父布局高度相等,接下来主标题向左浮动,并且weight=1表示占用剩余的宽度;副标题也是向左浮动,并且设置清除浮动属性,同时设置weight=1表示占用剩余的宽度;最后的小图也是设置为左浮动,并且设置清除浮动属性。这部分代码的具体实现在FOLTest2ViewController中的createItemLayout1_3的方法中实现.
C区块我们也可以用浮动布局来实现,我们只需要建立一个左右浮动布局,主标题部分向左浮动,并且宽度和父布局宽度相等,付标题部分向左浮动,并且宽度和父布局宽度相等,而图片部分则向右浮动即可。
最后我们可以依次建立A,B,C三种区块然后依次的加入到layout1中去,加入时只需要设置A的高度为180,而宽度则是layout1的一半即可,而其他两种则高度设置为80,且宽度设置为layout1的一半即可。
小结
浮动布局是一种功能非常强大的布局体系,从某种程度上来他甚至是相对布局的替代方案,而且要比相对布局要简单,因为里面的子视图之间是不需要设置约束和依赖关系的,单单凭借加入到布局视图的顺序,以及自身的宽高就能完成我们想要的功能。而且其提供的能力甚至要比CSS中的浮动属性更加强大。而我们在进行WEB前端开发时很多的界面布局其实都是通过CSS的浮动属性来完成的。因此我们也可以借助浮动布局来我们各种复杂的界面布局,而且浮动布局也能方便的实现线性布局以及流式布局的能力。如果您想了解更多的关于流式布局的功能请您访问我的github站点来了解更多:
OC版本:https://github.com/youngsoft/MyLinearLayout
Swift版本: https://github.com/youngsoft/TangramKit
如果您觉得库还不错,记得为我点赞哦。