功能强大。它能够减小咱们在开发UI界面时所花费的时间以及减小须要适配多种设备而所消耗的时间。实践代表使用MyLayout进行界面布局时能够减小几乎50%的工做量。前端
性能优越。MyLayout内部实现是基于frame计算来完成布局的,因此同等界面下性能是AutoLayout的5倍左右,所以复杂界面选择MyLayout将是最佳实践。git
布局体系丰富。MyLayout提供了iOS、Android、HTML/CSS等前端中的全部流行布局实现。所以不管你以前工做在何种平台上均可以选择熟悉的布局类上手进行开发操做。MyLayout还支持从服务器进行动态布局下发的能力。github
系统结合紧密。MyLayout能够同时和AutoLayout技术进行结合使用,同时能够用在XIB和Storyboard中进行可视化布局,同时还支持SizeClass技术用于多设备适配处理。数组
多语言实现。MyLayout提供了OC语言版本的实现,同时也提供了Swift语言版本的实现:TangramKit。两者的语法和使用方式类似,您能够任意选择一种语言进行代码布局。bash
国际化支持。MyLayout支持LTR和RTL两种方向的布局,其中的RTL模式能够用来支持希伯来语系的布局方式。服务器
无版本限制。MyLayout并无操做系统版本上的使用限制,理论上它最低甚至能够支持到iOS5.0。框架
下面的表格列出的是MyLayout所提供的九大布局类所实现的功能以及和其它系统的对标能力:ide
布局类名 | 功能介绍 | 对标功能 |
---|---|---|
MyLinearLayout | 线性布局: 提供视图依次从上往下或者从左往右进行单行单列排列的能力 |
iOS:UIStackView Android:LinearLayout Flutter:Row、Column SwiftUI:HStack、VStack |
MyFloatLayout | 浮动布局: 提供视图经过上下左右浮动停靠而进行排列布局的能力 |
CSS: float |
MyFlowLayout | 流式布局: 提供视图按垂直或者水平方向依次进行排列而且在知足特定条件(一行内的数量和尺寸值知足约定值)后会换行进行继续排列布局的能力 |
独有 |
MyFlexLayout | 弹性布局: 提供一个盒内的子视图能够进行伸缩对齐和换行排列而且知足flex规约的布局能力 |
CSS:flexbox |
MyGridLayout | 栅格布局: 提供了一种基于单元格进行垂直和水平的无限拆分而进行布局的能力,栅格布局同时具备布局动态下发的能力 |
CSS:相似Bootstrap、Grid |
MyTableLayout | 表格布局: 提供基于行列控制的表格布局的能力 |
Android: TableLayout、GridLayout HTML: table、tr、td |
MyRelativeLayout | 相对布局: 提供一种经过设置视图之间的尺寸和位置的相互依赖约束来实现布局的能力 |
iOS:AutoLayout Android:RelativeLayout、PercentRelativeLayout、ConstraintLayout |
MyFrameLayout | 框架布局: 提供视图在父视图上某个方位进行停靠以及层叠摆放布局的能力 |
Android:FrameLayout |
MyPathLayout | 路径布局: 提供子视图的位置经过数学函数运算而进行定位排列的能力 |
独有 |
SizeClass | 提供了根据屏幕尺寸和横竖屏而进行差别布局设置的能力。上述全部布局都支持SizeClass的功能 | iOS:SizeClass CSS: 相似Bootstrap |
在这些众多布局类中有些布局类提供了子视图的有规律的布局排列,好比线性布局、流式布局、表格布局、浮动布局、路径布局、弹性布局、栅格布局。有些布局类则提供了经过子视图之间的约束限制来实现布局排列,好比浮动布局、相对布局、框架布局。有些布局类则须要经过多个层次嵌套来实现界面需求,好比线性布局、流式布局、表格布局、弹性布局。有些布局类则能够把界面需求拍平而只用单层排版就能实现所需功能,好比浮动布局、相对布局、栅格布局。有些布局类则能够实现一些特殊排列,好比路径布局能够根据提供的数学函数来实现视图根据特定路径曲线来进行排列展现。有些布局类则能够提供从服务器进行动态下发以及用JSON进行布局描述的能力,好比栅格布局。有些布局类则能够实现和HTML/CSS对标的能力,好比浮动布局和弹性布局。所以在实现界面需求时,咱们能够灵活运用。在选择布局时我将使用布局类的优先级列出来,供你们参考:函数
浮动布局->流式布局->线性布局->弹性布局->栅格布局->相对布局->框架布局->表格布局->路径布局布局
您能够从以下地址下载这两个版本的工程DEMO:
👉OC语言版本MyLayout: github.com/youngsoft/M… 👉Swift语言版本TangramKit: github.com/youngsoft/T…
此次1.9.0版本的升级不管是新功能的添加、代码的重构、性能的提高都作了大量的改进,新增和改进的功能主要有:
下面是新版本的上述功能的详细介绍:
flexbox是目前Web前端比较流行的布局框架。它提供了一种在一个盒子内子视图依次排列并能够进行换行排列和进行拉伸和压缩的功能。目前也有不少将flexbox移植到native客户端的解决方案。固然flexbox也有必定的缺陷:好比不支持重叠覆盖、不支持相对间距、不支持行和列间距的统一设置、不支持不规则排列等等问题。
在之前的版本中流式布局MyFlowLayout就能够实现flexbox的大多数特性而且在此基础上进行了更多复杂功能的扩展。由于其语法和设置方式和flexbox不兼容,所以对于flexbox的喜好者来讲是增长了学习和使用的成本。而此次的新版本则提供了一个新的布局类:弹性布局MyFlexLayout:
/*
* 弹性布局是为了兼容flexbox语法而创建了一个布局,它是从MyFlowLayout派生。在MyFlowLayout中也是支持相似flexbox的一些特性的
* 由于它的属性和flexbox不兼容,因此提供一个新的类MyFlexLayout来彻底支持flexbox.
*/
@interface MyFlexLayout:MyFlowLayout
/**
用于弹盒布局视图自身的布局设置
*/
@property(nonatomic, strong, readonly) id<MyFlexBox> myFlex;
@end
复制代码
从上面的类定义中能够看出这个布局类是从流式布局MyFlowLayout类派生,咱们能够经过类中的myFlex属性来进行弹性布局视图的相关属性设置。myFlex中提供了链式语法以及属性设置语法两种操做形式,您能够选择喜欢的方式来操做和使用弹性布局。下面是属性myFlex的接口MyFlexBox的详细定义:
@protocol MyFlexBox <MyFlexItem>
@property(nonatomic, strong) id<MyFlexBoxAttrs> attrs;
/**
设置或检索伸缩盒对象的子元素在父容器中的位置。默认值:MyFlexDirection_Row
*/
-(id<MyFlexBox> (^)(MyFlexDirection))flex_direction;
/**
设置或检索伸缩盒对象的子元素超出父容器时是否换行。默认值:MyFlexWrap_NoWrap
*/
-(id<MyFlexBox> (^)(MyFlexWrap))flex_wrap;
/**
同时设置检索伸缩盒对象的子元素在父容器中的位置和伸缩盒对象的子元素超出父容器时是否换行。两者经过 | 运算进行组合
*/
-(id<MyFlexBox> (^)(int))flex_flow;
/**
设置或检索弹性盒子元素在主轴(横轴)方向上的对齐方式。可选值为:MyFlexGravity_Flex_Start | MyFlexGravity_Flex_End | MyFlexGravity_Center | MyFlexGravity_Space_Between | MyFlexGravity_Space_Around 中的一个,默认值为MyFlexGravity_Flex_Start
*/
-(id<MyFlexBox> (^)(MyFlexGravity))justify_content;
/**
设置或检索弹性盒子元素在侧轴(纵轴)方向上的对齐方式。可选值为:MyFlexGravity_Flex_Start | MyFlexGravity_Flex_End | MyFlexGravity_Center | MyFlexGravity_Baseline | MyFlexGravity_Stretch中的一个,默认值为MyFlexGravity_Flex_Start
*/
-(id<MyFlexBox> (^)(MyFlexGravity))align_items;
/**
设置或检索弹性盒堆叠伸缩行的对齐方式。可选值为:MyFlexGravity_Flex_Start | MyFlexGravity_Flex_End | MyFlexGravity_Center | MyFlexGravity_Between | MyFlexGravity_Around | MyFlexGravity_Stretch中的一个,默认值为MyFlexGravity_Stretch
*/
-(id<MyFlexBox> (^)(MyFlexGravity))align_content;
/**
指定主轴的子条目的数量。只有在flex_wrap设置为wrap时才有效。默认值是0表示会根据条目的尺寸自动进行换行。
*/
-(id<MyFlexBox> (^)(NSInteger))item_size;
/**
指定布局视图中每页的条目数量。这个值必须是item_size的倍数。
*/
-(id<MyFlexBox> (^)(NSInteger))page_size;
/**
指定布局会根据条目的尺寸自动排列,默认值是NO。
*/
-(id<MyFlexBox> (^)(BOOL))auto_arrange;
/**
设置弹性盒的内边距
*/
-(id<MyFlexBox> (^)(UIEdgeInsets))padding;
/**
设置弹性盒内全部条目视图之间的垂直间距
*/
-(id<MyFlexBox> (^)(CGFloat))vert_space;
/**
设置弹性盒内全部条目视图之间的水平间距
*/
-(id<MyFlexBox> (^)(CGFloat))horz_space;
@end
复制代码
而对于弹性盒视图中的条目子视图(item)来讲则能够经过UIView的一个分类扩展提供的myFlex进行属性设置:
@interface UIView(MyFlexLayout)
/**
用于弹盒视图中的子视图的布局设置。
*/
@property(nonatomic, strong, readonly) id<MyFlexItem> myFlex;
@end
复制代码
条目视图的myFlex属性的实现接口:MyFlexItem的定义以下:
@protocol MyFlexItem
@property(nonatomic, strong, readonly) id<MyFlexItemAttrs> attrs;
@property(nonatomic, weak, readonly) __kindof UIView *view;
/**
视图的宽度设置,若是宽度设置为大于0小于1则代表是相对于父视图宽度的比重值,若是是MyLayoutSize.wrap则代表宽度自适应,若是是MyLayoutSize.fill则代表宽度和父视图相等,若是是MyLayoutSize.empty则代表不设置宽度值。 其余的值就是一个固定宽度值。
*/
-(id<MyFlexItem> (^)(CGFloat))width;
/**
视图的宽度设置,percent代表占用父视图宽度的百分比值,inc代表在百分比值的基础上的增量值。
*/
-(id<MyFlexItem> (^)(CGFloat percent, CGFloat inc))width_percent;
/**
最小宽度限制设置
*/
-(id<MyFlexItem> (^)(CGFloat))min_width;
/**
最大宽度限制设置
*/
-(id<MyFlexItem> (^)(CGFloat))max_width;
/**
视图的高度设置,若是高度设置为大于0小于1则代表是相对于父视图高度的比重值,若是是MyLayoutSize.wrap则代表高度自适应,若是是MyLayoutSize.fill则代表高度和父视图相等,若是是MyLayoutSize.empty则代表不设置高度值,其余的值就是一个固定高度值。
*/
-(id<MyFlexItem> (^)(CGFloat))height;
/**
视图的高度设置,percent代表占用父视图高度的百分比值,inc代表在百分比值的基础上的增量值。
*/
-(id<MyFlexItem> (^)(CGFloat percent, CGFloat inc))height_percent;
/**
最小高度限制设置
*/
-(id<MyFlexItem> (^)(CGFloat))min_height;
/**
最大高度限制设置
*/
-(id<MyFlexItem> (^)(CGFloat))max_height;
//视图的外间距设置。
/**
视图的顶部外间距设置
*/
-(id<MyFlexItem> (^)(CGFloat))margin_top;
/**
视图的底部外间距设置
*/
-(id<MyFlexItem> (^)(CGFloat))margin_bottom;
/**
视图的左边外间距设置
*/
-(id<MyFlexItem> (^)(CGFloat))margin_left;
/**
视图的右边外间距设置
*/
-(id<MyFlexItem> (^)(CGFloat))margin_right;
/**
视图的四周外间距设置
*/
-(id<MyFlexItem> (^)(CGFloat))margin;
/**
视图的可视设置
*/
-(id<MyFlexItem> (^)(MyVisibility))visibility;
//添加到父视图中
-(__kindof UIView* (^)(UIView*))addTo;
//添加子视图
-(id<MyFlexItem> (^)(UIView*))add;
/**
条目在弹盒中的排列顺序,值越大越日后排。
*/
-(id<MyFlexItem> (^)(NSInteger))order;
/**
设置或检索弹性盒的扩展比率。默认值为0表示不扩展
*/
-(id<MyFlexItem> (^)(CGFloat))flex_grow;
/**
设置或检索弹性盒的收缩比率。默认值为1表示当条目尺寸超过弹性盒尺寸后会进行压缩。值越大压缩比越大
*/
-(id<MyFlexItem> (^)(CGFloat))flex_shrink;
/**
设置或检索弹性盒伸缩基准值。默认值为MyFlex_Auto表示由其余属性决定,若是值为大于0小于1则表示相对值,其余为一个固定的尺寸值。
*/
-(id<MyFlexItem> (^)(CGFloat))flex_basis;
/**
设置或检索弹性盒子元素自身在侧轴(纵轴)方向上的对齐方式。可选值为:MyFlexGravity_Flex_Start | MyFlexGravity_Flex_End | MyFlexGravity_Center | MyFlexGravity_Baseline | MyFlexGravity_Stretch中的一个,默认值为MyFlex_Auto
*/
-(id<MyFlexItem> (^)(MyFlexGravity))align_self;
@end
复制代码
从上面的定义中能够看出由于其设置和使用方法都和flexbox规约几乎保持一致,所以对于熟悉flexbox的人来讲使用几乎是零成本。好比咱们用MyFlexLayout来实现下面这个界面:
代码实现以下:
-(void)viewDidLoad{
[super viewDidLoad];
//用链式语法建立一个弹性布局,宽度和父视图一致,高度自适应
MyFlexLayout *layout = MyFlexLayout.new.myFlex
.flex_direction(MyFlexDirection_Row)
.flex_wrap(MyFlexWrap_Wrap)
.align_content(MyFlexGravity_Center)
.align_items(MyFlexGravity_Flex_End)
.vert_space(10)
.horz_space(10)
.padding(UIEdgeInsetsMake(10, 10, 10, 10))
.marign_top(50)
.width(MyLayoutSize.fill)
.height(MyLayoutSize.wrap)
.addTo(self.view);
UILabel *itemA = UILabel.new.myFlex
.width(MyLayoutSize.fill)
.height(30)
.addTo(layout);
UILabel *itemB = UILabel.new.myFlex
.flex_grow(1)
.align_self(MyFlexGravity_Flex_Start)
.height(30)
.addTo(layout);
UILabel *itemC = UILabel.new.myFlex
.flex_grow(1)
.height(40)
.addTo(layout);
UILabel *itemD = UILabel.new.myFlex
.flex_grow(1)
.height(50)
.addTo(layout);
layout.backgroundColor = [UIColor grayColor];
itemA.text = @"A";
itemA.backgroundColor = [UIColor redColor];
itemB.text = @"B";
itemB.backgroundColor = [UIColor greenColor];
itemC.text = @"C";
itemC.backgroundColor = [UIColor blueColor];
itemD.text = @"D";
itemD.backgroundColor = [UIColor yellowColor];
}
复制代码
除了使用链式语法进行布局和条目样式设置外,还能够直接经过属性赋值来进行样式设置。您能够经过MyFlexBox中的attrs以及MyFlexItem中的attrs这两个数据成员来以属性值的形式进行布局的和条目的样式设置。为了更好的演示MyFlexLayout的使用,我在MyLayout的Demo工程中创建了一个Flex布局(FlexLayout)。您能够在那里看到弹性布局相关的全部操做。
👉设想一个场景:某个视图的宽度在竖屏下是屏幕宽度的一半,而在横屏下则是屏幕高度的一半。换句话说就是视图的宽度是屏幕宽度和高度中的最小值的一半。 👉再设想一个场景:某个视图的右边位置但愿跟另外两个视图中最靠右的那个位置对齐,换句话说就是视图的右边位置是另外两个视图右边位置的最大值。
咱们称这种某个视图的位置或者尺寸是一个位置集合或者尺寸集合中的最大值或者最小值的约束为最值约束。用表达式以下:
位置 = MAX(位置1,位置2,位置3,...) 或者 位置 = MIN(位置1,位置2,位置3,...)
尺寸 = MAX(尺寸1,尺寸2,尺寸3,...)或者 尺寸 = MIN(尺寸1,尺寸2,尺寸3,...)
复制代码
MyLayout为了实现对位置最值的支持,在数组类NSArray上创建了一个扩展分类:
//位置最值扩展分类
@interface NSArray(MyLayoutMostPos)
//从数组中获得最小的位置值。要求数组的元素必须是MyLayoutPos或者NSNumber类型
@property(nonatomic, readonly) MyLayoutMostPos *myMinPos;
//从数组中获得最小的位置值。要求数组的元素必须是MyLayoutPos或者NSNumber类型
@property(nonatomic, readonly) MyLayoutMostPos *myMaxPos;
@end
复制代码
咱们能够经过数组中的myMinPos和myMaxPos两个只读属性来分别获取最小值和最大值的最值对象,获取位置最值对象时要求数组中的元素只能是NSNumber以及MyLayoutPos类的实例对象,它代表最值是这些具体数字或者位置对象中的最大或者最小值。好比下面的代码:
//A视图的左边位置是B视图左边位置,C视图右边位置,100这三个值中的最小的一个
A.leftPos.equalTo(@[B.leftPos, C.rightPos, @100].myMinPos);
//A视图的垂直居中位置是B视图顶部位置、100、C视图底部位置这三个值中的最大一个。
A.centerYPos.equalTo(@[B.topPos, @100, C.bottomPos].myMaxPos);
//A视图的左边位置是B视图左边位置+20、C视图右边位置-20 这两个位置中的最大一个。
A.leftPos.equalTo(@[B.leftPos.clone(20), C.rightPos.clone(20)].myMaxPos);
复制代码
在上面的最后一个例子中咱们看到使用了MyLayoutPos对象的clone方法,这个方法的做用是clone一个新的对象并带上必定的偏移值。MyLayoutPos中的clone方法就是专门为最值约束使用的,主要为了解决那些获取最值时但愿在某个位置的偏移的场景。
目前只有相对布局下的子视图才支持位置最值约束设置,其余布局下的子视图不支持。同时在设置位置最值约束的时候,要求数组内的元素的位置约束计算必需要在当前视图的位置约束计算以前完成,不然获得的结果将未可知。
MyLayout为了实现对尺寸最值的支持,在数组类NSArray上创建了一个扩展分类:
//尺寸最值扩展分类
@interface NSArray(MyLayoutMostSize)
//从数组中获得最小的尺寸值。要求数组的元素必须是MyLayoutSize或者NSNumber类型
@property(nonatomic, readonly) MyLayoutMostSize *myMinSize;
//从数组中获得最大的尺寸值。要求数组的元素必须是MyLayoutSize或者NSNumber类型
@property(nonatomic, readonly) MyLayoutMostSize *myMaxSize;
@end
复制代码
咱们能够经过数组中的myMinSize和myMaxSize两个只读属性来分别获取最小值和最大值的最值对象。获取尺寸最值对象时要求数组中的元素只能是NSNumber以及MyLayoutSize类的实例对象,它代表最值是这些具体数字或者尺寸对象中的最大或者最小值。好比下面的例子:
//A视图的宽度是B视图的宽度,C视图的高度,100这三个值中的最小的一个
A.widthSize.equalTo(@[B.widthSize, C.heightSize, @100].myMinSize);
//A视图的高度是A视图自身高度,B视图高度的一半加20,100这三个值中的最大一个。
A.heightSize.equalTo(@[@(MyLayoutSize.wrap), B.heightSize.clone(20, 0.5), @100].myMaxSize);
复制代码
在上面的最后一个例子中咱们看到使用了MyLayoutSize对象的clone方法,这个方法的做用是clone一个新的尺寸对象并带上必定的倍数和增量值。咱们还能够用一个特殊的尺寸值MyLayoutSize.wrap在最值数组中,它代表自身的尺寸也参与最值比较中。
最值尺寸约束设置,能够应用在全部布局下的视图中以及布局自己。可是在使用最值约束时,要求数组内的元素的尺寸约束计算必需要在当前视图的尺寸约束计算以前完成,不然获得的结果将未可知。
在一些场景中咱们但愿当全部子视图的尺寸总和超过布局视图的尺寸时为了能让全部子视图都获得彻底的显示而须要对子视图的尺寸进行适当的压缩,对于位置也是如此。这时候就须要应用到视图尺寸和位置的压缩技术了。举例来讲:假如一个横向的水平线性布局的宽度是120,里面的三个子视图A,B,C的宽度和间距分别为:A左间距20,A宽度30, B左间距10,B宽度60, C左间距20,C宽度40。在不进行压缩时界面显示的效果以下:
为了实现压缩的能力在MyLayoutSize和MyLayoutPos两个类中分别提供了一个新的属性shrink。这个属性值的意义代表当位置和尺寸超过布局视图时的压缩比重值。值越大代表被压缩的比重越大,值为0代表不会被压缩。系统默认的压缩比重值被设置为0。就以上面的例子来讲假如咱们分别设置视图A,B,C的宽度和间距的压缩比例值以下:
A.leftPos.equalTo(@20).shrink = 1;
A.widthSize.equalTo(@30).shrink = 1;
B.leftPos.equalTo(@10);
B.widthSize.equalTo(@50).shrink = 2;
C.leftPos.equalTo(@20).shrink = 1;
C.widthSize.equalTo(@40);
复制代码
这样在不压缩的状况全部子视图的间距和宽度总和为:20+30+10+50+20+40 = 170 ,减去布局视图的宽度120后超出了50。而上述设置的压缩比重值的总和为:1+1+2+1 = 5。所以最终的每一个位置和尺寸被压缩后的结果值分别为:
A的左间距 = 20 - 50 * (1/5.0) = 10
A的宽度 = 30 - 50 *(1/5.0) = 20
B的左间距 = 10 不会被压缩
B的宽度 = 50 - 50 *(2/5.0) = 30
C的左间距 = 20 - 50 *(1/5.0) = 10
C的宽度 = 40 不会被压缩
复制代码
最终界面展现的效果以下:
目前只有线性布局、框架布局、流式布局、表格布局、弹性布局下的子视图的宽度和尺寸才支持压缩特性,其余布局中的子视图不支持。并且压缩的特性只有在全部子视图的尺寸超出的时候才生效不然是不生效的。
须要注意的是弹性布局中的子视图的压缩特性通常不经过直接设置shrink属性来实现,而是经过设置flex_shrink来实现。
视图的压缩属性和视图的weight属性的区别是前者是用于视图尺寸的压缩,然后者则是用于视图尺寸的拉伸。具体的weight属性的使用请参考相关的文档和DEMO。
咱们能够经过设置布局视图的gravity属性来设置布局内子视图的总体停靠和对齐特性。在新版本中为了实现flexbox中的一些能力,特别增长了4个停靠属性:
MyGravity_Horz_Around
MyGravity_Horz_Stretch
MyGravity_Vert_Around
MyGravity_Vert_Stretch
复制代码
在之前的版本中若是咱们但愿拉伸子视图之间的间距时能够经过MyGravity_Horz_Between或者MyGravity_Vert_Between来实现。拉伸间距时第一个以及最后一个子视图离父布局视图的间距将是0,而子视图之间的间距将会平分剩余的空间。而MyGravity_Horz_Around和MyGravity_Vert_Around则是第一个和最后一个子视图离父布局视图的间距是子视图之间的间距的一半。下面的界面展现了Between和Around的区别:
在之前的版本中若是咱们但愿填充拉伸全部子视图之间的尺寸来占满布局视图的尺寸时咱们能够经过MyGravity_Horz_Fill或者MyGravity_Vert_Fill来实现。这两个停靠属性的功能会将布局视图中的剩余空间均匀的分配到全部子视图(设置有尺寸自适应的布局视图除外)的尺寸之上,而无论子视图是否设置了尺寸约束与否,从而实现子视图之间的尺寸拉伸效果。 而MyGravity_Horz_Stretch以及MyGravity_Vert_Stretch则效果和填充是同样的,只不过它只会拉伸那些没有设置尺寸约束的子视图以及设置了尺寸自适应的子视图(设置了尺寸自适应的布局视图除外)。下面的界面展现了Fill 和Stretch的区别:
目前只有线性布局、流式布局、浮动布局、框架布局、弹性布局中才具备总体停靠和对齐设置的效果,其余布局不支持。
在一些应用中咱们能够经过拖放功能来调整子视图的位置或者进行一些其余处理。MyLayout之前的版本中实现了这么一个DEMO。新版本中咱们将DEMO中拖放的能力进行了抽象而造成了一个新的拖放类:MyLayoutDragger。 在使用拖放类实现拖放功能时须要以下几个步骤:
从布局视图类中经过createLayoutDragger方法建立一个拖放类实例对象,并保存起来。
对添加到布局视图中的子视图分别添加以下事件:
[能够被拖放的子视图 addTarget:self action:@selector(handleTouchDrag:withEvent:) forControlEvents:UIControlEventTouchDragInside]; //注册拖动事件。
[能够被拖放的子视图 addTarget:self action:@selector(handleTouchDrag:withEvent:) forControlEvents:UIControlEventTouchDragOutside]; //注册外面拖动事件。
[能够被拖放的子视图 addTarget:self action:@selector(handleTouchDown:withEvent:) forControlEvents:UIControlEventTouchDown]; //注册按下事件
[能够被拖放的子视图 addTarget:self action:@selector(handleTouchUp:withEvent:) forControlEvents:UIControlEventTouchUpInside]; //注册抬起事件
[能够被拖放的子视图 addTarget:self action:@selector(handleTouchUp:withEvent:) forControlEvents:UIControlEventTouchCancel]; //注册终止事件
复制代码
- (IBAction)handleTouchDown:(id)sender withEvent:(UIEvent*)event {
//拖动子视图开始处理。
[self.dragger dragView:sender withEvent:event];
}
- (IBAction)handleTouchUp:(id)sender withEvent:(UIEvent*)event {
//中止子视图拖动处理。
[self.dragger dropView:sender withEvent:event];
}
- (IBAction)handleTouchDrag:(id)sender withEvent:(UIEvent*)event {
//子视图拖动中处理。
[self.dragger dragginView:sender withEvent:event];
}
复制代码
这样就能够自动实现对子视图的拖放功能了。咱们还能够经过拖放器对象来进行一些特性化设置,好比能够设置拖放的动画时长、能够设置哪些子视图在拖放时不会移动、以及是否能够在拖放时实现悬停效果等等。具体的演示代码请参考DEMO工程中的:FLLTest3ViewController
iOS13之后提供了黑白模式适配的能力。对于MyLayout来讲由于具备对边界线的支持的能力,边界线内部实现是采用的CALayer来实现,而CALayer对颜色的输入是CGColorRef对象,所以为了支持黑白模式适配也进行版本升级,以便让边界线也能实现黑白模式适配的能力。
在流式布局中咱们能够经过设置gravity属性和arrangedGravity属性来设置布局内子视图的总体停靠特性以及行内子视图之间的对齐特性。然而在实际中咱们可能但愿某些行的停靠对齐属性和其余行是不同的,也就是但愿可以定制每行的停靠对齐属性。这样经过行的停靠对齐属性就能够不经过插入占位视图或者不须要进行多层嵌套来实现咱们的界面需求。(若是用线性布局来实现多行多列则须要进行多个布局层次的嵌套处理)。就好比下面的这个界面:
为了支持行内对齐停靠自定义处理,流式布局提供了一个新的属性:
/**
单独为某一行定制的水平和垂直停靠对齐属性,默认状况下布局视图的gravity和arrangedGravity做用于全部行以及行内的停靠对齐。若是你想单独定制某一行的停靠对齐方式时
能够经过设置这个block属性。
lineGravity的入参分别是布局对象、当前行的索引(0开始)、当前行的条目视图数量、是不是最后一行四个参数。
函数返回的是此行以及行内的停靠对齐方式,若是返回MyGravity_None则表示使用布局默认的gravity和arrangedGravity停靠对齐属性。
*/
@property(nonatomic, copy) MyGravity (^lineGravity)(MyFlowLayout *layout, NSInteger lineIndex, NSInteger itemCount, BOOL isLastLine);
复制代码
咱们能够经过这个block的形式的属性来进行行内停靠对齐的自定义处理。具体的行内对齐停靠的使用能够参考DEMO工程中的FLLTest4ViewController和FLLTest9ViewController
新版本中对于垂直流式布局以及垂直浮动布局中的每一行子视图之间新增长了对基线对齐的支持。你能够经过设置流式布局的arrangedGravity的值为MyGravity_Vert_Baseline。以及设置浮动布局的gravity的值为MyGravity_Vert_Baseline来实现行内的基线对齐。其中基线的标准视图是行内的第一个文本视图。这样整个布局体系中水平线性布局、相对布局、垂直流式布局、垂直浮动布局、弹性布局均可以实现行内基线对齐的能力了。
动画的适当使用会加强用户的体验效果。MyLayout中若是咱们调整了子视图的约束后但愿有动画效果,那么能够调用布局视图的方法:
/**
*设置布局时的动画。并指定时间,选项,和完成时的处理,这个动画只会在调用后的下次布局时执行一次。
@param duration 指定动画的时间间隔
*/
-(void)layoutAnimationWithDuration:(NSTimeInterval)duration;
-(void)layoutAnimationWithDuration:(NSTimeInterval)duration options:(UIViewAnimationOptions)options completion:(void (^)(BOOL finished))completion;
复制代码
上述的方法调用后系统会在下一个布局周期发生时自动执行动画效果。在使用动画方法时咱们能够指定动画的时长以及一些选项还有动画完成后的回调处理。
所谓尺寸自适应就是视图的尺寸根据自身的内容和视图内的子视图的尺寸来动态肯定自身的尺寸,从而造成所谓的包裹的效果。尺寸自适应的目的是为了让视图中的全部内容都获得彻底的展现。
视图的自适应尺寸也算是一种特殊的尺寸。在老版本中若是咱们想让某个视图的宽度自适应时能够经过设置wrapContentWidth 属性为YES便可,而让视图的高度自适应时则能够经过设置wrapContentHeight属性为YES便可。而要设置视图的具体尺寸时则须要经过widthSize或者heightSize来实现。为了设置尺寸而分别使用两个属性来操做这是不合理的方式。所以新版本中再也不建议使用wrapContentWidth和wrapContentHeight以及wrapContentSize来设置尺寸自适应了,而是建议使用新的设置方式。
新版本中将尺寸的自适应设置合并到了widthSize和heightSize中。由于自适应也是一种尺寸值,只不过是特殊值。下面的代码是老版本和新版本的设置方法:
//老的方法
A.wrapContentWidth = YES;
//新的方法1
A.widthSize.equalTo(@(MyLayoutSize.wrap));
//新的方法2
A.myWidth = MyLayoutSize.wrap;
//老的方法:
B.wrapContentSize = YES;
//新的方法:
B.mySize = CGSizeMake(MyLayoutSize.wrap, MyLayoutSize.wrap);
//老的读取和判断的方法
if (A.wrapContentWidth) {}
//新的判断和读取的方法1
if (A.widthSize.isWrap){}
//新的判断和读取的方法2
if (A.myWidth == MyLayoutSize.wrap){}
复制代码
在新版本中咱们除了能够设置MyLayoutSize.wrap为尺寸自适应外,在MyLayoutSize类中还定义了另外两个类属性:MyLayoutSize.fill和MyLayoutSize.empty。它们也能够用来简化尺寸的设置。
好比下面的代码是等价的:
A.widthSize.equalTo(@(MyLayoutSize.wrap)) <==> A.myWidth = MyLayoutSize.wrap;
A.widthSize.equalTo(A.superview.widthSize) <==> A.myWidth = MyLayoutSize.fill;
A.widthSize.equalTo(nil) <==> A.myWidth = MyLayoutSize.empty;
复制代码
因为语法篇幅MyLayout中的不少功能都没有介绍到,若是您想进一步了解的话能够到github中下载对应的工程demo来进行详细了解。
👉MyLayout: github.com/youngsoft/M…
👉TangramKit: github.com/youngsoft/T…
👉个人掘金主页: juejin.im/user/593fb4…