实现虾米音乐首页视差滚动特效

最近项目迭代了一个新的版本,由于我们的设计师是虾米音乐的忠实粉丝,首页借鉴了虾米8.0首页的视差动画,在预研实现过程中有一些值得记录和分享的地方

虾米音乐8.0首页鉴赏

先看看虾米首页最终的展示效果,搜索栏会随着内容视图滚动偏移渐隐,内容视图有一个视差滚动的效果,达到滚动阈值后搜索栏会隐藏。

细心的同学可能会发现,Banner与导航栏滚动的速度相同,所以实现的难点,是如何实现Banner图与导航栏的相对静止,同时在表视图滚动到既定偏移量时,Banner图被表视图遮盖的效果。

页面结构分析

从呈现结果来看,整个视图可以分为两个部分,导航视图(CusNavigationView)及内容视图(MainContentView)。

导航视图同时包含了一个自定义搜索栏(SearchBar),自定义二级导航栏(NavigationBarLevel2),这个二级导航栏的实现我会单独写一篇博客来讲。

内容视图是一个表视图(TableView),以及使用表视图的头视图来添加轮播图(BannerView):

1
2
3
4
5
6
┏ CusNavigationView
┃ ┣ SearchBar
┃ ┗ NavigationBarLevel2
┗ MainContentView
┣ BannerView
┗ TableView

此处导航视图为了更好监听MainContentView的滚动偏移对子视图进行控制,没有使用系统的NavigationBar,而采用了自定义的方式。

让我们实现她!

在开始编写逻辑前,先根据上个Section分析的视图结构,声明相关视图属性及导航栏折叠前高度与折叠后高度

1
2
3
4
5
6
7
8
9
10
11
12
#define ZMC_Fold_Height 44	    //折叠后高度
#define ZMC_Unfold_Height 94 //折叠前高度
#define ZMC_Fold_Height_Offset (ZMC_Unfold_Height - ZMC_Fold_Height)

//Navi
@property (nonatomic, strong) UIView *cusNavigationView;
@property (nonatomic, strong) UIView *searchBarView;
@property (nonatomic, strong) UIView *segmentBarView;

//Table
@property (nonatomic, strong) UITableView *contentTableView;
@property (nonatomic, strong) UIView *bannerView;

首先先实现视差滚动效果,我们通过实现ScrollView代理,监听滚动事件,通过计算折叠动画内滚动进度比的方式,控制各个视图的相关属性,达到视差效果,代码如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
- (void)scrollViewDidScroll:(UIScrollView *)scrollView{

CGFloat offsetY = self.contentTableView.contentOffset.y + ZMC_Fold_Height_Offset;
CGFloat ratio = offsetY / ZMC_Fold_Height_Offset;


//如果有banner头视图
if ([self.contentTableView.tableHeaderView isEqual:self.bannerView]) {
//表格视图需要移动banner.height + ZMC_Fold_Height_Offset
//banner和navi相对静止
//移动比为 ZMC_Fold_Height_Offset / banner.height + ZMC_Fold_Height_Offset
ratio = offsetY / (self.bannerView.frame.size.height + ZMC_Fold_Height_Offset);
[_contentTableView sendSubviewToBack:self.bannerView];

}

if (ratio > 0 && ratio < 1) {
[self.cusNavigationView mas_updateConstraints:^(MASConstraintMaker *make) {
make.height.mas_equalTo(ZMC_Unfold_Height - ZMC_Fold_Height_Offset * ratio);
}];
[self.searchBarView setAlpha:1 - ratio];

if ([self.contentTableView.tableHeaderView isEqual:self.bannerView]) {
[_banner mas_updateConstraints:^(MASConstraintMaker *make) {
make.top.mas_equalTo(ratio * self.bannerView.frame.size.height + 20);
}];
}
}

if (ratio <= 0) {
[self.cusNavigationView mas_updateConstraints:^(MASConstraintMaker *make) {
make.height.mas_equalTo(ZMC_Unfold_Height);
}];
[self.searchBarView setAlpha:1];

if ([self.contentTableView.tableHeaderView isEqual:self.bannerView]) {
[_banner mas_updateConstraints:^(MASConstraintMaker *make) {
make.top.mas_equalTo(20);
}];
}
}

if (ratio >= 1) {
[self.cusNavigationView mas_updateConstraints:^(MASConstraintMaker *make) {
make.height.mas_equalTo(ZMC_Fold_Height);
}];
[self.searchBarView setAlpha:0];

if ([self.contentTableView.tableHeaderView isEqual:self.bannerView]) {
[_banner mas_updateConstraints:^(MASConstraintMaker *make) {
make.top.mas_equalTo(self.bannerView.frame.size.height + 20);
}];
}
}
}

光实现了视差滚动还不够,如果滚动到一半位置会出现半遮半掩的效果,非常影响体验,于是我们新建一个名为magneticScrollView:的方法,用于控制在结束滚动时进行视图检查,如果进度比在0~1之间,则根据既定阈值调整视图位置,达到弹性效果

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
- (void)magneticScrollView:(UIScrollView *)sc{
CGFloat offsetY = self.contentTableView.contentOffset.y + ZMC_Fold_Height_Offset;
CGFloat ratio = offsetY / ZMC_Fold_Height_Offset;

//如果有banner头视图
if ([self.contentTableView.tableHeaderView isEqual:self.bannerView]) {
//表格视图需要移动banner.height + ZMC_Fold_Height_Offset
//banner和navi相对静止
//移动比为 ZMC_Fold_Height_Offset / banner.height + ZMC_Fold_Height_Offset
ratio = offsetY / (self.bannerView.frame.size.height + ZMC_Fold_Height_Offset);
[_contentTableView sendSubviewToBack:self.bannerView];

}

if (ratio > 1 || ratio < 0) {
return;
}
if (ratio <= 0.5) {
[sc setContentOffset:CGPointMake(0, -ZMC_Fold_Height_Offset) animated:YES];
}
if (ratio > 0.5 && ratio <= 1) {
[sc setContentOffset:CGPointMake(0, [self.contentTableView.tableHeaderView isEqual:self.bannerView] ? self.bannerView.frame.size.height : 0) animated:YES];
}
}

该方法如果在滚动视图存在减速的情况下,则在减速完成后调用,若不存在减速,则直接调用

1
2
3
4
5
6
7
8
9
- (void)scrollViewDidEndDragging:(UIScrollView *)scrollView willDecelerate:(BOOL)decelerate{
if (!decelerate) {
[self magneticScrollView:scrollView];
}
}

- (void)scrollViewDidEndDecelerating:(UIScrollView *)scrollView{
[self magneticScrollView:scrollView];
}

那么,虾米8.0首页一个视差效果简单的demo就完成了,核心代码的代码量不到100行。当然,实际的业务需求肯定没有这么简单,重要的是一种实现思路。代码传送门