- 本例子主要實現2個主要功能
- 懸停
- UIScrollView 的中Item隨着滾動列表聯動變化
-
效果圖:
-
懸停的變化通過 監聽UIConllectionView ContentSet變化再改變待懸停視圖和y座標即可,核心代碼如下:
- (void)scrollViewDidScroll:(UIScrollView *)scrollView{
self.pageSuperViewSuperView.lgf_y = MAX(-18.0, (scrollView.contentOffset.y - 8.0));
}
*在滑動列表時,想達到滑動條跟隨列表一起滾動,需要知道每一分組開始的y座標,對於每一組中cell的座標時我們可以這樣做,即記住每一組的開始y座標,然後通過ScrollView的contentOffset y 座標來判斷這個值落在哪一個區間內,再取出這個值在之前記住這個y座標的索引
-(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 <UIKit/UIKit.h>
/**
滑動導航欄點擊時切換頁面監聽協議接口
*/
@protocol ScrollNavBarChangeListener <NSObject>
@optional
-(void) onChangeListener:(NSInteger) index;
@end
/**
滑動的導航條
*/
@interface ScrollNavBar : UIView<CAAnimationDelegate>
// 滑動導航欄點擊時切換頁面監聽事件
@property(nonatomic,weak) id<ScrollNavBarChangeListener> 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 <UIKit/UIKit.h>
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 ()<UICollectionViewDelegate,UICollectionViewDataSource,ScrollNavBarChangeListener>{
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