欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页  >  移动技术

IOS实现自定义布局瀑布流

程序员文章站 2022-06-29 08:57:03
瀑布流是电商应用展示商品通常采用的一种方式,如图示例 瀑布流的实现方式,通常有以下几种 通过uitableview实现(不常用) 通过uiscro...

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

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、自定义布局常用方法
初始化布局

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

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

- (bool)shouldinvalidatelayoutforboundschange:(cgrect)newbounds
{
  //默认返回yes
}

布局uicollectionview的元素

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

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

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

三、示例
1、实现效果

IOS实现自定义布局瀑布流

2、实现思路

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

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

- (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);
}

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

- (bool)shouldinvalidatelayoutforboundschange:(cgrect)newbounds
{
  return yes;
}

设置所有元素的布局属性

- (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停止滚动是的偏移量,使其为与中心点

- (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;
}

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

@property (nonatomic, strong) nsmutablearray *attrsarray;

懒加载,初始化attrsarray

- (nsmutablearray *)attrsarray
{
  if (_attrsarray == nil)
  {
    _attrsarray = [nsmutablearray array];
  }
  return _attrsarray;
}

初始化布局

- (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位置的元素

- (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;
}

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

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

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

/**图片名字*/

@property (nonatomic, copy) nsstring *imagename;

初始化cell

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

设置cell内imageview的image属性

- (void)setimagename:(nsstring *)imagename
{
  _imagename = [imagename copy];
  self.imageview.image = [uiimage imagenamed:imagename];
}

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

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

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

- (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对象,以便更改布局

@property (nonatomic, weak) uicollectionview *collectionview;

创建并设置collectionview

- (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

/**设置重用表示*/
static nsstring *const id = @"photo";
- (void)viewdidload
{
  [super viewdidload];

  [self setupcollectionview];

  //注册cell
  [self.collectionview registernib:[uinib nibwithnibname:nsstringfromclass([lypphotocell class]) bundle:nil] forcellwithreuseidentifier:id];
}

设置元素的个数

- (nsinteger)collectionview:(nonnull uicollectionview *)collectionview numberofitemsinsection:(nsinteger)section
{
  return self.imagenames.count;
}

设置每个元素的属性

- (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的代理方法,实现点击某个元素将其删除功能

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

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

- (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]; 
  }
}

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