推荐阅读:iOS开发——BAT面试题合集(持续更新中)
我们在移动项目开发中经常会用到滑动选择功能,例如在“米家”App的首页中,为了展示账户下所有智能设备,或者按类型、者房间展示部分相关设备,界面上做了如下设计:
下面我们就来介绍一下这个滑动选择控件的实现过程。
1. 需求分析
上面动图中展示的界面并不复杂,我们可以看的出来,这个滑动选择功能主要分为两个部分:顶部滑动选择bar和底部可手动滑动的scrollView,并且这上下两部分可以联动。(底部scrollView中的子view)
需求归纳如下:
(1)顶部为滑动可选topBar;
(2)topBar可手势左右滑动;
(3)topBar中的可选项数量可扩展;
(4)topBar中的选中项自动滚动至屏幕可见区域;
(5)topBar中的每一项可点击选择,选中项样式有变化;
(6)topBar中的选中项底部有个游标cursor,cursor的宽度可自适应;
(7)下部为可左右滑动scrollView;
(8)scrollView根据当前topBar选中项分屏展示,即topBar与scrollView联动;
2. 控件的框架层次
我们最终要将顶部topBar和下部的scrollView作为一个整体控件使用,因此最外层为pageContainer(继承于UIView)将二者封装起来,整个控件的初始化、设置方法都在pageContainer中。则整个控件的框架很简单,如下:
3. 具体实现
本问的实现的代码是由部门中的前辈封装,根据项目需求我们自己可以做一些相应的改动,首先谢谢那些前辈写出一个这么简单易用的控件,并提供给我们学习的机会。下面我们根据控件的框架层次结构,从下往上依次说明实现过程。
3.1 TopBarButton
本控件中的TopBarButton继承于UIButton,是控件顶部滑动栏中的一个item,重写UIButton的setSelected:方法就可以设置TopBarButton在选中时的状态。如果你的项目中对TopBarButton还有其他样式要求,可在该类中添加其他元素,并添加相应初始化方法。
<pre class="prettyprint hljs objectivec" style="padding: 0.5em; font-family: Menlo, Monaco, Consolas, "Courier New", monospace; color: rgb(68, 68, 68); border-radius: 4px; display: block; margin: 0px 0px 1.5em; font-size: 14px; line-height: 1.5em; word-break: break-all; word-wrap: break-word; white-space: pre; background-color: rgb(246, 246, 246); border: none; overflow-x: auto; font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: normal; orphans: 2; text-align: start; text-indent: 0px; text-transform: none; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; text-decoration-style: initial; text-decoration-color: initial;">//////// .h文件
@interface QiTopBarButton : UIButton
@end
//////// .m文件
import "QiTopBarButton.h"
define BADGE_WIDTH (13)
define TitleFontSize (13)
define ColorNormalDefault [UIColor blackColor]
define ColorSelectedDefault [UIColor redColor];
@interface QiTopBarButton()
@end
@implementation QiTopBarButton
-
(id)init {
self = [super init];
if (self) {
_colorNormal = ColorNormalDefault;
_colorSelected = ColorSelectedDefault;
[self setTitleColor:_colorSelected forState:UIControlStateSelected];
[self setTitleColor:_colorNormal forState:UIControlStateNormal];[self.titleLabel setFont:[UIFont systemFontOfSize:TitleFontSize]]; // 设置其他子view、属性等
}
return self;
} -
(void)setSelected:(BOOL)selected {
[super setSelected:selected];
if (selected) {
[self setTitleColor:_colorSelected forState:UIControlStateNormal];
UIFont *font = [UIFont systemFontOfSize:TitleFontSize + 1 weight:UIFontWeightBold];
[self.titleLabel setFont:font];
} else {
[self setTitleColor:_colorNormal forState:UIControlStateNormal];
UIFont *font = [UIFont systemFontOfSize:TitleFontSize weight:normal];
[self.titleLabel setFont:font];
}
}
@end
复制代码</pre>
3.2 PagesContainerTopBar
本控件中的PagesContainerTopBar继承于UIView,其中的子view主要包括scrollView、一组topBarButton、cursor。
<pre class="prettyprint hljs objectivec" style="padding: 0.5em; font-family: Menlo, Monaco, Consolas, "Courier New", monospace; color: rgb(68, 68, 68); border-radius: 4px; display: block; margin: 0px 0px 1.5em; font-size: 14px; line-height: 1.5em; word-break: break-all; word-wrap: break-word; white-space: pre; background-color: rgb(246, 246, 246); border: none; overflow-x: auto; font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: normal; orphans: 2; text-align: start; text-indent: 0px; text-transform: none; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; text-decoration-style: initial; text-decoration-color: initial;">//////// .h文件
import <UIKit/UIKit.h>
@protocol QiPagesContainerTopBarDelegate;
@interface QiPagesContainerTopBar : UIView
@property (nonatomic, weak) id<QiPagesContainerTopBarDelegate> target;
// 设置相邻button之间的间距(两个button的title之间的距离)
@property (nonatomic, assign) CGFloat buttonMargin;
pragma mark - update style
(void)setBackgroundImage:(UIImage *)image;
(void)setBackgroundImageHidden:(BOOL)isHidden;
(void)setCursorPosition:(CGFloat)percent;
(void)updateConentWithTitles:(NSArray *)titles;
(void)setIsButtonAlignmentLeft:(BOOL)isAlignmentLeft;
(void)setShowSeperateLines:(BOOL)showSeperateLines;
(void)setSelectedIndex:(NSInteger)idnex;
(NSInteger)getSelectedIndex;
// 设置滑块的颜色
- (void)setCursorColor:(UIColor *)color;
// 设置滑块长度 - (void)setCursorWidth:(CGFloat)width;
// 设置滑块长度 - (void)setCursorHeight:(CGFloat)height;
// 设置按钮选中和未选中的颜色 - (void)setTextColor:(UIColor *)normalColor andSelectedColor:(UIColor *)selectedColor;
@end
@protocol QiPagesContainerTopBarDelegate <NSObject>
@optional
// 选中topBar的一项
- (void)topBarSelectIndex:(NSInteger)index;
// 重复点击topBar时会调用该方法
- (void)topBarSelectIndicator:(NSInteger)index;
@end
//////// .m文件
import "QiPagesContainerTopBar.h"
import "QiTopBarButton.h"
//左右侧的间距
define MARGIN_HORI (0)
define CursorHeightDefault (1.5)
define BUTTON_MARGIN_DEFAULT (20)
@interface QiPagesContainerTopBar ()
@property (nonatomic, strong) UIImageView *backgroundImageView;
@property (nonatomic, strong) UIScrollView *scrollView;
@property (nonatomic, strong) UIView *cursor;
@property (nonatomic, assign) CGFloat cursorWidth;
@property (nonatomic, assign) CGFloat cursorHeight;
@property (nonatomic, strong) NSArray *arrayButtons;
@property (nonatomic, assign) BOOL isButtonAlignmentLeft;
@property (nonatomic, strong) NSArray *arraySeperateLines;
@property (nonatomic, assign) BOOL showSeperateLines;
@end
@implementation QiPagesContainerTopBar
-
(id)init {
self = [super init];
if (self) {
[self setup];
}
return self;
} -
(id)initWithFrame:(CGRect)frame {
self = [super initWithFrame:frame];
if (self) {
[self setup];
}
return self;
} -
(id)initWithCoder:(NSCoder *)aDecoder {
self = [super initWithCoder:aDecoder];
if (self) {
[self setup];
}
return self;
} -
(void)setup {
_buttonMargin = BUTTON_MARGIN_DEFAULT;
_cursorHeight = CursorHeightDefault;_backgroundImageView = [[UIImageView alloc] initWithFrame:self.bounds];
[self addSubview:_backgroundImageView];_scrollView = [[UIScrollView alloc] init];
_scrollView.showsHorizontalScrollIndicator = NO;
_scrollView.showsVerticalScrollIndicator = NO;
_scrollView.bounces = NO;
[self addSubview:_scrollView];_cursor = [[UIView alloc] initWithFrame:CGRectZero];
_cursor.backgroundColor = [UIColor redColor];
_cursor.layer.cornerRadius = _cursorHeight / 2.0;
[_scrollView addSubview:_cursor];
}
pragma mark - 设置各个控件的位置
-
(void)layoutSubviews {
[super layoutSubviews];
CGSize size = self.frame.size;
_backgroundImageView.frame = CGRectMake(0, 0, size.width, size.height);;
_scrollView.frame = CGRectMake(0, 0, size.width, size.height);
if ([_arrayButtons count] == 0) {
return;
}// 增加按钮两侧的间距
CGFloat contentWidth = MARGIN_HORI * 2;
for (int i=0; i<[_arrayButtons count]; i++) {
UIButton *button = [_arrayButtons objectAtIndex:i];
contentWidth += button.frame.size.width;
}// 按钮未排满整屏宽度时
if (!_isButtonAlignmentLeft && contentWidth < size.width) {
CGFloat buttonWidth = floorf((size.width - MARGIN_HORI * 2) / [_arrayButtons count]);
for (UIButton *button in _arrayButtons) {
CGRect frame = button.frame;
frame.size.width = buttonWidth;
button.frame = frame;
}
}// 设置按钮位置
NSInteger selectedIndex = 0;
CGFloat xOffset = MARGIN_HORI;
CGFloat buttonHeight = size.height;
for (int i=0; i<[_arrayButtons count]; i++) {
UIButton *button = [_arrayButtons objectAtIndex:i];
CGRect frame = button.frame;
frame.origin.x = xOffset;
frame.origin.y = 0;
frame.size.height = buttonHeight;
button.frame = frame;
xOffset += frame.size.width;
if (button.selected) {
selectedIndex = i;
}
}// 设置分割线位置
for (int i=0; i<[_arraySeperateLines count]; i++) {
UIView *line = [_arraySeperateLines objectAtIndex:i];
line.hidden = !_showSeperateLines;UIButton *buttonPrev = [_arrayButtons objectAtIndex:i]; UIButton *buttonNext = [_arrayButtons objectAtIndex:i+1]; CGRect frame = line.frame; frame.origin.x = (CGRectGetMaxX(buttonPrev.frame) + CGRectGetMinX(buttonNext.frame))/2; line.frame = frame;
}
_scrollView.contentSize = CGSizeMake(xOffset + MARGIN_HORI, size.height);
// 设置游标位置
UIButton *buttonSelected = [_arrayButtons objectAtIndex:selectedIndex];
CGRect frame = buttonSelected.frame;
[buttonSelected.titleLabel sizeToFit];
CGFloat cursorWidth = _cursorWidth != 0 ? _cursorWidth : buttonSelected.titleLabel.frame.size.width;
_cursor.frame = CGRectMake(frame.origin.x + (frame.size.width - cursorWidth) / 2, CGRectGetMaxY(frame) - _cursorHeight, cursorWidth, _cursorHeight);
}
pragma mark - 创建各个button
-
(void)updateConentWithTitles:(NSArray *)titles {
for (UIButton *button in _arrayButtons) {
[button removeFromSuperview];
}if ([titles count] == 0) {
return;
}NSMutableArray *tempArray = [NSMutableArray array];
for (int i=0; i<[titles count]; i++) {
NSString *title = [titles objectAtIndex:i];
UIButton *button = [self createCustomButtonWithTitle:title];
button.titleLabel.font = [UIFont systemFontOfSize:14];
button.tag = i;
[button sizeToFit];
CGRect frame = button.frame;
frame.size.width += _buttonMargin;
button.frame = frame;
[_scrollView addSubview:button];
[tempArray addObject:button];
}
_arrayButtons = [NSArray arrayWithArray:tempArray];
[_scrollView bringSubviewToFront:_cursor];
[self setSelectedIndex:0];CGFloat lineTop = CGRectGetHeight(self.frame) / 5;
CGFloat lineHeight = CGRectGetHeight(self.frame) - lineTop * 2;
tempArray = [NSMutableArray array];
for (int i=0; i<[_arrayButtons count]-1; i++) {
UIView *line = [[UIView alloc] initWithFrame:CGRectMake(0, lineTop, 0.8, lineHeight)];
line.backgroundColor = [UIColor redColor];
[_scrollView addSubview:line];
[tempArray addObject:line];
}
_arraySeperateLines = [NSArray arrayWithArray:tempArray];
} -
(UIButton *)createCustomButtonWithTitle:(NSString *)title {
UIButton *button = [[QiTopBarButton alloc] init];
[button setTitle:title forState:UIControlStateNormal];
[button addTarget:self action:@selector(buttonClicked:) forControlEvents:UIControlEventTouchUpInside];
return button;
}
/** 点击topbar的某一项 */
-
(void)buttonClicked:(id)sender {
UIButton *button = (UIButton *)sender;
NSInteger tag = button.tag;if (button.selected) {
if (_target && [_target respondsToSelector:@selector(topBarSelectIndicator:)]) {
[_target topBarSelectIndicator:tag];
}
return;
}[self setSelectedIndex:tag];
if (_target && [_target respondsToSelector:@selector(topBarSelectIndex:)]) {
[_target topBarSelectIndex:tag];
}
}
pragma mark 设置按钮的位置
-
(void)setIsButtonAlignmentLeft:(BOOL)isAlignmentLeft {
_isButtonAlignmentLeft = isAlignmentLeft;
} -
(void)setShowSeperateLines:(BOOL)showSeperateLines {
_showSeperateLines = showSeperateLines;
}
pragma mark 更新和设置位置
-
(void)setSelectedIndex:(NSInteger)index {
if (index > [_arrayButtons count]) {
return;
}for (int i=0; i<[_arrayButtons count]; i++) {
UIButton *button = [_arrayButtons objectAtIndex:i];
button.selected = (i == index);
}
[self updateScrollViewPosition];
} -
(NSInteger)getSelectedIndex {
NSInteger selectedIndex = 0;
for (int i=0; i<[_arrayButtons count]; i++) {
UIButton *button = [_arrayButtons objectAtIndex:i];
if (button.selected) {
selectedIndex = i;
}
}
return selectedIndex;
} -
(void)scrollRectToCenter:(CGRect)frame {
CGSize size = self.frame.size;
CGSize contentSize = self.scrollView.contentSize;CGFloat targetX = frame.origin.x - (size.width - frame.size.width) / 2;
CGFloat targetEndX = targetX + size.width;if (targetX < 0) {
targetX = 0;
}
if (targetEndX > contentSize.width) {
targetEndX = contentSize.width;
}
CGRect targetRect = CGRectMake(targetX, 0, targetEndX - targetX, frame.size.height);[self.scrollView scrollRectToVisible:targetRect animated:YES];
} -
(void)updateScrollViewPosition {
CGSize size = self.frame.size;
CGSize contentSize = self.scrollView.contentSize;
if (size.width >= contentSize.width) {
return;
}CGRect frame = CGRectZero;
for (int i=0; i<[_arrayButtons count]; i++) {
UIButton *button = [_arrayButtons objectAtIndex:i];
if (button.selected) {
frame = button.frame;
}
}[self scrollRectToCenter:frame];
} -
(void)setCursorPosition:(CGFloat)percent {
CGFloat indexOffset = percent * [_arrayButtons count];
NSInteger preIndex = floorf(indexOffset);
NSInteger nextIndex = ceilf(indexOffset);if (preIndex >= 0 && nextIndex <= [_arrayButtons count]) {
UIButton *preBtn = [_arrayButtons objectAtIndex:preIndex];
UIButton *nextBtn = [_arrayButtons objectAtIndex:nextIndex];CGFloat cursorWidth = _cursorWidth; if (_cursorWidth == 0) { cursorWidth = preBtn.titleLabel.frame.size.width + (indexOffset - preIndex) * (nextBtn.titleLabel.frame.size.width - preBtn.titleLabel.frame.size.width); CGRect frame = _cursor.frame; frame.size.width = cursorWidth; _cursor.frame = frame; } CGFloat cursorCenterX = preBtn.center.x + (indexOffset - preIndex) * (nextBtn.center.x - preBtn.center.x); _cursor.center = CGPointMake(cursorCenterX , _cursor.center.y);
}
} -
(QiTopBarButton *)getCustomButtonAtIndex:(NSInteger)index {
if (index >= 0 && index < [_arrayButtons count]) {
QiTopBarButton *button = [_arrayButtons objectAtIndex:index];
if ([button isKindOfClass:[QiTopBarButton class]]) {
return button;
}
}
return nil;
} -
(void)setBackgroundImage:(UIImage *)image {
_backgroundImageView.image = image;
} -
(void)setBackgroundImageHidden:(BOOL)isHidden {
_backgroundImageView.hidden = isHidden;
} -
(void)setCursorColor:(UIColor *)color {
_cursor.backgroundColor = color;
} -
(void)setCursorWidth:(CGFloat)width {
_cursorWidth = width;
} -
(void)setTextColor:(UIColor *)normalColor andSelectedColor:(UIColor *)selectedColor {
for (QiTopBarButton *button in _arrayButtons) {
button.colorNormal = normalColor;
button.colorSelected = selectedColor;
[button setTitleColor:normalColor forState:UIControlStateNormal];
[button setTitleColor:selectedColor forState:UIControlStateSelected];
}
}
@end
复制代码</pre>
3.3 PagesContainer
PagesContainer是最外层的View,用户直接调用实例化PagesContainer并调用相应方法进行设置,即可使用该滑动选择控件。PagesContainer将顶部topBar和下部scrollView封装在内,topBar的点击事件与scrollView的滑动事件均传递到scrollViewDidScroll:方法中,再调用topBar的setCursorPosition:方法来设置游标的位置。
<pre class="prettyprint hljs objectivec" style="padding: 0.5em; font-family: Menlo, Monaco, Consolas, "Courier New", monospace; color: rgb(68, 68, 68); border-radius: 4px; display: block; margin: 0px 0px 1.5em; font-size: 14px; line-height: 1.5em; word-break: break-all; word-wrap: break-word; white-space: pre; background-color: rgb(246, 246, 246); border: none; overflow-x: auto; font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: normal; orphans: 2; text-align: start; text-indent: 0px; text-transform: none; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; text-decoration-style: initial; text-decoration-color: initial;">//////// .h文件
import <UIKit/UIKit.h>
@protocol QiPagesContainerDelegate;
@class QiPagesContainerTopBar;
@interface QiPagesContainer : UIView
@property (nonatomic, weak) id<QiPagesContainerDelegate>delegate;
/**
设置相邻button之间的间距。间距是从该button的文字结束到下个button的文字开始之间的距离
默认值是20
*/
- (void)setButtonMargin:(CGFloat)margin;
/**
设置顶部的标题
*/
- (void)updateContentWithTitles:(NSArray *)titles;
/**
设置顶部按钮位置
*/
- (void)setIsButtonAlignmentLeft:(BOOL)isAlignmentLeft;
/*!
设置是否显示按钮间分割线
*/
- (void)setShowSeperateLines:(BOOL)showSeperateLines;
/**
设置底部的View,每个View会占据该容器的大小
*/
- (void)updateContentWithViews:(NSArray *)views;
/**
设置所应选择的页,不通知外部
*/
- (void)setDefaultSelectedPageIndex:(NSInteger)index;
/**
设置所应选择的页
*/
- (void)setSelectedPageIndex:(NSInteger)index;
/**
得到当前的页面
*/
- (NSInteger)getCurrentPageIndex;
/**
得到当前正在显示的view
*/
- (UIView *)getCurrentPageView;
/**
得到index对应的view
*/
- (UIView *)getPageViewWithIndex:(NSInteger)index;
/**
获取顶部的tabBar
*/
- (QiPagesContainerTopBar*)topBar;
/**
设置按钮选中和未选中的颜色
*/
- (void)setTextColor:(UIColor *)normalColor andSelectedColor:(UIColor *)selectedColor;
// 设置滑块的颜色
- (void)setCursorColor:(UIColor *)color;
// 设置滑块长度 - (void)setCursorWidth:(CGFloat)width;
// 设置滑块长度 - (void)setCursorHeight:(CGFloat)height;
@end
@protocol QiPagesContainerDelegate <NSObject>
@optional
/** page切换 */
- (void)pageContainder:(QiPagesContainer *)container selectIndex:(NSInteger)index;
/** 点击当前的指示器 */
- (void)onClickPageIndicator:(QiPagesContainer *)container selectIndex:(NSInteger)index;
@end
//////// .m文件
import "QiPagesContainer.h"
import "QiPagesContainerTopBar.h"
import "QiAllowPanGestureScrollView.h"
define TOPBAR_HEIGHT 34
@interface QiPagesContainer () <UIScrollViewDelegate, QiPagesContainerTopBarDelegate>
@property (nonatomic, strong) QiPagesContainerTopBar *topBar;
@property (nonatomic, strong) UIScrollView *scrollView;
@property (nonatomic, strong) UIView *bottomLineView;
@property (nonatomic, strong) NSArray *arrayViews;
@property (nonatomic, assign) NSInteger currentPageIndex;
@end
@implementation QiPagesContainer
-
(id)initWithFrame:(CGRect)frame {
self = [super initWithFrame:frame];
if (self) {
[self setup];
}
return self;
} -
(id)init {
self = [super init];
if (self) {
[self setup];
}
return self;
} -
(id)initWithCoder:(NSCoder *)aDecoder {
self = [super initWithCoder:aDecoder];
if (self) {
[self setup];
}
return self;
} -
(void)setup {
_topBar = [[QiPagesContainerTopBar alloc] initWithFrame:CGRectZero];
_topBar.target = self;
[self addSubview:_topBar];_scrollView = [[QiAllowPanGestureScrollView alloc] initWithFrame:CGRectZero];
_scrollView.delegate = self;
_scrollView.pagingEnabled = YES;
_scrollView.showsHorizontalScrollIndicator = NO;
[_scrollView setScrollsToTop:NO];
[_scrollView setAlwaysBounceHorizontal:NO];
[_scrollView setAlwaysBounceVertical:NO];
[_scrollView setBounces:NO];
[self addSubview:self.scrollView];[self layoutSubviews];
} -
(void)layoutSubviews {
[super layoutSubviews];
CGSize size = self.frame.size;
CGFloat scrollViewHeight = size.height - TOPBAR_HEIGHT;
_topBar.frame = CGRectMake(0, 0, size.width, TOPBAR_HEIGHT);
_scrollView.frame = CGRectMake(0, TOPBAR_HEIGHT, size.width, scrollViewHeight);for (int i=0; i<[_arrayViews count]; i++) {
UIView v = [_arrayViews objectAtIndex:i];
v.frame = CGRectMake(isize.width, 0, size.width, scrollViewHeight);
}
_scrollView.contentSize = CGSizeMake(size.width * [_arrayViews count], scrollViewHeight);
} -
(void)setButtonMargin:(CGFloat)margin {
_topBar.buttonMargin = margin;
}
pragma mark - UIScrollViewDelegate
-
(void)scrollViewDidScroll:(UIScrollView *)scrollView {
if (scrollView.contentSize.width > 0) {
[_topBar setCursorPosition:scrollView.contentOffset.x / scrollView.contentSize.width];
}
} -
(void)scrollViewDidEndDecelerating:(UIScrollView *)scrollView {
NSInteger pageIndex = scrollView.contentOffset.x / scrollView.frame.size.width;
if (pageIndex != _currentPageIndex) {
_currentPageIndex = pageIndex;
[_topBar setSelectedIndex:pageIndex];
[self notifyDelegateSelectedIndex:pageIndex];
}
}
pragma mark - update content
-
(void)setDefaultSelectedPageIndex:(NSInteger)index {
if (index >= 0 && index <= [_arrayViews count] && index != _currentPageIndex) {
[_topBar setSelectedIndex:index];
_currentPageIndex = index;[_scrollView setContentOffset:CGPointMake(index * _scrollView.frame.size.width, 0) animated:YES];
}
} -
(void)setSelectedPageIndex:(NSInteger)index {
if (index >= 0 && index <= [_arrayViews count] && index != _currentPageIndex) {
[_topBar setSelectedIndex:index];
[self topBarSelectIndex:index];
}
} -
(NSInteger)getCurrentPageIndex {
return [_topBar getSelectedIndex];
} -
(UIView *)getCurrentPageView {
return [_arrayViews objectAtIndex:[_topBar getSelectedIndex]];
} -
(UIView *)getPageViewWithIndex:(NSInteger)index {
if (index<[_arrayViews count]) {
return [_arrayViews objectAtIndex:index];
} else {
return nil;
}
} -
(void)updateContentWithTitles:(NSArray *)titles {
[_topBar updateConentWithTitles:titles];
} -
(void)setIsButtonAlignmentLeft:(BOOL)isAlignmentLeft {
[_topBar setIsButtonAlignmentLeft:isAlignmentLeft];
} -
(void)setShowSeperateLines:(BOOL)showSeperateLines {
[_topBar setShowSeperateLines:showSeperateLines];
} -
(void)updateContentWithViews:(NSArray *)views {
for (UIView *view in _arrayViews) {
[view removeFromSuperview];
}
if ([views count] == 0) {
return;
}
_arrayViews = [NSArray arrayWithArray:views];for (int i=0; i<[views count]; i++) {
UIView *view = [views objectAtIndex:i];
[_scrollView addSubview:view];
}
[self layoutSubviews];
}
pragma mark - QIPagesContainerTopBarDelegate
-
(void)topBarSelectIndex:(NSInteger)index {
if (index < [_arrayViews count]) {
_currentPageIndex = index;[_scrollView setContentOffset:CGPointMake(index * _scrollView.frame.size.width, 0) animated:YES]; [self notifyDelegateSelectedIndex:index];
}
}
/** 重复点击topBar时会调用该方法 */ -
(void)topBarSelectIndicator:(NSInteger)index {
if (index < [_arrayViews count]) {
_currentPageIndex = index;
[_scrollView setContentOffset:CGPointMake(index * _scrollView.frame.size.width, 0) animated:YES];if (_delegate && [_delegate respondsToSelector:@selector(onClickPageIndicator:selectIndex:)]) { [_delegate onClickPageIndicator:self selectIndex:index]; }
}
} -
(void)notifyDelegateSelectedIndex:(NSInteger )index {
if (_delegate && [_delegate respondsToSelector:@selector(pageContainder:selectIndex:)]) {
[_delegate pageContainder:self selectIndex:index];
}
}
pragma mark - 设置按钮选中和未选中的颜色
-
(void)setTextColor:(UIColor *)normalColor andSelectedColor:(UIColor *)selectedColor {
[_topBar setTextColor:normalColor andSelectedColor:selectedColor];
}
pragma mark - 设置滑块的颜色
-
(void)setCursorColor:(UIColor *)color {
[_topBar setCursorColor:color];
}
pragma mark - 设置滑块长度
-
(void)setCursorWidth:(CGFloat)width {
[_topBar setCursorWidth:width];
}
pragma mark - 设置滑块长度
-
(void)setCursorHeight:(CGFloat)height {
[_topBar setCursorHeight:height];
}
pragma mark - 获取顶部的tabBar
- (QiPagesContainerTopBar*)topBar{
return _topBar;
}
@end
复制代码</pre>
3.3 控件在项目中的调用
1)实现QiPagesContainerDelegate协议,监听控件的选择事件:
<pre class="prettyprint hljs less" style="padding: 0.5em; font-family: Menlo, Monaco, Consolas, "Courier New", monospace; color: rgb(68, 68, 68); border-radius: 4px; display: block; margin: 0px 0px 1.5em; font-size: 14px; line-height: 1.5em; word-break: break-all; word-wrap: break-word; white-space: pre; background-color: rgb(246, 246, 246); border: none; overflow-x: auto; font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: normal; orphans: 2; text-align: start; text-indent: 0px; text-transform: none; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; text-decoration-style: initial; text-decoration-color: initial;">#pragma mark - IHPagesContainerDelegate
- (void)pageContainder:(QiPagesContainer *)container selectIndex:(NSInteger)index {
}
- (void)onClickPageIndicator:(QiPagesContainer *)container selectIndex:(NSInteger)index {
}
复制代码</pre>
2)初始化控件,并设置控件样式:
<pre class="prettyprint hljs objectivec" style="padding: 0.5em; font-family: Menlo, Monaco, Consolas, "Courier New", monospace; color: rgb(68, 68, 68); border-radius: 4px; display: block; margin: 0px 0px 1.5em; font-size: 14px; line-height: 1.5em; word-break: break-all; word-wrap: break-word; white-space: pre; background-color: rgb(246, 246, 246); border: none; overflow-x: auto; font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: normal; orphans: 2; text-align: start; text-indent: 0px; text-transform: none; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; text-decoration-style: initial; text-decoration-color: initial;">- (void) setupViews {
CGFloat margin = 0;
CGSize size = self.view.frame.size;
NSArray *titles = [NSArray arrayWithObjects:@"我的设备", @"卧室", @"厨房厨房厨房厨房", @"门厅", nil];
NSMutableArray *tempTableViews = [NSMutableArray array];
_pageContainer = [[QiPagesContainer alloc] initWithFrame:CGRectMake(margin, 0, size.width - margin * 2, size.height)];
_pageContainer.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight;
[_pageContainer setBackgroundColor:[UIColor lightGrayColor]];
_pageContainer.delegate = self;
[_pageContainer updateContentWithTitles:titles];
[_pageContainer setIsButtonAlignmentLeft:YES];
[_pageContainer setCursorHeight:3.0];
[_pageContainer setCursorColor:[UIColor whiteColor]];
[_pageContainer setTextColor:[UIColor whiteColor] andSelectedColor:[UIColor whiteColor]];
UIView *topBar = (UIView *)[_pageContainer topBar];
for (int i=0; i<[titles count]; i++) {
UIView *subView = [[UIView alloc] initWithFrame:CGRectMake(0, 0, size.width, size.height - topBar.frame.size.height)];
subView.backgroundColor = [UIColor colorWithRed:arc4random()%255/255.0 green:arc4random()%255/255.0 blue:arc4random()%255/255.0 alpha:0.3];
[tempTableViews addObject:subView];
}
[_pageContainer updateContentWithViews:tempTableViews];
[self.view addSubview:_pageContainer];
}
复制代码</pre>
自定义控件展示:
4. 关于cursor大小及位置设置的说明
cursor被封装在PagesContainerTopBar内,设置cursor的位置及大小的方法是setCursorPosition:,在设置之前游标之前,我们可以获取到的信息:
- setCursorPosition:位于PagesContainer中的scrollViewDidScroll:方法之中,即PagesContainer中的scrollView滚动时就会调用这个设置游标的方法,传入的参数是一个百分比scrollView.contentOffset.x / scrollView.contentSize.width;
- 顶部的PagesContainerTopBar点击选中事件、下部的scrollView滚动事件, *均会走scrollViewDidScroll:回调方法 *。
我们要满足的需求:
- 在设置cursor的位置时,游标的宽度大小根据位于游标之前按钮(preBtn)宽度和之后按钮(nextBtn)宽度动态变化,即靠近preBtn时cursor的宽就越接近preBtn的宽,靠近nextBtn时就越接近nextBtn的宽。
实现思路:
1)获取当前索引的偏移量(float型),并计算并找出preBtn和nextBtn:
<pre class="prettyprint hljs objectivec" style="padding: 0.5em; font-family: Menlo, Monaco, Consolas, "Courier New", monospace; color: rgb(68, 68, 68); border-radius: 4px; display: block; margin: 0px 0px 1.5em; font-size: 14px; line-height: 1.5em; word-break: break-all; word-wrap: break-word; white-space: pre; background-color: rgb(246, 246, 246); border: none; overflow-x: auto; font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: normal; orphans: 2; text-align: start; text-indent: 0px; text-transform: none; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; text-decoration-style: initial; text-decoration-color: initial;">CGFloat indexOffset = percent * [_arrayButtons count];
NSInteger preIndex = floorf(indexOffset);
NSInteger nextIndex = ceilf(indexOffset);
UIButton *preBtn = [_arrayButtons objectAtIndex:preIndex];
UIButton *nextBtn = [_arrayButtons objectAtIndex:nextIndex];
复制代码</pre>
2)ursorWidth的最终取值是与选定按钮的title等宽,ursorWidth的值始终以preBtn为标准,并根据当前滑动偏移量indexOffset-preIndex与按钮的title长度只差的乘积进行变化:
<pre class="prettyprint hljs gradle" style="padding: 0.5em; font-family: Menlo, Monaco, Consolas, "Courier New", monospace; color: rgb(68, 68, 68); border-radius: 4px; display: block; margin: 0px 0px 1.5em; font-size: 14px; line-height: 1.5em; word-break: break-all; word-wrap: break-word; white-space: pre; background-color: rgb(246, 246, 246); border: none; overflow-x: auto; font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: normal; orphans: 2; text-align: start; text-indent: 0px; text-transform: none; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; text-decoration-style: initial; text-decoration-color: initial;">// 如果cursor的长度是按比例变化的
ursorWidth = preBtn.titleLabel.frame.size.width + (indexOffset - preIndex) * (nextBtn.titleLabel.frame.size.width - preBtn.titleLabel.frame.size.width);
复制代码</pre>
3)控件底部的scrollView滑动时会不断触发scrollViewDidScroll:,便也会多次调用setCursorPosition:方法传入位置,cursorWidth实时变化并更新至cursor,并设置cursor的确切位置,这样就可以看到顺畅的cursor位置及宽度变化:
<pre class="prettyprint hljs objectivec" style="padding: 0.5em; font-family: Menlo, Monaco, Consolas, "Courier New", monospace; color: rgb(68, 68, 68); border-radius: 4px; display: block; margin: 0px 0px 1.5em; font-size: 14px; line-height: 1.5em; word-break: break-all; word-wrap: break-word; white-space: pre; background-color: rgb(246, 246, 246); border: none; overflow-x: auto; font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: normal; orphans: 2; text-align: start; text-indent: 0px; text-transform: none; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; text-decoration-style: initial; text-decoration-color: initial;">CGRect frame = _cursor.frame;
frame.size.width = cursorWidth;
_cursor.frame = frame;
CGFloat cursorCenterX = preBtn.center.x + (indexOffset - preIndex) * (nextBtn.center.x - preBtn.center.x);
_cursor.center = CGPointMake(cursorCenterX , _cursor.center.y);
复制代码</pre>