- 本例子主要实现2个主要功能
- 悬停
- UIScrollView 的中Item随着滚动列表联动变化
-
效果图:
- 悬停的变化通过 监听UIConllectionView ContentSet变化再改变待悬停视图和y坐标即可,核心代码如下:
- (void)scrollViewDidScroll:(UIScrollView *)scrollView{
self.pageSuperViewSuperView.lgf_y = MAX(-18.0, (scrollView.contentOffset.y - 8.0));
}
*在滑动列表时,想达到滑动条,需要知道每一分组开始的y坐标,对于每一组中cell调试固定时我们可以这样做
-(void) initData{
self.dataArray = [[NSMutableArray alloc] init];
self.dataArray = @[@{@"便民生活" : @[@"充值中心", @"信用卡还款", @"生活缴费", @"城市服务", @"我的快递", @"医疗健康", @"记账本", @"发票管家", @"车主服务", @"交通出行", @"体育服务", @"安全备忘"]},
@{@"财富管理" : @[@"余额宝", @"花呗", @"芝麻信用", @"借呗", @"蚂蚁保险", @"汇率换算"]},
@{@"资金往来" : @[@"转账", @"红包", @"AA收款", @"亲情号", @"商家服务"]},
@{@"购物娱乐" : @[@"出境", @"彩票", @"奖励金"]},
@{@"教育公益" : @[@"校园生活", @"蚂蚁森林", @"蚂蚁庄园", @"中小学", @"运动", @"亲子账户"]},
@{@"第三方服务" : @[@"淘票票电影", @"滴滴出行", @"饿了么外卖", @"天猫", @"淘宝", @"火车票机票", @"飞猪酒店", @"蚂上租租房", @"高德打车", @"哈啰出行"]}].mutableCopy;
// 计算需要选中的 contentOffset.y 保存
self.pageSelectYArray = [[NSMutableArray alloc] init];
__block CGFloat oldY = 0.0;
__block CGFloat newY = 0.0;
[self.dataArray enumerateObjectsUsingBlock:^(NSMutableDictionary * _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) {
NSArray *items = [obj allValues].firstObject;
NSInteger num = ceilf(items.count / 4.0);
newY = newY + (idx == 0 ? 10.0 : 50.0) + num * 70.0;
[self.pageSelectYArray addObject:@[@(oldY), @(newY)]];
oldY = oldY + (idx == 0 ? 10.0 : 50.0) + num * 70.0;
}];
[self setHeader];
}
然后在列表滚动的时候计算当前滚动到顶部头视图是哪一分组即可
#pragma mark - UIScrollView Delegate
- (void)scrollViewDidScroll:(UIScrollView *)scrollView {
if (scrollView == self.collectionViewFour) {
// 这一句用于设置 pageSuperViewSuperView 顶端悬停效果
self.pageSuperViewSuperView.lgf_y = MAX(-18.0, (scrollView.contentOffset.y - 8.0));
if (!self.isSelectTitle){
[self.pageSelectYArray enumerateObjectsUsingBlock:^(NSArray * _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) {
CGFloat realY = scrollView.contentOffset.y;
if (realY > [obj.firstObject floatValue] && realY < [obj.lastObject floatValue]) {
[self.fptView startLineMoveAnimFromValue:idx];
}
}];
}
}
}
- 导航滚动的菜单栏需要开出这么几个接口
- 滑动底部下划线的接口
- 点击Item 的回调代理
#import
/**
滑动导航栏点击时切换页面监听协议接口
*/
@protocol ScrollNavBarChangeListener
@optional
-(void) onChangeListener:(NSInteger) index;
@end
/**
滑动的导航条
*/
@interface ScrollNavBar : UIView
// 滑动导航栏点击时切换页面监听事件
@property(nonatomic,weak) id delegate;
@property(nonatomic,strong) UIView* bottomLine;
@property(nonatomic,strong) NSMutableArray* titleList;
@property(nonatomic,strong) NSMutableArray* btnList;
@property(nonatomic,assign) double itemWidth;
@property(nonatomic,strong) CABasicAnimation *moveAnimation;
@property(nonatomic,assign) NSInteger nCurIndex;
@property(nonatomic,strong) UIScrollView* segmentScroll;
@property(nonatomic,assign) CGPoint finalPos; //点击滑动条的标签,最终的位置
/**
初始化标题
@param titles 标题列表
*/
-(void)iniTitles:(NSMutableArray*) titles;
/**
添加中间滚动视图到列表中,这个视图的个数应该与标题一一对应,超过的部分直接忽略
@param views 中间视图容器
*/
-(void)initSegmentView:(NSMutableArray*) views;
/**
更新标题栏状态
@param idx 更新的索引
*/
-(void)updateTitleBtnStatus:(NSInteger) idx;
/**
启动底部下划线的动画
*/
-(void)startLineMoveAnimFromValue:(NSInteger) idx;
@end
- 源文件为:
#import "ScrollNavBar.h"
#define SCREEN_WIDTH ([UIScreen mainScreen].bounds.size.width)
#define SCREEN_HEIGHT ([UIScreen mainScreen].bounds.size.height)
@implementation ScrollNavBar
-(instancetype) initWithFrame:(CGRect)frame{
if(self = [super initWithFrame:frame]){
[self initAttr];
}
return self;
}
-(void) initAttr{
self.nCurIndex = 0;
self.btnList = [NSMutableArray array];
}
-(void)iniTitles:(NSMutableArray*) titles{
self.titleList = titles;
self.itemWidth = SCREEN_WIDTH / [self.titleList count];
[self.titleList enumerateObjectsUsingBlock:^(NSString* title, NSUInteger idx, BOOL * _Nonnull stop) {
UIButton* button = [[UIButton alloc] initWithFrame:CGRectMake(idx * self.itemWidth, 0, self.itemWidth, 40.0)];
button.backgroundColor = [UIColor whiteColor];
button.tag = idx;
[button addTarget:self action:@selector(onClickListener:) forControlEvents:UIControlEventTouchUpInside];
[button setTitle:title forState:UIControlStateNormal];
button.titleLabel.font = [UIFont systemFontOfSize:12];
[button setTitleColor:[UIColor colorWithRed:119.0 / 255.0 green:119.0 / 255.0 blue:119.0 / 255.0 alpha:1.0] forState:UIControlStateNormal];
[self addSubview:button];
[self.btnList addObject:button];
}];
//创建底部线条
UIView* lineView = [[UIView alloc] initWithFrame:CGRectMake(0.0f, 40.0, SCREEN_WIDTH, 0.5f)];
lineView.backgroundColor = [UIColor colorWithRed:232.0 / 255.0 green:232.0 / 255.0 blue:233.0 / 255.0 alpha:1.0];
[self addSubview:lineView];
self.bottomLine = [[UIView alloc] initWithFrame:CGRectMake(0.0f, 40.0, self.itemWidth, 1.5f)];
self.bottomLine.backgroundColor = [UIColor colorWithRed:118.0f / 255.0 green:198.f / 255.0 blue:192.0f / 255.0 alpha:1.0];
[self addSubview:self.bottomLine];
}
//本接口主要,创建中间滚动视图
-(void)initSegmentView:(NSMutableArray*) views{
}
-(void)onClickListener:(UIButton*) button{
if(self.delegate != nil){
[self.delegate onChangeListener:button.tag];
}
[self.segmentScroll scrollRectToVisible:CGRectMake(SCREEN_WIDTH * button.tag, 42.5, SCREEN_WIDTH, SCREEN_HEIGHT) animated:NO];
NSValue* fromValue = [NSValue valueWithCGPoint:CGPointMake(self.nCurIndex * self.itemWidth + 0.5 * self.itemWidth,40.0)];
NSValue* toValue = [NSValue valueWithCGPoint:CGPointMake(button.tag * self.itemWidth + 0.5 * self.itemWidth, 40.0)];
[self startLineMoveAnimFromValue:fromValue toValue:toValue duration:0.3];
self.nCurIndex = button.tag;
[self updateTitleBtnStatus:button.tag];
self.finalPos = CGPointMake(button.tag * self.itemWidth + 0.5 * self.itemWidth, 40.0);
}
-(void) updateTitleBtnStatus:(NSInteger)idx{
[self.btnList enumerateObjectsUsingBlock:^(UIButton* button, NSUInteger index, BOOL * _Nonnull stop) {
if(idx == index){
[button setTitleColor:[UIColor colorWithRed:118.0f / 255.0 green:198.f / 255.0 blue:192.0f / 255.0 alpha:1.0] forState:UIControlStateNormal];
}else{
[button setTitleColor:[UIColor colorWithRed:119.0 / 255.0 green:119.0 / 255.0 blue:119.0 / 255.0 alpha:1.0] forState:UIControlStateNormal];
}
}];
}
-(void)startLineMoveAnimFromValue:(NSInteger) idx{
NSValue* fromValue = [NSValue valueWithCGPoint:CGPointMake(self.nCurIndex * self.itemWidth + 0.5 * self.itemWidth,40.0)];
NSValue* toValue = [NSValue valueWithCGPoint:CGPointMake(idx * self.itemWidth + 0.5 * self.itemWidth, 40.0)];
[self startLineMoveAnimFromValue:fromValue toValue:toValue duration:0.3];
self.nCurIndex = idx;
[self updateTitleBtnStatus:idx];
self.finalPos = CGPointMake(idx * self.itemWidth + 0.5 * self.itemWidth, 40.0);
}
//private method---线条移动启动动画
-(void) startLineMoveAnimFromValue:(id) fromValue toValue:(id) toValue duration:(CFTimeInterval) time{
self.moveAnimation = [CABasicAnimation animationWithKeyPath:@"position"];
self.moveAnimation.fromValue = fromValue;
self.moveAnimation.toValue = toValue;
self.moveAnimation.delegate = self;
self.moveAnimation.removedOnCompletion = NO;
self.moveAnimation.fillMode = kCAFillModeForwards;
self.moveAnimation.duration = time;
[self.bottomLine.layer removeAllAnimations];
[self.bottomLine.layer addAnimation:self.moveAnimation forKey:@"onStart"];
}
-(void) animationDidStop:(CAAnimation *)anim finished:(BOOL)flag{
if([self.bottomLine.layer.animationKeys.lastObject isEqualToString:@"onStart"]){
CGRect frame = self.bottomLine.frame;
frame.origin.x = self.finalPos.x;
self.bottomLine.frame = frame;
}
}
-(void)dealloc{
}
-(void) updateLabelStatus:(NSNotification *)msg{
NSDictionary* tmpInfo = [msg object];
UIScrollView* scroll = (UIScrollView*)[tmpInfo objectForKey:@"scroll"];
[self.bottomLine.layer removeAllAnimations];
CGRect frame = self.bottomLine.frame;
frame.origin.x = scroll.contentOffset.x / SCREEN_WIDTH * self.itemWidth;
self.bottomLine.frame = frame;
[self.bottomLine layoutIfNeeded];
self.nCurIndex = scroll.contentOffset.x / SCREEN_WIDTH;
[self updateTitleBtnStatus:self.nCurIndex];
}
@end
- 测试用的vc为
#import
NS_ASSUME_NONNULL_BEGIN
@interface StickScrollVC : UIViewController
@property (copy, nonatomic) NSString *type;
@property (copy, nonatomic) NSArray *titles;
@end
NS_ASSUME_NONNULL_END
- 源文件为:
#import "StickScrollVC.h"
#import "CollectionViewLayout.h"
#import "ItemView.h"
#import "MicroTool.h"
#import "UIScrollView+LXBScrollView.h"
#import "ScrollNavBar.h"
#import "UIView+LXB.h"
static NSString *ItemIdentifier = @"ItemIdentifier";
static NSString *leaveDetailsHeadID = @"leaveDetailsHeadID";
static NSString *leaveDetailsFooterID = @"leaveDetailsFooterID";
@interface StickScrollVC (){
NSMutableArray *listArray;
}
@property (assign, nonatomic) CGFloat headerHeight;
// 我的应用数据源数组
@property (strong, nonatomic) NSMutableArray *myDataArray;
// 最近使用数据源数组
@property (strong, nonatomic) NSMutableArray *latelyDataArray;
// 底下数据源数组
@property (strong, nonatomic) NSMutableArray *dataArray;
// 记录用于滚动选择判断的 contentOffset.y
@property (strong, nonatomic) NSMutableArray *pageSelectYArray;
@property (strong, nonatomic) UIView *pageSuperView;
@property (strong, nonatomic) UIView *pageSuperViewSuperView;
@property (strong, nonatomic) UIView *headerView;
@property (strong, nonatomic) UICollectionView *collectionViewFour;
@property (strong, nonatomic) ScrollNavBar *fptView;
@property (assign, nonatomic) BOOL isSelectTitle;
@end
@implementation StickScrollVC
- (void)viewDidLoad {
[super viewDidLoad];
self.view.backgroundColor = [UIColor whiteColor];
self.view.backgroundColor = BgColor;
listArray = [NSMutableArray arrayWithObjects:@"便民生活",@"财富管理",@"资金往来",@"购物娱乐",@"教育公益",@"第三方服务", nil];
[self createCollectionView];
UIButton* back = [[UIButton alloc] initWithFrame:CGRectMake(30, 20, 130, 30)];
back.backgroundColor = [UIColor grayColor];
back.tag = 10;
[back setTitle:@"back" forState:UIControlStateNormal];
[back addTarget:self action:@selector(onClick:) forControlEvents:UIControlEventTouchUpInside];
[self.view addSubview:back];
[self initData];
}
-(void) initData{
self.dataArray = [[NSMutableArray alloc] init];
self.dataArray = @[@{@"便民生活" : @[@"充值中心", @"信用卡还款", @"生活缴费", @"城市服务", @"我的快递", @"医疗健康", @"记账本", @"发票管家", @"车主服务", @"交通出行", @"体育服务", @"安全备忘"]},
@{@"财富管理" : @[@"余额宝", @"花呗", @"芝麻信用", @"借呗", @"蚂蚁保险", @"汇率换算"]},
@{@"资金往来" : @[@"转账", @"红包", @"AA收款", @"亲情号", @"商家服务"]},
@{@"购物娱乐" : @[@"出境", @"彩票", @"奖励金"]},
@{@"教育公益" : @[@"校园生活", @"蚂蚁森林", @"蚂蚁庄园", @"中小学", @"运动", @"亲子账户"]},
@{@"第三方服务" : @[@"淘票票电影", @"滴滴出行", @"饿了么外卖", @"天猫", @"淘宝", @"火车票机票", @"飞猪酒店", @"蚂上租租房", @"高德打车", @"哈啰出行"]}].mutableCopy;
// 计算需要选中的 contentOffset.y 保存
self.pageSelectYArray = [[NSMutableArray alloc] init];
__block CGFloat oldY = 0.0;
__block CGFloat newY = 0.0;
[self.dataArray enumerateObjectsUsingBlock:^(NSMutableDictionary * _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) {
NSArray *items = [obj allValues].firstObject;
NSInteger num = ceilf(items.count / 4.0);
newY = newY + (idx == 0 ? 10.0 : 50.0) + num * 70.0;
[self.pageSelectYArray addObject:@[@(oldY), @(newY)]];
oldY = oldY + (idx == 0 ? 10.0 : 50.0) + num * 70.0;
}];
[self setHeader];
}
#pragma mark - 头部视图配置
- (void)setHeader {
// 设置头高度(可动态适配,我这边只用于示例代码因此是写死的高度)
self.headerHeight = 454.0 + 10.0;
// 添加头
self.headerView = [[UIView alloc] init];
self.headerView.frame = CGRectMake(0.0, -self.headerHeight, lgf_ScreenWidth, self.headerHeight);
[self.collectionViewFour addSubview:self.headerView];
[self.collectionViewFour sendSubviewToBack:self.headerView];
// 添加 LGFFreePTView 父控件
self.pageSuperViewSuperView = [[UIView alloc] init];
self.pageSuperViewSuperView.frame = CGRectMake(0.0, -10.0, lgf_ScreenWidth, 48.0);
self.pageSuperViewSuperView.backgroundColor = [UIColor greenColor];
[self.collectionViewFour addSubview:self.pageSuperViewSuperView];
[self setCVContentInset:0.0];
self.fptView = [[ScrollNavBar alloc] initWithFrame:CGRectMake(0, 10, lgf_ScreenWidth, 83)];
self.fptView.delegate = self;
[self.fptView iniTitles:listArray];
[self.pageSuperViewSuperView addSubview:self.fptView];
}
- (void)setCVContentInset:(CGFloat)top {
CGFloat lastY = lgf_ScreenHeight - IPhoneX_NAVIGATION_BAR_HEIGHT - 40 - ([[[self.pageSelectYArray lastObject] lastObject] floatValue] - [[[self.pageSelectYArray lastObject] firstObject] floatValue] + top);
[self.collectionViewFour setContentInset:UIEdgeInsetsMake(self.headerHeight, 0.0, lastY, 0.0)];
[self.collectionViewFour lgf_ScrollToTopAnimated:NO];
}
-(void)onClick:(UIButton*) view{
[self.navigationController popViewControllerAnimated:false];
}
-(void)createCollectionView{
CGRect size = CGRectMake(0, 50, self.view.frame.size.width, self.view.frame.size.height - 64);
CollectionViewLayout *mcvl=[[CollectionViewLayout alloc] init];
self.collectionViewFour = [[UICollectionView alloc] initWithFrame:size collectionViewLayout:mcvl];
[self.collectionViewFour registerClass:[ItemView class] forCellWithReuseIdentifier:ItemIdentifier];
self.collectionViewFour.showsHorizontalScrollIndicator=NO;
self.collectionViewFour.showsVerticalScrollIndicator=NO;
self.collectionViewFour.backgroundColor=[UIColor whiteColor];
self.collectionViewFour.delegate = self;
self.collectionViewFour.dataSource = self;
//一定要注册headview
[self.collectionViewFour registerClass:[UICollectionReusableView class] forSupplementaryViewOfKind:UICollectionElementKindSectionHeader withReuseIdentifier:leaveDetailsHeadID];
//一定要注册footerview
[self.collectionViewFour registerClass:[UICollectionReusableView class] forSupplementaryViewOfKind:UICollectionElementKindSectionFooter withReuseIdentifier:leaveDetailsFooterID];
[self.view addSubview:self.collectionViewFour];
}
//有多少个sections
- (NSInteger)numberOfSectionsInCollectionView:(UICollectionView *)collectionView{
return listArray.count;
}
//每个section 中有多少个items
- (NSInteger)collectionView:(UICollectionView *)collectionView numberOfItemsInSection:(NSInteger)section{
NSArray *items = [self.dataArray[section] allValues].firstObject;
return items.count;
}
//section X item X位置处应该显示什么内容
- (UICollectionViewCell *)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath{
ItemView *cell=nil;
if (cell==nil) {
cell = [collectionView dequeueReusableCellWithReuseIdentifier:ItemIdentifier forIndexPath:indexPath];
}
cell.contentView.backgroundColor = [UIColor whiteColor];
if (indexPath.row/2 == 0) {
cell.imgView.backgroundColor = [UIColor redColor];
}else{
cell.imgView.backgroundColor = [UIColor greenColor];
}
cell.nameLabel.text = [self.dataArray[indexPath.section] allValues].firstObject[indexPath.item];
return cell;
}
//cell的header与footer的显示内容
- (UICollectionReusableView *)collectionView:(UICollectionView *)collectionView viewForSupplementaryElementOfKind:(NSString *)kind atIndexPath:(NSIndexPath *)indexPath{
if (kind == UICollectionElementKindSectionHeader){
UICollectionReusableView *reusableHeaderView = nil;
if (reusableHeaderView==nil) {
reusableHeaderView = [collectionView dequeueReusableSupplementaryViewOfKind:UICollectionElementKindSectionHeader
withReuseIdentifier:leaveDetailsHeadID
forIndexPath:indexPath];
reusableHeaderView.backgroundColor = [UIColor whiteColor];
UILabel *label = (UILabel *)[reusableHeaderView viewWithTag:100];
if (!label) {
label = [[UILabel alloc] initWithFrame:CGRectMake(10, 0, self.view.frame.size.width, 40)];
label.tag = 100;
[reusableHeaderView addSubview:label];
}
label.text = listArray[indexPath.section];
}
if (indexPath.section == 0) {
reusableHeaderView.lgf_height = 0.0;
} else {
reusableHeaderView.lgf_height = 40.0;
}
return reusableHeaderView;
}else if (kind == UICollectionElementKindSectionFooter){
UICollectionReusableView *reusableFooterView = nil;
if (reusableFooterView == nil) {
reusableFooterView = [collectionView dequeueReusableSupplementaryViewOfKind:UICollectionElementKindSectionFooter withReuseIdentifier:leaveDetailsFooterID forIndexPath:indexPath];
reusableFooterView.backgroundColor = BgColor;
}
return reusableFooterView;
}
return nil;
}
#pragma mark - UIScrollView Delegate
- (void)scrollViewDidScroll:(UIScrollView *)scrollView {
if (scrollView == self.collectionViewFour) {
// 这一句用于设置 pageSuperViewSuperView 顶端悬停效果
self.pageSuperViewSuperView.lgf_y = MAX(-18.0, (scrollView.contentOffset.y - 8.0));
if (!self.isSelectTitle){
[self.pageSelectYArray enumerateObjectsUsingBlock:^(NSArray * _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) {
CGFloat realY = scrollView.contentOffset.y;
if (realY > [obj.firstObject floatValue] && realY < [obj.lastObject floatValue]) {
[self.fptView startLineMoveAnimFromValue:idx];
}
}];
}
}
}
//滑动条的滚动接口
- (void)onChangeListener:(NSInteger)index{
[NSObject cancelPreviousPerformRequestsWithTarget:self selector:@selector(configIsSelectTitle) object:nil];
self.isSelectTitle = YES;
[self.collectionViewFour setContentOffset:CGPointMake(0, [[self.pageSelectYArray[index] firstObject] floatValue] ) animated:YES];
[self performSelector:@selector(configIsSelectTitle) withObject:nil afterDelay:0.3];
}
- (void)configIsSelectTitle {
self.isSelectTitle = NO;
}
@end