OC - 30.如何封装自定义布局
概述
- 对于经常使用的控件或类,通常将其分装为一个单独的类来供外界使用,以此达到事半功倍的效果
- 由于分装的类不依赖于其他的类,所以若要使用该类,可直接将该类拖进项目文件即可
- 在进行分装的时候,通常需要用到代理设计模式
代理设计模式
代理设计模式的组成
客户类(通常作为代理)
- 通常委托这是角色来完成业务逻辑
真实角色
- 将客户类的业务逻辑转化为方法列表,即代理协议
代理协议
- 定义了需要实现的业务逻辑
- 定义了一组方法列表,包括必须实现的方法或选择实现的方法
- 代理协议是代理对象所要遵循一组规则
代理角色
- 若要作为代理,需要遵守代理协议,并且实现必须实现的代理方法
- 代理角色可以通过调用代理协议中的方法完成业务逻辑,也可以附加自己的操作
文字描述通常是抽象的,一下通过图示来阐述代理设计模式

自定义布局类的封装
业务逻辑
如图

布局每个cell的业务逻辑
由于设置每个cell的布局属性的业务逻辑较复杂,特附上如下思维导图

封装思路
封装需要根据客户类业务逻辑需求来提供接口- 通过代理协议的可选实现的方法获取的属性值的属性,需要设置默认值
- 未提供默认值的且必须使用的属性,需要通过必须实现的方法来获得
自定义布局提供的接口
可选- 列数
- 列之间的间距
- 行之间的间距
- 内边距
自定义布局提供的接口
必选- 每个元素的高度,宽度可以通过列数和列间距计算得到
封装步骤
设置代理协议,提供接口
//声明LYPWaterFlowLayout为一个类
@class LYPWaterFlowLayout;
@protocol LYPWaterFlowLayoutDelegate <NSObject>
//必须实现的方法
@required
/**获取瀑布流每个元素的高度*/
- (CGFloat)waterFlowLayout:(LYPWaterFlowLayout *)waterFlowLayout heightForItemAtIndex:(NSInteger)index itemWith:(CGFloat)itemWith;
//可选实现的方法
@optional
/**获取瀑布流的列数*/
- (NSInteger)columnCountInWaterFlowLayout:(LYPWaterFlowLayout *)waterFlowLayout;
/**获取瀑布流列间距*/
- (CGFloat)columnMarginInWaterFlowLayout:(LYPWaterFlowLayout *)waterFlowLayout;
/**获取瀑布流的行间距*/
- (CGFloat)rowMarginInWaterFlowLayout:(LYPWaterFlowLayout *)waterFlowLayout;
/**获取瀑布流的内边距*/
- (UIEdgeInsets)edgeInsetsInWaterFlowLayout:(LYPWaterFlowLayout *)waterFlowLayout;
@end设置代理属性
@interface LYPWaterFlowLayout : UICollectionViewLayout
/**代理*/
@property (nonatomic, weak) id<LYPWaterFlowLayoutDelegate> delegate;
@end设置通过可选代理方法获取属性值的属性的默认值
/**默认的列数*/
static const NSInteger LYPDefaultColumnCount = 3;
/**默认每一列之间的间距*/
static const CGFloat LYPDefaultColumMargin = 10;
/**默认每一行之间的间距*/
static const CGFloat LYPDefaultRowMargin = 10;
/**默认边缘间距*/
static const UIEdgeInsets LYPDefaultEdgeInsets = {10, 10, 10, 10};设置通过可选代理方法获取属性值的属性的访问方式
若代理提供属性值,则忽略默认值- (NSInteger)columnCount
{
//判断代理是否实现了获取列数的可选方法
if ([self.delegate respondsToSelector:@selector(columnCountInWaterFlowLayout:)])
{
//实现,返回通过代理设置的列数
return [self.delegate columnCountInWaterFlowLayout:self];
}
else
{
//为实现,返回默认的列数
return LYPDefaultColumnCount;
}
}- 注:其他属性值的获取与上述方法几乎完全相同,不再赘述
设置布局
设置需要的成员属性
/**所有cell的布局属性*/
@property (nonatomic, strong) NSMutableArray *attrsArray;
/**所有列的当前高度*/
@property (nonatomic, strong) NSMutableArray *columnHeights;通过懒加载的方式初始化成员属性
/**--attrsArray--懒加载*/
- (NSMutableArray *)attrsArray
{
if (_attrsArray == nil)
{
_attrsArray = [NSMutableArray array];
}
return _attrsArray;
}
/**--columnHeights--懒加载*/
- (NSMutableArray *)columnHeights
{
if (_columnHeights == nil)
{
_columnHeights = [NSMutableArray array];
}
return _columnHeights;
}初始化布局
- (void)prepareLayout
{
[super prepareLayout]; /**清除之前跟布局相关的所有属性,重新设置新的布局*/
//清除之前计算的所有列的高度
[self.columnHeights removeAllObjects];
//设置所有列的初始高度
for (NSInteger i = 0; i<self.columnCount; i++)
{
self.columnHeights[i] = @(self.edgeInsets.top);
}
//清除之前所有的布局属性
[self.attrsArray removeAllObjects]; /**开始创建每一个cell对应的布局属性*/
NSInteger count = [self.collectionView numberOfItemsInSection:0];
for (NSInteger i = 0; i<count; i++)
{
NSIndexPath *indexPath = [NSIndexPath indexPathForItem:i inSection:0];
//获取indexPath位置cell对应的布局属性
UICollectionViewLayoutAttributes *attrs = [self layoutAttributesForItemAtIndexPath:indexPath];
//将indexPath位置的cell的布局属性添加到所有cell的布局属性数组中
[self.attrsArray addObject:attrs];
}
}返回包含所有cell的布局属性的数组
- (nullable NSArray<UICollectionViewLayoutAttributes *> *)layoutAttributesForElementsInRect:(CGRect)rect
{
return self.attrsArray;
}设置每一个cell的布局属性
- (nullable UICollectionViewLayoutAttributes *)layoutAttributesForItemAtIndexPath:(nonnull NSIndexPath *)indexPath
{
//获取indexPath位置的布局属性
UICollectionViewLayoutAttributes *attrs = [UICollectionViewLayoutAttributes layoutAttributesForCellWithIndexPath:indexPath]; /**设置cell布局属性的frame*/ /***确定cell的尺寸***/
//获取collectionView的宽度
CGFloat collectionViewWidth = self.collectionView.frame.size.width;
//cell宽度
CGFloat width = ((collectionViewWidth - self.edgeInsets.left - self.edgeInsets.right - (self.columnCount - 1) * self.columMargin)) / self.columnCount;
//cell高度
CGFloat height = [self.delegate waterFlowLayout:self heightForItemAtIndex:indexPath.item itemWith:width]; /***设置cell的位置***/
NSInteger destColumn = 0;
CGFloat minColumnHeight = [self.columnHeights[0] doubleValue];
for (NSInteger i = 1; i<self.columnCount; i++)
{
CGFloat columnHeight = [self.columnHeights[i] doubleValue];
if (minColumnHeight > columnHeight)
{
minColumnHeight = columnHeight;
destColumn = i;
}
}
//计算cell的位置
CGFloat x = self.edgeInsets.left + destColumn * (width + self.columMargin);
CGFloat y = minColumnHeight;
//判断是不是第一行
if (y != self.edgeInsets.top)
{
//若不是第一行,需要加上行间距
y += self.rowMargin;
} /**给cell的布局属性的frame赋值*/
attrs.frame = CGRectMake(x, y, width, height); //更新最短那列的高度
self.columnHeights[destColumn] = @(CGRectGetMaxY(attrs.frame)); /**返回indexPath位置的cell的布局属性*/
return attrs;
}设置collectionView内容的尺寸
- (CGSize)collectionViewContentSize
{
//获取最高的那一列的高度
CGFloat maxColumnHeight = [self.columnHeights[0] doubleValue];
for (NSInteger i = 1; i<self.columnCount; i++)
{
CGFloat columnHeight = [self.columnHeights[i] doubleValue];
if (maxColumnHeight < columnHeight)
{
maxColumnHeight = columnHeight;
}
}
//返回collectionView的contentSize,高度为最高的高度加上一个行间距
return CGSizeMake(0, maxColumnHeight + self.rowMargin);
}
OC - 30.如何封装自定义布局的更多相关文章
- OC - 31.通过封装的自定义布局快速实现商品展示
概述 实现效果 设计思路 采用MVC架构,即模型—视图-控制器架构 使用MJExtension框架实现字典转模型 使用MJRefresh框架实现上拉和下拉刷新 上拉刷新,加载新的数据 下拉刷新,加载更 ...
- OC - 29.自定义布局实现瀑布流
概述 瀑布流是电商应用展示商品通常采用的一种方式,如图示例 瀑布流的实现方式,通常有以下几种 通过UITableView实现(不常用) 通过UIScrollView实现(工作量较大) 通过UIColl ...
- Swift - 使用网格(UICollectionView)的自定义布局实现复杂页面
网格UICollectionView除了使用流布局,还可以使用自定义布局.实现自定义布局需要继承UICollectionViewLayout,同时还要重载下面的三个方法: 1 2 3 4 5 6 7 ...
- Collection View 自定义布局(custom flow layout)
Collection view自定义布局 一般我们自定义布局都会新建一个类,继承自UICollectionViewFlowLayout,然后重写几个方法: prepareLayout():当准备开始布 ...
- AcitonBar 自定义布局
Android系统中ActionBar默认的布局不美观且难于控制,通过为ActionBar自定义布局的方式可以灵活控制ActionBar. 自定义Activity主题和ActionBar样式 在新建的 ...
- Xamarin自定义布局系列——瀑布流布局
Xamarin.Forms以Xamarin.Android和Xamarin.iOS等为基础,自己实现了一整套比较完整的UI框架,包含了绝大多数常用的控件,如下图 虽然XF(Xamarin.Forms简 ...
- Flutter自定义布局套路
开始 在Android中我们要实现一个布局需要继承ViewGroup, 重写其中的onLayout和onMeasure方法. 其中onLayout负责给子控件设置布局区域, onMeaseure度量子 ...
- ActionBar 自定义布局定义
ActionBar 自定义布局定义 Android系统中ActionBar默认的布局不美观且难于控制,通过为ActionBar自定义布局的方式可以灵活控制ActionBar. 效果: 工具/原料 ...
- 干货之UIButton的title和image自定义布局
当需要实现一个自定义布局图片和标题的按钮时候,不知道有多少少年直接布局了UIButton,亦或是自定义一个UIView,然后以空白UIButton.UILabel.UIImageVew作为subVie ...
随机推荐
- 显示 SQLite 日志
通过在 Logcat 查看 SQL 执行语句可以帮助你调试 SQLite 问题, 使用 ADB SHELL 执行如下命令即可在 Logcat 输出 SQL 执行日志: adb shell setpro ...
- leetcode面试准备:Summary Ranges
1 题目 Given a sorted integer array without duplicates, return the summary of its ranges. For example, ...
- IPCS资源
ipcrm用法 ipcrm -M shmkey 移除用shmkey创建的共享内存段 ipcrm -m shmid 移除用shmid标识的共享内存段 ipcrm -Q msgkey 移除用ms ...
- canvas-画图改进版
前几天在canvas——画板中做了个很简陋的画板,只能画简单的线条,可以选择颜色和线条粗度,今天在此简陋的画板上增加了新的形状,撤销,保存,橡皮擦等功能,虽然功能还是很简单,刚接触canvas,过程中 ...
- 【转】[WCF REST] 帮助页面与自动消息格式(JSON/XML)选择
可以说WebHttpBinding和WebHttpBehavior是整个Web HTTP编程模型最为核心的两个类型,前者主要解决消息编码问题,而余下的工作基本上落在了终结点行为WebHttpBehav ...
- git bash【初级入门篇】
最近公司打算使用git代替之前的svn版本控制工具,趁此机会打算好好学学git,这个号称当今世界最牛的分布式版本控制工具. 一.[git和svn的主要区别] 1.去中心化 svn以及微软的TFS均采用 ...
- [转载]jquery的extend和fn.extend
jQuery为开发插件提拱了两个方法,分别是: jQuery.fn.extend(object); jQuery.extend(object); jQuery.extend(object); 为扩展j ...
- sshd_config 配置文件
Ssh-server 服务端 sshd_concfig Port Protocol HostKey /etc/ssh/ssh_host_rsa_key HostKey /etc/ssh/ssh_hos ...
- Java正则表达式(1)
String类的三个内建正则表达式工具: 1.matches()方法 示例:检查一个句子是否以大写字母开头,以句号结尾 public static boolean checkFormat(String ...
- hdu 1081 矩阵最大连续子序列
问题描述:二位平面图,每一个坐标都有值,正值或负值,求任意矩形中和的最大值问题 解决方案:求解图中每一个坐标为起点,求任意长度宽度的矩形的和 #include<iostream> #inc ...