分页菜单

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

 

posted @ 2016-09-12 09:34  S型身材的猪  阅读(267)  评论(0编辑  收藏  举报