iOS 绘画学习(5)


阴影

为了给绘制添加阴影,可以在绘制之前,给上下文一个阴影值。阴影的位置用CGSize表示,CGSize里的两个正数表示向下和向右方向。这个模糊值是一个可以无穷大的正数。苹果没有解析这个拉伸是如何工作的,不过经验显示,12是一个刚好的值,99就会显得太锐利。下面是我们在绘制之前,添加的代码:

con = UIGraphicsGetCurrentContext();
CGContextSetShadow(con, CGSizeMake(7, 7), 12);
[self.arrow drawAtPoint:CGPointMake(0,0)]; // ... and so on

但是这样效果好像不是很明显,而且我们每次绘制箭头都会让阴影重叠。我们希望有一个阴影作用于所有的箭头,为了达到这个目的,我们可以使用一个透明图层,基本上算是一个子上下文:

CGContextRef con = UIGraphicsGetCurrentContext();
CGContextSetShadow(con, CGSizeMake(7, 7), 12);
CGContextBeginTransparencyLayer(con, nil);
[self.arrow drawAtPoint:CGPointMake(0,0)];
for (int i=0; i<3; i++) {
    CGContextTranslateCTM(con, 20, 100);
    CGContextRotateCTM(con, 30 * M_PI/180.0);
    CGContextTranslateCTM(con, -20, -100);
    [self.arrow drawAtPoint:CGPointMake(0,0)];
}
CGContextEndTransparencyLayer(con);

擦除

函数CGContextClearRect 会清除矩形区域里所有已存在的绘制。结合剪切,它能清除任何形状的区域。

CGContextClearRect 的行为根据上下文是否是透明而有所不同。在绘制图像时,如何图像上下文是透明的--UIGraphicsBeginImageContextWithOptions 第二个参数是NO --CGContextClearRect 会清除为透明的,否则就会清除为黑色。

当在一个视图view中直接绘制时(例如drawRect: 或者 drawLayer:inContext:),如果视图的背景颜色是nil或者是一种含有哪怕一点透明的颜色,CGContextClearRect的结果都是透明的。如果背景颜色是完全不透明的,那么CGContextClearRect的结果将是黑色。这是因为视图背景颜色决定了这个视图上下文是透明的,还是不透明的。

上图的绘制是完全一样的,除了背景颜色一个是完全不透明蓝色,一个是不透明度是0.99的蓝色。我们用CGContextClearRect 清除掉左上角的正方形。 这个视图的drawRect:看上去像下面那样:

CGContextRef con = UIGraphicsGetCurrentContext();
CGContextSetFillColorWithColor(con, [UIColor blueColor].CGColor);
CGContextFillRect(con, rect);
CGContextClearRect(con, CGRectMake(0,0,30,30));

点和像素

点是描述一个在x轴和y轴方向上无维的位置。当我们在图形上下文中绘制时,指定了在哪里绘制,这个位置是与设备分辨率无关的。因为Core Graphics使用CTM和抗锯齿来对应你的绘制在实际的设备输出上。

但是像素是存在的。一个像素是真实世界中一个实际的,数字化的显示单元。。我们有时候会看到有人建议把线的位置放在0.5的位置上,来把这条线的像素居中,这个建议可能会有用,但是把情况简单化了。UIView有一个contentScaleFactor参数,这个值不是1.0,就是2.0。你可以通过这个参数把像素转换成点。考虑到我们大多数情况是填充一个矩形,而不是描边一个路径。所以下面的代码可以在任意设备上都绘制一条完美的1像素宽的垂直线。

CGContextFillRect(con, CGRectMake(100,0,1.0/self.contentScaleFactor,100));

内容模式

一个视图会在它本身还绘制一些其他的东西,和仅仅有背景颜色和子视图不同,它是有内容的。这意味着当重新调整大小时它的 contentMode 属性变得非常重要。正如我前面说到的,绘画系统总是避免要求一个视图重新绘制自己,而是使用之前绘画操作缓存的结果(图像后备存储)。所以,如果一个视图被重新调整大小,系统可能只是简单的根据你的contentMode属性来拉伸,收缩或者重新定位缓存中的绘画。

下面我沿用之前的代码绘制箭头,子类化一个view,然后在drawRect:中绘制箭头,只是在延时一段时间后把这个view 的高度变成原来的两倍,得到下面的结果:

void (^resize) (void) = ^{
    CGRect f = mv.bounds; // mv is the MyView instance
    f.size.height *= 2;
    mv.bounds = f;
};
dispatch_time_t popTime = dispatch_time(DISPATCH_TIME_NOW, NSEC_PER_SEC);
dispatch_after(popTime, dispatch_get_main_queue(), resize);

但是很快,drawRect:这个方法还是会调用,这样我们的绘制就会被重新刷新。由于我们的代码中并没有指定箭头的高度与视图的bounds 的高度有关,所以箭头会变成原来的大小,而不是被拉伸的箭头。

由于我们的drawRect:方法中指定的箭头的大小和位置与视图的bounds的原点有关,就是左上角,因此我们可以设置视图的contentMode属性为UIViewContentModeTopLeft.还有,我们可以设置contentMode属性为UIViewContentModeRedraw,这样自动拉伸和收缩就是关闭,当视图被重新调整大小,它的setNeedsDisplay 方法会被调用,接着触发drawRect:来重新绘制内容。

posted @ 2014-07-24 14:35  剑尖  阅读(978)  评论(0编辑  收藏  举报