服务器之家:专注于服务器技术及软件下载分享
分类导航

PHP教程|ASP.NET教程|Java教程|ASP教程|编程技术|正则表达式|C/C++|IOS|C#|Swift|Android|VB|R语言|JavaScript|易语言|vb.net|

服务器之家 - 编程语言 - IOS - IOS实现自定义布局瀑布流

IOS实现自定义布局瀑布流

2021-01-04 17:03世俗孤岛 IOS

这篇文章主要介绍了IOS实现自定义布局瀑布流,画面感非常炫丽,想要学习的朋友不要错过本文

瀑布流是电商应用展示商品通常采用的一种方式,如图示例

IOS实现自定义布局瀑布流

瀑布流的实现方式,通常有以下几种

  • 通过uitableview实现(不常用)
  • 通过uiscrollview实现(工作量较大)
  • 通过uicollectionview实现(通常采用的方式)

一、uicollectionview基础
1、uicollectionview与uitableview有很多相似的地方,如

  • 都通过数据源提供数据
  • 都通过代理执行相关的事件
  • 都可以自定义cell,且涉及到cell的重用
  • 都继承自uiscrollview,具有滚动效果

2、uicollectionview的特性

  • 需要有一个uicollectionviewlayout类(通常是uicollectionviewflowlayout类)或其子类对象,来决定cell的布局
  • 可以实现uicollectionviewlayout的子类,来定制uicollectionview的滚动方向以及cell的布局

3、uicollectionviewlayout的子类uicollectionviewflowlayout

  • uicollectionviewflowlayout即流水布局
  • 流水布局uicollectionview的最常用的一种布局方式

二、自定义布局
1、自定义布局需要实现uicollectionviewlayout的子类
2、自定义布局常用方法
初始化布局

?
1
2
3
4
- (void)preparelayout
{
  //通常在该方法中完成布局的初始化操作
}

当尺寸改变时,是否更新布局

?
1
2
3
4
- (bool)shouldinvalidatelayoutforboundschange:(cgrect)newbounds
{
  //默认返回yes
}

布局uicollectionview的元素

?
1
2
3
4
5
6
7
8
- (nullable nsarray<__kindof uicollectionviewlayoutattributes *> *)layoutattributesforelementsinrect:(cgrect)rect
{
  //该方法需要返回rect区域中所有元素布局属性的数组
}
- (nullable uicollectionviewlayoutattributes *)layoutattributesforitematindexpath:(nsindexpath *)indexpath
{
  //该方法返回indexpath位置的元素的布局属性
}

修改uicollectionview停止滚动是的偏移量

?
1
2
3
4
- (cgpoint)targetcontentoffsetforproposedcontentoffset:(cgpoint)proposedcontentoffset withscrollingvelocity:(cgpoint)velocity
{
  //返回值是,uicollectionview最终停留的点
}

三、示例
1、实现效果

IOS实现自定义布局瀑布流

2、实现思路

  • 通过uicollectionview实现
  • 自定义布局,即横向流水布局和圆形普通布局
  • 通过uicollectionview的代理方法,当点击cell时,将其删除
  • 通过监听uiview的触摸事件,当点解控制器的view时更改布局

3、实现步骤
自定横向流水布局
初始化布局

?
1
2
3
4
5
6
7
8
9
- (void)preparelayout
{
  [super preparelayout];
  //设置滚动方向
  self.scrolldirection = uicollectionviewscrolldirectionhorizontal;
  //设置内边距
  cgfloat inset = (self.collectionview.frame.size.width - self.itemsize.width) * 0.5;
  self.sectioninset = uiedgeinsetsmake(0, inset, 0, inset);
}

指定当尺寸改变时,更新布局

?
1
2
3
4
- (bool)shouldinvalidatelayoutforboundschange:(cgrect)newbounds
{
  return yes;
}

设置所有元素的布局属性

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
- (nullable nsarray<uicollectionviewlayoutattributes *> *)layoutattributesforelementsinrect:(cgrect)rect
{
  //获取rect区域中所有元素的布局属性
  nsarray *array = [super layoutattributesforelementsinrect:rect];
 
  //获取uicollectionview的中点,以contentview的左上角为原点
  cgfloat centerx = self.collectionview.contentoffset.x + self.collectionview.frame.size.width * 0.5;
 
  //重置rect区域中所有元素的布局属性,即基于他们距离uicollectionview的中点的剧烈,改变其大小
  for (uicollectionviewlayoutattributes *attribute in array)
  {
    //获取距离中点的距离
    cgfloat delta = abs(attribute.center.x - centerx);
    //计算缩放比例
    cgfloat scale = 1 - delta / self.collectionview.bounds.size.width;
    //设置布局属性
    attribute.transform = cgaffinetransformmakescale(scale, scale);
  }
 
  //返回所有元素的布局属性
  return [array copy];
}

设置uicollectionview停止滚动是的偏移量,使其为与中心点

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
- (cgpoint)targetcontentoffsetforproposedcontentoffset:(cgpoint)proposedcontentoffset withscrollingvelocity:(cgpoint)velocity
{
  //计算最终显示的矩形框
  cgrect rect;
  rect.origin.x = proposedcontentoffset.x;
  rect.origin.y = 0;
  rect.size = self.collectionview.frame.size;
 
  //获取最终显示在矩形框中的元素的布局属性
  nsarray *array = [super layoutattributesforelementsinrect:rect];
 
  //获取uicollectionview的中点,以contentview的左上角为原点
  cgfloat centerx = proposedcontentoffset.x + self.collectionview.frame.size.width * 0.5;
 
  //获取所有元素到中点的最短距离
  cgfloat mindelta = maxfloat;
  for (uicollectionviewlayoutattributes *attribute in array)
  {
    cgfloat delta = attribute.center.x - centerx;
    if (abs(mindelta) > abs(delta))
    {
      mindelta = delta;
    }
  }
 
  //改变uicollectionview的偏移量
  proposedcontentoffset.x += mindelta;
  return proposedcontentoffset;
}

自定义圆形普通布局
定义成员属性,保存所有的布局属性

?
1
@property (nonatomic, strong) nsmutablearray *attrsarray;

懒加载,初始化attrsarray

?
1
2
3
4
5
6
7
8
- (nsmutablearray *)attrsarray
{
  if (_attrsarray == nil)
  {
    _attrsarray = [nsmutablearray array];
  }
  return _attrsarray;
}

初始化布局

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
- (void)preparelayout
{
  [super preparelayout];
 
  //移除所有旧的布局属性
  [self.attrsarray removeallobjects];
 
  //获取元素的个数
  nsinteger count = [self.collectionview numberofitemsinsection:0];
  //布局所有的元素
  for (nsinteger i = 0; i<count; i++)
  {
    nsindexpath *indexpath = [nsindexpath indexpathforitem:i insection:0];
    //设置并获取indexpath位置元素的布局属性
    uicollectionviewlayoutattributes *attrs = [self layoutattributesforitematindexpath:indexpath];
    //将indexpath位置元素的布局属性添加到所有布局属性数组中
    [self.attrsarray addobject:attrs];
  }
}

布局indexpath位置的元素

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
- (nullable uicollectionviewlayoutattributes *)layoutattributesforitematindexpath:(nonnull nsindexpath *)indexpath
{
  //获取元素的个数
  nsinteger count = [self.collectionview numberofitemsinsection:0];
 
  /**设置圆心布局*/
  //设置圆形的半径
  cgfloat radius = 70;
  //圆心的位置
  cgfloat ox = self.collectionview.frame.size.width * 0.5;
  cgfloat oy = self.collectionview.frame.size.height * 0.5;
  //获取indexpath位置的元素的布局属性
  uicollectionviewlayoutattributes *attrs = [uicollectionviewlayoutattributes layoutattributesforcellwithindexpath:indexpath];
  //设置尺寸
  attrs.size = cgsizemake(50, 50);
  //设置位置
  if (count == 1)
  {
    attrs.center = cgpointmake(ox, oy);
  }
  else
  {
    cgfloat angle = (2 * m_pi / count) * indexpath.item;
    cgfloat centerx = ox + radius * sin(angle);
    cgfloat centery = oy + radius * cos(angle);
    attrs.center = cgpointmake(centerx, centery);
  }
  //返回indexpath位置元素的布局属性
  return attrs;
}

布局指定区域内所有的元素

?
1
2
3
4
5
- (nullable nsarray<uicollectionviewlayoutattributes *> *)layoutattributesforelementsinrect:(cgrect)rect
{
  //返回所有元素的布局属性
  return self.attrsarray;
}

通过xib自定义ce ll
设置成员属性,保存cell内部的图片

/**图片名字*/

?
1
@property (nonatomic, copy) nsstring *imagename;

初始化cell

?
1
2
3
4
5
6
7
- (void)awakefromnib
{
  //通过layer设置边框
  self.imageview.layer.bordercolor = [uicolor whitecolor].cgcolor;
  self.imageview.layer.borderwidth = 6;
  self.imageview.layer.cornerradius = 3;
}

设置cell内imageview的image属性

?
1
2
3
4
5
- (void)setimagename:(nsstring *)imagename
{
  _imagename = [imagename copy];
  self.imageview.image = [uiimage imagenamed:imagename];
}

加载图片资源
通过成员属性,保存所有的图片名

?
1
2
/**所有的图片*/
@property (nonatomic, strong) nsmutablearray *imagenames;

懒加载,初始化图片名数组

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
- (nsmutablearray *)imagenames
{
  if (_imagenames == nil)
  {
    nsmutablearray *imagenames = [nsmutablearray array];
    for (nsinteger i = 0; i<20; i++)
    {
      nsstring *imagename = [nsstring stringwithformat:@"%zd", i + 1];
      [imagenames addobject:imagename];
    }
    _imagenames = imagenames;
  }
  return _imagenames;
}

创建uicollectionview
通过成员属性保存uicollectionview对象,以便更改布局

?
1
@property (nonatomic, weak) uicollectionview *collectionview;

创建并设置collectionview

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
- (void)setupcollectionview
{
  //设置frame
  cgfloat collectionvieww = self.view.bounds.size.width;
  cgfloat collectionviewh = 200;
  cgrect frame = cgrectmake(0, 150, collectionvieww, collectionviewh);
 
  //创建布局
  lypcirclelayout *layout = [[lypcirclelayout alloc] init];
 
  //创建collectionview
  uicollectionview *collectionview = [[uicollectionview alloc] initwithframe:frame collectionviewlayout:layout];
  self.collectionview = collectionview;
 
  //设置collectionview的数据源和代理
  collectionview.datasource = self;
  collectionview.delegate = self;
 
  //添加collectionview到控制器的view中
  [self.view addsubview:collectionview];
}

实现uicollectionview的数据源方法
注册cell

?
1
2
3
4
5
6
7
8
9
10
11
/**设置重用表示*/
static nsstring *const id = @"photo";
- (void)viewdidload
{
  [super viewdidload];
 
  [self setupcollectionview];
 
  //注册cell
  [self.collectionview registernib:[uinib nibwithnibname:nsstringfromclass([lypphotocell class]) bundle:nil] forcellwithreuseidentifier:id];
}

设置元素的个数

?
1
2
3
4
- (nsinteger)collectionview:(nonnull uicollectionview *)collectionview numberofitemsinsection:(nsinteger)section
{
  return self.imagenames.count;
}

设置每个元素的属性

?
1
2
3
4
5
6
7
8
9
10
- (uicollectionviewcell *)collectionview:(nonnull uicollectionview *)collectionview cellforitematindexpath:(nonnull nsindexpath *)indexpath
{
  //根据重用标示从缓存池中取出cell,若缓存池中没有,则自动创建
  lypphotocell *cell = [collectionview dequeuereusablecellwithreuseidentifier:id forindexpath:indexpath];
  //设置cell的imagename属性
  cell.imagename = self.imagenames[indexpath.item];
 
  //返回cell
  return cell;
}

实现uicollectionview的代理方法,实现点击某个元素将其删除功能

?
1
2
3
4
5
6
7
- (void)collectionview:(nonnull uicollectionview *)collectionview didselectitematindexpath:(nonnull nsindexpath *)indexpath
{
  //将图片名从数组中移除
  [self.imagenames removeobjectatindex:indexpath.item];
  //删除collectionview中的indexpath位置的元素
  [self.collectionview deleteitemsatindexpaths:@[indexpath]];
}

监听控制器view的点击,更换布局

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
- (void)touchesbegan:(nonnull nsset<uitouch *> *)touches withevent:(nullable uievent *)event
{
  //判断当前布局的种类
  if ([self.collectionview.collectionviewlayout iskindofclass:[lyplinelayout class]])
  {
    //流水布局,切换至圆形布局
    [self.collectionview setcollectionviewlayout:[[lypcirclelayout alloc] init] animated:yes];
  } else
  {
    //圆形布局,切换至流水布局
    lyplinelayout *layout = [[lyplinelayout alloc] init];
    //设置元素的尺寸,若不设置,将使用自动计算尺寸
    layout.itemsize = cgsizemake(130, 130);
    [self.collectionview setcollectionviewlayout:layout animated:yes];
  }
}

以上就是本文的全部内容,希望对大家的学习有所帮助。

延伸 · 阅读

精彩推荐
  • IOSiOS布局渲染之UIView方法的调用时机详解

    iOS布局渲染之UIView方法的调用时机详解

    在你刚开始开发 iOS 应用时,最难避免或者是调试的就是和布局相关的问题,下面这篇文章主要给大家介绍了关于iOS布局渲染之UIView方法调用时机的相关资料...

    windtersharp7642021-05-04
  • IOSiOS通过逆向理解Block的内存模型

    iOS通过逆向理解Block的内存模型

    自从对 iOS 的逆向初窥门径后,我也经常通过它来分析一些比较大的应用,参考一下这些应用中某些功能的实现。这个探索的过程乐趣多多,不仅能满足自...

    Swiftyper12832021-03-03
  • IOSIOS 屏幕适配方案实现缩放window的示例代码

    IOS 屏幕适配方案实现缩放window的示例代码

    这篇文章主要介绍了IOS 屏幕适配方案实现缩放window的示例代码,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要...

    xiari5772021-06-01
  • IOS关于iOS自适应cell行高的那些事儿

    关于iOS自适应cell行高的那些事儿

    这篇文章主要给大家介绍了关于iOS自适应cell行高的那些事儿,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的...

    daisy6092021-05-17
  • IOSiOS中tableview 两级cell的展开与收回的示例代码

    iOS中tableview 两级cell的展开与收回的示例代码

    本篇文章主要介绍了iOS中tableview 两级cell的展开与收回的示例代码,小编觉得挺不错的,现在分享给大家,也给大家做个参考。一起跟随小编过来看看吧...

    J_Kang3862021-04-22
  • IOSiOS 雷达效果实例详解

    iOS 雷达效果实例详解

    这篇文章主要介绍了iOS 雷达效果实例详解的相关资料,需要的朋友可以参考下...

    SimpleWorld11022021-01-28
  • IOSIOS开发之字典转字符串的实例详解

    IOS开发之字典转字符串的实例详解

    这篇文章主要介绍了IOS开发之字典转字符串的实例详解的相关资料,希望通过本文能帮助到大家,让大家掌握这样的方法,需要的朋友可以参考下...

    苦练内功5832021-04-01
  • IOS解析iOS开发中的FirstResponder第一响应对象

    解析iOS开发中的FirstResponder第一响应对象

    这篇文章主要介绍了解析iOS开发中的FirstResponder第一响应对象,包括View的FirstResponder的释放问题,需要的朋友可以参考下...

    一片枫叶4662020-12-25