iOS banner+自定义pageControl

背景

公司一个项目UI改版,首页有个banner,他的pageControl跟我们平常见到的不太相像。大概长这样:


IMG_0737.PNG

思路

原先我是打算直接修改选中的样式。但是发现它只提供了修改默认颜色和选中颜色两个属性。但是由于oc是一门动态性的语言,我们可以通过找到这个被选中的原点的视图,然后使用KVC的方式修改它。
方法参考:iOS 修改UIPageControl样式
但是这并不能实现我想要的效果。因为..

未切换情况下.PNG
切换之后.PNG

结论

pageControl实际上就是一组小圆点+选中图形与banner图片的联动。所以我们可以自己画一个分页控制器。

开始

  1. 创建圆点数组。
    圆点的数量是根据banner图片的数量来的,所以我们需要在.h文件中开放一个设置变量pageCount,在.m文件中重写set方法画圆点。
    题外话:圆点的约束。一直在用Masonry来做布局,但是发现自己对它的了解很片面。在前辈那里学到了使用它的等间距布局方法。
- (void)setPageCount:(NSInteger)pageCount {
    if (!pageCount) {
        return;
    }
    __weak typeof(self) weakSelf = self;
    ...(省略)
    [_dotBgView mas_updateConstraints:^(MASConstraintMaker *make) {
        make.width.equalTo(@(40+pageCount*15-10));
    }];
    ...(省略)
    [_dotArray mas_distributeViewsAlongAxis:(MASAxisType)MASAxisTypeHorizontal withFixedItemLength:5 leadSpacing:20 tailSpacing:20];
   
    [_dotArray mas_makeConstraints:^(MASConstraintMaker *make) {
        make.centerY.equalTo(weakSelf.dotBgView);
        UIView *firstView = (UIView *)weakSelf.dotArray[0];
        make.height.mas_equalTo(firstView.mas_width);

    }];
    ...(省略)
}

dotArray是存放圆点的可变数组,dotBgView是圆点父视图,用来限制圆点散列范围。使用这种方法能方便的对一组视图进行等间距布局。

  1. 创建选中图形。
    画一个符合设计稿尺寸的view就可以。不过有一点需要注意,由于我们在初始化的时候就创建了它。但是圆点是在pageCount的set方法里再创建添加的,所以我们需要在圆点创建完成后在调用一遍
[_dotBgView addSubview:_selectView];

这样才能保证选中视图一直覆盖住圆点。

  1. 选中视图与banner图片的联动。
    这个联动关系有两种:

    3.1 pageControl内部的点击 -> bannerScroll的contentOffset改变
    给pageControl添加一个手势,根据手势的点击位置与当前选中视图的前后关系(注意边界情况,不能超过最左边和最右边。),来判断选中视图的移动。currentPage用来记录当前选中行。


- (void)tapView:(UITapGestureRecognizer *)tap {
    __weak typeof(self) weakSelf = self;
    CGFloat locationX = _dotBgView.frame.origin.x + _selectView.frame.origin.x;
    CGPoint point = [tap locationInView:self];
    if (point.x > locationX) {
        if (_currentPage<_dotArray.count-1) {
            _currentPage+=1;
        }
    } else {
        if (_currentPage > 0) {
            _currentPage-=1;
        }
    }
    [UIView animateWithDuration:1.0 animations:^{
        [self->_selectView mas_remakeConstraints:^(MASConstraintMaker *make) {
            make.size.mas_equalTo(CGSizeMake(12, 5));
            UIView *indexView = (UIView *)weakSelf.dotArray[weakSelf.currentPage];
            make.centerY.equalTo(indexView);
            make.centerX.equalTo(indexView);
        }];
    }];
    if (self.selectAction) {
        self.selectAction(_currentPage);
    }
}

selectAction是向banner传递消息的闭包,告诉banner当前选中了第几个圆点,需要展示哪张图片。

_pageControl.selectAction = ^(NSInteger index) {
        [UIView animateWithDuration:0.8 animations:^{
            weakSlef.scrollView.contentOffset = CGPointMake((BannerImageWidth+10) * index, weakSlef.scrollView.contentOffset.y);
        }];
    };

3.2 bannerScroll的滚动 -> pageControl的选中视图位置改变
currentPage除了在pageControl内部记录当前选中行,还需要担当 banner传递消息给pageControl的帮手。所以currentPage也是BannerPageControl.h中外放的变量,然后在.m文件重写set方法,改变选中视图位置。

- (void)setCurrentPage:(NSInteger)currentPage {
    __weak typeof(self) weakSelf = self;
    _currentPage = currentPage;
    [UIView animateWithDuration:1.0 animations:^{
        [weakSelf.selectView mas_remakeConstraints:^(MASConstraintMaker *make) {
            make.size.mas_equalTo(CGSizeMake(12, 5));
            UIView *currentView = (UIView *)weakSelf.dotArray[currentPage];
            make.centerY.equalTo(currentView);
            make.centerX.equalTo(currentView);
        }];
    }];
}

banner相关-滑动滚动

项目总用到的banner是很久之前从别的地方拷来的代码,但是不知道来源了。。之前的banner是一屏一屏的滚动。但是现有的项目是 在两侧能够看见上一张和下一张。重写了他的滚动逻辑。
目前是:如果滚动超过图片的一半,则滚动到下一张。滚动未超过图片的一半,则滚动到未超过图片的上一张。

部分代码
NSInteger currentIndex;
float indexF = endX / itemWidth;
currentIndex = (NSInteger)(indexF+0.5);
[UIView animateWithDuration:0.8 animations:^{
      scrollView.contentOffset = CGPointMake(currentIndex * itemWidth, scrollView.contentOffset.y);
}];
self.pageControl.currentPage = currentIndex;

banner相关-点击事件

我们给放置图片的父视图scrollView一个tap的手势。然后会在用户点击这个scroll的时候触发手势事件。
其中有两点需要注意:

  1. 只能点击image所在区域才能传递手势事件;
  2. 需要传递被点击的image所在的下标。

有两种方式可以解决:

  1. 拿到scroll视图内所有点击和不可点击的locationX坐标范围。根据拿到的点击位置,去判断当前位置是否可点击,以及可点击位置所在的下标index。
    由于图片张数量是可变的,为了拿到这个可点击+不可点击 范围,我们会用到循环
    scrollLeftPadding + (imageInset+ImageWidth)*i
    如果在tap的落点在这个范围内,说明是可点击的,循环因子【i】就是他的下标。
    在图片较多的情况下这种方式不可取。
  2. 将location的坐标转换为屏幕内可见的坐标。在0-ScreenWidth之间。我们可以通过减去scroll的偏移量去拿到它,下标就是
    偏移量/(image宽度+image间距)
    这样我们就能方便的去判断点击和不可点击,而且不用去循环,所以我们选择这种实现方式。代码如下
- (void)selectIndexImage:(UITapGestureRecognizer *)tap {
    CGPoint location = [tap locationInView:_scrollView];
    CGFloat offSetX = location.x - _scrollView.contentOffset.x;
    if (offSetX<17.5 || offSetX>(BannerImageWidth-17.5)) {
        return;
    }
    NSLog(@"_scrollView.contentOffset.x=%2f",_scrollView.contentOffset.x);
    NSInteger index = _scrollView.contentOffset.x / (BannerImageWidth+10);
    if (self.clickWithBlock) {
        self.clickWithBlock(index);
    }
}

then

如果能加个动画效果就完美了
代码:https://github.com/bumingxialuo/BannerPageControl.git
对你有用的话点个star~

你可能感兴趣的:(iOS banner+自定义pageControl)