晒网达人,晒网达人! 周末歇息昨日烦躁。硬生生又把该养起来的习惯给弄炸了。
可今天还是得回归报告下成果吧。
今日接触的案例是京东APP之中商品详情页,有关scrollView特性的各种使用。以下结合案例讲起来吧
使用三个View实现六个View的作用
看标题估计是个人都会匪夷所思,这有什么用?有必要那么麻烦实现这个效果吗
直接创建六个View不好吗,方便归方便,但是涉及的原理起码得懂一下对不对?优化相关的还在努力学习中之后有看到就回来改改。指不定就有用了呢
那是怎么个实现方式呢?
简单讲讲
首先创建一个ScrollView,在上面添加三个View。通过ScrollView的DidSroll 以及 DidEnd方法,利用偏移值contentoffSet控制索引的加减,从而通过数组修改imageView,并且修改当前scrollView的偏移值。
从而在视觉上达到一View多用的效果。
说起来是比较绕口。但这里只要上张图,就会明白了
是的,细心的人已经发现。所谓的3个View 承载6个View的方法,其实就是除开第一个以及最后一个View之外,所有的窗口都由centerView承载, 就和魔术变戏法一样
所有的内容都展示在centerView,每次滑动结束后通过修改当前scrollView的偏移值,重新回到centerView中。
这里上代码
//实现三个View 承载6个View- (void)scrollViewDidEndDecelerating:(UIScrollView *)scrollView{ if(_currentIndex > 0 && _currentIndex < _imageArr.count - 1){ //通过imageArr数组更改ImageView图片内容 self.leftView.image = [UIImage imageNamed:self.imageArr[_currentIndex -1]]; self.centerView.image = [UIImage imageNamed:self.imageArr[_currentIndex]]; self.centerView.frame = CGRectMake(self.xg_Witch, 0, self.xg_Witch, self.xg_Witch); self.rightView.image = [UIImage imageNamed:self.imageArr[_currentIndex +1]]; //修改scrollView的偏移量 //如果最后偏移值不等于当前屏幕宽度,则进入判定误区,无法进行屏幕多用 lastXOffset = self.xg_Witch; // ** /*scrollRectToVisible 与contentOffSet功能一致, 但是scrollRectToVisible可以设置关闭位移时的动画效果,出现动画效果则穿帮,contentOffSet则不行 */ [self.imageScrollView scrollRectToVisible:CGRectMake(self.xg_Witch, 0.f, self.xg_Witch, self.xg_Height) animated:NO]; }}
里面在下注释也提到了一句,如果偏移量不等于当前屏幕宽度时,就进入判定误区。这又是什么意思呢?
这时候我们再来看一下我画的图,将整体看做一个scrollView的话。那么
leftView的bounds坐标是(0 , 0 , 屏幕宽度 , 屏幕高度 )
centerView的bounds坐标是(屏幕宽度 , 0 , 屏幕宽度 , 屏幕高度 )
rightView的bounds坐标是(屏幕宽度 *2 , 0 , 屏幕宽度 , 屏幕高度 )
以iphoneX为例。屏幕宽度375。当从leftView偏移至CenterView时候,偏移值contentOffSet是375没有错。
当centerView偏移至rightView的时候,偏移值是多少呢?是俩倍的屏幕宽度750。不理解的人可以多看几遍图再仔细琢磨下坐标点的位置应该就能获知了。
如果我们需要复用centerView作为展示View的话。那么当前centerView的偏移值必须是 375< offset < 750 。因为这样才不会通过偏移值滑出centerView。每次滑动结束后都通过修改scrollView的偏移值,再次移动到centerView中
在滑出centerView的时候,通过方法修改scrollView的contentOffset值来达到效果。
如果大于750的话,在索引还没到last时,就已经可以滑出center。再往后就陷入了if-else的判断误区了。
所以此时需要修改lastXOffset,在需要复用的centerView的过程中保持宽度为375
然后是DidScroll方法,以及上文中一直提起的索引就是currentIndex,默认值为0。这里提一下。
这里通过if-else循环判定,更改currentIndex值,从而改变left、center、right中imageView的image值。
- (void)scrollViewDidScroll:(UIScrollView *)scrollView{ CGFloat offsetX = scrollView.contentOffset.x; if (offsetX <= self.xg_Witch /2 && lastXOffset > self.xg_Witch /2) ··· { self.currentIndex -= 1; }else if (offsetX >= self.xg_Witch /2 && lastXOffset3 *self.xg_Witch /2) { self.currentIndex -= 1; }else if (offsetX >= 3 * self.xg_Witch /2 && lastXOffset <3 *self.xg_Witch /2) { self.currentIndex += 1; } lastXOffset = offsetX;}
滑动scrollView时,实现覆盖效果。
在很多APP的体验中,左滑动scrollView时。新出现的View会覆盖原先的View。这和scrollView原先的效果不完全不一样。那么到底怎么实现的呢?
原理也很简单,修改偏移值
当左滑时,新View出现时,在DidScroll方法中 修改旧View的偏移值。使view.frame.x值 等于 原先view.frame.x坐标 + 1/2 屏幕宽度。
这里还是上一份代码
//上一页覆盖下一页的效果 if (offsetX < self.xg_Witch) { _leftView.xg_X = 0.f + offsetX/2; }else if (offsetX > self.xg_Witch && offsetX < 2 * self.xg_Witch){ _centerView.xg_X = self.xg_Witch + (offsetX - self.xg_Witch)/2; }
但是直接用代码也不行。在移动centerView的时候,问题就出现了。
因为修改LeftView.frame.x坐标的时候,没有更正centerView的坐标值。移动centerView的时候会页面错乱。
出现的结果如图
这是因为scrollView是根据设定好的属性page,进行移动。每次移动一个屏幕的距离。那么当leftView的frame.x被修改时
scrollView根据page属性移动的距离就减少了一半。也就会出现图中出现的centerView、rightView各一半的结果。
所以当移动centerView时,就会出现以上的结果
怎么办是好?
因为centerView是复用View这样的特殊情况。所以
此时只需要在scrollView第一次移动结束DidEnd后强制更正一下centerView的坐标,使得他的frame值恒定在初始值即可。
这样每次往右滑动时,确确实实也让RightView覆盖在centerView上。但是移动结束后通过修改CenterView.frame以及ScrollView显示区域坐标以达到目的。
通过ContentOffset,实现页码View3D旋转以及数值变更
这里标题也已经剧透了一切了。
我们同样的,通过contentOffSet 偏移值来计算出页码View旋转的角度
来来来,解方程了解方程了。旋转角度angel为未知数,已知总旋转角度为90°,偏移最大值为屏幕宽度。可通过DidSroll获取当前偏移值。求angel
那不就是
angel / offset = 90°/ 屏幕宽度
angel = 90° /屏幕宽度 * 偏移值
然后通过layer.transfrom = CATransfrom3DMakeRotation(angel(位移角度),0(x轴),1(y轴),0(z轴))来移动当前的页码View
因为旋转是按照y轴来转的,所以x、z轴设定为0。
剩下的就直接看代码吧
- (void)pageNumberAnimation:(CGFloat)offsetX{ CGFloat angel = 0.f; //当第一个页面没有滑动完毕前 //逆时针旋转的时候数字会成镜面状态 //所以当M_PI旋转了90度时,重新计算坐标使其正向旋转 if (offsetX <= self.xg_Witch /2) { angel = M_PI/self.xg_Witch * offsetX; }else if (offsetX <= self.xg_Witch && offsetX > self.xg_Witch /2) { angel = (offsetX - self.xg_Witch) * M_PI/self.xg_Witch; // (-M_PI/2~0) }else if (offsetX <= 3 * self.xg_Witch/2 && offsetX > self.xg_Witch) { angel = (offsetX - self.xg_Witch) * M_PI/self.xg_Witch; }else if (offsetX <= 2 * self.xg_Witch && offsetX > 3 * self.xg_Witch / 2) { angel = (offsetX - 2 * self.xg_Witch) * M_PI/self.xg_Witch; } //angel为正数,旋转是按逆时针旋转的。如果为负数旋转则为顺时针 self.pageNumberView.layer.transform = CATransform3DMakeRotation(angel, 0, 1, 0);}
嵌套ScrollView
在项目实战中,有时候总会碰到这么一个问题,为什么俩个scrollView添加在一起,一个打横做banner。一个竖直当做内容页作滚动时。
在横向banner中向下滑动时,整个屏幕一动不动。
如果没有研究手势这一块的事情的话,我大概会觉得是编程软件出问题了。(我自己出问题)。其实这就是一个手势冲突的问题、同时也是一个层次问题。
俩个scrollView互相独立,在横向scrollView中向下滑动手势,一般情况下是不会影响竖直的scrollView的。因为他们在ViewController中的关系是对等的(凭什么我要听你指挥)
解决方法也是异常简单。只要让滑动优先级较高的View成为另外一个View的父亲 。成为他的子控件就能避免优先级的冲突。
在京东、淘宝的商品页中也经常会出现这样的嵌套问题。还需要注意下层级问题的。不然哪天问题都找不到了。
scrollView上、下拉动效果的实现
这个功能相信学过IOS编程的人都不会陌生,因为涉及的原理和代码就是对 ScrollView的delegate的方法,以及由于手势触摸移动,生成scrollView的偏移值contentOffSet 俩者进行控制。
一般拉动后更变页面的效果都是在触摸拖拽结束时方法中实现,以下便是在下实现方式
- (void)scrollViewDidEndDragging:(UIScrollView *)scrollView willDecelerate:(BOOL)decelerate{ //手指停止拖动时 if (decelerate) { if (scrollView == self.updownScrollView) { CGFloat offSetY = scrollView.contentOffset.y; if (offSetY >= self.view.xg_Height + detailOffSetY) { [UIView animateWithDuration:0.3 animations:^{ self.contentView.xg_Y -= self.view.xg_Height; }]; } }else { //获取webView中的scrollView的位移距离 CGFloat offSetY = self.webView.webView.scrollView.contentOffset.y; if (offSetY <= -detailOffsetY) { [UIView animateWithDuration:0.3 animations:^{ self.contentView.xg_Y = 0.f; }]; } } }}
结合图片来讲,当拖拽结束后
scrollView的偏移值大于当前内容页View的高加上拖拽文本区的高度时,通过UIView中Animal方法修改当前view的bounds.y,减去拖拽文本区的高度 +当前显示页面的高度时。
即可显示隐藏内容页。
往上拖拽也是一样的,自己所做的案例中使用了WKWebView。其本质是scrollView。只要判断向下拖拽的滑动距离是否大于临界值。修改内容页的bounds.y 就ok了。
至于说XY的值怎么判断加减。根据这里的规则那就是
右加左减,下加上减~~
结语
实话说在scrollView坐标的偏移值理解上还是蛮多不到位的,囫囵吞枣。
这里先留下一篇不成熟的博文,来日方长。择日再改!
但也算是匍匐前行吧,一步一个脚印!