天天看点

iOS_无限滚动

Github上查看并下载源代码地址 最终效果图:

iOS_无限滚动

实现思路1:使用2个view,一个是当前的view,一个是重用的view 此方法中无论数据源有多少个,UIScrollView只保留其中2个视图,currentView和reUsingView,默认,scrollView的滚动范围3个width,并且currentView始终摆在中间位置(即x始终摆在一个width处) 当你滑动UIScrollView的时候,无非是向前滑动,或者是向后滑动。当你每次开始滑动的时候,我根据妳滑动的方向,切换reUsingView视图里面的内容。

然后在妳停止滚动的时候,重置currentView

看看下面的区别:

[objc] view plain copy

- (void)scrollViewDidEndDecelerating:(UIScrollView *)aScrollView {  

    [_scrollView setContentOffset:CGPointMake(_scrollView.frame.size.width, 0) animated:YES];  

}  

我们发现每一次滑动完成之后,UIScrollView总是重新切换回默认的中这一个视图。

<span style="font-size:14px;">//
//  ViewController.m
//  SuJi
//
//  Created by beyond on 16/7/9.
//  Copyright © 2016年 beyond. All rights reserved.
//

#import "ViewController.h"
#import "UIView+Frame.h"
@interface ViewController ()<UIScrollViewDelegate>
{
    UIScrollView *_scrollView;
    
    
    // 无限滚动的数据源数组
    NSArray *_slideImageArr;
    
    // 用来循环滚动的两个view
    UIImageView *_currentImageView;
    UIImageView *_resuingImageView;
    
    int _imageIndex;
    BOOL _isScrollDirectionNext;
    
    // 起始滚动 和 结束滚动的时候,不足一页,则不做任何处理
    CGFloat _startPositionX;
}
@end

@implementation ViewController

- (void)viewDidLoad {
    [super viewDidLoad];
    
    
    _slideImageArr = @[@"",@"",@"",@""];
    [self addSubviews];
    
    
}
- (void)addSubviews
{
    [self addBgImageView];
    
    [self addScrollView];
    
}

- (void)addBgImageView
{
    UIImageView *bgView = [[UIImageView alloc]init];
    bgView.frame = self.view.frame;
    UIImage *bgImage = [UIImage imageNamed:@"IMG_4397.JPG"];
    CGFloat top = 25; // 顶端盖高度
    CGFloat bottom = 25 ; // 底端盖高度
    CGFloat left = 10; // 左端盖宽度
    CGFloat right = 10; // 右端盖宽度
    UIEdgeInsets insets = UIEdgeInsetsMake(top, left, bottom, right);
    // 指定为拉伸模式,伸缩后重新赋值
    bgImage = [bgImage resizableImageWithCapInsets:insets resizingMode:UIImageResizingModeStretch];
    
    
    bgView.image = bgImage;
    [self.view addSubview:bgView];
    
    UIImageView *bgImageView = [[UIImageView alloc]init];
    bgImageView.frame = self.view.frame;
    bgImageView.contentMode = UIViewContentModeScaleAspectFit;
    bgImageView.image = [UIImage imageNamed:@"IMG_4398.png"];
    [self.view addSubview:bgImageView];
    
    // 加个灰色的蒙版
    UIView *grayView = [[UIView alloc]initWithFrame:bgImageView.frame];
    grayView.backgroundColor = [UIColor blackColor];
    grayView.alpha = 0.6;
    [self.view addSubview:grayView];
    
    
    
}

- (void)addScrollView
{
    _scrollView = [[UIScrollView alloc]init];
    _scrollView.pagingEnabled = YES;
    _scrollView.delegate = self;
    
    [_scrollView addSubview:[UIButton buttonWithType:UIButtonTypeContactAdd]];
    
    _scrollView.frame = self.view.frame;
    _scrollView.contentSize = CGSizeMake(0, self.view.frame.size.height + 1);
    _scrollView.backgroundColor = [UIColor clearColor];
    [self.view addSubview:_scrollView];
    
    // 初始化设置
    CGFloat width = _scrollView.frame.size.width;
    _scrollView.contentSize = CGSizeMake(3 * width, 0);
    _scrollView.contentOffset = CGPointMake(width, 0);
    
    
    /*
     第二种方式:只有两个view
     1个当前的View及1个缓冲的View
     将当前的View放在中间。
     判断滑动的位置,优先去缓冲的View找
     优点:对内存消耗少
     
     */
    
    UIImageView *currentImageView = [[UIImageView alloc] init];
    currentImageView.frame = CGRectMake(width, 0, width, _scrollView.frame.size.height);
    
    currentImageView.image = [UIImage imageNamed:@"00.jpg"];
    [_scrollView addSubview:currentImageView];
    _currentImageView = currentImageView;
    // 初始化 缓冲的ImageView
    [self setUpReusingImageView];
    _imageIndex = 0;
    
    
    
}
/** @programmer [email protected]
 *  @brief  初始化 缓冲的ImageView
 *  @param  <#无#>
 *  @return <#无#>
 */
- (void)setUpReusingImageView
{
    CGFloat width = _scrollView.frame.size.width;
    UIImageView *resuingImageView = [[UIImageView alloc] init];
    resuingImageView.frame = CGRectMake(0, 0, width, _scrollView.frame.size.height);
    
    
    [_scrollView addSubview:resuingImageView];
    _resuingImageView = resuingImageView;
}


#pragma mark - scrollView代理
- (void)scrollViewWillBeginDragging:(UIScrollView *)scrollView
{
    _startPositionX = scrollView.contentOffset.x;
}

- (void)scrollViewDidScroll:(UIScrollView *)scrollView
{
        if (scrollView.contentOffset.x > _currentImageView.frame.origin.x) {
        // 向右滚动时
            NSInteger val = _imageIndex + 1;
            if (_imageIndex >= [_slideImageArr count] - 1) {
                val = 0;
            }
            
            // 取缓冲区的View
            NSString *imgName = [NSString stringWithFormat:@"0%zd.jpg",val];
            _resuingImageView.image = [UIImage imageNamed:imgName];
            _resuingImageView.x = CGRectGetMinX(_currentImageView.frame) + _currentImageView.width;
            _isScrollDirectionNext = YES;
        }else{
            // 向左滚动的时候
            NSInteger val = _imageIndex - 1;
            if (val < 0) {
                val = [_slideImageArr count]-1;
            }
            _resuingImageView.image = [UIImage imageNamed:[NSString stringWithFormat:@"0%zd.jpg",val]];
            _resuingImageView.x = 0;
            _isScrollDirectionNext = NO;
        }
    
}

- (void)scrollViewDidEndDecelerating:(UIScrollView *)scrollView
{
    scrollView.userInteractionEnabled = YES;
    
    if (abs(_startPositionX - scrollView.contentOffset.x) < scrollView.width) {
        return;
    }
    
    // 是否是往右边滑动
    if (_isScrollDirectionNext) {
        _imageIndex++;
    }else{
        _imageIndex--;
    }
    
    // 补全
    if (_imageIndex < 0) {
        _imageIndex = [_slideImageArr count]-1;
    } else if(_imageIndex > [_slideImageArr count]-1){
        _imageIndex = 0;
    }
    
    _currentImageView.image = [UIImage imageNamed:[NSString stringWithFormat:@"0%zd.jpg",_imageIndex]];
    _currentImageView.frame = CGRectMake(_scrollView.width, 0, _scrollView.width, _scrollView.frame.size.height);
    [_scrollView setContentOffset:CGPointMake(_scrollView.frame.size.width, 0) animated:NO];
    
    
}
- (void)scrollViewDidEndDragging:(UIScrollView *)scrollView willDecelerate:(BOOL)decelerate
{
    scrollView.userInteractionEnabled = NO;
}
@end
</span>
           

实现方式2: 使用CollectionView实现,带pageContrl + timer定时器 思路,使用1组,但是告诉控制器有modelArrCount*5000个item,并且cellForRow时,创建根据index取模modelArrCount,取出数据源(实际只有8个),并且item的宽度就是一个屏幕的宽度 代码片段:

</pre><pre>
           
#import "BeyondViewController.h"
// 快速Frame
#import "UIView+Frame.h"

#import "BeyondNewsCell.h"
#import "BeyondNews.h"
#import "MJExtension.h"

// 只有一组,但是该组 有5000*8行
#define TotalItems (5000 * self.newses.count)
// 第一次出现的时候,didAppear时,就出现在中间,左边对齐
#define MiddleItem (NSUInteger)(TotalItems * 0.5)

@interface BeyondViewController () <UICollectionViewDataSource, UICollectionViewDelegate>
@property (weak, nonatomic) IBOutlet UICollectionView *collectionView;
@property (weak, nonatomic) IBOutlet UIPageControl *pageCtrl;
@property (strong, nonatomic) NSArray *newses;
@property (strong, nonatomic) NSTimer *timer;
@end

@implementation BeyondViewController
#pragma mark - 懒加载
- (NSArray *)newses
{
    if (!_newses) {
        self.newses = [BeyondNews objectArrayWithFilename:@"newses.plist"];
        // 总页数
        self.pageCtrl.numberOfPages = self.newses.count;
    }
    return _newses;
}
#pragma mark - 生命周期
- (void)viewDidLoad
{
    [super viewDidLoad];
    
    // 注册cell【UICollectionViewCell不让代码创建】
    [self.collectionView registerNib:[UINib nibWithNibName:@"BeyondNewsCell" bundle:nil] forCellWithReuseIdentifier:@"news"];
    
    // 添加定时器
    [self addTimer];
    
}
- (void)viewDidAppear:(BOOL)animated
{
    [super viewDidAppear:animated];
    [self.collectionView scrollToItemAtIndexPath:[NSIndexPath indexPathForItem:MiddleItem inSection:0] atScrollPosition:UICollectionViewScrollPositionLeft animated:NO];
}
#pragma mark - 时钟方法
- (void)addTimer
{
    self.timer = [NSTimer scheduledTimerWithTimeInterval:1.0 target:self selector:@selector(schedule) userInfo:nil repeats:YES];
    [[NSRunLoop currentRunLoop] addTimer:self.timer forMode:NSRunLoopCommonModes];
}
- (void)removeTimer
{
    [self.timer invalidate];
    self.timer = nil;
}
// 定时器方法
- (void)schedule
{
    // 得到当前显示的item
    NSIndexPath *visiablePath = [[self.collectionView indexPathsForVisibleItems] firstObject];
    
    NSUInteger visiableItem = visiablePath.item;
    if ((visiablePath.item % self.newses.count)  == 0) { // 第0张图片
        [self.collectionView scrollToItemAtIndexPath:[NSIndexPath indexPathForItem:MiddleItem inSection:0] atScrollPosition:UICollectionViewScrollPositionLeft animated:NO];
        visiableItem = MiddleItem;
    }
    
    // 滚动到下一个item
    NSUInteger nextItem = visiableItem + 1;
    [self.collectionView scrollToItemAtIndexPath:[NSIndexPath indexPathForItem:nextItem inSection:0] atScrollPosition:UICollectionViewScrollPositionLeft animated:YES];
}
#pragma mark - 数据源和代理方法
- (NSInteger)numberOfSectionsInCollectionView:(UICollectionView *)collectionView
{
    return 1;
}

- (NSInteger)collectionView:(UICollectionView *)collectionView numberOfItemsInSection:(NSInteger)section
{
    return TotalItems;
}

- (UICollectionViewCell *)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath
{
    static NSString *cellID = @"news";
    // 直接取
    BeyondNewsCell *cell = [collectionView dequeueReusableCellWithReuseIdentifier:cellID forIndexPath:indexPath];
    // UICollectionViewCell没有根据id进行创建方法,只能 从xib加载
    //    UICollectionViewFlowLayout *layout 中 可以指定cell的高度
    
    cell.news = self.newses[indexPath.item % self.newses.count];
    
    return cell;
}
// 重要,监听代理 停止滚动的事件
- (void)collectionView:(UICollectionView *)collectionView didEndDisplayingCell:(UICollectionViewCell *)cell forItemAtIndexPath:(NSIndexPath *)indexPath
{
    NSIndexPath *visiablePath = [[collectionView indexPathsForVisibleItems] firstObject];
    self.pageCtrl.currentPage = visiablePath.item % self.newses.count;
}

#pragma mark - scrollView代理
// 开始拖拽
- (void)scrollViewWillBeginDragging:(UIScrollView *)scrollView
{
    [self removeTimer];
}
// 停止拖拽
- (void)scrollViewDidEndDecelerating:(UIScrollView *)scrollView
{
    [self addTimer];
}
@end
           

使用到的分类:

//
//  Frame.h
//  08-无限滚动
//
//  Created by beyond on 15-3-27.
//  Copyright (c) 2015年 itcast. All rights reserved.
//

#import <UIKit/UIKit.h>

@interface UIView (Frame)
@property (assign, nonatomic) CGFloat x;
@property (assign, nonatomic) CGFloat y;
@property (assign, nonatomic) CGFloat width;
@property (assign, nonatomic) CGFloat height;
@property (assign, nonatomic) CGSize size;
@property (assign, nonatomic) CGPoint origin;
@end
           
//
//  Frame.m
//  08-无限滚动
//
//  Created by beyond on 15-3-27.
//  Copyright (c) 2015年 itcast. All rights reserved.
//

#import "UIView+Frame.h"

@implementation UIView (Frame)
- (void)setX:(CGFloat)x
{
    CGRect frame = self.frame;
    frame.origin.x = x;
    self.frame = frame;
}

- (CGFloat)x
{
    return self.frame.origin.x;
}

- (void)setY:(CGFloat)y
{
    CGRect frame = self.frame;
    frame.origin.y = y;
    self.frame = frame;
}

- (CGFloat)y
{
    return self.frame.origin.y;
}

- (void)setWidth:(CGFloat)width
{
    CGRect frame = self.frame;
    frame.size.width = width;
    self.frame = frame;
}

- (CGFloat)width
{
    return self.frame.size.width;
}

- (void)setHeight:(CGFloat)height
{
    CGRect frame = self.frame;
    frame.size.height = height;
    self.frame = frame;
}

- (CGFloat)height
{
    return self.frame.size.height;
}

- (void)setSize:(CGSize)size
{
    CGRect frame = self.frame;
    frame.size = size;
    self.frame = frame;
}

- (CGSize)size
{
    return self.frame.size;
}

- (void)setOrigin:(CGPoint)origin
{
    CGRect frame = self.frame;
    frame.origin = origin;
    self.frame = frame;
}

- (CGPoint)origin
{
    return self.frame.origin;
}
@end
           

MainStoryBoard,在FlowLayout中指定item的size

iOS_无限滚动

Xib创建CollectionCell,注意指定重用ID

iOS_无限滚动