探索scrollView的实现*复用

UIScrollView滚动视图,绝对算的上是iOS开发中最重要的控件,用来展示多于一个屏幕的内容,可以滚动显示超过屏幕外的内容的特性使其产生了更多强大的子类:UITableView、UICollectionView、UITextView等等。尽管功能如此强大,但是scrollView本质上只是一个UIView的黑魔法,本文将剖析UIScrollView这种强大特性的实现过程

图层渲染

这里不得不提到UIView和CALayer的关系。在UIKit框架中,UIView是所有界面元素的基础,我们页面上可见的控件几乎都是从这个类派生出来的。之所以说几乎意味着我们也可以不通过UIView及其子类的途径来展示一些页面效果,比如有渐变效果的进度条——通过CALayer直接完成。关于两者的具体区别以及关系,我们不在这里详说,只需要知道每一个UIView管理着一个CALayer,所有我们看到的内容都是由后者进行渲染的。
当我们添加子视图的时候,会基于当前视图的坐标系原点进行计算,然后在设置好的位置对子视图的layer进行渲染,假设现在添加一个frame为40, 40, 120, 40的按钮,那么渲染图示如下:

1.png

在按钮添加到当前视图之前,按钮自身先进行了渲染,然后在距离父视图上边40点,左边40点的位置进行组合。正是因为这种组合的渲染模式,在顶层的视图总是会遮盖下层的视图。通过下面的视图组合流程,我们也能明白为什么创建的view的bounds总是{0, 0, width, height}

1.png

根据上面的视图组合,我们试想一下,如果button的坐标是(100, 40),那么这个按钮还会显示在view上面吗?答案是肯定的,因为根据上面视图组合的实现,我们可以得出一个结论:当前的视图也存在一个父视图,父视图也存在其所在的父视图。如此循环,直到这个视图是keyWindow为止。那么我们就有下面的结构图示

1.png

因此按钮是处在我们可视的范围内的。但是,按照这种组合方式,scrollView的实现就显得非常的神奇了,因为在scrollView上面的子视图一旦超过了它的显示范围。这里需要说到view的clipsToBoundslayer的maskToBounds属性,这两个属性尽管名字不一样,但是如果你在堆栈调用的时候进行调试,会发现最终调用的是maskToBounds方法。这两个值任意一个设置YES的时候,在上面视图组合的③步骤中,超出父视图范围内的部分将不进行渲染。
那么scrollView是否跟我们猜测的一样,通过设置maskToBounds这个值来屏蔽超出其显示范围的子视图呢?如果是的话,那么scrollView就只是一个普通的UIView。我们通过下面的代码验证

UIScrollView * scrollView = [[UIScrollView alloc] initWithFrame: CGRectMake(0, 0, 200, 180)];
scrollView.backgroundColor = [UIColor orangeColor];
scrollView.center = self.view.center;
[self.view addSubview: scrollView];UIView * subview = [[UIView alloc] initWithFrame: CGRectMake(60, 60, 180, 180)];
subview.backgroundColor = [UIColor blueColor];
[scrollView addSubview: subview];

这时候scrollView的效果是这样的:

1.png

接下来设置scrollView的maskToBounds属性

 scrollView.layer.masksToBounds = NO;

效果图

1.png

可以看到,scrollView本质上不过是一个默认遮盖范围外子视图的UIView罢了。那么,UIView到底使用了什么黑魔法来实现滚动视图呢?


contentOffset

用过scrollView的开发者对这个属性都不陌生,contentOffset决定了当前scrollView显示内容的范围,即是当前scrollView的左上角的显示位置坐标。通过图片轮播控件来探究这个属性的实现

1.png

上图中scrollView发生了滚动,使得显示的图片从1变成2。在这个过程中,contentOffset也从(0, 0)变为(width, 0) 从这张图上看更像是子视图的位置发生了移动,从右向左移动。但是在这一切发生的过程中,子视图的frame没有发生过任何变化,因此与其说是滚动,不如说是scrollView基于子视图的所在的坐标系发生了偏移:

1.png

两张图都表示了图片轮播的过程,但是第二张更加接近scrollView滚动实现的本质——基于自身的坐标系发生了位置偏移。因此,contentOffset实际上表示的是scrollView的bounds的改变,其实现大概如下

 - (void)setContentOffset: (CGPoint)contentOffset
 {
    _contentOffset = contentOffset;    CGRect bounds = self.bounds;
    bounds.origin = contentOffset;    self.bounds = bounds;
 }

contentSize

如果说contentOffset决定了scrollView的窗口,那么contentSize决定了这个窗口背后的风光。

1.png

contentSize决定了scrollView显示内容的尺寸范围,从上图看,我们可以知道,在contentSize的宽度或者长度任意一个尺寸大于scrollView等边长度的时候,scrollView才能实现滚动效果。当然了,单单是contentSize是不足以让我们实现scrollView的滚动范围限制的,这是contentSizecontentOffset的共同实现效果:

1.png


contentInset

contentInset是一个相当有用的属性,我在做的一个毛玻璃效果导航栏上下拉效果时就通过这个属性实现。这个属性可以在某种意义上增加或者减少我们的滚动尺寸范围:

1.png

可以看到,contentInset让我们原本contentOffsetcontentSize协同作用的滚动范围发生了改变,原本最上角(0, 0)的限制坐标变成了(-contentInset.left, -contentInset.top)
既然contentInset只是简单的改变了滚动范围的规则,为什么我们不直接通过contentSize来实现呢?这是由于更多时间,我们还需要在滚动视图的某个方向上面留下一块空白的区域进行自定义,这时候直接设置contentInset是最快的方式。而换成contentSize来实现,我们还必须同时改变bounds跟center来实现(不要直接改变frame,在组合视图时,frame最后是由bounds和center决定的)

时间: 2024-08-02 10:40:41

探索scrollView的实现*复用的相关文章

深入探析 Rational AppScan Standard Edition 多步骤操作

序言 IBM Rational AppScan Standard(下文简称 AppScan)作为面向 Web 应用安全黑盒检测的自动化工具,得到业界的广泛认可和应用.很多人使用 AppScan 时都采用其强大的手工探索加自动探测的方式,然而这种方式并不适用于所有场景.使用 AppScan 进行安全扫描时,我们必须保证 AppScan 探索出来的 URL 的有效性(尤其是用户想导出这些探索结果以供复用的情况下),有效性即指该 URL 对应的 HTTP 请求能被服务器端接受并按照期望的方式进行处理.

COMET探索系列二【Ajax轮询复用模型】

COMET探索系列二[Ajax轮询复用模型] 写在前面:Ajax轮询相信大家都信手拈来在用,可是有这么一个问题,如果一个网站中同时有好多个地方需要用到这种轮询呢?就拿我们网站来说,有一个未读消息数提醒.还有一个时实时加载最新说说.昨天又加了一个全网喊话,以后还会要有类似功能添加是肯定的,难道要为每个功能都创建一个独立的轮询?要知道轮询请求中有大半是无用,会对服务器资源和宽带造成巨大的浪费.因此在页面中每增加一个轮询点,对服务器的压力及宽带浪费都将成倍的增长.再考虑一个情况,如果当前网页中需要的不

[探索]在开发中尽量提高代码的复用性

ctrl+c 和 ctrl+v 给我们带来了很多的便利,但是也使我们变得懒惰,不愿思考. 1.前言 相信很多人和我一样,在开发项目的时候,因为项目赶,或者一时没想到等原因.频繁使用 ctrl+c 和 ctrl+v ,导致代码很多都是重复的.这几天,也看了自己以前写的代码,简单的探索了一下,挑选几个实例,分享下如何在特定场景下,保证代码质量前提下,提高代码复用性. 提高代码的复用性,应该是不同场景,不同解决方案的.同时也要保证代码质量.不建议强制提高代码复用性,如果提高代码复用性会大大的降低代码的

ScrollView定时器复用

起始偏移量设置为一个宽度 [NSTimer scheduledTimerWithTimeInterval:2 target:self selector:@selector(refreshPic) userInfo:nil repeats:YES]; //定时器实现 - (void)refreshPic{ if (_mainScrollView.contentOffset.x/_mainScrollView.frame.size.width==4) { _mainScrollView.conten

Android控件篇之视图控件scrollview探索

ScrollView继承关系 Scrollview的源码位置android.widget.ScrollView,该视图类主要继承与FrameLayout public class ScrollView extends FrameLayout java.lang.Object android.view.View android.view.ViewGroup android.widget.FrameLayout android.widget.ScrollView 类概述 一种可供用户滚动的层次结构布

腾讯优测优分享 | 探索react native首屏渲染最佳实践

腾讯优测是专业的移动云测试平台,旗下的优分享不定时提供大量移动研发及测试相关的干货~此文主要与以下内容相关,希望对大家有帮助. react native给了我们使用javascript开发原生app的能力,在使用react native完成兴趣部落安卓端发现tab改造后,我们开始对由react native实现的界面进行持续优化.目标只有一个,在享受react native带来的新特性的同时,在体验上无限逼近原生实现.作为一名前端开发,本文会从前端角度,探索react native首屏渲染最佳实

探索react native首屏渲染最佳实践

1.前言 react native给了我们使用javascript开发原生app的能力,在使用react native完成兴趣部落安卓端发现tab改造后,我们开始对由react native实现的界面进行持续优化.目标只有一个,在享受react native带来的新特性的同时,在体验上无限逼近原生实现.作为一名前端开发,本文会从前端角度,探索react native首屏渲染最佳实践. 2.首屏耗时计算方法 2.1我们关注的耗时 优化首屏渲染耗时,需要先定义首屏耗时的衡量方法.将react nat

React Native 中 ScrollView 性能探究

1 基本使用 ScrollView 是 React Native(后面简称:RN) 中最常见的组件之一.理解 ScrollView 的原理,有利于写出高性能的 RN 应用. ScrollView 的基本使用也非常简单,如下: ... 它和 View 组件一样,可以包含一个或者多个子组件.对子组件的布局可以是垂直或者水平的,通过属性 horizontal=true/false 来控制.甚至还默认支持"下拉"刷新操作.另外还有一个特别赞的特性,超出屏幕的 View 会自动被移除,从而节省资

Android学习笔记-构建一个可复用的自定义BaseAdapter

转载自http://www.runoob.com/w3cnote/android-tutorial-customer-baseadapter.html   作者:coder-pig 本节引言: 如题,本节给大家带来的是构建一个可复用的自定义BaseAdapter,我们每每涉及到ListView GridView等其他的Adapter控件,都需要自己另外写一个BaseAdapter类,这样显得非常麻烦, 又比如,我们想在一个界面显示两个ListView的话,我们也是需要些两个BaseAdapter