背景
公司一個(gè)項(xiàng)目UI改版,首頁(yè)有個(gè)banner,他的pageControl跟我們平常見到的不太相像。大概長(zhǎng)這樣:
思路
原先我是打算直接修改選中的樣式。但是發(fā)現(xiàn)它只提供了修改默認(rèn)顏色和選中顏色兩個(gè)屬性。但是由于oc是一門動(dòng)態(tài)性的語(yǔ)言,我們可以通過找到這個(gè)被選中的原點(diǎn)的視圖,然后使用KVC的方式修改它。
方法參考:iOS 修改UIPageControl樣式
但是這并不能實(shí)現(xiàn)我想要的效果。因?yàn)?.
結(jié)論
pageControl實(shí)際上就是一組小圓點(diǎn)+選中圖形與banner圖片的聯(lián)動(dòng)。所以我們可以自己畫一個(gè)分頁(yè)控制器。
開始
- 創(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)行等間距布局。
- 創(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)。
-
選中視圖與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)需要注意:
- 只能點(diǎn)擊image所在區(qū)域才能傳遞手勢(shì)事件;
- 需要傳遞被點(diǎn)擊的image所在的下標(biāo)。
有兩種方式可以解決:
- 拿到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)。
在圖片較多的情況下這種方式不可取。 - 將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~