效果图:

网易导航条.gif
在项目中有类似的需求,仿网易新闻Navbar主要借助UIScrollview与 addChildViewController 实现这个效果。
在滚动时候有可能会有两个需求
- 点击item 滚动到中间
- 在超出屏幕时点击才发生滚动
所以在自定义SegmentControl的时候 定义一个枚举:
typedef enum : NSUInteger {
LXSegmentedControlTypeCenterScroll, // 中心滚动风格
LXSegmentedControlTypeEndScroll, // 超出屏幕滚动风格
} LXSegmentedControlScrollType; // 默认为滚动风格
第一步 定义一个LXSegmentControl 继承于UIScrollview。接口中暴露以下属性,有类方法构造以及对象方法构造,滚动类型选择,接口方法中已经传入代理UIViewController。
@protocol LXSegmentControlDelegate<NSObject>
-(void)LXSegmentControl:(LXSegmentControl *)segmentControl didSelectBtnAtIndex:(NSInteger)index;
@end;
@interface LXSegmentControl : UIScrollView
//对象方法创建 LXSegmentControl
-(instancetype)initWithFrame:(CGRect)frame delegate:(id <LXSegmentControlDelegate>)delegate titleArr:(NSArray *)titleArr;
//类方法创建 LXSegmentControl
+(instancetype)segmentControlWithFrame:(CGRect)frame delegate:(id <LXSegmentControlDelegate>)delegate titleArr:(NSArray *)titleArr;
@property(nonatomic,weak)id <LXSegmentControlDelegate> SeDelegate;
@property(nonatomic,assign)LXSegmentedControlScrollType scrollType;
///** 滚动Conrolller的时候 SegmentControl需要做的处理 */
- (void)titleBtnSelectedWithScrollView:(UIScrollView *)scrollView;
@end
第二部 根据传入的title 数组创建button 然后设置UIScrollview的内容大小。button的大小通过API计算出字符串的长度,然后设置margin。
for (NSUInteger i = 0; i <_title_Arr.count; i++) {
self.title_btn =[UIButton buttonWithType:UIButtonTypeCustom];
_title_btn.titleLabel.font = LXFont(btn_fondOfSize);
_title_btn.tag = i;
//计算内容的size
CGSize buttonSize =[self sizeWithText:_title_Arr[i] font:LXFont(btn_fondOfSize) maxSize:CGSizeMake(MAXFLOAT, button_H)];
//计算内容的宽度
CGFloat button_W = 2 *btn_Margin + buttonSize.width;
_title_btn.frame = CGRectMake(button_X, button_Y, button_W, button_H);
[_title_btn setTitle:_title_Arr[i] forState:UIControlStateNormal];
[_title_btn setTitleColor:[UIColor blackColor] forState:UIControlStateNormal];
[_title_btn setTitleColor:[UIColor redColor] forState:UIControlStateSelected];
//计算每个button的 X 值
button_X = button_X + button_W;
//点击事件
[_title_btn addTarget:self action:@selector(buttonAction:) forControlEvents:UIControlEventTouchUpInside];
//默认选中第0 个button
if (i == 0) {
[self buttonAction:_title_btn];
}
//存入所有的 title_btn
[self.titleBtn_mArr addObject:_title_btn];
[self addSubview:_title_btn];
}
//计算 scrollview 的宽度
UIButton *lastButton = self.titleBtn_mArr.lastObject;
CGFloat scrollViewWidth = CGRectGetMaxX(lastButton.frame);
self.contentSize = CGSizeMake(scrollViewWidth, self.height);
最关键的处理在于 处理button 的点击方法 在点击时候处理contentoffset,是在点击屏幕最后一个按钮然后滚动UIScrollview,还是选择居中模式。
#pragma mark - - - 按钮的点击事件
- (void)buttonAction:(UIButton *)sender
{
[self titleBtnSelectededCenter:sender];
// 2、代理方法实现
NSInteger index = sender.tag;
if ([self.SeDelegate respondsToSelector:@selector(LXSegmentControl:didSelectBtnAtIndex:)]) {
[self.SeDelegate LXSegmentControl:self didSelectBtnAtIndex:index];
}
//3 、 改变指示器的位置
[self titleBtnSelected:sender];
}
/** 滚动标题选中居中 */
- (void)titleBtnSelectededCenter:(UIButton *)centerBtn {
switch (self.scrollType) {
case LXSegmentedControlTypeCenterScroll:
[self centerScroll:centerBtn];
break;
case LXSegmentedControlTypeEndScroll:
[self endScroll:centerBtn];
default:
break;
}
}
-(void)centerScroll:(UIButton *)centerBtn
{
//计算偏移量
CGFloat offsetX = centerBtn.center.x - Device_Width * 0.5;
if (offsetX < 0) offsetX = 0;
// 获取最大滚动范围
CGFloat maxOffsetX = self.contentSize.width - Device_Width;
if (offsetX > maxOffsetX) offsetX = maxOffsetX;
// 滚动标题滚动条
[self setContentOffset:CGPointMake(offsetX, 0) animated:YES];
}
-(void)endScroll:(UIButton *)centerBtn
{
CGFloat offsetX;
if (CGRectGetMaxX(centerBtn.frame) >= Device_Width) {
offsetX = CGRectGetMaxX(centerBtn.frame) - Device_Width;
if (centerBtn.tag <[_title_Arr count]-1) {
offsetX = offsetX + centerBtn.frame.size.width;
}
}else
{
offsetX = 0 ;
}
[self setContentOffset:CGPointMake(offsetX, 0) animated:YES];
}
接口中需要暴露 一个方法当我们滚动子控制器的时候item 需要作出的改变
/** 标题选中颜色改变以及指示器位置变化 */
- (void)titleBtnSelectedWithScrollView:(UIScrollView *)scrollView {
// 1、计算滚动到哪一页
NSInteger index = scrollView.contentOffset.x / scrollView.frame.size.width;
// 2、把对应的标题选中
UIButton *selectedBtn = self.titleBtn_mArr[index];
// 3、滚动时,改变标题选中
[self titleBtnSelected:selectedBtn];
}
接下来是ViewControll中的处理
实现LXSegmentControl的代理
-(void)LXSegmentControl:(LXSegmentControl *)segmentControl didSelectBtnAtIndex:(NSInteger)index
{
// 1 计算滚动的位置
CGFloat offsetX = index * self.view.frame.size.width;
self.mainScrollView.contentOffset = CGPointMake(offsetX, 0);
// 2.给对应位置添加对应子控制器
[self showVc:index];
}
当我们在添加子视图的view的时候判断是不是该控制器的视图已经进行了懒加载
vc.isViewLoaded
// 显示控制器的view
- (void)showVc:(NSInteger)index {
CGFloat offsetX = index * self.view.frame.size.width;
UIViewController *vc = self.childViewControllers[index];
// 判断控制器的view有没有加载过,如果已经加载过,就不需要加载
if (vc.isViewLoaded) return;
vc.view.backgroundColor = LBRandomColor;
[self.mainScrollView addSubview:vc.view];
vc.view.frame = CGRectMake(offsetX, 0, self.view.frame.size.width, self.view.frame.size.height);
}
最后 处理在滑动切换子控制器的时候需要LXSegmentControl需要做的处理
#pragma mark - UIScrollViewDelegate
- (void)scrollViewDidEndDecelerating:(UIScrollView *)scrollView{
// 计算滚动到哪一页
NSInteger index = scrollView.contentOffset.x / scrollView.frame.size.width;
// 1.添加子控制器view
[self showVc:index];
// 2.把对应的标题选中 接口中已经暴露
[self.segmentControl titleBtnSelectedWithScrollView:scrollView];
}
demo地址 两种Navbar滚动类型