澳门威利斯人_威利斯人娱乐「手机版」

来自 澳门威利斯人 2020-03-18 12:09 的文章
当前位置: 澳门威利斯人 > 澳门威利斯人 > 正文

CollectionView自定义风火轮,FlowLayout与风火轮布局

最终效果如下所示:

切切实实经过请看 Ray Wenderlich上的教程,互连网也能找到别的开拓者翻译。这里只记录多少个自己认为比较重要的

CollectionView达成以下效果.

图片 1demo.gif

对此自定义构造,最首要的有多少个函数:

图片 2

其一意义是我们合作社的多少个模块的作用, 这个时候从未出于未有对 collectionView 留神研商,所以对这一个分界面包车型地铁达成机制并不是很熟习, 到今日早就有段时日了, 这段时日对 collectionView 也加深领会了一部分, 于是试着和睦写一下实施(这时使大家公司叁个大腕写的卡塔尔国

图片 3自定义结构首要进度.png

Simulator Screen Shot 2016年5月24日 上午9.32.54.png

本人希图分一下几步来得以达成这些意义:

- prepareLayout

思路:
先说一下这么些效果的达成思路,首先必要鲜明该瀑布流有多少列,然后供给规定每种cell 的冲天,用三个数组记录下每一列的已增加上去的cell的高度和.然后增加下一个cell的时候搜索具有列中中度最小的列,再增多上去.
譬如:在该例子中,总共有两列,当增多完第二遍之个cell,即首先行加多完了,要加多第多个cell,就要求搜索第贰回之列中中度最短的那一列,然后增多到最短那一列上边,依此类推.

  1. 落实圆形构造(这么些结构功效在 Apple 的实例代码中有, 具体代码请自行 Google卡塔尔国
  2. 兑现圆形的风火轮效果
  3. 对有些供给遮盖的职分展开隐瞒

用以排列cell前,总结各类cell地方新闻,制止将cell加到 collectionview 上时权且总结:

图片 4

环形架构在此以前Apple 提供的代码中是直接遵照角度总计的各类 Item 的职责, 大家也用平等的思忖, 差别的是大家要将角度记录下来, 那个角度是跟 collectionView 的 contentOffset 有关的, 因为当客商在滑行的时候, contentOffset 在更新,这时候理应重新依据 contentOffset 总括各样 Item 的角度 --- 在心底有个影像

  • 在collectionview第二回表现内容时调用
  • 结构更新时,调用此函数来准备好下叁遍的布局

3.png

  1. 创制自定义布局

实现:
首先大家须要领头化collectionView,步骤与 "iOS-CollectionView 底子" 相仿.

- collectionViewContentSize

该成效需求自定义构造,达成瀑布流效果.
创立三个搭架子类,世襲于UICollectionViewLayout. (流水布局对应的类世襲于UICollectionViewFlowLayout, UICollectionViewFlowLayout 是 UICollectionViewLayout的子类卡塔尔(قطر‎

 #import <UIKit/UIKit.h> @interface CircleCollectionViewLayout : UICollectionViewLayout /** * 半径 */ @property (nonatomic, assign) CGFloat radius; /** * 大小 */ @property (nonatomic, assign) CGSize itemSize; @end - (instancetype)init { if (self = [super init]) { [self initial]; } return self; } - (instancetype)initWithCoder:(NSCoder *)aDecoder { if (self = [super initWithCoder:aDecoder]) { [self initial]; } return self; } - initial { self.itemSize = CGSizeMake(ItemWidth, ItemHieght); self.radius = (CGRectGetWidth([UIScreen mainScreen].bounds))* 0.5f - ItemWidth - RightMargin; }

collectionView可滑动的间距

在布局类须要重写八个情势,分别是:

概念好半径大小之后, 大家还须要个属性 相邻多个Item之间的夹角是微微度于是大家在 extension 中定义 anglePerItem属性, 存款和储蓄夹角, 并在 initial 中做早先化

- (NSArray<UICollectionViewLayoutAttributes *> *)layoutAttributesForElementsInRect:rect

1. 重写prepareLayout方法

-成效:在此个法子做一些早先化操作
-注意:一定要调用 [super prepareLayout]

 // item 大小 55 * 55 #define ItemWidth 55 #define ItemHieght ItemWidth #define RightMargin 5 @interface CircleCollectionViewLayout () // 单位夹角 @property (nonatomic, assign) CGFloat anglePerItem; @end - initial { self.itemSize = CGSizeMake(ItemWidth, ItemHieght); self.radius = (CGRectGetWidth([UIScreen mainScreen].bounds) - ItemWidth)* 0.5f - RightMargin; // 单位夹角为 45度 self.anglePerItem = M_PI_2 / 2; }

回去叁个可视rect范围内被出示cell的UICollectionViewLayoutAttributes

2.重写layoutAttributesForItemAtIndexPath:方法

-功用:再次来到indexPath 地方cell对应的布局属性

大家以前说过, 种种 Item 要有二个 angle, 用来规定在 contentOffset 时, 对应的 item 的角度是稍微, 所以那个时候大家需求自定义 LayoutAttributes

UICollectionViewLayoutAttributes类管理了结构相关的习性,如cell的frametransformboundszIndexcenter等。它们的概念与用法与UIView中分别属性一致。

3. 重写layoutAttributesForElementsInRect:方法

-作用:
以此办法的再次回到值是个数组
那个数组中寄存的是UICollectionViewLayoutAttributes对象
UICollectionViewLayoutAttributes 对象说了算了cell的排布方式

自定义 LayoutAttributes

环形构造

环形布局最重点的少数就是,让具有cell围绕叁个点转起来。

至于view的团团转,能够设定transform,让view旋转一定的角度。然则那样,全体的view都以绕其center旋转,不可能落得大家的效用。

咱们也足以设定anchorPoint,因为有着关于view的几何运算都与其有关。关于anchorPoint,看这篇文章

关于anchorPoint DEMO

图片 5anchorPoint Demo.png

import UIKitclass ViewController: UIViewController { @IBOutlet weak var anchorPointView: UIView! var viewArray : [UIView] = [] var isMoved = true override func viewDidLoad() { super.viewDidLoad() // Do any additional setup after loading the view, typically from a nib. let tapGesture = UITapGestureRecognizer(target: self, action: "tap") self.view.addGestureRecognizer(tapGesture) } override func viewDidAppear(animated: Bool) { super.viewDidAppear for i in  { let eView = UIView(frame: self.anchorPointView.frame) eView.backgroundColor = UIColor.blueColor() viewArray.append self.view.addSubview } } func tap() { //修改anchorPoint self.anchorPointView.layer.anchorPoint = CGPointMake(0.5, 0.5   3.0) //旋转 anchorPointView.transform = CGAffineTransformMakeRotation(CGFloat(M_PI / 10) * 0) //修正view的位置 //因为修改了anchorPoint,而position不变,从而导致view上移300个点(anchorPointView 宽高都为100) //300根据 *(anchorPoint在y上增加值3) var center = self.anchorPointView.center center.y  = 300 self.anchorPointView.center = center var i = 1.0 for view in viewArray { view.layer.anchorPoint = CGPointMake(0.5, 0.5   3.0) view.transform = CGAffineTransformMakeRotation(CGFloat(M_PI / 10 * i)) i  = 1.0 * 1 var center = view.center center.y  = 300 view.center = center } }}

4.重写collectionViewContentSize方法

-功用:决定collectionView的可滚动范围

代码完毕:
布局类.m 文件

1.定义如下静态变量

/**  列数*/
static const CGFloat columCount = 3;
/**  每一列间距*/
static const CGFloat columMargin = 10;
/**  每一列间距*/
static const CGFloat rowMargin = 10;
/**  边缘间距*/
static const UIEdgeInsets defaultEdgeInsets = {10,10,10,10};

2.扬言两特性子

/** 布局属性数组*/
@property (nonatomic,strong) NSMutableArray *attrsArray;

/** 存放所有列的当前高度*/
@property (nonatomic,strong) NSMutableArray *columnHeight;

懒加载 attrsArray, columnHeight

- (NSMutableArray *)attrsArray
{
    if (!_attrsArray) {

        _attrsArray = [NSMutableArray array];
    }
    return _attrsArray;
}

- (NSMutableArray *)columnHeight
{
    if (!_columnHeight) {

        _columnHeight = [NSMutableArray array];
    }
    return _columnHeight;
}

重写prepareLayout方法

/**  初始化*/
- (void)prepareLayout
{
    [super prepareLayout];

    //如果刷新布局就会重新调用prepareLayout这个方法,所以要先把高度数组清空
    [self.columnHeight removeAllObjects];
    for (int i = 0; i < self.columCount; i  ) {

        [self.columnHeight addObject:@(self.defaultEdgeInsets.top)];
    }

    NSInteger count = [self.collectionView numberOfItemsInSection:0];

    [self.attrsArray removeAllObjects];
    for (NSInteger i = 0; i < count; i  ) {

        NSIndexPath *indexPath = [NSIndexPath indexPathForRow:i inSection:0];
        //获取indexPath 对应cell 的布局属性
        UICollectionViewLayoutAttributes *attr = [self layoutAttributesForItemAtIndexPath:indexPath];
        [self.attrsArray addObject:attr];
    }
}

该措施重返对应cell上的构造属性.我们得以在此个格局中装置cell 的布局样式.在prepareLayout方法中,大家依据这么些方法,传入对应的IndexPath进而取得到构造属性attr,然后加多到数组中.

/**
*  返回indexPath 位置cell对应的布局属性
*/
- (UICollectionViewLayoutAttributes *)layoutAttributesForItemAtIndexPath:(NSIndexPath *)indexPath
{
   UICollectionViewLayoutAttributes *attr = [UICollectionViewLayoutAttributes layoutAttributesForCellWithIndexPath:indexPath];

   //使用for循环,找出高度最短的那一列
   //最短高度的列
   NSInteger destColumn = 0;
   CGFloat minColumnHeight = [self.columnHeight[0] doubleValue];

   for (NSInteger i = 1; i < self.columCount; i  ) {

       CGFloat columnHeight  =[self.columnHeight[i] doubleValue];
       if (minColumnHeight > columnHeight) {

           minColumnHeight = columnHeight;
           destColumn = i;
       }
   }

   CGFloat w = (self.collectionView.frame.size.width - self.defaultEdgeInsets.left - self.defaultEdgeInsets.right - (self.columCount - 1) * self.columMargin )/self.columCount;

   //(使用代理在外部决定cell 的高度,下面会介绍)
   CGFloat h = [self.delegate waterFlowLayout:self heightForRowAtIndex:indexPath.item itemWidth:w];

   CGFloat x = self.defaultEdgeInsets.left   destColumn*(w   self.columMargin);
   CGFloat y = minColumnHeight ;

   if (y != self.defaultEdgeInsets.top) {

       y  = self.rowMargin;
   }

   attr.frame = CGRectMake(x,y,w,h);

   self.columnHeight[destColumn] =  @(y  h);
   return attr;
}

重写layoutAttributesForElementsInRect:方法

/**
 *  决定cell 的排布
 */
- (NSArray<UICollectionViewLayoutAttributes *> *)layoutAttributesForElementsInRect:(CGRect)rect
{
    return self.attrsArray;
}

决定collectionView的可滚动范围

- (CGSize)collectionViewContentSize
{

    CGFloat maxHeight = [self.columnHeight[0] doubleValue];
    for (int i = 1; i < self.columCount; i  ) {

        CGFloat value = [self.columnHeight[i] doubleValue];
        if (maxHeight < value) {

            maxHeight = value;
        }
    }
    return CGSizeMake(0, maxHeight self.defaultEdgeInsets.bottom);
}

到此,瀑布流的机能就出来了.
而是能够想到,那样来搭创设造,瀑布流的列数,cell与cell之间的间距以致边缘距就稳定了,明显那是相当不足灵活的.大家必须求把那么些参数抛给接收该布局的类去决定,那样才是二个通用的代码.

来到构造类.h 文件中,加多合计甚至代理

@class WaterFlowLayout;

@protocol WaterFlowLayoutDelegate <NSObject>

@required
//决定cell的高度,必须实现方法
- (CGFloat)waterFlowLayout:(WaterFlowLayout *)waterFlowLayout heightForRowAtIndex:(NSInteger)index itemWidth:(CGFloat)width;

@optional
//决定cell的列数
- (NSInteger)cloumnCountInWaterFlowLayout:(WaterFlowLayout *)waterFlowLayout;

//决定cell 的列的距离
- (CGFloat)columMarginInWaterFlowLayout:(WaterFlowLayout *)waterFlowLayout;

//决定cell 的行的距离
- (CGFloat)rowMarginInWaterFlowLayout:(WaterFlowLayout *)waterFlowLayout;

//决定cell 的边缘距
- (UIEdgeInsets)edgeInsetInWaterFlowLayout:(WaterFlowLayout *)waterFlowLayout;

@end

@interface WaterFlowLayout : UICollectionViewLayout

/**代理*/
@property (nonatomic,assign) id <WaterFlowLayoutDelegate>delegate;

- (NSInteger)columCount;
- (CGFloat)columMargin;
- (CGFloat)rowMargin;
- (UIEdgeInsets)defaultEdgeInsets;
@end

重临构造类.m文件中,达成证明的方法.在此边供给分明,外界必得经过兑今世理给定cell的高度.别的,即便外界通过落实代理给定列数、列间隔、行间隔、边缘距就用给定的,不然使用私下认可的列数、列间隔、行间隔、边缘距.

- (NSInteger)columCount{

    if ([self.delegate respondsToSelector:@selector(cloumnCountInWaterFlowLayout:)]) {
        return  [self.delegate cloumnCountInWaterFlowLayout:self];
    }
    else{
        return columCount;
    }
}

- (CGFloat)columMargin{

    if ([self.delegate respondsToSelector:@selector(columMarginInWaterFlowLayout:)]) {
        return  [self.delegate columMarginInWaterFlowLayout:self];
    }
    else{
        return columMargin;
    }
}

- (CGFloat)rowMargin{

    if ([self.delegate respondsToSelector:@selector(rowMarginInWaterFlowLayout:)]) {
        return  [self.delegate rowMarginInWaterFlowLayout:self];
    }
    else{
        return rowMargin;
    }
}

- (UIEdgeInsets)defaultEdgeInsets{

    if ([self.delegate respondsToSelector:@selector(edgeInsetInWaterFlowLayout:)]) {
        return  [self.delegate edgeInsetInWaterFlowLayout:self];
    }
    else{
        return defaultEdgeInsets;
    }
}

到此,CollectionView瀑布流框架搭建实现了!!

#import <UIKit/UIKit.h>@interface CircleCollectionViewLayoutAttributes : UICollectionViewLayoutAttributes// 锚点@property (nonatomic, assign) CGPoint anchorPoint;// 角度@property (nonatomic, assign) CGFloat angle;@end#import "CircleCollectionViewLayoutAttributes.h"@implementation CircleCollectionViewLayoutAttributes- (instancetype)init { if (self = [super init]) { self.anchorPoint = CGPointMake; self.angle = 0; } return self;}- setAngle:angle { _angle = angle; self.zIndex = angle * 1000000; // 将角度同时用做item 的旋转 self.transform = CGAffineTransformMakeRotation;}// UICollectionViewLayoutAttributes 实现 <NSCoping> 协议- copyWithZone:zone { CircleCollectionViewLayoutAttributes *copyAttributes = (CircleCollectionViewLayoutAttributes *)[super copyWithZone:zone]; copyAttributes.anchorPoint = self.anchorPoint; copyAttributes.angle = self.angle; return copyAttributes;}@end

自定义UICollectionViewLayoutAttributes

依照上边环形布局,想要让cell环形排列,必获悉道cell的anchorPoint与 旋转角度angle。

旋转角度angle对应 UICollectionViewLayoutAttributes 的 transform,而anchorPoint作为三个CALayer的习性,在UICollectionViewLayoutAttributes未有对景挂画的习性,但此间要求让cell知道自个儿的anchorPoint,本事让其科学旋转。这里就必要自定义UICollectionViewLayoutAttributes。

接收自定义子类,与利用UICollectionViewLayoutAttributes相比较,你须求扩张部分代码。

  1. 在自定义布局的相干落到实处中:
  • layoutAttributesClass {//再次回到自定义UICollectionViewLayoutAttributes子类return [CircularCollectionViewLayoutAttributes class];}
这样每当需要生成一个新的LayoutAttributes object时就会调用此函数2. 在需要显示的 UICollectionViewCell的实现中
  • applyLayoutAttributes:(UICollectionViewLayoutAttributes *)layoutAttributes {

    [super applyLayoutAttributes:layoutAttributes];if ([layoutAttributes isKindOfClass:[CircularCollectionViewLayoutAttributes class]]) {CircularCollectionViewLayoutAttributes *attributes = (CircularCollectionViewLayoutAttributes *)layoutAttributes;

    CGPoint centerPoint = self.center;self.layer.anchorPoint = attributes.anchorPoint;centerPoint.y  = self.selected? (attributes.anchorPoint.y - 0.5) * CGRectGetHeight(self.bounds) - 50: (attributes.anchorPoint.y - 0.5) * CGRectGetHeight(self.bounds);self.center = centerPoint;
    

    }}

这样新增加的UICollectionViewLayoutAttributes属性,才有机会作用在cell上###布局核心过程代码
  • prepareLayout {

    [super prepareLayout];

    CGFloat centerX = self.collectionView.contentOffset.x CGRectGetWidth(self.collectionView.bounds) / 2.0;CGFloat anchorPointY = ((_itemSize.height / 2) _radius ) / _itemSize.height;

    int startIndex = 0;int endIndex = [self itemCount] - 1;// CGFloat theta = atan2(CGRectGetWidth(self.collectionView.bounds) / 2.0,// _radius (_itemSize.height / 2.0) - CGRectGetHeight(self.collectionView.bounds) / 2.0);// if ([self angle] < -theta) {//// startIndex = (floor( (-[self angle] - theta) / [self anglePerItem]));//// }//// endIndex = MIN(endIndex, ceil((-[self angle] theta) / [self anglePerItem]) );//// if (endIndex < startIndex) {//// startIndex = 0;// endIndex = 0;//// }

    for (int i = startIndex; i <= endIndex; i ) {

     CircularCollectionViewLayoutAttributes *attribute = [CircularCollectionViewLayoutAttributes layoutAttributesForCellWithIndexPath:[NSIndexPath indexPathForItem:i inSection:0]]; attribute.size = _itemSize; attribute.center = CGPointMake(centerX, CGRectGetMidY(self.collectionView.bounds)); attribute.anchorPoint = CGPointMake(0.5, anchorPointY); attribute.angle = [self angle]   [self anglePerItem]*i; [_layoutAttributes addObject:attribute];
    

    }

}

  • (NSArray<UICollectionViewLayoutAttributes *> *卡塔尔(قطر‎layoutAttributesForElementsInRect:rect {//再次回到prepareLayout总结好的布局return _layoutAttributes;

}

  • (UICollectionViewLayoutAttributes *)layoutAttributesForItemAtIndexPath:(NSIndexPath *)indexPath {

//return _layoutAttributes[indexPath.row];

}

  • collectionViewContentSize {

    return CGSizeMake([self.collectionView numberOfItemsInSection:0] * _itemSize.width, CGRectGetHeight(self.collectionView.bounds));}

  • layoutAttributesClass {

    return [CircularCollectionViewLayoutAttributes class];}

  • shouldInvalidateLayoutForBoundsChange:newBounds {//每趟滑动,都触发新的结构return true;}

回到 Layout 类

因为大家自定义了 Attributes 类, 所以那个时候要告诉 Layout 类, 我们自定义的 Attributes

  layoutAttributesClass { return [CircleCollectionViewLayoutAttributes class];}

因为急需客商去滑动, 又因为 CollectionView 世襲自 ScrollView, 运营滑动的三个供给条件正是 contentSize某一个样子的值当先scrollView.bounds 对应方向的值

- collectionViewContentSize { NSInteger numberOfItem = [self.collectionView numberOfItemsInSection:0]; return CGSizeMake(numberOfItem * ItemWidth , self.collectionView.bounds.size.height);}

好了预备干活骨干到位, 接下来开头布局

在这里边不可不要询问 collectionView 的构造步骤

本文由澳门威利斯人发布于澳门威利斯人,转载请注明出处:CollectionView自定义风火轮,FlowLayout与风火轮布局

关键词: 澳门威利斯人 自定义 风火轮 CollectionVi