iOS的布局体系-流式布局MyFlowLayout
wptr33 2025-04-11 08:29 12 浏览
iOS布局体系的概览
在我的CSDN博客中的几篇文章分别介绍MyLayout布局体系中的视图从一个方向依次排列的线性布局(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
相关推荐
- Linux高性能服务器设计
-
C10K和C10M计算机领域的很多技术都是需求推动的,上世纪90年代,由于互联网的飞速发展,网络服务器无法支撑快速增长的用户规模。1999年,DanKegel提出了著名的C10问题:一台服务器上同时...
- 独立游戏开发者常犯的十大错误
-
...
- 学C了一头雾水该咋办?
-
学C了一头雾水该怎么办?最简单的方法就是你再学一遍呗。俗话说熟能生巧,铁杵也能磨成针。但是一味的为学而学,这个好像没什么卵用。为什么学了还是一头雾水,重点就在这,找出为什么会这个样子?1、概念理解不深...
- C++基础语法梳理:inline 内联函数!虚函数可以是内联函数吗?
-
上节我们分析了C++基础语法的const,static以及this指针,那么这节内容我们来看一下inline内联函数吧!inline内联函数...
- C语言实战小游戏:井字棋(三子棋)大战!文内含有源码
-
井字棋是黑白棋的一种。井字棋是一种民间传统游戏,又叫九宫棋、圈圈叉叉、一条龙、三子旗等。将正方形对角线连起来,相对两边依次摆上三个双方棋子,只要将自己的三个棋子走成一条线,对方就算输了。但是,有很多时...
- C++语言到底是不是C语言的超集之一
-
C与C++两个关系亲密的编程语言,它们本质上是两中语言,只是C++语言设计时要求尽可能的兼容C语言特性,因此C语言中99%以上的功能都可以使用C++完成。本文探讨那些存在于C语言中的特性,但是在C++...
- 在C++中,如何避免出现Bug?
-
C++中的主要问题之一是存在大量行为未定义或对程序员来说意外的构造。我们在使用静态分析器检查各种项目时经常会遇到这些问题。但正如我们所知,最佳做法是在编译阶段尽早检测错误。让我们来看看现代C++中的一...
- ESL-通过事件控制FreeSWITCH
-
通过事件提供的最底层控制机制,允许我们有效地利用工具箱,适时选择使用其中的单个工具。FreeSWITCH是一个核心交换与混合矩阵,它周围有几十个模块提供各种功能特性。我们完全控制了所有的即时信息,这些...
- 物理老师教你学C++语言(中篇)
-
一、条件语句与实验判断...
- C语言入门指南
-
当然!以下是关于C语言入门编程的基础介绍和入门建议,希望能帮你顺利起步:C语言入门指南...
- C++选择结构,让程序自动进行决策
-
什么是选择结构?正常的程序都是从上至下顺序执行,这就是顺序结构...
- C++特性使用建议
-
1.引用参数使用引用替代指针且所有不变的引用参数必须加上const。在C语言中,如果函数需要修改变量的值,参数必须为指针,如...
- C++程序员学习Zig指南(中篇)
-
1.复合数据类型结构体与方法的对比C++类:...
- 研一自学C++啃得动吗?
-
研一自学C++啃得动吗?在开始前我有一些资料,是我根据网友给的问题精心整理了一份「C++的资料从专业入门到高级教程」,点个关注在评论区回复“888”之后私信回复“888”,全部无偿共享给大家!!!个人...
- C++关键字介绍
-
下表列出了C++中的常用关键字,这些关键字不能作为变量名或其他标识符名称。1、autoC++11的auto用于表示变量的自动类型推断。即在声明变量的时候,根据变量初始值的类型自动为此变量选择匹配的...
- 一周热门
-
-
C# 13 和 .NET 9 全知道 :13 使用 ASP.NET Core 构建网站 (1)
-
因果推断Matching方式实现代码 因果推断模型
-
git pull命令使用实例 git pull--rebase
-
git pull 和git fetch 命令分别有什么作用?二者有什么区别?
-
面试官:git pull是哪两个指令的组合?
-
git 执行pull错误如何撤销 git pull fail
-
git fetch 和git pull 的异同 git中fetch和pull的区别
-
git pull 之后本地代码被覆盖 解决方案
-
还可以这样玩?Git基本原理及各种骚操作,涨知识了
-
git命令之pull git.pull
-
- 最近发表
- 标签列表
-
- git pull (33)
- git fetch (35)
- mysql insert (35)
- mysql distinct (37)
- concat_ws (36)
- java continue (36)
- jenkins官网 (37)
- mysql 子查询 (37)
- python元组 (33)
- mysql max (33)
- vba instr (33)
- mybatis 分页 (35)
- vba split (37)
- redis watch (34)
- python list sort (37)
- nvarchar2 (34)
- mysql not null (36)
- hmset (35)
- python telnet (35)
- python readlines() 方法 (36)
- munmap (35)
- docker network create (35)
- redis 集合 (37)
- python sftp (37)
- setpriority (34)