支持自动轮播及手动左右滑的banner

2020-10-21  本文已影响0人  蓝天白云_Sam

基本思路

  1. 如果只有一个数据,则不需要滚动
  2. 如果多于一个数据,则在源数据的最前面插入源数据的最后一个元素,末尾插入源数据的第一个元素组成新的数据源fakeData,如源数据为 originalData = @[ @(0), @(1), @(2), @(3) ];
    则新数据为 fakeData = @[@(3), @(0), @(1), @(2), @(3) , @(0)];
  3. 列表显示的时候以fakeData为数据源进行显示
  4. 如果列表滚动到开始位置,此时实际显示的是最后一个元素,可使用setContentOffset:animated:滚动到self.width * [self originalDataCount]的位置
  5. 如果列表滚动到最后的位置,此时实际显示的是第一个元素,可使用setContentOffset:animated:滚动到self.width的位置
  1. 使用 [self.collectionView setContentOffset:CGPointMake(offset, 0) animated:YES]; 定时自动轮播
  2. 监听自动轮播结束事件并启动下一次轮播timer:
    - (void)scrollViewDidEndScrollingAnimation:(UIScrollView *)scrollView
  1. 开始滑动,停止自动轮播Timer:
    - (void)scrollViewWillBeginDragging:(UIScrollView *)scrollView
  2. 手指脱离屏幕,如果没有加速度,则调整UIScrollView ontentOffse 并启动下一次轮播 timer :
    - (void)scrollViewDidEndDragging:(UIScrollView *)scrollView willDecelerate:(BOOL)decelerate
  3. 手指脱离屏幕,如果有加速度,则等加速完成后调整UIScrollView ontentOffse 并启动下一次轮播 timer :
    - (void)scrollViewDidEndDecelerating:(nonnull UIScrollView *)scrollView
//
//  WSScrollBannerView.h
//  
//
//  Created by 蓝天 on 2020/10/20.
//  Copyright © 2020 BlueSky Studio. All rights reserved.
//

#import <UIKit/UIKit.h>

NS_ASSUME_NONNULL_BEGIN


@interface WSScrollBannerView : UIView <UICollectionViewDelegate>

@property (nonatomic, strong) NSArray *originalData;
@property (nonatomic, strong, readonly) NSArray *fakeData;

@property (nonatomic, strong, readonly) UICollectionView *collectionView;
@property (nonatomic, assign) NSInteger scrollInterval;

- (UICollectionView *)buildCollectionView;

/// 停止滚动,可在viewWillDisappear调用
- (void)stopScroll;

/// 开始滚动,可在viewDidAppear的时候调用
- (void)startScroll;

@end

NS_ASSUME_NONNULL_END

//
//  WSScrollBannerView.m
//  
//
//  Created by 蓝天 on 2020/10/20.
//  Copyright © 2020 BlueSky Studio. All rights reserved.
//

#import "WSScrollBannerView.h"


@interface WSScrollBannerView ()

@property (nonatomic, strong) UICollectionView *collectionView;
@property (nonatomic, strong) NSArray *fakeData;
@property (nonatomic, strong, nullable) NSTimer *scrollTimer;

@end


@implementation WSScrollBannerView

/*
// Only override drawRect: if you perform custom drawing.
// An empty implementation adversely affects performance during animation.
- (void)drawRect:(CGRect)rect {
    // Drawing code
}
*/

- (UICollectionView *)buildCollectionView
{
    CGSize itemSize = CGSizeMake(SCREEN_WIDTH - 16 * 2, 72);

    UICollectionViewFlowLayout *collectionViewFlowLayout = [[UICollectionViewFlowLayout alloc] init];
    collectionViewFlowLayout.scrollDirection = UICollectionViewScrollDirectionHorizontal;
    collectionViewFlowLayout.itemSize = itemSize;
    collectionViewFlowLayout.minimumLineSpacing = 32;
    collectionViewFlowLayout.sectionInset = UIEdgeInsetsMake(0, 16, 0, 16);

    UICollectionView *collectionView = [[UICollectionView alloc] initWithFrame:CGRectZero collectionViewLayout:collectionViewFlowLayout];
    return collectionView;
}


- (instancetype)initWithFrame:(CGRect)frame
{
    self = [super initWithFrame:frame];
    if (self) {
        UICollectionView *collectionView = self.collectionView = [self buildCollectionView];
        [self addSubview:collectionView];

        collectionView.delegate = self;
        collectionView.pagingEnabled = YES;
        collectionView.showsHorizontalScrollIndicator = NO;
        collectionView.showsVerticalScrollIndicator = NO;
        collectionView.bounces = NO;

        [collectionView mas_makeConstraints:^(MASConstraintMaker *make) {
            make.edges.equalTo(self);
        }];
    }
    return self;
}

- (void)setOriginalData:(NSArray *)originalData
{
    _originalData = originalData;
    [self.collectionView setContentOffset:CGPointMake(self.width, 0) animated:NO];
    [self stopTimer];
    if (originalData.count == 0) {
        return;
    }

    NSMutableArray *fakeData = [[NSMutableArray alloc] initWithArray:originalData];
    if (originalData.count > 1) {
        [fakeData insertObject:originalData.lastObject atIndex:0];
        [fakeData addObject:originalData.firstObject];
        [self onEndScroll:@"updateWithUserInfos"];
    }
    self.fakeData = fakeData;
}

- (NSInteger)originalDataCount
{
    return self.originalData.count;
}

- (void)stopTimer
{
    [self.scrollTimer invalidate];
    self.scrollTimer = nil;
}

//自动轮播, setContentOffset:animated且animated为YES的情况
- (void)scrollViewDidEndScrollingAnimation:(UIScrollView *)scrollView
{
    [self onEndScroll:@"scrollViewDidEndScrollingAnimation"];
}


//开始滑动,需要停止timer
- (void)scrollViewWillBeginDragging:(UIScrollView *)scrollView
{
    [self stopTimer];
}

- (void)scrollViewWillEndDragging:(UIScrollView *)scrollView withVelocity:(CGPoint)velocity targetContentOffset:(inout CGPoint *)targetContentOffset
{
    if (targetContentOffset->x <= 0.0) {
        targetContentOffset->x = 0.0;
    }else if (targetContentOffset->x >= (NSInteger)(self.width * ([self originalDataCount] + 1))){
        targetContentOffset->x = (self.width * ([self originalDataCount] + 1));
    }
}

//停止滑动
- (void)scrollViewDidEndDragging:(UIScrollView *)scrollView willDecelerate:(BOOL)decelerate
{
    if (decelerate == NO) {
        [self onEndScroll:@"scrollViewDidEndDragging"];
    }
}

//停止加速
- (void)scrollViewDidEndDecelerating:(nonnull UIScrollView *)scrollView
{
    [self onEndScroll:@"scrollViewDidEndDecelerating"];
}


- (void)onEndScroll:(NSString *)from
{
    if (self.fakeData.count <= 1) {
        return;
    }

    NSInteger offset = (NSInteger)self.collectionView.contentOffset.x;
    //如果列表滚动到开始位置,即显示的是最后一个元素,需要调整列表offset到self.width * [self originalDataCount]位置
    if (offset == 0) {
        [self.collectionView setContentOffset:CGPointMake(self.width * [self originalDataCount], 0) animated:NO];
    } else if (offset == (NSInteger)(self.width * ([self originalDataCount] + 1))) {
        //否则,如果列表滚动到最后位置,即显示的是第一个元素,需要调整列表offset到self.width位置
        [self.collectionView setContentOffset:CGPointMake(self.width, 0) animated:NO];
    }

    [self stopTimer];
    NSInteger scrollInterval = max(3, self.scrollInterval);
    KS_WEAK_SELF(self);
    self.scrollTimer = [NSTimer scheduledTimerWithTimeInterval:scrollInterval block:^(NSTimer *timer) {
        CHECK_SELF_AND_RETURN();
        CGFloat offset = self.collectionView.contentOffset.x + self.width;
        [self.collectionView setContentOffset:CGPointMake(offset, 0) animated:YES];
    } repeats:NO];
}

- (void)stopScroll
{
    [self stopTimer];
}

- (void)startScroll
{
    if (!self.hidden) {
        [self onEndScroll:@"beginScroll"];
    }
}


- (void)setHidden:(BOOL)hidden
{
    [super setHidden:hidden];
    if (hidden) {
        [self stopTimer];
    }
}

- (void)removeFromSuperview
{
    [super removeFromSuperview];
    [self stopTimer];
}

@end
上一篇 下一篇

猜你喜欢

热点阅读