新浪微博个人主页效果实现

效果图

注意细节:

  1. 三个tableview的滚动都可以推动顶部图片和切换栏的滚动。
  2. 只要切换栏没有贴住导航栏,3个tableview都是从第一个cell开始显示。
  3. 切换栏贴住导航栏时,每个tableview的显示位置会被保持。
  4. 切换栏贴住导航栏,手指在当前页面拖动使切换栏脱离导航栏然后再贴住导航栏,另外2个tableview的显示状态被保持。

实现:

结构

  1. 研究微博个人主页容易发现这里使用了3个tableview。我在主控制器上添加3个子控制器,每一个子控制器管理一个tableview,然后将3个tableview添加到主控制器view上,所有的子控制器都继承自BaseTableViewController,方便后面的滚动监听。
  2. 每个tableview右边的指示条滚动范围都包含了顶部视图(图片)。在之前的版本中,我没有让tableview们共用一个顶部视图,而是给它们设置了同样的tableHeaderView(包含同样的imageView和同样的将会被切换栏覆盖的空白区域)。虽然这种结构能够实现微博个人主页的效果,但是不方便扩展,比如给tableview增加滑动手势,来左右切换要显示的界面。
  3. 在最新版本中,我给每个tableview都设置了240高度(图片高度200+切换栏高度40)的空白UIView作为tableHeaerView,并让3个tableview共用的一个headerView(包括要显示的图片和切换栏)。
    首先将headerView加入到当前显示的tableview 上,跟着tableview滚动。当偏移量能够达到切换栏贴着导航栏时,将headerView加入到主控制器view中,固定位置;当偏移量要使切换栏脱离导航栏时,又将headerView加入到当前显示的tableview中;要切换当前显示的tableview,也需要根据偏移量来判断加入到哪个view上。

监听

我在这里采用代理方式来监听每个tableview在Y轴上的偏移量,主控制器作为BaseTableViewController的代理。 协议方法:

@protocol TableViewScrollingProtocol <NSObject>
- (void)tableViewScroll:(UITableView *)tableView offsetY:(CGFloat)offsetY;
- (void)tableViewWillBeginDecelerating:(UITableView *)tableView offsetY:(CGFloat)offsetY;
- (void)tableViewDidEndDecelerating:(UITableView *)tableView offsetY:(CGFloat)offsetY;
@end

计算

- (void)tableViewScroll:(UITableView *)tableView offsetY:(CGFloat)offsetY{
    if (offsetY > headerImgHeight - topBarHeight) {
        if (![_headerView.superview isEqual:self.view]) {
            [self.view insertSubview:_headerView belowSubview:_navView];
        }
        CGRect rect = self.headerView.frame;
        rect.origin.y = topBarHeight - headerImgHeight;
        self.headerView.frame = rect;
    } else {
        if (![_headerView.superview isEqual:tableView]) {
            for (UIView *view in tableView.subviews) {
                if ([view isKindOfClass:[UIImageView class]]) {
                    [tableView insertSubview:_headerView belowSubview:view];
                    break;
                }
            }
        }
        CGRect rect = self.headerView.frame;
        rect.origin.y = 0;
        self.headerView.frame = rect;
    }
}

这段代码主要是实时监听tableview的Y轴偏移量,如果当前offsetY大于136(图片高度200减去导航栏状态栏高度64))的话,就将headerView添加到主控器view上,此时切换栏紧贴导航栏。如果小于的话,就将headerView添加到当前显示的tableView上,跟随tableview滑动。

切换栏在没有贴住导航栏时,三个tableview的Y轴偏移量是同样的,贴住时就不再一样。 我在主控制器里设置了一个字典来存储每个tableview的偏移量,key为tableview所在控制器的地址(之前我的使用的是tableview的地址,造成的问题是在tableview上拖拽,会造成其他两个控制器view被提前加载一次)。这样我就可以随时记录和改变每个偏移量,然后在切换到要显示的tableview时,直接设置给它的contentoffset.y就可以了。
- (void)tableViewDidEndDragging:(UITableView *)tableView offsetY:(CGFloat)offsetY {
_segCtrl.userInteractionEnabled = YES;

    NSString *addressStr = [NSString stringWithFormat:@"%p", _showingVC];
    if (offsetY > headerImgHeight - topBarHeight) {
        [self.offsetYDict enumerateKeysAndObjectsUsingBlock:^(NSString  *key, id  _Nonnull obj, BOOL * _Nonnull stop) {
            if ([key isEqualToString:addressStr]) {
                _offsetYDict[key] = @(offsetY);
            } else if ([_offsetYDict[key] floatValue] <= headerImgHeight - topBarHeight) {
                _offsetYDict[key] = @(headerImgHeight - topBarHeight);
            }
        }];
    } else {
        if (offsetY <= headerImgHeight - topBarHeight) {
            [self.offsetYDict enumerateKeysAndObjectsUsingBlock:^(NSString  *key, id  _Nonnull obj, BOOL * _Nonnull stop) {
                _offsetYDict[key] = @(offsetY);
            }];
        }
    }
}

当手指离开屏幕时,如果当前tableview没有速度了,就要在tableViewDidEndDragging:offsetY:方法里面获取tableview的偏移量。如果offsetY大于136的话,先把自己的存起来。而另外两个tableview,如果它们之前的偏移量小于等于136,现在就要改成136了(切换过去的话,让它们刚好偏移到切换栏贴住导航栏);如果offsetY小于等于136,就将三个tableview的存储偏移量设为一样。
如果当前tableview还有速度,就要在tableViewDidEndDecelerating: offsetY:方法里面设置,处理方式一样。
- (void)segmentedControlChangedValue:(HMSegmentedControl*)sender {
[_showingVC.view removeFromSuperview];

    BaseTableViewController *newVC = self.childViewControllers[sender.selectedSegmentIndex];
    if (!newVC.view.superview) {
        [self.view addSubview:newVC.view];
        newVC.view.frame = self.view.bounds;
    }
    
    NSString *nextAddressStr = [NSString stringWithFormat:@"%p", newVC];
    CGFloat offsetY = [_offsetYDict[nextAddressStr] floatValue];
    newVC.tableView.contentOffset = CGPointMake(0, offsetY);
    
    [self.view insertSubview:newVC.view belowSubview:self.navView];
    if (offsetY <= headerImgHeight - topBarHeight) {
        [newVC.view addSubview:_headerView];
        for (UIView *view in newVC.view.subviews) {
            if ([view isKindOfClass:[UIImageView class]]) {
                [newVC.view insertSubview:_headerView belowSubview:view];
                break;
            }
        }
        CGRect rect = self.headerView.frame;
        rect.origin.y = 0;
        self.headerView.frame = rect;
    }  else {
        [self.view insertSubview:_headerView belowSubview:_navView];
        CGRect rect = self.headerView.frame;
        rect.origin.y = topBarHeight - headerImgHeight;
        self.headerView.frame = rect;
    }
    _showingVC = newVC;
}

这段代码就是移除之前的tableview,添加将要显示的。 唯一注意点就是要根据偏移量来判断将headerView加到主控制器view上还是tableview上。

其它

1.关于导航栏颜色渐变的处理,我这里采用的是将系统导航栏设为无色。
- (void)viewWillAppear:(BOOL)animated {
[super viewWillAppear:animated];
[self.navigationController.navigationBar setBackgroundImage:[UIImage new]forBarMetrics:UIBarMetricsDefault];
self.navigationController.navigationBar.shadowImage = [UIImage new];
}

- (void)viewWillDisappear:(BOOL)animated {
    [super viewWillDisappear:animated];
    [self.navigationController.navigationBar setBackgroundImage:nil forBarMetrics:UIBarMetricsDefault];
    self.navigationController.navigationBar.shadowImage = nil;
}

然后添加一个navView(UIView)盖在导航栏上,然后滚动监听改变其alpha就好了。

2.为了记录准确的偏移量,跟微博一样,采取了在滚动过程中第一次单击切换栏使tableView停止滚动,第二次单击切换栏才切换tableView的做法。因为tableView自带了单击停止滚动的效果,所以如果切换栏没有贴住导航栏时,它的父控件headerView作为tableView的一部分,单击切换栏就可以让tableView停止。如果切换栏贴住导航栏时,它的父控件headerView就是控制器view的一部分了,我的做法是此时屏蔽掉headerView对触摸事件的响应,这样子切换栏后面的tableView部分就可以响应该单击事件了。详情请看源码,分析过程请看http://www.jianshu.com/p/2f664e71c527

3.状态栏颜色变化已经添加,gif图没有体现出来。详情请看源码,分析过程请看http://www.jianshu.com/p/ee1c9c91a477

4.研究发现新浪微博的顶部视图并没有下拉放大的效果,而是图片隐藏了一部分,下拉会显示出来。下拉放大实现起来比较容易,并且我觉得意义不大,在此就不在添加了。

Demo下载地址:https://github.com/wobangnidashui/WeiboHomepage

推荐阅读更多精彩内容