UI学习:无限轮播视图

无限轮播视图

在仿写 ZARA 的时候, 在商品展示页需要写一个无限轮播视图用于展示商品, 笔者在这一部分遇到了很多困难, 现在在这里做一总结

首先创建一个 UIScrollView, 用于添加图片进行商品展示

笔者在定义部分定义了以下几个需要用到的属性

objc 复制代码
// Searchpage.h 

@interface Searchpage 

// 分栏控件
@property (nonatomic, strong) UISegmentedControl* segmentedControl;

// 滚动视图
@property (nonatomic, strong) UIScrollView* scrollView;

// 滚动视图的图片名称
@property (nonatomic, strong) NSMutableArray* imageNames;

// 定时器, 使视图自动轮播
@property (nonatomic, strong) NSTimer* timer;

@end

其中:

  • 分栏控件用于通过商品标签快速切换商品
  • 滚动视图用于展示商品图片
  • 定时器用于实现自动轮播

首先实现顶部分类栏的创建

objc 复制代码
// 设置分栏控件
- (void)setSegmentedControl {

    NSArray *items = @[@"女士", @"儿童", @"男士"];

    self.segmentedControl = [[UISegmentedControl alloc] initWithItems:items];

    // 设置默认选中
    self.segmentedControl.selectedSegmentIndex = 0;

    // 设置文字颜色
    [self.segmentedControl setTitleTextAttributes:@{
        NSForegroundColorAttributeName : [UIColor whiteColor]
    } forState:UIControlStateNormal];

    // 设置选中文字颜色
    [self.segmentedControl setTitleTextAttributes:@{
        NSForegroundColorAttributeName : [UIColor redColor]
    } forState:UIControlStateSelected];

    // 设置背景颜色
    self.segmentedControl.backgroundColor = [UIColor blackColor];

    // 设置滑块颜色
    self.segmentedControl.selectedSegmentTintColor = [UIColor greenColor];

    [self.view addSubview:self.segmentedControl];

    // 使用Masonry进行约束
    [self.segmentedControl mas_makeConstraints:^(MASConstraintMaker *make) {
        make.top.mas_equalTo(self.view).offset(80);
        make.left.mas_equalTo(self.view);
        make.width.mas_equalTo(self.view);
        make.height.mas_equalTo(60);
    }];

    // 添加点击事件
    [self.segmentedControl addTarget:self
                              action:@selector(segmentChanged:)
                    forControlEvents:UIControlEventValueChanged];
}

这里主要完成了以下几个功能:

  • 创建 UISegmentedControl
  • 设置默认选中的栏目
  • 设置文字颜色与滑块颜色
  • 使用 Masonry 添加约束
  • 添加栏目切换事件

顶部分类栏完成后, 接下来开始实现滚动视图部分

objc 复制代码
// 设置滚动视图
- (void)setInterface {

    // 初始化图片数组
    self.imageNames = [[NSMutableArray alloc] init];

    // 无限轮播核心
    // 在数组头尾添加临时图片
    [self.imageNames addObject:@"3.jpg"];

    for (int i = 1; i <= 3; i++) {
        NSString *imageName = [NSString stringWithFormat:@"%d.jpg", i];
        [self.imageNames addObject:imageName];
    }

    [self.imageNames addObject:@"1.jpg"];

    // 创建scrollView
    self.scrollView = [[UIScrollView alloc] init];

    [self.view addSubview:self.scrollView];

    [self.scrollView mas_makeConstraints:^(MASConstraintMaker *make) {
        make.top.mas_equalTo(160);
        make.left.mas_equalTo(0);
        make.width.mas_equalTo(self.view);
        make.bottom.mas_equalTo(self.view).offset(-80);
    }];

    // 强制刷新布局
    [self.view layoutIfNeeded];

    // 设置contentSize
    self.scrollView.contentSize = CGSizeMake(self.view.bounds.size.width * self.imageNames.count, 0);

    // 默认显示第一页
    self.scrollView.contentOffset = CGPointMake(self.scrollView.bounds.size.width, 0);

    // 分页滑动
    self.scrollView.pagingEnabled = YES;

    // 添加图片
    for (int i = 0; i < self.imageNames.count; i++) {

        UIImage *image = [UIImage imageNamed:self.imageNames[i]];

        UIImageView *iView = [[UIImageView alloc] initWithImage:image];

        [self.scrollView addSubview:iView];
        
        [iView mas_makeConstraints:^(MASConstraintMaker *make) {
            make.left.mas_equalTo(self.scrollView.bounds.size.width * i);
            make.top.mas_equalTo(self.scrollView);
            make.width.mas_equalTo(self.scrollView.mas_width);
            make.height.mas_equalTo(self.scrollView.mas_height);
        }];
    }

    // 设置代理
    self.scrollView.delegate = self;
}

这里用到了[self.view layoutIfNeeded]; 这个方法的作用是立即让当前视图进行一次布局更新,使约束马上生效。

在使用 MasonryAuto Layout 时:

写完约束后,系统其实并不会立刻计算控件的真实大小和位置。

系统默认会等到:

  • 当前 RunLoop 结束
  • 或下一次刷新界面

才统一进行布局计算。

在刚设置完 Masonry 约束后,Auto Layout 还没有开始真正计算 frame,所以此时 view 的 frame / bounds 很可能还是初始值(通常是 0)

可以看以下例子

objc 复制代码
    // 设置滚动视图
    self.scrollView = [[UIScrollView alloc] init];
    [self.view addSubview: self.scrollView];
    [self.scrollView mas_makeConstraints:^(MASConstraintMaker *make) {
            make.top.mas_equalTo(160);
            make.left.mas_equalTo(0);
            make.width.mas_equalTo(self.view);
            make.bottom.mas_equalTo(self.view).offset(-80);
    }];
    
    NSLog(@"%f", self.scrollView.bounds.size.width);
    
    // 强制刷新视图, 使约束生效
    [self.view layoutIfNeeded];
    
    NSLog(@"%f", self.scrollView.bounds.size.width); 

在设置完约束后,打印scrollView的width, 输出是 0 通过[self.view layoutIfNeeded];方法强制重新布局的时候, 就可以使约束立即生效

最关键的部分就是无限轮播的实现

正常情况下图片数组只有:

复制代码
1.jpg
2.jpg
3.jpg

但这样无法形成循环轮播

因此需要在数组头尾各插入一张临时图片

最终数组会变成:

复制代码
3.jpg
1.jpg
2.jpg
3.jpg
1.jpg

这样在滑动到边界时, 就可以偷偷修改 contentOffset, 用户视觉上就会感觉是无限滑动的效果。

在这里就需要用到UIScrollView的协议, 这里先遵守协议UIScrollViewDelegate. 并设置代理为当前的视图控制器

objc 复制代码
@interface Searchpage : UIViewController <UIScrollViewDelegate>

@end
    
    
     // 设置滚动视图的代理为当前控制器
    self.scrollView.delegate = self;

那么就可以在协议方法中进行无限视图的轮播的实现了

通过滚动视图的偏移来计算视图的页码

objc 复制代码
// 获取当前滚动视图页码
- (NSInteger)currentPage {
    // 获取滚动视图的坐标
    // 计算出是当前视图的页码
    CGFloat width = self.scrollView.bounds.size.width;
    return self.scrollView.contentOffset.x / width - 1;
}

然后在滑动滚动视图的时候同时切换分栏控件的标签, 特别注意的是, 当滑动到临时页的时候, 要修改contentOffset, 实现无限视图的效果

objc 复制代码
- (void)scrollViewDidScroll:(UIScrollView *)scrollView {

//    CGFloat page = self.scrollView.contentOffset.x / self.scrollView.bounds.size.width;
    
    CGFloat page = [self currentPage];
    if (page == -1) {
        [self.scrollView setContentOffset: CGPointMake(self.scrollView.bounds.size.width * (self.imageNames.count - 2), 0) animated: NO];
        page = [self currentPage];
    } else if (page == self.imageNames.count - 2) {
        [self.scrollView setContentOffset: CGPointMake(self.scrollView.bounds.size.width, 0) animated: NO];
        page = [self currentPage];
    }
    
    self.segmentedControl.selectedSegmentIndex = page;
}

通过分栏控件的改变切换滚动视图的偏移, 这一部分可以通过分栏控件的标签索引来定位滚动视图的偏移量

objc 复制代码
// 分栏控件点击响应
- (void)segmentChanged:(UISegmentedControl *) sender {
    NSInteger index = sender.selectedSegmentIndex;
    
    // 当分栏控件的索引发生改变,滚动视图也跟随滚动到相对应的页面
    // 设置当前scrollView的左边界
    CGFloat left = self.view.bounds.size.width * (index + 1);
    // 调用方法滚动到相应的页面
    // 滑动到指定的坐标 (x, y)
    // YES: 带动画
    [self.scrollView setContentOffset: CGPointMake(left, 0)  animated: NO];
}

通过定时器来实现自动轮播

可以通过定时器来实现滚动视图的自动翻页

首先创建定时器和设置事件

objc 复制代码
- (void) setTimer {
    // repeats: YES: 重复执行
    self.timer = [NSTimer scheduledTimerWithTimeInterval: 2 target: self selector: @selector(nextPage) userInfo: nil repeats: YES];
}

// 定时器事件, 定时翻页
- (void) nextPage {
    NSInteger nextIndex = self.segmentedControl.selectedSegmentIndex + 1;
    if (nextIndex == 3) {
        nextIndex = 0;
    }
    self.segmentedControl.selectedSegmentIndex = nextIndex;
    
    // 需要手动改变滚动视图
    [self segmentChanged: self.segmentedControl];
}

这里设置了时间间隔 2 秒翻页, 然后就需要思考在什么时候开启定时器和关闭定时器, 当用户手动滑动滚动视图的就需要关闭定时器, 用户不滑动的时候都要进行自动翻页

这样就可以使用滚动视图的代理方法- (void) scrollViewWillBeginDragging:(UIScrollView *)scrollView\- (void) scrollViewDidEndDecelerating:(UIScrollView *)scrollView 分别在当滑动时和结束滑动时调用

首先, 在滑动时候关闭定时器并置为空 (防止重复创建定时器导致无法关闭)

objc 复制代码
// 当用户滑动视图的时候, 关闭定时器
- (void) scrollViewWillBeginDragging:(UIScrollView *)scrollView {
    // 停止定时器并从运行循环中移除
    [self.timer invalidate];
    // 置为空,防止多次创建
    self.timer = nil
}

然后当滑动结束的时候重新创建一个定时器进行自动翻页

objc 复制代码
- (void) scrollViewDidEndDecelerating:(UIScrollView *)scrollView {
    [self setTimer];
}
相关推荐
xieliyu.1 小时前
Java手搓二叉树:基础遍历与核心操作全解析
java·开发语言·数据结构·学习
kdxiaojie1 小时前
U-Boot分析【学习笔记】(6)
linux·笔记·学习
DragonnAi1 小时前
论文解读:SFINet 空间-频率统一学习框架用于多模态图像融合
深度学习·学习·计算机视觉
晓梦林1 小时前
Commit靶场学习笔记
笔记·学习·安全·web安全
星夜夏空991 小时前
STM32单片机学习(4)——嵌入式概述
stm32·单片机·学习
nashane2 小时前
HarmonyOS 6学习:HWAsan监测开启后应用崩溃的终极解决方案
学习·华为·harmonyos·harmonyos 5
谙弆悕博士2 小时前
Lua学习笔记
c语言·开发语言·笔记·学习·lua·创业创新·业界资讯
MonkeyKing2 小时前
iOS Block 底层深度解析:结构、变量捕获、copy逻辑与循环引用本质
ios
MonkeyKing2 小时前
iOS ARC 本质:__strong / __weak / __unsafe_unretained / __autoreleasing 深度解析
ios