用过 Airbnb 的都知道首页有一个 tableView 头部视图层叠效果



实现起来很简单, 这里就来看一下怎么实现这个效果.

实现思路
  • 先搭建一个tableView基础, 新建一个工程,把原有的 Storyboard 中的 UIViewController 删除,拉一个 UITableViewController 进来,并把 is Initial View Controller 勾上, 把 ViewController 的父类改成 UITableViewController, 最后把在 Storyboard 中把 UITableViewControllerClass 改成 ViewController, command + R 运行.
  • 接下来继承 UIView 创建一个带Xib的自己的HeaderView,在HeaderView中使用autolayout布局一个UIImageView` 容器视图,拉入美女图片,显示.
  • 回到 ViewController 中,把刚才创建的 HeaderView 作为一个子视图插入到 tableView 下方.设置 tableView 上部额外的滚动区域,用来显示头部视图.
  • scrollViewDidScroll 方法中设置一个层叠的覆盖速率,保存想要的 frame,
  • viewWillLayoutSubviews 方法中更新头部视图的 frame, command + R, 搞定.
核心思路

在我们滚动的时候,计算好头部视图垂直方向上的变化,保存到 frame 中,在 viewWillLayoutSubviews 不断更新头部视图的 frame.

具体代码分析
  • @interface 新建两个属性:
@interface ViewController ()

/** header */
@property(nonatomic, strong)HeaderView *header;

/** headerFrame */
@property(nonatomic, assign)CGRect headerFrame;

@end
复制代码
  • 约定两个常量的值
// 头部视图高度
const CGFloat headerHeight = 400;
// 层叠的覆盖速率
const CGFloat speed = 0.6; // speed <= 1  
复制代码
  • viewDidLoad 方法里,设置头部视图的插入层级关系, 给 tableView 顶部插入额外的滚动区域,用来显示头部视图, 应用启动, 滚动到最顶部,显示额外的头部视图.
- (void)viewDidLoad {
    [super viewDidLoad];
    
    HeaderView *header = [HeaderView viewFromXib];
    // 一定要将header插入到tableView的下面,才有层叠覆盖的效果
    [self.view insertSubview:header atIndex:0];
    self.header = header;
    
    // 给tableView顶部插入额外的滚动区域,用来显示头部视图
    self.tableView.contentInset = UIEdgeInsetsMake(headerHeight, 0, 0, 0);
    // 应用启动, 滚动到最顶部,显示额外的头部视图
    [self.tableView setContentOffset:CGPointMake(0, -headerHeight)];
    
    [self.tableView registerClass:[UITableViewCell class] forCellReuseIdentifier:reuseID];
}
复制代码
  • scrollViewDidScroll 方法里就是实现效果的核心代码, 计算 headerView 以我们滚动时的速度的 speed 倍速度滚动时对应的 frame 值,并保存起来.
-(void)scrollViewDidScroll:(UIScrollView *)scrollView{
    // 由于上面设置了应用额外的滚动区域,所以应用一启动就会来到这个方法
    // scrollView.contentOffset.y的值是添加的额外滚动的值headerHeight, 此时headerHeight * speed - headerHeight * (1 - speed) = headerHeight, 从而一启动的时候, 头部视图是按照我们想要的位置布局的
    // 以后当tableView滚动的时候, 头部视图就以我们滚动时的速度的speed倍滚动 , 把这个值保存到self.headerFrame, 在viewWillLayoutSubviews中更新头部视图的frame

    CGRect org = CGRectMake(0, -headerHeight, self.view.frame.size.width, headerHeight);
    org.origin.y = scrollView.contentOffset.y * speed - headerHeight * (1 - speed);
    self.headerFrame = org;
}
复制代码
  • viewWillLayoutSubviews 中不断更新头部视图的 frame.
-(void)viewWillLayoutSubviews{
    [super viewWillLayoutSubviews];
    // 每次布局子控件的时候,都要更新头部视图的frame
    self.header.frame = self.headerFrame;
}
复制代码