分页菜单
github下载地址:https://github.com/leshengping/SPPageMenu
.h文件
#import <UIKit/UIKit.h> @class SPPageMenu; @protocol SPPageMenuDelegate <NSObject> @optional - (void)pageMenu:(SPPageMenu *)pageMenu buttonClickedAtIndex:(NSInteger)index; - (void)pageMenu:(SPPageMenu *)pageMenu buttonClickedFromIndex:(NSInteger)fromIndex toIndex:(NSInteger)toIndex; @end @interface SPPageMenu : UIView @property (nonatomic, weak) id<SPPageMenuDelegate> delegate; /** 分割线颜色 */ @property (nonatomic, strong) UIColor *breaklineColor; /** button的字体,默认为15号字体 */ @property (nonatomic, strong) UIFont *buttonFont; /** 选中的button的字体颜色(跟踪器的颜色始终与选中的button的字体颜色是一致的)*/ @property (nonatomic, strong) UIColor *selectedColor; /** 未选中的button字体颜色 */ @property (nonatomic, strong) UIColor *unSelectedColor; /** 是否显示分割线,默认为YES */ @property (nonatomic, assign, getter=isShowBreakline) BOOL showBreakline; /** 是否开启动画,默认为NO */ @property (nonatomic, assign, getter=isOpenAnimation) BOOL openAnimation; /** 是否显示跟踪器,默认为YES */ @property (nonatomic, assign, getter=isShowTracker) BOOL showTracker; /** 跟踪器(跟踪button的下划线) */ @property (nonatomic, weak) UIView *tracker; /** 跟踪器的高度 */ @property (nonatomic, assign) CGFloat trackerHeight; /** button之间的间距 */ @property (nonatomic, assign) CGFloat padding; @property (nonatomic, weak, readonly) UIButton *selectedButton; // 选中的button /* * 调用这个方法创建菜单栏 */ + (SPPageMenu *)pageMenuWithFrame:(CGRect)frame array:(NSArray *)array; /* * 外界只要告诉该类index,内部会处理哪个button被选中 */ - (void)selectButtonAtIndex:(NSInteger)index; /* * 1.这个方法的功能是实现跟踪器跟随scrollView的滚动而滚动; * 2.调用这个方法必须在scrollViewDidScrollView里面调; * 3.beginOffset:scrollView刚开始滑动的时候起始偏移量,在scrollViewWillBeginDragging:方法内部获取起始偏移量; * 4.scrollView:正在滑动的scrollView; * 5.因此针对这个功能外界必须实现scrollViewWillBeginDragging:和scrollViewDidScrollView两个代理方法 */ - (void)makeTrackerFollowScrollViewMoveWithBeginOffset:(CGFloat)beginOffset scrollView:(UIScrollView *)scrollView; @end
.m
#import "SPPageMenu.h" #define tagIndex 2016 @interface SPPageMenu() @property (nonatomic, strong) NSArray *menuArray; // 该数组中装的是字符串,由外界传进来 @property (nonatomic, weak) UIScrollView *scrollView; // 该scrollView用来承载菜单button @property (nonatomic, weak) UIView *breakline; // 分割线 @property (nonatomic, strong) NSMutableArray *menuButtonArray; // 此数组用来装button @end static CGFloat menuButtonX = 0; @implementation SPPageMenu - (instancetype)initWithFrame:(CGRect)frame { if (self = [super initWithFrame:frame]) { // 分割线,添加分割线必须在添加scrollView前面,并且将scrollView的背景色设置成无色(全透明)。如果在添加scrollView之后再添加分割线,那么menuButton的底部下划线将会有一部分高度(此高度就是分割线的高度)被分割线挡住 UIView *breakline = [[UIView alloc] initWithFrame:CGRectMake(0, frame.size.height - 1.0f, frame.size.width, 1.0f)]; breakline.backgroundColor = [UIColor lightGrayColor]; self.breakline = breakline; [self addSubview:breakline]; // 创建承载菜单button的scrollView UIScrollView *scrollView = [[UIScrollView alloc] initWithFrame:CGRectMake(0, 0, frame.size.width, frame.size.height)]; scrollView.showsVerticalScrollIndicator = NO; scrollView.showsHorizontalScrollIndicator = NO; self.scrollView = scrollView; // 设置全透明颜色(必须) scrollView.backgroundColor = [UIColor clearColor]; [self addSubview:scrollView]; } return self; } // 此方法是留给外界的接口,以创建菜单栏 + (SPPageMenu *)pageMenuWithFrame:(CGRect)frame array:(NSArray *)array { SPPageMenu *menu = [[SPPageMenu alloc] initWithFrame:frame]; menu.menuArray = array; return menu; } - (void)setMenuArray:(NSMutableArray *)menuArray { _menuArray = menuArray; [self configureMenuButtonToScrollView]; } // 添加以及配置menubutton的相关属性 - (void)configureMenuButtonToScrollView { // 移除已经有按钮,以防某个地方再次调用了此方法时,重复创建button [self.scrollView.subviews enumerateObjectsUsingBlock:^(__kindof UIView * _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) { [obj removeFromSuperview]; }]; if (!self.buttonFont) { // 默认为15号字体 _buttonFont = [UIFont systemFontOfSize:15]; } // 设置button之间的间距 if (!self.padding) { _padding = 30.f; } // 第一个menuButton的x值 menuButtonX = 0.5 * _padding; // 装载button的数组初始化 self.menuButtonArray = [NSMutableArray array]; // 创建button for (int i = 0; i < _menuArray.count; i++) { UIButton *menuButton = [UIButton buttonWithType:UIButtonTypeCustom]; // 以下是menuButton的相关设置 menuButton.tag = tagIndex + i; [menuButton addTarget:self action:@selector(menuBtnClick:) forControlEvents:UIControlEventTouchUpInside]; [menuButton setTitle:self.menuArray[i] forState:UIControlStateNormal]; [menuButton setBackgroundColor:[UIColor clearColor]]; menuButton.titleLabel.font = _buttonFont; menuButton.titleLabel.textAlignment = NSTextAlignmentCenter; [self setUpButtonColor:menuButton]; if (self.scrollView.subviews.count > 0) { // 非第一个,其x值等于最后一个button的终结x值+间距 menuButtonX = CGRectGetMaxX([self.scrollView.subviews lastObject].frame) + _padding; } [self sizeWithString:self.menuArray[i] menuButton:menuButton menuButtonX:menuButtonX]; if (i == 0) { // 默认第一个选中 self.selectedButton = menuButton; // 跟踪button的下划线 UIView *tracker = [[UIView alloc] init]; CGFloat x = 0.0f; CGFloat w = CGRectGetMaxX(_selectedButton.frame) + 0.5 * _padding; if (!self.trackerHeight) { _trackerHeight = 2.0f; } CGFloat h = _trackerHeight; CGFloat y = CGRectGetMaxY(_selectedButton.frame) - h; tracker.frame = CGRectMake(x, y, w, h); self.tracker = tracker; tracker.backgroundColor = [UIColor redColor]; [self.scrollView addSubview:tracker]; } // 添加menuButton必须在添加tracker之后,必须保证scrollView里面装的最后一个控件是menuButton,因为menuButtonX依赖于最后一个menuButton [self.scrollView addSubview:menuButton]; // 将button全部装到一个数组里面,以方便以后需拿到这些button时,可以从数组里面取出来 [self.menuButtonArray addObject:menuButton]; } [self setScrollViewContentSize]; } // 设置scrollView的总容量 - (void)setScrollViewContentSize { if (self.scrollView.subviews.count > 0) { self.scrollView.contentSize = CGSizeMake(CGRectGetMaxX([self.scrollView.subviews lastObject].frame) + 0.5 * _padding, 0); } } // 计算button的frame - (void)sizeWithString:(NSString *)string menuButton:(UIButton *)menuButton menuButtonX:(CGFloat)menuButtonX { // button需要根据文字返回动态宽度 CGSize size = [string boundingRectWithSize:CGSizeMake(MAXFLOAT, 0) options:NSStringDrawingUsesLineFragmentOrigin attributes:@{NSFontAttributeName:_buttonFont} context:nil].size; menuButton.frame = CGRectMake(menuButtonX, 0, size.width, self.scrollView.frame.size.height); } // 重写set方法,设置button字体 - (void)setButtonFont:(UIFont *)buttonFont { _buttonFont = buttonFont; // 从数组中取出菜单上的button,设置每个button的字体。如果不这样做,也可以在此直接调用setUpMenuButton方法,但是那样似乎多做了些无用的操作 menuButtonX = 0.5 * _padding; for (int i = 0 ;i < self.menuButtonArray.count ;i++) { // 取出当前button UIButton *menuButton = self.menuButtonArray[i]; // 重新设置button的字体 menuButton.titleLabel.font = buttonFont; if (i > 0) { // 取出上一个button UIButton *lastButton = self.menuButtonArray[i - 1]; // 当前button的x值等于上一个button的终结x值+间距 menuButtonX = CGRectGetMaxX(lastButton.frame) + _padding; } [self sizeWithString:menuButton.titleLabel.text menuButton:menuButton menuButtonX:menuButtonX]; } } // 设置所有button的初始颜色 - (void)setUpButtonColor:(UIButton *)menuButton { if (self.unSelectedColor) { [menuButton setTitleColor:self.unSelectedColor forState:UIControlStateNormal]; } else { [menuButton setTitleColor:[UIColor colorWithRed:104/255.0 green:104/255.0 blue:124/255.0 alpha:1.f] forState:UIControlStateNormal]; } } // 重写set方法,设置选中的button - (void)setSelectedButton:(UIButton *)selectedButton { // 这个if语句就是判断选中的button是否和之前选中的button是否一致,比如用户两次点击同一个button,那么第二次点击调用此方法时就直接retutn,没必要再去设置什么,设置了也是重复而已 if (selectedButton == _selectedButton) { return; } // 计算scrollView的偏移量 CGFloat originX = [self calculationScrollViewOffSet:selectedButton]; [self.scrollView setContentOffset:CGPointMake(originX, 0) animated:YES]; // 改变按钮颜色 // _selectedButton指的是上一次选中的utton // 这就是为什么_selectedButton = selectedButton写在最后一行的原因,如果写在第一行,则_selectedButton与selectedButton就永远是同一个了 if (self.unSelectedColor) { [_selectedButton setTitleColor:self.unSelectedColor forState:UIControlStateNormal]; } else { [_selectedButton setTitleColor:[UIColor colorWithRed:104/255.0 green:104/255.0 blue:124/255.0 alpha:1.f] forState:UIControlStateNormal]; } if (self.selectedColor) { [selectedButton setTitleColor:_selectedColor forState:UIControlStateNormal]; } else { // 默认颜色 [selectedButton setTitleColor:[UIColor redColor] forState:UIControlStateNormal]; } // CGRect newFrame = self.tracker.frame; // // x // newFrame.origin.x = selectedButton.frame.origin.x - 0.5 * _padding; // // 宽 // newFrame.size.width = selectedButton.frame.size.width + _padding; // // 执行动画 // [UIView animateWithDuration:0.1 animations:^{ // self.tracker.frame = newFrame; // }]; // 开启动画,默认不开启 if (_openAnimation) { [self openAnimationWithButton1:_selectedButton button2:selectedButton]; } // 赋值必须写在最后,因为_selectedButton记录的是当前选中的button,当下一次在进入此方法时,要保证_selectedButton记录的是上一次选中的button _selectedButton = selectedButton; } // 计算scrollView的偏移量 - (CGFloat)calculationScrollViewOffSet:(UIButton *)selectedButton { // CGRectGetMidX(self.scrollView.frame)指的是屏幕水平中心位置,它的值是固定不变的 CGFloat originX = selectedButton.center.x - CGRectGetMidX(self.scrollView.frame); CGFloat maxOffsetX = self.scrollView.contentSize.width - self.scrollView.frame.size.width; if (originX < 0) { // 说明选中的那个button的中心位置小于屏幕中心位置 originX = 0; } else if (originX > maxOffsetX){ // 当选中的那个button中心位置大于屏幕中心位置时,那么直接将scrollView水平偏移originX,这时候的originX还是等于selectedButton.center.x - CGRectGetMidX(self.scrollView.frame),当此差值一直增加,一直增加到大于maxOffsetX时,则scrollView保持原先偏移量,不再继续偏移 originX = maxOffsetX; } return originX; } // 重写set方法,是否开启动画 - (void)setOpenAnimation:(BOOL)openAnimation { _openAnimation = openAnimation; if (openAnimation) { // 取出第一个button UIButton *menuButton = [self.menuButtonArray firstObject]; // 如果外界开启了动画,则给第一个button加上放大动画。如果不这样做,外界开启动画后,第一个button是不会有放大效果的,只有点击了其它button之后才会有动画效果。 CABasicAnimation *animation = [self enlargeAnimation]; [menuButton.titleLabel.layer addAnimation:animation forKey:@"animation"]; } else { // 遍历所有的button,如果外界关闭了动画,则将所有button上动画移除 [self.menuButtonArray enumerateObjectsUsingBlock:^(UIButton* _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) { [obj.titleLabel.layer removeAllAnimations]; }]; } } // 开启动画 - (void)openAnimationWithButton1:(UIButton *)lastButton button2:(UIButton *)currentButton { CABasicAnimation *animation1 = [self enlargeAnimation]; CABasicAnimation *animation2 = [self narrowAnimation]; [lastButton.titleLabel.layer addAnimation:animation2 forKey:@"animation2"]; [currentButton.titleLabel.layer addAnimation:animation1 forKey:@"animation1"]; } // 返回放大的动画 - (CABasicAnimation *)enlargeAnimation { CABasicAnimation *animation = [CABasicAnimation animationWithKeyPath:@"transform.scale"]; animation.fromValue = [NSNumber numberWithFloat:1.0f]; animation.toValue = [NSNumber numberWithFloat:1.2f]; animation.duration = 0.1; animation.repeatCount = 1; // 以下两个属性是让动画保持动画结束的状态 animation.fillMode = kCAFillModeForwards; animation.removedOnCompletion = NO; animation.autoreverses = NO; return animation; } // 返回缩小动画 - (CABasicAnimation *)narrowAnimation { CABasicAnimation *animation = [CABasicAnimation animationWithKeyPath:@"transform.scale"]; animation.fromValue = [NSNumber numberWithFloat:1.2f]; animation.toValue = [NSNumber numberWithFloat:1.0f]; animation.duration = 0.1; animation.repeatCount = 1; animation.fillMode = kCAFillModeForwards; animation.removedOnCompletion = NO; animation.autoreverses = NO; return animation; } // 重写set方法,设置选中button的颜色 - (void)setSelectedColor:(UIColor *)selectedColor { _selectedColor = selectedColor; [_selectedButton setTitleColor:selectedColor forState:UIControlStateNormal]; _tracker.backgroundColor = selectedColor; } // 重写set方法,设置未选中的button颜色 - (void)setUnSelectedColor:(UIColor *)unSelectedColor { _unSelectedColor = unSelectedColor; for (UIButton *menuButton in self.menuButtonArray) { if (menuButton == _selectedButton) { continue; // 跳过选中的那个button } [menuButton setTitleColor:unSelectedColor forState:UIControlStateNormal]; } } // button点击方法 - (void)menuBtnClick:(UIButton *)sender { if (self.selectedButton == sender) { return; } if ([self.delegate respondsToSelector:@selector(pageMenu:buttonClickedAtIndex:)]) { [self.delegate pageMenu:self buttonClickedAtIndex:sender.tag - tagIndex]; } if ([self.delegate respondsToSelector:@selector(pageMenu:buttonClickedFromIndex:toIndex:)]) { [self.delegate pageMenu:self buttonClickedFromIndex:self.selectedButton.tag - tagIndex toIndex:sender.tag - tagIndex ]; } self.selectedButton = sender; } // 重写set方法,分割线是否显示 - (void)setShowBreakline:(BOOL)showBreakline{ _showBreakline = showBreakline; self.breakline.hidden = !showBreakline; } // 设置分割线颜色 - (void)setBreaklineColor:(UIColor *)color{ self.breakline.backgroundColor = color; } // 是否显示跟踪器 - (void)setShowTracker:(BOOL)showTracker { _showBreakline = showTracker; _tracker.hidden = !showTracker; } // 设置menuButton之间的间距 - (void)setPadding:(CGFloat)padding { _padding = padding; menuButtonX = 0.5 * padding; for (int i = 0; i < self.menuButtonArray.count; i++) { // 取出当前menuButton; UIButton *menuButton = _menuButtonArray[i]; if (i > 0) { // 取出上一个button UIButton *lastButton = self.menuButtonArray[i - 1]; // 当前button的x值等于上一个button的终结x值+间距 menuButtonX = CGRectGetMaxX(lastButton.frame) + _padding; } [self sizeWithString:menuButton.titleLabel.text menuButton:menuButton menuButtonX:menuButtonX]; if (i == 0) { CGRect newFrame = self.tracker.frame; // x newFrame.origin.x = menuButton.frame.origin.x - 0.5 * _padding; // 宽 newFrame.size.width = menuButton.frame.size.width + _padding; self.tracker.frame = newFrame; } } [self setScrollViewContentSize]; } // 设置跟踪器的高度 - (void)settrackerHeight:(CGFloat)trackerHeight { _trackerHeight = trackerHeight; CGRect newFrame = self.tracker.frame; newFrame.size.height = trackerHeight; newFrame.origin.y = CGRectGetMaxY(_selectedButton.frame) - trackerHeight; self.tracker.frame = newFrame; } - (void)selectButtonAtIndex:(NSInteger)index{ UIButton *selectedBtn = (UIButton *)[self.scrollView subviews][index + 1]; [self menuBtnClick:selectedBtn]; } - (void)followScrollViewOffsetX:(CGFloat)offsetX { CGRect newFrame = _tracker.frame; newFrame.origin.x = offsetX; _tracker.frame = newFrame; } - (void)makeTrackerFollowScrollViewMoveWithBeginOffset:(CGFloat)beginOffset scrollView:(UIScrollView *)scrollView { CGFloat offSetX = scrollView.contentOffset.x; CGFloat tempProgress = offSetX / scrollView.bounds.size.width; CGFloat progress = tempProgress - floor(tempProgress); NSInteger oldIndex; NSInteger currentIndex; if (offSetX - beginOffset >= 0) { // 向右 oldIndex = offSetX / scrollView.bounds.size.width; currentIndex = oldIndex + 1; if (currentIndex >= self.menuArray.count) { currentIndex = oldIndex - 1; } if (offSetX - beginOffset == scrollView.bounds.size.width) {// 滚动完成 progress = 1.0; currentIndex = oldIndex; } } else { // 向左 currentIndex = offSetX / scrollView.bounds.size.width; oldIndex = currentIndex + 1; progress = 1.0 - progress; } [self moveTrackerWithProgress:progress fromIndex:oldIndex toIndex:currentIndex]; } - (void)moveTrackerWithProgress:(CGFloat)progress fromIndex:(NSInteger)fromIndex toIndex:(NSInteger)toIndex { UIButton *oldButton = self.menuButtonArray[fromIndex]; UIButton *currentButton = self.menuButtonArray[toIndex]; CGFloat xDistance = currentButton.frame.origin.x - oldButton.frame.origin.x; CGFloat wDistance = currentButton.frame.size.width - oldButton.frame.size.width; CGRect newFrame = self.tracker.frame; newFrame.origin.x = oldButton.frame.origin.x + xDistance * progress - 0.5*_padding; newFrame.size.width = oldButton.frame.size.width + wDistance * progress + _padding; self.tracker.frame = newFrame; } @end