iOS banner+自定義pageControl

背景

公司一個(gè)項(xiàng)目UI改版,首頁(yè)有個(gè)banner,他的pageControl跟我們平常見到的不太相像。大概長(zhǎng)這樣:


IMG_0737.PNG

思路

原先我是打算直接修改選中的樣式。但是發(fā)現(xiàn)它只提供了修改默認(rèn)顏色和選中顏色兩個(gè)屬性。但是由于oc是一門動(dòng)態(tài)性的語(yǔ)言,我們可以通過找到這個(gè)被選中的原點(diǎn)的視圖,然后使用KVC的方式修改它。
方法參考:iOS 修改UIPageControl樣式
但是這并不能實(shí)現(xiàn)我想要的效果。因?yàn)?.

未切換情況下.PNG
切換之后.PNG

結(jié)論

pageControl實(shí)際上就是一組小圓點(diǎn)+選中圖形與banner圖片的聯(lián)動(dòng)。所以我們可以自己畫一個(gè)分頁(yè)控制器。

開始

  1. 創(chuàng)建圓點(diǎn)數(shù)組。
    圓點(diǎn)的數(shù)量是根據(jù)banner圖片的數(shù)量來的,所以我們需要在.h文件中開放一個(gè)設(shè)置變量pageCount,在.m文件中重寫set方法畫圓點(diǎn)。
    題外話:圓點(diǎn)的約束。一直在用Masonry來做布局,但是發(fā)現(xiàn)自己對(duì)它的了解很片面。在前輩那里學(xué)到了使用它的等間距布局方法。
- (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是存放圓點(diǎn)的可變數(shù)組,dotBgView是圓點(diǎn)父視圖,用來限制圓點(diǎn)散列范圍。使用這種方法能方便的對(duì)一組視圖進(jìn)行等間距布局。

  1. 創(chuàng)建選中圖形。
    畫一個(gè)符合設(shè)計(jì)稿尺寸的view就可以。不過有一點(diǎn)需要注意,由于我們?cè)诔跏蓟臅r(shí)候就創(chuàng)建了它。但是圓點(diǎn)是在pageCount的set方法里再創(chuàng)建添加的,所以我們需要在圓點(diǎn)創(chuàng)建完成后在調(diào)用一遍
[_dotBgView addSubview:_selectView];

這樣才能保證選中視圖一直覆蓋住圓點(diǎn)。

  1. 選中視圖與banner圖片的聯(lián)動(dòng)。
    這個(gè)聯(lián)動(dòng)關(guān)系有兩種:

    3.1 pageControl內(nèi)部的點(diǎn)擊 -> bannerScroll的contentOffset改變
    給pageControl添加一個(gè)手勢(shì),根據(jù)手勢(shì)的點(diǎn)擊位置與當(dāng)前選中視圖的前后關(guān)系(注意邊界情況,不能超過最左邊和最右邊。),來判斷選中視圖的移動(dòng)。currentPage用來記錄當(dāng)前選中行。


- (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當(dāng)前選中了第幾個(gè)圓點(diǎn),需要展示哪張圖片。

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

3.2 bannerScroll的滾動(dòng) -> pageControl的選中視圖位置改變
currentPage除了在pageControl內(nèi)部記錄當(dāng)前選中行,還需要擔(dān)當(dāng) 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相關(guān)-滑動(dòng)滾動(dòng)

項(xiàng)目總用到的banner是很久之前從別的地方拷來的代碼,但是不知道來源了。。之前的banner是一屏一屏的滾動(dòng)。但是現(xiàn)有的項(xiàng)目是 在兩側(cè)能夠看見上一張和下一張。重寫了他的滾動(dòng)邏輯。
目前是:如果滾動(dòng)超過圖片的一半,則滾動(dòng)到下一張。滾動(dòng)未超過圖片的一半,則滾動(dòng)到未超過圖片的上一張。

部分代碼
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相關(guān)-點(diǎn)擊事件

我們給放置圖片的父視圖scrollView一個(gè)tap的手勢(shì)。然后會(huì)在用戶點(diǎn)擊這個(gè)scroll的時(shí)候觸發(fā)手勢(shì)事件。
其中有兩點(diǎn)需要注意:

  1. 只能點(diǎn)擊image所在區(qū)域才能傳遞手勢(shì)事件;
  2. 需要傳遞被點(diǎn)擊的image所在的下標(biāo)。

有兩種方式可以解決:

  1. 拿到scroll視圖內(nèi)所有點(diǎn)擊和不可點(diǎn)擊的locationX坐標(biāo)范圍。根據(jù)拿到的點(diǎn)擊位置,去判斷當(dāng)前位置是否可點(diǎn)擊,以及可點(diǎn)擊位置所在的下標(biāo)index。
    由于圖片張數(shù)量是可變的,為了拿到這個(gè)可點(diǎn)擊+不可點(diǎn)擊 范圍,我們會(huì)用到循環(huán)
    scrollLeftPadding + (imageInset+ImageWidth)*i
    如果在tap的落點(diǎn)在這個(gè)范圍內(nèi),說明是可點(diǎn)擊的,循環(huán)因子【i】就是他的下標(biāo)。
    在圖片較多的情況下這種方式不可取。
  2. 將location的坐標(biāo)轉(zhuǎn)換為屏幕內(nèi)可見的坐標(biāo)。在0-ScreenWidth之間。我們可以通過減去scroll的偏移量去拿到它,下標(biāo)就是
    偏移量/(image寬度+image間距)
    這樣我們就能方便的去判斷點(diǎn)擊和不可點(diǎn)擊,而且不用去循環(huán),所以我們選擇這種實(shí)現(xiàn)方式。代碼如下
- (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

如果能加個(gè)動(dòng)畫效果就完美了
代碼:https://github.com/bumingxialuo/BannerPageControl.git
對(duì)你有用的話點(diǎn)個(gè)star~

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

友情鏈接更多精彩內(nèi)容