花了两天时间写了一个支付宝的首页。
首先关于最终的首页架构:
- 整体使用的一个UITableView和2个HeadView(MenuGroudView(包扣HomeNavi、AdView、MiniNavi)、GridView)来布局。未采用网上2个ScrollView的方式 ;
- 单独写了一个VC_A来处理UITableView的滑动事件,另外写一个VC_A1继承VC_A来处理UITableView的DataSource和delegate。目的保证功能独立;
- 2个HeadView直接添加到UITableView上,未设置为tableView.tableHeaderView,保证这2个HeadView的位置可控。未采用网上的添加到VC.view上,目的是:HeadView已超出UITableView上边距,手势无法接受。
- HeadView在tableView上的显示空间有tableView.contentInset预留出来。
- 对iPhone X的适配采用对UITableView进行更多的偏移量来适配;
- 对导航栏的处理,选择隐藏掉,对系统对tableView的默认偏移特性也进行关闭;
- 对页面上下滑动处理采用KVO监听,参考,在监听中控制MenuGroudView的高度以及2个HeadView的位置。
最终效果:
6s iPhone X核心代码:
VC部分:
//
// JYALPHomeSimulateVC.m
// MYCCBRegisterSubAccount
//
// Created by JackYing on 2018/7/31.
// Copyright © 2018年 JackYing. All rights reserved.
//
#import "JYALPHomeSimulateVC.h"
#import "JYHGridView.h"
#import "JYHMenuGroudView.h"
@interface JYALPHomeSimulateVC () {
/// 表格最顶部到顶部的间隙
CGFloat _topOffset;
/// 表格顶部内容顶部到表格顶部的间隙
CGFloat _topMargin;
/// 快捷菜单,会被隐藏到导航栏
JYHMenuGroudView *groudView;
/// 常用菜单,直接跟随tableview进行滑动
JYHGridView *gridView;
}
@property (nonatomic, strong) UITableView *tableView;
@property (nonatomic, strong) UIScrollView *scrollView;
@end
@implementation JYALPHomeSimulateVC
@synthesize tableView = _tableView;
- (void)viewDidLoad {
[super viewDidLoad];
// Do any additional setup after loading the view.
self.title = @"支付宝首页";
[self.navigationController setNavigationBarHidden:YES animated:NO];
// 关闭系统默认偏移
if (@available(iOS 11.0, *)) {
self.tableView.contentInsetAdjustmentBehavior = UIScrollViewContentInsetAdjustmentNever;
} else {
self.automaticallyAdjustsScrollViewInsets = NO;
}
[self.tableView addObserver:self forKeyPath:@"contentOffset" options:(NSKeyValueObservingOptionNew) context:nil];
[self addHeadSubViews];
}
- (void)dealloc {
[self.tableView removeObserver:self forKeyPath:@"contentOffset"];
}
- (UITableView *)tableView {
if (!_tableView) {
_tableView = [[UITableView alloc] initWithFrame:self.view.bounds style:(UITableViewStyleGrouped)];
_tableView.keyboardDismissMode = UIScrollViewKeyboardDismissModeOnDrag;
_tableView.separatorStyle = UITableViewCellSeparatorStyleSingleLine;
_tableView.pagingEnabled = YES;
_topMargin = 16;
// 对iPhone X进行适配
_topOffset = 300 + kJYALPHomeStatueHeight;
_tableView.contentInset = UIEdgeInsetsMake(_topOffset + _topMargin, 0, 0, 0); // +16目的是顶部到菜单底部有空隙
_tableView.scrollIndicatorInsets = _tableView.contentInset;
}
return _tableView;
}
- (void)addHeadSubViews {
// 直接将gridView、groudView放在table上可避免上半部分手势的处理,
// 不需要控制gridView.frame
gridView = [[JYHGridView alloc] init];
//gridView.backgroundColor = [UIColor redColor];
[self.tableView addSubview:gridView];
groudView = [[JYHMenuGroudView alloc] init];
groudView.delegate = self;
[self.tableView addSubview:groudView]; // groudView在上层显示,形成groudView.navi在最上层视觉
[self layoutHeadSubViews];
}
/// 原始状态
- (void)layoutHeadSubViews {
CGFloat y = -(_topOffset + _topMargin);
CGFloat height = _topOffset / 2;
groudView.frame = CGRectMake(0, y, CGRectGetWidth(self.tableView.frame), height);
gridView.frame = CGRectMake(0, CGRectGetMaxY(groudView.frame), CGRectGetWidth(self.tableView.frame), height);
}
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary<NSKeyValueChangeKey,id> *)change context:(void *)context {
CGFloat offsetY = self.tableView.contentOffset.y;
// 恢复初始位置
if (offsetY == -(_topOffset + _topMargin)) {
[self layoutHeadSubViews];
[groudView setSubViewAlpha:1];
groudView.adView.hidden = NO;
}
else if (offsetY < -(_topOffset + _topMargin)) {// 向下滑动
[groudView setSubViewAlpha:1];
CGRect groudViewFrameNew = groudView.frame;
groudViewFrameNew.size.height = _topOffset/2;
groudViewFrameNew.origin.y = offsetY; // table偏移多少就相向移动多少
groudView.frame = groudViewFrameNew;
CGRect gridViewFrameNew = gridView.frame;
gridViewFrameNew.origin.y = CGRectGetMaxY(groudViewFrameNew);
gridView.frame = gridViewFrameNew;
}
else { // 向上滑动
// 直接将gridView、groudView放在table上不需要控制gridView.frame
CGFloat groudViewHeight = (ABS(offsetY) - _topOffset/2 - _topMargin);
CGRect groudViewFrameNew = groudView.frame;
if (groudViewHeight >= 64 && groudViewHeight <= _topOffset / 2) { // 减少视图面积
groudViewFrameNew.size.height = groudViewHeight;
}
groudViewFrameNew.origin.y = offsetY; // table偏移多少就相向移动多少
groudView.frame = groudViewFrameNew;
// 控制alpha值以及隐藏显示
CGFloat alpha = (-offsetY / (_topOffset + _topMargin));
[groudView setSubViewAlpha:alpha];
}
}
- (void)menuView:(UIView *)menuView didSeletedMenu:(id)menu {
NSAssert(NO, @"叶子子类实现!");
}
@end
设置alpha值及显示隐藏:
- (void)setSubViewAlpha:(CGFloat)alpha {
//NSLog(@"1. ---->%lf", alpha);
if (alpha == 1) { // 回到原始位置
self.adView.alpha = self.homeNavi.alpha = 1;
self.adView.hidden = self.homeNavi.hidden = NO;
self.miniMenu.hidden = YES;
} else {
if (alpha > 0.8) {
self.adView.hidden = self.homeNavi.hidden = NO;
}
if (alpha < 0.95) {
self.miniMenu.hidden = NO;
}
alpha = alpha - (1 - alpha) * 3; // 加速减小
//NSLog(@"adView: ---->%lf", alpha);
self.adView.alpha = alpha;
alpha -= alpha * 0.5;
//NSLog(@"homeNavi: ---->%lf", alpha);
self.homeNavi.alpha = alpha; // homeNavi比adView先消失
alpha = (1 - alpha + alpha * 0.2); // 加速增大
//NSLog(@"miniMenu: ---->%lf", alpha);
self.miniMenu.alpha = alpha;
if (alpha > 0.9) {
alpha = (alpha + alpha * 0.1);
alpha = alpha < 1 ? alpha : 1;
//NSLog(@"2. ---->%lf", alpha);
self.adView.hidden = self.homeNavi.hidden = YES;
self.miniMenu.hidden = NO;
self.miniMenu.alpha = alpha;
}
}
}
对状态栏进行适配:
CGFloat naviMaxY = 44 + kJYALPHomeStatueHeight;
self.homeNavi.frame = CGRectMake(0, 0, CGRectGetWidth(self.frame), naviMaxY);
self.adView.frame = CGRectMake(0, naviMaxY, CGRectGetWidth(self.frame), CGRectGetHeight(self.frame) - naviMaxY);
self.miniMenu.frame = CGRectMake(0, 0, CGRectGetWidth(self.frame), naviMaxY);
趟过的坑:
- miniNavi和AdView的alpha值和显示隐藏的控制;
- 计算gridView和groudView在滑动过程中相对于TableView的偏移量;
- iPhone X的适配在VC中将statueBar高度进行预留,在groudView中控制各子视图的位置;
- 在滑动向下过程中到达下半部分时,groudView、gridView的高度应该是固定的,不需要在进行计算;
a. 所以处理这里时错以为是因为runloop的mode==tracking时,frame进行计算,但不对UI进行更新,等track转为default时在更新,进而有延迟感。查看offsetY的具体值后发现,在sc进行快速tracking时offsetY的变化跨度很大,所以在计算groudViewHeight时产生的跨度也很大,导致最终tracking结束时直接将groudView设置为初始值而产生的闪屏现象:如从143-->150,有跳跃感。 - 关于偏移,因为tableView向上滑动时offset在增大,而groudView.y也需要从最初的-topOffset到到0再到正数,一直保持到tableview的顶端,所以, table偏移多少就相向移动多少,即groudView.y = offsetY。
- 为了避免处理手势,所以将2个HeadView直接添加到UITableView上,方便在HeadView上滑动时,tableview也会相应正常滑动,而未将其设置为tableHeaderView,是保证这2个HeadView的位置方便控制。
网友评论