背景
自以为完美的解决方案demo在此-TFMultiTabScrollView
这种界面效果需要一大段话来描述,也没有专门的名词,简单说,就是简书APP的个人简介的样式
同样也是微博/Twitter的个人中心样式。总结下特点就是:
- 一个头部界面+一个分页栏+有N个内容分页
- 然后每一页可以各自上下滑动,同时可以横向滑动切换分页
那么这个和网易新闻的界面有什么区别?看上去好像一样。
重点就在这个头部界面,头部上下滚动也是可以带动内容上下,而头部横向滑动却不会带动内容分页切换。
我找了下目前的一些方案,发现并没有很完美的,比如嵌套UIScrollview的滑动冲突解决方案。这也是一开始我想的,似乎大家很容易走到嵌套scrollView的路线上,而这条路似乎是个死胡同。
我的方案
层级图
- 横向滑动用来切换分页的scrollView大小占满整个界面,而不只是头部以下的位置
- N个分页的scrollView放在横向的ScrollView上
- 头部放在当前显示的那个分页ScrollView上
这样做可以达到:
- 横向滚动可以切换分页
- 头部上下滑动可以带动当前分页内容上下移动,因为头部就在内容分页的scrollView上
但是还有几个问题需要解决:
- 分页scrollView的内容会被头部遮挡一部分
- 横向切换分页后,新的分页头部是不是没有了?还是需要用3个头部?但是用3个是不是切换起来会很难看?
- 头部横向滑动时,会带动分页横向切换,这个效果是不需要的
解决:
- 调整分页scrollView的contentInsert.top,让顶部内容空出
- 这个问题是关键,当初就是想到这里而否定了,而没有走下去。解决方法就是:
- 在横向滑动开始的时候,把头部从分页内容scrollView(橙色的)上拿下来,放到横向滑动的scrollView(绿色的)上,这时头部就覆盖在橙色scrollView上。
- 然后根据滑动的contentOffset.x不断调整头部的frame,让它看起来没有动
- 等到滑到目标分页时,再把头部又放回到当前的内容ScrollView上。
整个过程里,视觉上看起来好像头部视图一直没动。这效果就达到了。而且因为头部视图又放到了当前的分页内容ScrollView上,所以之前的效果继续保持。
- 这是手势冲突的问题,自定义头部视图的view,然后添加一个
UIPanGestureRecognizer
手势,然后实现手势冲突的方法:
-(BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer shouldRequireFailureOfGestureRecognizer:(UIGestureRecognizer *)otherGestureRecognizer{
//_tabContainer是横向滑动的scrollView
if (otherGestureRecognizer.view == _tabContainer) {
return NO;
}
return YES;
}
_tabContainer是横向滑动的scrollView,如果是和头部的pan手势和横向滑动ScrollView的手势冲突了,就返回NO。因为头部视图在上层,所以它的首先优先了。这样就把横向滑动的手势给屏蔽了。
这么做只是为了空白的头部视图不会触发横向内容切换,如果在头部视图加入更多控件,比如再加一个UIScrollView,它本身是可以自动屏蔽横向ScrollView(绿色)的手势的。
这样做就避开了scrollView嵌套滑动手势的问题
到这主要的问题就解决了,剩下的就是分页内容ScrollView上下滑动时,头部的悬浮问题了。
使用KVO检测分页scrollView的contentOffset,然后:
- 在向上下滑的时候,头部跟随上下滑,这里什么都不需要处理,因为把头部放到了scrollView上,这是自带效果。
- 当tab分页栏,就是简书例子里“动态-文章-更多”那一栏,顶到了视图的顶部后,不断调整头部的frame.y让他看起来不动
- 当头部完全显示出来后,也不断调整frame.y让它看起来不动
额外效果
简书、微博和Twitter的效果是同一种,其实还有另一种类似的,就是美团的外卖商店界面,区别就是:美团这里是内容只要上下滑动,头部都是跟随动的,而简书微博这边是只有内容视图滑到顶部,也就是内容的顶接住了头部视图的顶的时候,才能拖动头部。在逻辑上,美团那种跟Safari的地址栏是一个感觉。
为了区分这两种效果,设置了一个属性moveHeaderOnlyContentTop
,YES时就是简书微博效果,NO就是美团外卖商店效果。默认YES。有些分页内容可能很少,导致tab分页栏滑不到顶部,而如果其他分页可以滑到顶部,这时就有一个问题,tab栏在顶部的时候,切换到内容不足的页面,就会导致tab分页栏刷的一下掉下来。这效果很不好。所以我加了一个属性
autoFillContent
。如果YES,则计算内容高度,设置contentInsert.bottom,在scrollView的底部增加一段空白。这样内容不足时,tab分页栏依然可以到顶部。默认YES。有时tab分页栏不想直接贴住视图的顶部,比如有一个导航栏效果,内容往上滑的时候,导航栏出来,往下滑时候呢,导航栏渐变消失。这时要给导航栏留出空位,tab分页栏就不能直接挨着顶部,所以我加了一个属性
topSpace
,这个是用来调控tab栏和顶部的间距的。默认0。
不完美的尝试
1. 三层嵌套scrollView
这种界面结构,很容易想到的就是3个scrollView嵌套:一个大的竖向scrollView----横向分页scrollView----每个分页自身的scrollView。最外层的竖向scrollView为了是头部能够滑动,所以说这个头部才是这里的症结啊!但这种结构带来的是两个竖向的scrollView的滑动切换问题:
- 当tab分页栏滑到定后,要滑动内部的scrollView
- 到头部除了tab栏还有更多显示出来时,要滑动外部的scrollView来带动头部。
那么为什么不一直滑动外面的scrollView呢?这其实也是一种方案,只要把内容视图的内容完全的展开,然后只滑动外层的scrollView来切换显示内容。不好的是这样就没法使用tableView的重用功能了,比如你有很多的cell,像简书的动态那一栏。但分页是可以做的,只要加载新的内容后在继续全部展开。但这个不完美的瑕疵让人不爽。
一开始我以为scrollView的哪个滑动是hitView的问题,但后来发现scrollView的滑动是它自带的pan手势来控制滑动的。手势一旦识别了,就不会再识别,除非是新的触碰,这就是这种方案为什么要放手再重新滑动才能内外scrollView切换。
如果scrollView的滑动不是它自带的手势处理,而是一个统一的手势,而scrollView只要接受来自这个统一的手势的滑动数据就好了。这样只需要把数据的输出调整成另一个scrollView就可以完成完美的滑动切换。这里就牵扯到一点设计的问题了。
2. 模拟滚动的头部视图
这种方案是:
整体的父视图 ---- 横向的scrollView ---- 分页的竖向scrollView
整体的父视图 ----- 头部
也就是头部视图和横向的scrollView是同一个层级上,然后头部在上面覆盖。
这样要要解决的关键问题就是:头部上竖向滑动,要带动分页内容色scrollView滑动。而横向滑动啊,点击啊之类的都没有问题了,因为它已经不再嵌套scrollView的层级里了。
所以我自定义了一个头部(demo里的TFScrollSimulateView
),然后给它加了pan手势来模拟scrollView的滚动,其实手指拖动是很好实现的,头部的手势拖动了多少就修改scrollView的contentOffset多少,麻烦的是:
- 手指快速的一滑,手指离开后,scrollView还要继续滚动,而且要速度越来越慢。
- scrollView拉倒边缘时的弹簧效果
所以我做了一个计时器,在手指离开后,不断的计算滑动的距离,而pan手势是有velocityInView:
方法可以取得速度。这样其实基本可以解决问题了,但有个不完美的是scrollView减速的公式只能猜:
module -= KTimerInterval * _friction * (10000 + module * module); //阻力和速度平方成正比,速度减去a*t
但是后来在一个项目里找到了相关公式:弹簧公式
3. 用图片欺骗
其实如果不需要横向滑动切换分页的效果,只能点击切换的话,这个问题的难度直线下降,因为这样就不需要横向的scrollView了,内外层的scrollView可以合成一个,就完全不存在什么嵌套的问题了。
但是如果一定要有横向切换的效果呢?可以在开始横向滑动的时候,做两张图片覆盖在内容部分。手指滑动的时候,是图片在切换,但是这个图片长得跟分页的scrollView一样,让人看起来好像是分页内容切换了一样。
- 使用core graphic的截图方法,在刚开始滑动的时候,把当前分页scrollView的界面截取下来,然后覆盖到相同位置。(就跟你把别人的桌面截一张图,然后做成背景,再把桌面图标全隐藏,看起来好像没变-_-)
- 把之前的内容图片摆在左右,然后滑动切换时,实际是这两个图片在切换。
这个具体没去实现,只是想法,这种欺骗眼睛的手法是挺有意思的。
2017.6.28更新
修改了问题:
- 分页
scrollView
在滑动过程中会添加新的子视图,导致遮住头部,比如UITableView
的sectionHeader
. -
_currentVisableHeaderH
没有初始值导致滑动出错 - 分页切换时,如果频繁点击会导致界面闪烁
完善功能:
- 在不需要自动扩充底部内容的时候,有可能某个分页内容很少,导致头部滑动不到顶部,在切换的时候会出现“突然掉下来”的现象,现在改成动画,体验更好些。
- 做好了某个分页滑动,其他分页同步滑动内容的处理。能够达到从A分页离开时,如果你已经滑动到了第10行是贴着头部视图的底部的,在其他分页移动了头部视图后,A分页的内容也会跟随移动,保证回去时还是第10行贴着头部。但是这个只有在
moveHeaderOnlyContentTop
为NO
的时候有效。