欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

深入理解UIScrollView

程序员文章站 2024-03-23 09:01:28
...

前言

这篇文章是关于UIScrollView的工作原理,不涉及基础的语法和功能实现,文章主要是我的总结和实战,主要介绍了BoundsFrame属性的区别,只有理解了这两个属性才能更好的理解UIScrollView的工作方式,文章为原创,转载请注明出处

参考文章 ObjC期刊 和 Bounds和Frame的区别


  • 了解UIScrollView是怎么工作之前,需要先了解UIView,特别是视图渲染的两步

光栅化和组合

  • 光栅化:光栅化是产生一组绘图指令并且生成一张图片。比如绘制一个圆角矩形,带图片,文字居中显示的UIButton。这些图片没有马上被绘制到屏幕上,而是被自己的视图留到下一个步骤使用
  • 组合:每个图片都会产生一个光栅化图片,这些图片被一个接一个的绘制,并产生一个屏幕大小的图片
  • 图片被一个接一个的绘制到父视图上面,视图最顶层的是UIWindow,它组合好的便是我们看到的
  • 现在回想一下每个视图都有一个BoundsFrame,视图的Frame和Bounds的大小通常是一样的(但也可以不一样),但它们的origin通常是不同的,弄懂这两个是理解UIScrollView的关键,在光栅化的过程中,视图不关心自己的Frame或在图层层级中的位置,它只关心绘制自己的Content,这个绘制被发生在每个视图-drawRect方法中
  • 在-drawRect:调用前,视图会创建一个空的图片来绘制Content。这个图片的坐标系统是视图的Bounds,几乎每个视图的Bounds的Origin都是{0, 0},因此当在左上角绘制时会发生在{0, 0},在右下角绘制时会出现在{width, height},当超出了光栅化的部分将会被丢弃。

但事实上显示时只要不设置属性clipsToBounds属性为true,绘制在外面的视图还是会被显示出来

  • 在组合的过程中,每个视图将自己光栅化图片组合到自己的父视图的光栅化的图片上。Frame决定了自己在父视图的位置,Frame的Origin属性决定了视图的光栅化图片对于父坐标的偏移量。因为Bounds和Frame的大小是一样的,所以光栅化的图片是像素对齐的,这保证了光栅化的图片不会被缩小或拉伸

CompositedPositionX = View.frame.origin.x - SuperView.bounds.origin.x
CompositedPositionY = View.frame.origin.y - SuperView.bounds.origin.y

大多数视图的Bounds的Origin都是{0, 0}

CompositedPositionX = View.frame.origin.x
CompositedPositionY = View.frame.origin.y

  • 这样做是有道理的,当改变视图的Frame.origin时它会相对父坐标而移动,在父坐标外的视图会在光栅化过程时被截取,但技术上讲还是会显示出来,但那是光栅化过程之后的事了。

关于Bounds和Frame的区别

 CGRect rectForFirstView = CGRectMake(50.0, 100.0, 200.0, 200.0);
 UIView *firstView = [[UIView alloc]initWithFrame:rectForFirstView];
 firstView.backgroundColor = [UIColor blueColor];
 // [firstView setBounds:CGRectMake(20.0, 80.0, 200.0, 200.0)];
    
 CGRect rectForSecondView = CGRectMake(100.0, 100.0, 100.0, 100.0);
 UIView *secondView = [[UIView alloc]initWithFrame:rectForSecondView];
 secondView.backgroundColor = [UIColor yellowColor];
    
 [self.view addSubview:firstView];
 [firstView addSubview:secondView];

深入理解UIScrollView

  • 首先注释掉改变第一个视图Bounds的代码,将第二个视图作为第一个视图的子视图,第一个视图作为View的子视图,这时两个视图的位置如上

此时添加如下代码块

 [firstView setBounds:CGRectMake(20.0, 80.0, 200.0, 200.0)];

深入理解UIScrollView

  • 视图此时相对父视图的位置是这样的

父视图的Frame.origin并没有改变,但是子视图的位置却被改变了,改变父视图Bounds.origin改变后,整个视图的变化是这样的

  • 再增加如下代码
 [firstView setBounds:CGRectMake(0.0, 0.0, 300.0, 300.0)];
 NSLog(@"firstView.Bounds:%@", NSStringFromCGRect(firstView.frame));

控制台打印消息如下
firstView.Bounds: {{0, 50}, {300, 300}}

  • 可见当Bounds的Size改变的时候,整个Frame变成和Bounds同样的大小,并且改变了原点坐标

changedFrame.origin.x = (frame.origin.x - changedBounds.size.width) / 2.0
changedFrame.origin.y = (frame.origin.y - changedBounds.size.height) / 2.0


UIScrollView的Content Offset

现在所讲的跟UIScrollView有什么关系呢,考虑实现一种滚动,有一个拖动Frame不断改变的视图。这达到了同样的效果,当拖动在右边,那么改变的是Frame.origin.x,你看,这就是UIScrollView

  • UIScrollView中有很多代表性的视图,当实现平移这个功能的时候,用户移动手指,你需要时刻改变每个视图的Frames。当组合光栅化到了它父视图的什么位置,记住这个公式

CompositedPosition.x = View.frame.origin.x - SuperView.bounds.origin.x
CompositedPosition.y = View.frame.origin.y - SuperView.bounds.origin.y

  • Bounds.origin.x的值总是0,但当它不为0呢(如上文),当改变父视图的Bounds为{-30, -30}时,子视图也会随之移动。

这是UIScrollView的工作原理,当设置它的ContentOffset属性时,它实际上改变了Bounds.origin属性,实际上,ContentOffset甚至是不存在的,它的代码实现大致如下

-(void)setContentOffset:(CGPoint)offset{
	CGRect Bounds = [self bounds];
	bounds.origin = offset;
	[self setBounds: bounds];
}

当改变UIScrollView的Bounds的Origin到一定大小时,超过组合范围内的视图会消失

相关标签: UIKit ios