iOS使用UICollectionView实现可旋转菜单

先来张效果图:



之前项目中利用UICollectionView实现了一个圆环形分布的菜单,但不能随着手势进行旋转,正好这两天放假,参考一些大神的文章,捣鼓出了可以进行旋转的菜单。下面说一下代码实现。

1.由于UICollectionVIew是依赖于UICollectionViewFlowLayout进行布局,而UICollectionViewFlowLayout 继承自 UICollectionViewLayout,所以如果我们想进行个性化布局,可以新建一个UICollectionViewLayout的子类来进行个性化布局。

#import <UIKit/UIKit.h>

@interface YMLRotationLayout : UICollectionViewLayout


/** 按钮半径 */
@property (assign, nonatomic) CGFloat itemRadius;

/** 按钮中心相对菜单中心旋转角度*/
@property (assign, nonatomic) CGFloat rotationAngle;

@end
#import "YMLRotationLayout.h"

@implementation YMLRotationLayout{
    
    NSMutableArray * _attributeAttay;
    CGFloat _rLength;
    NSInteger _itemCount;
}

- (void)prepareLayout{
    [super prepareLayout];
    
    // 按钮个数
    _itemCount = (int)[self.collectionView numberOfItemsInSection:0];
    _attributeAttay = [[NSMutableArray alloc] init];
    // 先设定大圆的半径 取长和宽最短的
    CGFloat radius = MIN(self.collectionView.frame.size.width, self.collectionView.frame.size.height) / 2.2;
    // 圆心位置
    CGPoint center = CGPointMake(self.collectionView.frame.size.width / 2.0, self.collectionView.frame.size.height / 2.0);
    
    _rLength = _itemRadius;
    
    // 设置每个item的大小
    for (int idx = 0; idx < _itemCount; idx ++) {
        
        NSIndexPath *indexPath = [NSIndexPath indexPathForItem:idx inSection:0];
        UICollectionViewLayoutAttributes * attris = [UICollectionViewLayoutAttributes layoutAttributesForCellWithIndexPath:indexPath];
        // 设置item大小
        attris.size = CGSizeMake(_rLength, _rLength);
        
        if (_itemCount == 1) {            
            attris.center = self.collectionView.center;
        } else {
            
            // 计算每个item的圆心位置
            /*
             .
             . .
             .   . r
             .     .
             .........
             */
            // 计算每个item中心的坐标
            // 算出的x,y值还要减去item自身的半径大小
            float x = center.x + cosf(2 * M_PI / _itemCount * idx + _rotationAngle) * (radius - _rLength / 2.0);
            float y = center.y + sinf(2 * M_PI / _itemCount * idx + _rotationAngle) * (radius - _rLength / 2.0);
            
            attris.center = CGPointMake(x, y);
        }
        [_attributeAttay addObject:attris];
    }
}

// contentSize 的大小
- (CGSize)collectionViewContentSize{
    return self.collectionView.frame.size;
}

// cell / header / footer 的frame数组
- (NSArray<UICollectionViewLayoutAttributes *> *)layoutAttributesForElementsInRect:(CGRect)rect{
    return _attributeAttay;
}


@end


2.由于需要监听滑动手势以调整布局,所以需要重载UICollectionVIew 事件方法

- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event{
    
    CGPoint centerPoint = self.center;
    UITouch *touch = touches.anyObject;
    CGPoint point  = [touch locationInView:self];
    
    CGFloat rLength = sqrt((point.x - centerPoint.x) * (point.x - centerPoint.x) + (point.y - centerPoint.y) * (point.y - centerPoint.y));
    
    // 手势范围限制
    if (!(rLength <= [self.largeRadius floatValue] && rLength >= [self.smallRadius floatValue])) {
        return;
    }
    
    [[NSNotificationCenter defaultCenter] postNotificationName:@"touchBegin" object:nil userInfo:@{@"x":[NSString stringWithFormat:@"%f",point.x],@"y":[NSString stringWithFormat:@"%f",point.y]}];
}

- (void)touchesMoved:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event{
    
    CGPoint centerPoint = self.center;
    UITouch *touch = touches.anyObject;
    CGPoint point = [touch locationInView:self];
    
    CGFloat rLength = sqrt((point.x - centerPoint.x) * (point.x - centerPoint.x) + (point.y - centerPoint.y) * (point.y - centerPoint.y));
    
    // 手势范围限制
    if (!(rLength <= [self.largeRadius floatValue] && rLength >= [self.smallRadius floatValue])) {
        return;
    }
    
    [[NSNotificationCenter defaultCenter] postNotificationName:@"touchMoving" object:nil userInfo:@{@"x":[NSString stringWithFormat:@"%f",point.x],@"y":[NSString stringWithFormat:@"%f",point.y]}];
}

3.上面两步完成之后,再依据滑动前后手势的位置,计算滑动角度,重新计算每个item的坐标,然后重新布局

#pragma mark -- 按钮滑动,重新布局
// 滑动开始
- (void)touchBegin:(NSNotification *)sender{
    if (!_rotate) return;
    _centerPoint = self.collectionView.center;
    NSDictionary *dic = sender.userInfo;
    CGPoint point = CGPointMake([dic[@"x"] floatValue], [dic[@"y"] floatValue]);
    _lastPoint = point;
}

// 正在滑动中
- (void)touchMoving:(NSNotification *)sender{
    if (!_rotate) return;
    NSDictionary *dic = sender.userInfo;
    CGPoint point = CGPointMake([dic[@"x"] floatValue], [dic[@"y"] floatValue]);
    
    // 以collectionView center为中心计算滑动角度
    CGFloat rads = [self angleBetweenFirstLineStart:_centerPoint firstLineEnd:_lastPoint andSecondLineStart:_centerPoint secondLineEnd:point];
    
    if (_lastPoint.x != _centerPoint.x && point.x != _centerPoint.x) {
        
        CGFloat k1 = (_lastPoint.y - _centerPoint.y) / (_lastPoint.x - _centerPoint.x);
        CGFloat k2 = (point.y - _centerPoint.y) / (point.x - _centerPoint.x);
        if (k2 > k1) {
            _totalRads += rads;
        } else {
            _totalRads -= rads;
        }
    }
    
    _layout.rotationAngle = _totalRads;
    // 重新布局
    [_layout invalidateLayout];
    
    // 更新记录点
    _lastPoint = point;
}

// 两条直线之间的夹角
- (CGFloat)angleBetweenFirstLineStart:(CGPoint)firstLineStart firstLineEnd:(CGPoint)firstLineEnd andSecondLineStart:(CGPoint)secondLineStart secondLineEnd:(CGPoint)secondLineEnd{
    
    CGFloat a1 = firstLineEnd.x - firstLineStart.x;
    CGFloat b1 = firstLineEnd.y - firstLineStart.y;
    CGFloat a2 = secondLineEnd.x - secondLineStart.x;
    CGFloat b2 = secondLineEnd.y - secondLineStart.y;
    
    // 夹角余弦
    double cos = (a1 * a2 + b1 * b2) / (sqrt(pow(a1, 2) + pow(b1, 2)) * sqrt(pow(a2, 2) + pow(b2, 2)));
    // 浮点计算结果可能超过1,需要控制
    cos = cos > 1 ? 1 : cos;
    return acos(cos);
}


demo地址:https://github.com/HuberyYang/YMLMenuDemo.git

本文新地址:iOS使用UICollectionView实现可旋转菜单


评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值