控制器切换,类似网易新闻的分页菜单

github下载地址:https://github.com/SPStore/SPPageMenu
虽然这种工具网上已经多得不能再多了,但是有两大功能是它们所欠缺的:
1、 跟踪器在跟踪按钮的时候,跟踪器的宽度能够时刻跟随按钮宽度的改变而改变。注意是时刻跟随,不是滑动结束的时候才跟随
2、菜单上按钮的展示效果有3种:这主要依赖两个属性allowBeyondScreen和equalWidths,具体怎么设置参考demo
第一种:当按钮数较多时,能够以scrollView的形式左右滑动。
第二种:所有按钮被限制在屏幕宽度之内,每个按钮等宽,等间距。
第三种:所有按钮被限制在屏幕宽度之内,每个按钮根据文字自适应宽度,间距自适应。
当然,我这个工具的功能仍然不够,很多菜单栏的右侧有一个功能按钮,点击这个功能按钮可以做其他事情,我这个工具暂时没加这个功能,我有时间一定会加上去。

屏幕快照 2017-01-11 上午10.55.01.png

.h文件

#import <UIKit/UIKit.h>

@class SPPageMenu;
@protocol SPPageMenuDelegate <NSObject>

@optional
/** 
 * pageMenu:菜单对象
 * index:当前选中的button下标
 */
- (void)pageMenu:(SPPageMenu *)pageMenu buttonClickedAtIndex:(NSInteger)index;
/** 
 * pageMenu:菜单对象
 * fromIndex:上一个被选中的button下标
 * toIndex:当前被选中的button下标
 */
- (void)pageMenu:(SPPageMenu *)pageMenu buttonClickedFromIndex:(NSInteger)fromIndex toIndex:(NSInteger)toIndex;
@end


@interface SPPageMenu : UIView

@property (nonatomic, weak) id<SPPageMenuDelegate> delegate;

/** block方式监听button被点击,外界可选择代理方式,也可以选择block方式 */
@property (nonatomic, copy) void(^buttonClickedBlock)(NSInteger index);
@property (nonatomic, copy) void(^buttonClicked_from_to_Block)(NSInteger fromIndex,  NSInteger toIndex);


/** button之间的间距,默认为30 */
@property (nonatomic, assign) CGFloat spacing;
/** 第一个button的左边距,默认为间距的一半 */
@property (nonatomic, assign) CGFloat firstButtonX;
/** button的字体,默认为15号字体 */
@property (nonatomic, strong) UIFont *buttonFont;
/** 选中的button的字体颜色 */
@property (nonatomic, strong) UIColor *selectedTitleColor;
/** 未选中的button字体颜色,默认为黑色 */
@property (nonatomic, strong) UIColor *unSelectedTitleColor;
/** 分割线颜色,默认为亮灰色 */
@property (nonatomic, strong) UIColor *breaklineColor;
/** 是否显示分割线,默认为YES */
@property (nonatomic, assign, getter=isShowBreakline) BOOL showBreakline;
/** 是否显示跟踪器,默认为YES */
@property (nonatomic, assign, getter=isShowTracker) BOOL showTracker;
/** 跟踪器的高度,默认为2.0f */
@property (nonatomic, assign) CGFloat trackerHeight;
/** 跟踪器的颜色,默认与选中的button字体颜色一致 */
@property (nonatomic, strong) UIColor *trackerColor;
/** 是否开启动画,默认为NO */
@property (nonatomic, assign, getter=isOpenAnimation) BOOL openAnimation;


/** 当以下两个属性同时为NO时,spacing和firstButtonX属性将不受用户控制,这是合情合理的 */
/** 是否允许超出屏幕,默认为YES,如果设置了NO,则菜单上的所有button都将示在在屏幕范围之内,并且默认等宽,整体居中显示 ,如果想要button根据文字自适应宽度,还要配合下面的“equalWidths”属性 */
@property (nonatomic, assign, getter=isAllowBeyondScreen) BOOL allowBeyondScreen;
/** 是否等宽,默认为YES,这个属性只有在屏幕范围之内的布局方式才有效 */
@property (nonatomic, assign, getter=isEqualWidths) BOOL equalWidths;

/** 快速创建菜单 */
+ (SPPageMenu *)pageMenuWithFrame:(CGRect)frame array:(NSArray *)array;


/*
 *  外界只要告诉该类index,内部会处理哪个button被选中
 */
- (void)selectButtonAtIndex:(NSInteger)index;

/*
 *  1.这个方法的功能是实现跟踪器跟随scrollView的滚动而滚动;
 *  2.调用这个方法必须在scrollViewDidScrollView里面调;
 *  3.beginOffset:scrollView刚开始滑动的时候起始偏移量,在scrollViewWillBeginDragging:方法内部获取起始偏移量;
 *  4.scrollView:外面正在拖拽的scrollView;
 */
- (void)moveTrackerFollowScrollView:(UIScrollView *)scrollView beginOffset:(CGFloat)beginOffset;
@end

.m文件

#import "SPPageMenu.h"

@interface SPPageMenu()

@property (nonatomic, strong) UIView *backgroundView;
@property (nonatomic, strong) UIScrollView *scrollView;

/** 该数组中装的是字符串 */
@property (nonatomic, strong) NSArray<NSString *> *menuTitleArray;
/** 跟踪器(跟踪button的下划线) */
@property (nonatomic, weak) UIView *tracker;
/** 分割线 */
@property (nonatomic, weak) UIView *breakline;
/** 选中的button */
@property (nonatomic, strong) UIButton *selectedButton;

/** 装载menuButton的数组 */
@property (nonatomic, strong) NSMutableArray<UIButton *> *menuButtonArray;

/** 用来判断外界有没有设置firstButtonX */
@property (nonatomic, assign, getter=isSettedFirstButtonX) BOOL settedFirstButtonX;
/** 用来判断外界有没有设置spacing */
@property (nonatomic, assign, getter=isSettedspacing) BOOL settedspacing;

@end

static NSInteger tagIndex = 2016;

@implementation SPPageMenu

#pragma mark - 初始化方法
- (instancetype)initWithFrame:(CGRect)frame {
    self = [super initWithFrame:frame];
    if (self) {
        [self initialize];
    }
    return self;
}

- (instancetype)initWithCoder:(NSCoder *)aDecoder {
    if (self = [super initWithCoder:aDecoder]) {
        [self initialize];
    }
    return self;
}

// 初始化设置
- (void)initialize {
    _buttonFont = [UIFont systemFontOfSize:15];
    _selectedTitleColor = [UIColor redColor];
    _unSelectedTitleColor = [UIColor blackColor];
    _breaklineColor = [UIColor lightGrayColor];
    _showBreakline = YES;
    _openAnimation = NO;
    _showTracker = YES;
    _trackerHeight = 2.0f;
    _trackerColor = _selectedTitleColor;
    _spacing = 30.0f;
    _firstButtonX = 0.5 * _spacing;
    _allowBeyondScreen = YES;
    _equalWidths = YES;
    
    [self setupSubView];
}


- (void)setupSubView {
    // 背景view
    UIView *backgroundView = [[UIView alloc] init];
    [self addSubview:backgroundView];
    self.backgroundView = backgroundView;
    
    // 创建分割线。这个分割线起着很重要的作用,起初我并没有创建一个单独的view作为分割线,而是利用backgroundView与其子控件scrollView之间的底部设置一定的间隙,造成分割线的效果。但是这样会产生一个很严重的隐患。如果不单独创建分割线,那么backgroundView的第一个子控件将会是scrollView,而正好外界把这个封装好的菜单添加为控制器view的第一个子控件,在默认情况下,外界控制器的导航栏会将scrollView里面的内容往下压64。而此菜单栏的高度一般不会设置很高(<64),这就会导致scrollView里面的子控件看不见。因此,必须使得backgroundView的第一个子控件不是scrollView。
    UIView *breakLine = [[UIView alloc] init];
    breakLine.backgroundColor = _breaklineColor;
    [self.backgroundView addSubview:breakLine];
    self.breakline = breakLine;
    
    // 创建承载菜单button的scrollView
    UIScrollView *scrollView = [[UIScrollView alloc] init];
    scrollView.showsVerticalScrollIndicator = NO;
    scrollView.showsHorizontalScrollIndicator = NO;
    [backgroundView addSubview:scrollView];
    self.scrollView = scrollView;
    scrollView.backgroundColor = [UIColor clearColor];
    
    [self layoutIfNeeded];
    
}

#pragma mark - public method
// 此方法是留给外界的接口,以创建菜单栏
+ (SPPageMenu *)pageMenuWithFrame:(CGRect)frame array:(NSArray *)array {
    SPPageMenu *menu = [[SPPageMenu alloc] initWithFrame:frame];
    menu.menuTitleArray = array;
    return menu;
}

- (void)setMenuTitleArray:(NSArray<NSString *> *)menuTitleArray {
    _menuTitleArray = menuTitleArray;
    [self configureMenuButtonToScrollView];
}


#pragma mark - private method
// 添加以及配置menubutton的相关属性
- (void)configureMenuButtonToScrollView {

    // 创建button
    CGFloat lastMenuButtonMaxX = 0.0f;
    for (int i = 0; i < _menuTitleArray.count; i++) {
        UIButton *menuButton = [UIButton buttonWithType:UIButtonTypeCustom];
        menuButton.tag = tagIndex + i;
        [menuButton addTarget:self action:@selector(menuBtnClick:) forControlEvents:UIControlEventTouchUpInside];
        [menuButton setTitle:self.menuTitleArray[i] forState:UIControlStateNormal];
        menuButton.backgroundColor = [UIColor clearColor];
        menuButton.titleLabel.font = _buttonFont;
        menuButton.titleLabel.textAlignment = NSTextAlignmentCenter;
        [menuButton setTitleColor:[UIColor blackColor] forState:UIControlStateNormal];
        [menuButton sizeToFit];
        
        [self.scrollView addSubview:menuButton];
        [self.menuButtonArray addObject:menuButton];
        
        // 设置button的frame
        [self setupMenuButtonFrame:menuButton
                              font:_buttonFont
                lastMenuButtonMaxX:lastMenuButtonMaxX
                             index:i];
        // 记录此时menuButton的最大x值
        lastMenuButtonMaxX = CGRectGetMaxX(menuButton.frame);
        // 设置scrollView的容量
        self.scrollView.contentSize = CGSizeMake(lastMenuButtonMaxX + _firstButtonX, 0);
    }

    // 创建跟踪器
    [self creatTracker:self.scrollView.subviews.firstObject];
    
    [self menuBtnClick:self.scrollView.subviews.firstObject];
}

// 创建跟踪器
- (void)creatTracker:(UIButton *)button {

    UIView *tracker = [[UIView alloc] init];
    self.tracker = tracker;
    tracker.backgroundColor = _trackerColor;
    // 设置tracker的frame
    [self setupTrackerFrame:button];
 
    [self.scrollView addSubview:tracker];
    
}

// button的点击方法
- (void)menuBtnClick:(UIButton *)button {
    // 是不是第一次进入,先默认为YES
    static BOOL firstEnter = YES;
    
    // 执行代理方法
    [self delegatePerformMethodWithFromIndex:self.selectedButton.tag - tagIndex
                                     toIndex:button.tag - tagIndex];
    // 回调block
    if (self.buttonClickedBlock) {
        self.buttonClickedBlock(button.tag - tagIndex);
    }
    
    if (self.buttonClicked_from_to_Block) {
        self.buttonClicked_from_to_Block(self.selectedButton.tag - tagIndex,button.tag - tagIndex);
    }
    
    // 如果点击的是同一个button,retun掉,因为后面的操作没必要重复。
    if (self.selectedButton == button) {
        return;
    }
    
    if (!firstEnter) {
        // 移动跟踪器
        [self moveTracker:button];
    }
    
    // 给button添加缩放动画
    if (self.openAnimation) {
       [self openAnimationWithLastSelectedButton:self.selectedButton currentSelectedButton:button];
    }
    
    // 设置button的字体颜色
    [self setupButtonTitleColor:button];
    
    // 记录当前选中的button
    self.selectedButton = button;
    
    
    
    // 让scrollView发生偏移(重点)
    [self moveScrollViewWithSelectedButton:button];
    
    
    firstEnter = NO;
}

- (void)setupButtonTitleColor:(UIButton *)button {
    
    [self.selectedButton setTitleColor:_unSelectedTitleColor forState:UIControlStateNormal];
    [button setTitleColor:_selectedTitleColor forState:UIControlStateNormal];
    
}

- (void)delegatePerformMethodWithFromIndex:(NSUInteger)fromIndex toIndex:(NSUInteger)toIndex {
    if ([self.delegate respondsToSelector:@selector(pageMenu:buttonClickedAtIndex:)]) {
        [self.delegate pageMenu:self buttonClickedAtIndex:toIndex];
    }
    if ([self.delegate respondsToSelector:@selector(pageMenu:buttonClickedFromIndex:toIndex:)]) {
        [self.delegate pageMenu:self buttonClickedFromIndex:fromIndex toIndex:toIndex];
    }
}


- (void)moveTracker:(UIButton *)button {
    [UIView animateWithDuration:0.25 animations:^{
        CGPoint trackerCenter = self.tracker.center;
        CGRect trackerFrame = self.tracker.frame;
        trackerCenter.x = button.center.x;
        trackerFrame.size.width = button.frame.size.width+_spacing;
        self.tracker.frame = trackerFrame;
        self.tracker.center = trackerCenter;
    }];
}


// 点击button让scrollView发生偏移
- (void)moveScrollViewWithSelectedButton:(UIButton *)selectedButton {
    // CGRectGetMidX(self.scrollView.frame)指的是屏幕水平中心位置,它的值是固定不变的
    // 选中button的中心x值与scrollView的中心x值之差
    CGFloat offSetX = selectedButton.center.x - CGRectGetMidX(self.scrollView.frame);
    // scrollView的容量宽与自身宽之差(难点)
    CGFloat maxOffsetX = self.scrollView.contentSize.width - self.scrollView.frame.size.width;
    // 如果选中的button中心x值小于或者等于scrollView的中心x值,或者scrollView的容量宽度小于scrollView本身,此时点击button时不发生任何偏移,置offSetX为0
    if (offSetX <= 0 || maxOffsetX <= 0) {
        offSetX = 0;
    }
    // 如果offSetX大于maxOffsetX,说明scrollView已经滑到尽头,此时button也发生任何偏移了
    else if (offSetX > maxOffsetX){
        offSetX = maxOffsetX;
    }
    
    [self.scrollView setContentOffset:CGPointMake(offSetX, 0) animated:YES];
}

// 设置tracker的frame
- (void)setupTrackerFrame:(UIButton *)button {
    CGFloat trackerH = _trackerHeight;
    CGFloat trackerW = button.frame.size.width+_spacing;
    CGFloat trackerX = button.frame.origin.x-0.5*_spacing;
    CGFloat trackerY = self.scrollView.frame.size.height - trackerH;
    self.tracker.frame = CGRectMake(trackerX, trackerY, trackerW, trackerH);
}

- (void)setupMenuButtonFrame:(UIButton *)menuButton font:(UIFont *)buttonFont lastMenuButtonMaxX:(CGFloat)lastMenuButtonMaxX index:(NSInteger)index {
    // canScroll的状态决定着菜单中的button的布局方式
    // menuButton的宽度
    CGFloat menuButtonW = [menuButton.titleLabel.text boundingRectWithSize:CGSizeMake(MAXFLOAT, 0) options:NSStringDrawingUsesLineFragmentOrigin attributes:@{NSFontAttributeName:_buttonFont} context:nil].size.width;
    CGFloat menuButtonH = self.scrollView.frame.size.height-1;
    CGFloat menuButtonX = (index == 0) ? _firstButtonX : (lastMenuButtonMaxX + _spacing);
    CGFloat menuButtonY = 0;
    menuButton.frame = CGRectMake(menuButtonX, menuButtonY, menuButtonW, menuButtonH);
}

- (void)resetMenuButtonFrame {
    __block CGFloat lastMenuButtonMaxX = 0.0f;
    
    if (_allowBeyondScreen) { // 允许超出屏幕
        [self.menuButtonArray enumerateObjectsUsingBlock:^(UIButton * _Nonnull menuButton, NSUInteger idx, BOOL * _Nonnull stop) {
            menuButton.titleLabel.font = self.buttonFont;
            [self setupMenuButtonFrame:menuButton font:_buttonFont lastMenuButtonMaxX:lastMenuButtonMaxX index:idx];
            lastMenuButtonMaxX = CGRectGetMaxX(menuButton.frame);
        }];
    } else { // 不允许超出屏幕
        if (_equalWidths) { // 等宽
            [self.menuButtonArray enumerateObjectsUsingBlock:^(UIButton * _Nonnull menuButton, NSUInteger idx, BOOL * _Nonnull stop) {
                menuButton.titleLabel.font = self.buttonFont;
                
                CGFloat menuButtonW = (self.scrollView.frame.size.width-2*_firstButtonX-(self.menuTitleArray.count-1)*_spacing) / self.menuTitleArray.count;
                CGFloat menuButtonH = self.scrollView.frame.size.height-1;
                CGFloat menuButtonX = _firstButtonX + idx * (menuButtonW+_spacing);
                CGFloat menuButtonY = 0;
                menuButton.backgroundColor = [UIColor clearColor];
                menuButton.frame = CGRectMake(menuButtonX, menuButtonY, menuButtonW, menuButtonH);
            }];
        } else {  // 不等宽(根据文字返回宽度,间距自适应)
            CGFloat menuButtonW_Sum = 0;
            NSMutableArray *menuButtonW_Array = [NSMutableArray array];
            
            CGFloat scrollViewWidth = self.scrollView.frame.size.width;
            
            NSInteger count = self.menuTitleArray.count;
            // 提前计算button宽
            for (NSString *title in self.menuTitleArray) {
                CGFloat menuButtonW = [title boundingRectWithSize:CGSizeMake(MAXFLOAT, 0) options:NSStringDrawingUsesLineFragmentOrigin attributes:@{NSFontAttributeName:_buttonFont} context:nil].size.width;
                
                // 求出所有button的宽度之和,目的是算出间距
                menuButtonW_Sum += menuButtonW;
                [menuButtonW_Array addObject:@(menuButtonW)];
            }
            _spacing = (scrollViewWidth - menuButtonW_Sum) / (count+1);
            
            [self.menuButtonArray enumerateObjectsUsingBlock:^(UIButton * _Nonnull menuButton, NSUInteger idx, BOOL * _Nonnull stop) {
                CGFloat menuButtonW = [menuButtonW_Array[idx] floatValue];
                CGFloat menuButtonH = self.scrollView.frame.size.height-1;
                CGFloat menuButtonX = _spacing + lastMenuButtonMaxX;
                CGFloat menuButtonY = 0;
                menuButton.frame = CGRectMake(menuButtonX, menuButtonY, menuButtonW, menuButtonH);
                lastMenuButtonMaxX = CGRectGetMaxX(menuButton.frame);
            }];
        }
        
    
    }
    
    // 设置scrollView的容量
    self.scrollView.contentSize = CGSizeMake(lastMenuButtonMaxX + _firstButtonX, 0);
}

#pragma mark - 基本布局
- (void)layoutSubviews {
    [super layoutSubviews];

    CGFloat w = self.frame.size.width;
    CGFloat h = self.frame.size.height;
    
    self.backgroundView.frame = CGRectMake(0, 0, w, h);
    
    self.breakline.frame = CGRectMake(0, h - 1, w, 1);
    
    // 减_breaklineHeight是为了有分割线的效果
    self.scrollView.frame = CGRectMake(0, 0, w, h);
}


#pragma mark - setter方法
- (NSMutableArray *)menuButtonArray {
    if (!_menuButtonArray) {
        _menuButtonArray = [NSMutableArray array];
    }
    return _menuButtonArray;
}

// 设置button的间距
- (void)setspacing:(CGFloat)spacing {
    _spacing = spacing;
    
    // 外界是否设置了spacing,如果能进来,说明设置了. 因此在内部不要调用该set方法
    _settedspacing = YES;
    
    // 如果外界没有设置firstButtonX,默认为新的spacing的一半
    if (!_settedFirstButtonX) {
        _firstButtonX = 0.5 * spacing;
    }
    
    // 重设button的frame
    [self resetMenuButtonFrame];
    
    UIButton *menuButton = self.menuButtonArray.firstObject;
    [self setupTrackerFrame:menuButton];
}

// 设置第一个button的左间距
- (void)setFirstButtonX:(CGFloat)firstButtonX {
    _firstButtonX = firstButtonX;
    
    // 外界是否设置了firstButtonX,如果能进来,说明设置了. 因此在内部不要调用该set方法
    _settedFirstButtonX = YES;
    
    [self resetMenuButtonFrame];
    
    UIButton *menuButton = self.menuButtonArray.firstObject;
    [self setupTrackerFrame:menuButton];
}

// 设置字体,字体变了,button的frame和跟踪器的frame需要重新设置
- (void)setButtonFont:(UIFont *)buttonFont {
    _buttonFont = buttonFont;
    
    [self resetMenuButtonFrame];
    
    UIButton *menuButton = self.menuButtonArray.firstObject;
    [self setupTrackerFrame:menuButton];
}

// 设置选中的button文字颜色
- (void)setSelectedTitleColor:(UIColor *)selectedTitleColor {
    _selectedTitleColor = selectedTitleColor;
    
    [self.selectedButton setTitleColor:selectedTitleColor forState:UIControlStateNormal];
    self.tracker.backgroundColor = selectedTitleColor;
}

// 设置没有选中的button文字颜色
- (void)setUnSelectedTitleColor:(UIColor *)unSelectedTitleColor {
    _unSelectedTitleColor = unSelectedTitleColor;
    
    for (UIButton *menuButton in self.menuButtonArray) {
        if (menuButton == _selectedButton) {
            continue;  // 跳过选中的那个button
        }
        [menuButton setTitleColor:unSelectedTitleColor forState:UIControlStateNormal];
    }
}

// 设置分割线颜色
- (void)setBreaklineColor:(UIColor *)breaklineColor {
    _breaklineColor = breaklineColor;
    self.breakline.backgroundColor = breaklineColor;
}

// 设置是否显示分割线
- (void)setShowBreakline:(BOOL)showBreakline {
    _showBreakline = showBreakline;
    self.breakline.hidden = !showBreakline;
}

// 设置是否显示跟踪器
- (void)setShowTracker:(BOOL)showTracker {
    _showTracker = showTracker;
    self.tracker.hidden = !showTracker;
}

// 设置跟踪器的高度
- (void)setTrackerHeight:(CGFloat)trackerHeight {
    _trackerHeight = trackerHeight;
    
    CGRect trackerFrame = self.tracker.frame;
    trackerFrame.size.height = trackerHeight;
    trackerFrame.origin.y = self.scrollView.frame.size.height - trackerHeight;
    self.tracker.frame = trackerFrame;
}

// 设置跟踪器的颜色
- (void)setTrackerColor:(UIColor *)trackerColor {
    _trackerColor = trackerColor;
    self.tracker.backgroundColor = trackerColor;
}

// 设置是否开启动画
- (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];
        }];
    }
}

// 返回放大的动画
- (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;
}

// 开启动画
- (void)openAnimationWithLastSelectedButton:(UIButton *)lastSelectedButton currentSelectedButton:(UIButton *)currentSelectedButton {
    CABasicAnimation *animation1 = [self enlargeAnimation];
    CABasicAnimation *animation2 = [self narrowAnimation];
    [lastSelectedButton.titleLabel.layer addAnimation:animation2 forKey:@"animation2"];
    [currentSelectedButton.titleLabel.layer addAnimation:animation1 forKey:@"animation1"];
}

// 是否允许超出屏幕
- (void)setAllowBeyondScreen:(BOOL)allowBeyondScreen {
    _allowBeyondScreen = allowBeyondScreen;
    // 如果不能超出屏幕,且外界没有设置spacing,让间距默认为0.
    if (!self.allowBeyondScreen && !_settedspacing) {
        _spacing = 0.0f;
        _firstButtonX = 0.0f;
    }
    
    [self resetMenuButtonFrame];
    
    UIButton *menuButton = self.menuButtonArray.firstObject;
    [self setupTrackerFrame:menuButton];
}

// 是否让button等宽
- (void)setEqualWidths:(BOOL)equalWidths {
    _equalWidths = equalWidths;
    
    [self resetMenuButtonFrame];
    
    UIButton *menuButton = self.menuButtonArray.firstObject;
    [self setupTrackerFrame:menuButton];
}

#pragma mark - 提供给外界的方法
- (void)selectButtonAtIndex:(NSInteger)index{
    UIButton *selectedBtn = (UIButton *)self.menuButtonArray[index];
    [self menuBtnClick:selectedBtn];

}

- (void)moveTrackerFollowScrollView:(UIScrollView *)scrollView beginOffset:(CGFloat)beginOffset {

    // dragging是scrollView的一个属性, 如果为YES,说明用户中正在拖拽scrollView。
    // 如果用户在外面调用了这个方法 那么本方法会在点击菜单按钮的时候和用户拖拽外面的scrollView的时候调用.
    // 如果是用户点击菜单按钮进入的此方法,那dragging必然为NO(没有拖拽),并且没有在减速,此时直接retun,让跟踪器跟着菜单按钮走。
    // 如果是用户在外面拖拽scrollView而进入的此方法,那dragging必然为YES(正在拖拽),此时让跟踪器跟着scrollView的偏移量走
    // 当手指松开,decelerating属性为YES,表示scrolview正在减速
    if (!scrollView.dragging && !scrollView.decelerating) {
        return;
    }
    
    if (scrollView.contentOffset.x < 0 || scrollView.contentOffset.x > scrollView.contentSize.width-scrollView.bounds.size.width) {
        return;
    }
    
    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.menuTitleArray.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*_spacing;
    newFrame.size.width = oldButton.frame.size.width + wDistance * progress + _spacing;
    self.tracker.frame = newFrame;
}


@end

github下载地址:https://github.com/SPStore/SPPageMenu

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 157,924评论 4 360
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 66,902评论 1 290
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 107,716评论 0 239
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 43,783评论 0 203
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,166评论 3 286
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,510评论 1 216
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,784评论 2 311
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,476评论 0 196
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,196评论 1 241
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,459评论 2 243
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 31,978评论 1 258
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,321评论 2 252
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 32,964评论 3 235
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,046评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,803评论 0 193
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,530评论 2 271
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,420评论 2 265

推荐阅读更多精彩内容