支持自动轮播及手动左右滑的banner
2020-10-21 本文已影响0人
蓝天白云_Sam
基本思路
- 如果只有一个数据,则不需要滚动
- 如果多于一个数据,则在源数据的最前面插入源数据的最后一个元素,末尾插入源数据的第一个元素组成新的数据源
fakeData
,如源数据为originalData = @[ @(0), @(1), @(2), @(3) ];
则新数据为fakeData = @[@(3), @(0), @(1), @(2), @(3) , @(0)];
- 列表显示的时候以
fakeData
为数据源进行显示 - 如果列表滚动到开始位置,此时实际显示的是最后一个元素,可使用
setContentOffset:animated:
滚动到self.width * [self originalDataCount]
的位置 - 如果列表滚动到最后的位置,此时实际显示的是第一个元素,可使用
setContentOffset:animated:
滚动到self.width
的位置
- 自动轮播:
- 使用
[self.collectionView setContentOffset:CGPointMake(offset, 0) animated:YES];
定时自动轮播 - 监听自动轮播结束事件并启动下一次轮播timer:
- (void)scrollViewDidEndScrollingAnimation:(UIScrollView *)scrollView
- 左右滑动处理:
- 开始滑动,停止自动轮播Timer:
- (void)scrollViewWillBeginDragging:(UIScrollView *)scrollView
- 手指脱离屏幕,如果没有加速度,则调整
UIScrollView ontentOffse
并启动下一次轮播 timer :
- (void)scrollViewDidEndDragging:(UIScrollView *)scrollView willDecelerate:(BOOL)decelerate
- 手指脱离屏幕,如果有加速度,则等加速完成后调整
UIScrollView ontentOffse
并启动下一次轮播 timer :
- (void)scrollViewDidEndDecelerating:(nonnull UIScrollView *)scrollView
- 头文件:
WSScrollBannerView.h
//
// 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
//
// 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