iOS图形几何学基础

之前我们介绍了CALayer的一些重要的属性,在本章中,我将继续介绍关于CALayer的一些属性的使用,在本章结束之后,我们将来实现一些动画效果

布局

UIView有三个比较重要的布局属性:

  • frame (代表了图层的外部坐标,也就是相对于父视图的位置)
  • bounds (代表了内部坐标,其参照的是自己,左上角的左边永远是{0,0})
  • center (代表了相对于父图层的anchorPoint所在的位置,这个我们后面会介绍,现在将他想象成中心点就行)

而在CALayr中也有三个与之相对的属性:

  • frame
  • bounds
  • position

他们一一对应

从上面这张图中我们可以看出,frame的左上角坐标,是相对于父图层的位置

注意
我们通常修改视图的frame、bounds、center其实都只是存取方法,实质上修改的还是位于视图下的CALayer的frame、bounds和center

在我们日常的做图层变换中,经常做一些旋转或者缩放,这时候,frame和bounds的就不再一致了

在这幅图中,我们将图层做了一个旋转,我们可以看到,frame是整个图层占据的外层的那个大矩形的区域,而bounds是图层的内部坐标,所以bounds依然没有改变

锚点

之前我们提到过anchorPoint,视图的center和图层的position属性,指定的anchorPoint相对于父图层的位置。图层的anchorPoint通过position来控制它的frame位置,其实就是通过anchorPoint来移动图层

默认anchorPoint位于图层的中点,但是anchorPoint是属性并没有被UIView暴露出来,这也是视图的属性被叫做center的原因。

接下来,我要讲的都是跟图层相关,需要抛开视图的概念,不然,就比较难以理解了

因为图层的anchorPoint可以被移动,我们之前说过图层的anchorPoint通过position来控制它的frame的位置,那么如果我们将图层的anchorPoint置于图层的frame的左上角,那么图层将会怎么移动呢,图层将会向右下角移动

这里需要大家好好理解一下,position属性为什么没有变,而frame却改变了

anchorPoint的坐标 也是使用的相对坐标来表示,当然也可以指定他是小于0或者大于1,来将图层内容放在图层范围之外

那我们在想,我们可以通过图层的frame随意改变图层的位置,为什么还要通过anchorPoint来改变图层的位置,而且这个这么难以理解

我们来举一个例子:

我们需要模拟一个闹钟,闹钟的钟面由四张图片组成,为了简单,我们还是使用传统的四个UIImgaeView来加载,当然你也可以使用contents属性

我们使用NSTimer来更新闹钟,使用视图的transform属性来旋转钟表

1
2
3
4
5
6
7
8
9
- (void)viewDidLoad
{
[super viewDidLoad];
//start timer
self.timer = [NSTimer scheduledTimerWithTimeInterval:1.0 target:self selector:@selector(tick) userInfo:nil repeats:YES];

//set initial hand positions
[self tick];
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
//convert time to hours, minutes 

NSCalendar *calendar = [[NSCalendar alloc] initWithCalendarIdentifier:NSGregorianCalendar];
NSUInteger units = NSHourCalendarUnit | NSMinuteCalendarUnit | NSSecondCalendarUnit;
NSDateComponents *components = [calendar components:units fromDate:[NSDate date]];
CGFloat hoursAngle = (components.hour / 12.0) * M_PI * 2.0;
//calculate hour hand angle //calculate minute hand angle
CGFloat minsAngle = (components.minute / 60.0) * M_PI * 2.0;
//calculate second hand angle
CGFloat secsAngle = (components.second / 60.0) * M_PI * 2.0;
//rotate hands
self.hourHand.transform = CGAffineTransformMakeRotation(hoursAngle);
self.minuteHand.transform = CGAffineTransformMakeRotation(minsAngle);
self.secondHand.transform = CGAffineTransformMakeRotation(secsAngle);
}

这样看起来有点怪,我们可以看到,明显的时针分针的转动,都是通过中心位置转动,为什么呢?
因为默认的anchorPoint在中心位置,图片的旋转,都是围绕anchorPoint转动的,这时候我们就需要移动anchorPoint的位置了

1
2
3
self.secondHand.layer.anchorPoint = CGPointMake(0.5f, 0.9f); 
self.minuteHand.layer.anchorPoint = CGPointMake(0.5f, 0.9f);
self.hourHand.layer.anchorPoint = CGPointMake(0.5f, 0.9f);

将每个指针的anchorPoint移动到需要他围绕旋转的位置

圆角

圆角是iOS的一个标志性美学效果,我们想要实现圆角效果其实很简单

CALayer有一个属性叫做conrnerRadius控制着图层的角的曲率,默认情况下,他为0表示
矩形,但是我们将他设置成任意的值

我们在当前的VC中添加一个UIImageView,并设置他的cornerRadius为20个点,我们来看看效果

1
2
3
4
UIImageView *imagView = [[UIImageView alloc] initWithImage:[UIImage imageNamed:@"timg"]];
imagView.frame = CGRectMake(50, 100, 150, 200);
imagView.layer.cornerRadius = 20;
[self.view addSubview:imagView];

我们看到这个视图并没有显示出圆角,这是什么原因?

这是因为

默认情况下,这个曲率值只影响背景颜色而不影响背景图片或是子图层

不过,如果我们将masksToBounds设置为yes,那么图层里面的所有东西都会被截取

1
imagView.layer.masksToBounds = YES;

这样效果就出来了

再来试一个例子,我们在这个图层的左上角加上一个超出了图片边界的一个view

1
2
3
4
5
6
7
8
9
UIImageView *imagView = [[UIImageView alloc] initWithImage:[UIImage imageNamed:@"timg"]];
imagView.frame = CGRectMake(50, 100, 150, 200);
// imagView.layer.masksToBounds = YES;
imagView.layer.cornerRadius = 20;
[self.view addSubview:imagView];

UIView *view = [[UIView alloc] initWithFrame:CGRectMake(-10, -10, 20, 20)];
view.backgroundColor = [UIColor redColor];
[imagView addSubview:view];

如果我们未将图层的masksToBounds设置为yes,那么我们得到的效果是这样的

我们之前讲过,不管是图层还是视图,都会默认渲染超出父图层的视图或者图层

如果我们将masksToBounds设置成了yes,按照我们之前讲的,这个红色的区域应该会被裁剪,那我们来看看效果

这样就完全将图层裁剪

图层边框

CALayer还有两个非常有用的属性,borderWidth和borderColor,这两个可以用来设置图层边的样式

我们设置一下图片的边框

1
2
3
4
5
6
7
8
9
10
11
12
UIImageView *imagView = [[UIImageView alloc] initWithImage:[UIImage imageNamed:@"timg"]];
imagView.frame = CGRectMake(50, 100, 150, 200);
// imagView.layer.masksToBounds = YES;
imagView.layer.cornerRadius = 20;
[self.view addSubview:imagView];

UIView *view = [[UIView alloc] initWithFrame:CGRectMake(-10, -10, 20, 20)];
view.backgroundColor = [UIColor redColor];
[imagView addSubview:view];

imagView.layer.borderColor = [UIColor greenColor].CGColor;
imagView.layer.borderWidth = 4;

大家不要奇怪,为什么我们的边框会是这样的,那是因为图层的边框是跟着边界走的,和图层的内容并不相关,我们之前设置了图层的圆角,那么图层的边框就已经成为了有圆角的,但是图层内容,因为我们没有设置masksToBounds,所以图层的内容没有被裁剪

设置一下masksToBounds就可以了

所以设置圆角的时候不要忘记了masksToBounds属性

阴影

接下来,我们来介绍阴影,我们可以通过shadowOpacity来设置图层的阴影,shadowOpacity是一个必须在0.0(不可见)和1.0(完全不透明)之间的浮点数

如果我们要改变阴影的表现,有三个属性,可以通过他们来改变阴影的表现

  • shadowColor
  • shadowOffset
  • shadowRadius
1
2
3
4
5
6
7
8
UIImageView *imagView = [[UIImageView alloc] initWithImage:[UIImage imageNamed:@"1"]];
imagView.frame = CGRectMake(50, 100, 150, 200);
[self.view addSubview:imagView];

imagView.layer.shadowColor = [UIColor redColor].CGColor;
imagView.layer.shadowOffset = CGSizeMake(10, 20);
imagView.layer.shadowRadius = 10;
imagView.layer.shadowOpacity = 0.5;

shadowColor肯定是控制阴影颜色的,这个不用讲
shadowOffset控制着阴影的方向和距离,他是一个CGSize,宽度控制这阴影横向的位移,高度控制着纵向的位移
shadowRadius控制着阴影的模糊度

我们通过阴影可以将我们的视图的立体感增强