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 ...
随机推荐
- 《深度探索c++对象模型》chapter3 Data语意学
一个空的class:如 class X{} ; sizeof(X)==1; sizeof为什么为1,他有一个隐晦的1 byte,那是被编译器安插进去的一个char,这使得class2的两个object ...
- bzoj2743
其实和bzoj1878类似只不过要求的是区间内数量多于1个的数字种数其实还是按照bzoj1878做只不过我们是把每一种数字下一个出现的位置+1,并把这个位置置为0 ..] of longint; ma ...
- Node.js权威指南 (1) - Node.js介绍
1.1 Node.js概述 / 2 1.1.1 使用Node.js能够解决什么问题 / 2 1.1.2 实现高性能服务器 / 2 1.1.3 非阻塞型I/O及事件环机制 / 2 1.1.4 Node. ...
- 【转】SendMessage及WPRAME、LPARAME
原文网址:http://www.cnblogs.com/renyuan/archive/2012/11/26/2789103.html SendMessage及WPRAME.LPARAME typed ...
- NVIDIA CG语言 函数之所有数学类函数(Mathematical Functions)
数学类函数(Mathematical Functions) abs(x) 返回标量和向量x的绝对值 如果x是向量,则返回每一个成员的绝对值 acos(x) 返回标量和向量x的反余弦 x的范围是[-1, ...
- CentOS7.0分布式安装HADOOP 2.6.0笔记-转载的
三台虚拟机,IP地址通过路由器静态DHCP分配 (这样就无需设置host了). 三台机器信息如下 - 1. hadoop-a: 192.168.0.20 #master 2. ha ...
- string.Format字符串格式说明
先举几个简单的应用案例: 1.格式化货币(跟系统的环境有关,中文系统默认格式化人民币,英文系统格式化美元) string.Format("{0:C}",0.2) 结果为:¥0.20 ...
- AFNetworking2.0 NSHipster翻译
AFNetworking 是当前 iOS 和 Mac OS X 开发中最广泛使用的开源项目之一.它帮助了成千上万叫好又叫座的应用,也为其它出色的开源库提供了基础.这个项目是社区里最活跃.最有影响力的项 ...
- spring项目中监听器作用-ContextLoaderListener(转)
1 spring框架的启动入口 ContextLoaderListener 2 作用:在启动Web 容器时,自动装配Spring applicationContext.xml 的配置信息. 因为它实现 ...
- Secant Method (Website)
Secant Method: https://www.youtube.com/watch?v=qC9xnsfOd30 Secant Method : http://mathworld.wolfram ...