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 ...
随机推荐
- PendingIntent的Flags
PendingIntent是一个Intent的描述.包装,给予了这个PendingIntent 的组件在指定的事件发生或指定的时间到达时启动Activty.Service或者Broadcast. 根据 ...
- 10.在Global全局文件中的Application_BeginRequest示例
只要有人访问本网站,都要执行全局文件的Application_BeginRequest事件.因此我们可以防盗链. 示例要求:凡不是网站本机登录的都给客户端提示,用图片显示. 分析:由于网页在加载时不是 ...
- ORACLE数据缓冲区DB cache
DB CACHE是以数据块为单位组织的缓冲区,数据库刚刚启动的时候,DB CACHE中几乎没有用户数据的缓冲,当会话访问数据库中的表或索引时,首先会检查DB CACHE中是否存在该数据,如果不存在,就 ...
- 利用ROWID快速执行关联更新
一.构造相关表P1,P2 create table p1(id int,name char(10)); create table p2(id int,name char(10)); 二.批量插入数据 ...
- java web判断是否登录
在使用session的情况下,如何判断是否已登录呢? 1,登录成功的后续操作 session.setAttribute(Constant2.SESSION_KEY_LOGINED_USER, user ...
- 关于View和VIewController的关系和理解
之前看过关老师的视频之后就有写关于视图切换的程序,不过那是很久之前了,那时候也不知道什么是View,什么是VIewController,就知道照着写.这次项目的时候,我又碰到了这个问题,我自以为比较能 ...
- 《Linear Algebra and Its Applications》-chaper6-正交性和最小二乘法-最小二乘问题
最小二乘问题: 结合之前给出向量空间中的正交.子空间W.正交投影.正交分解定理.最佳逼近原理,这里就可以比较圆满的解决最小二乘问题了. 首先我们得说明一下问题本身,就是在生产实践过程中,对于巨型线性方 ...
- 如何高性能的给UIImageView加个圆角?(不准说layer.cornerRadius!)
豆电雨 搬砖自味精:http://awhisper.github.io/2016/03/12/滚动圆角卡顿刨根问底/ 使用Quartz2D直接绘制图片 步骤: a.创建目标大小(cropWidth, ...
- 找不到这个cache.properties缓存文件
Android Studio在导入第三库同步时报错: C:\Users\Administrator\.gradle\caches\2.4\scripts\asLocalRepo88_4u65z0u2 ...
- UBUNTU 札记(53条经验)
adobe_pdf 菜单栏 /etc/profile 是一个global config file,会影响系统全局用户,如果你只想对single user生效的话,可以修改vi ~/.bash_prof ...