使用Quartz绘制带阴影的圆角图

来源:互联网 发布:网络质量 编辑:程序博客网 时间:2024/04/30 21:01

最近在做iOS开发的时候,遇到这样的需求:需要开发一个高度自适应的控件,背景使用图案平铺,控件下方两个角为圆角,控件的下边有5像素宽的阴影。具体如图所示:

需求效果图

方案A

在做这个需求时首先想到是用colorWithPatternImage来平铺图片,然后使用CALayer添加阴影。

123456789101112131415161718192021222324252627282930313233
//
// RoundedCornerImageA.m
// tutugogo
//
// Created by ohsc on 11-12-28.
// Copyright (c) 2011年 __MyCompanyName__. All rights reserved.
//
#import "RoundedCornerImageA.h"
#define kHeight 64
#define kRoundSize 10
@implementation RoundedCornerImageA
- (id)initWithFrame:(CGRect)frame
{
    CGRect aFrame = CGRectMake(frame.origin.x, frame.origin.y, frame.size.width, kHeight);
    self = [super initWithFrame:aFrame];
    if (self) {
        // Initialization code
        self.backgroundColor = [UIColor colorWithPatternImage:[UIImage imageNamed:@"pattern.png"]];
        self.layer.cornerRadius = kRoundSize;
        self.layer.masksToBounds = YES;
        
        [self.layer setShadowColor:[UIColor colorWithRed:0 green:0 blue:0 alpha:0.3].CGColor];
        [self.layer setShadowOffset:CGSizeMake(0, 0)];
        [self.layer setShadowOpacity:1.0];
        [self.layer setShadowRadius:kRoundSize];
    }
    return self;
}
@end
view rawRoundedCornerImageA.mThis Gist brought to you by GitHub.

不过这样做的严重缺陷是阴影被裁切掉了。

方案B

所以考虑使用Quartz的CGContextDrawTiledImage来平铺背景,使用layer制造阴影。

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162
//
// RoundedCornerImageB.m
// tutugogo
//
// Created by ohsc on 11-12-28.
// Copyright (c) 2011年 __MyCompanyName__. All rights reserved.
//
#import "RoundedCornerImageB.h"
#define kHeight 64
#define kRoundSize 10
@implementation RoundedCornerImageB
- (id)initWithFrame:(CGRect)frame
{
    CGRect aFrame = CGRectMake(frame.origin.x, frame.origin.y, frame.size.width, kHeight);
    self = [super initWithFrame:aFrame];
    if (self) {
        // Initialization code
        
        CALayer *bgLayer = [CALayer layer];
        bgLayer.frame = CGRectMake(0, 0, self.layer.frame.size.width, self.layer.frame.size.height);
        bgLayer.delegate = [[RoundedCornerImageBBgLayerHelper alloc] init];
        [bgLayer setNeedsDisplay];
        [self.layer addSublayer:bgLayer];
    }
    return self;
}
@end
@implementation RoundedCornerImageBBgLayerHelper
- (void)drawLayer:(CALayer *)layer inContext:(CGContextRef)ctx
{
    CGFloat minx = CGRectGetMinX(layer.frame) , midx = CGRectGetMidX(layer.frame), maxx = CGRectGetMaxX(layer.frame) ;
    CGFloat miny = CGRectGetMinY(layer.frame) , maxy = CGRectGetMaxY(layer.frame)-5 ;
    
    CGMutablePathRef path = CGPathCreateMutable();
    CGPathMoveToPoint(path, NULL, minx, miny);
    CGPathAddArcToPoint(path, NULL, minx, maxy, midx, maxy, kRoundSize);
    CGPathAddArcToPoint(path, NULL, maxx, maxy, maxx, miny, kRoundSize);
    CGPathAddLineToPoint(path, NULL, maxx, miny);
    CGPathAddLineToPoint(path, NULL, minx, miny);
    CGPathCloseSubpath(path);
    
    CGContextAddPath(ctx, path);
    CGContextClip(ctx);
    
    UIImage *image = [UIImage imageNamed:@"pattern.png"];
    CGContextDrawTiledImage(ctx, CGRectMake(0, 0, image.size.width, image.size.height), image.CGImage);
    
    CGPathRelease(path);
    
    [layer setShadowColor:[UIColor colorWithRed:0 green:0 blue:0 alpha:0.3].CGColor];
    [layer setShadowOffset:CGSizeMake(0, 0)];
    [layer setShadowOpacity:1.0];
    [layer setShadowRadius:kRoundSize];
}
@end
view rawRoundedCornerImageB.mThis Gist brought to you by GitHub.

这样做虽然可以大概的实现需求,不过阴影的宽度不可控制。

方案C

于是我尝试使用Quartz的CGContextSetShadowWithColor来绘制阴影,但是怎么绘制阴影呢?使用CGContextDrawTiledImage平铺背景时会占满指定的区域,这样也有没有了绘制阴影的空间。

在stackoverflow搜索了一圈,有人建议先使用固定色填充圆角区域,再在这个区域平铺图片。

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061
//
// RoundedCornerImageC.m
// tutugogo
//
// Created by ohsc on 11-12-28.
// Copyright (c) 2011年 __MyCompanyName__. All rights reserved.
//
#import "RoundedCornerImageC.h"
#define kHeight 64
#define kRoundSize 10
@implementation RoundedCornerImageC
- (id)initWithFrame:(CGRect)frame
{
    CGRect aFrame = CGRectMake(frame.origin.x, frame.origin.y, frame.size.width, kHeight);
    self = [super initWithFrame:aFrame];
    if (self) {
        // Initialization code
        
        CALayer *bgLayer = [CALayer layer];
        bgLayer.frame = CGRectMake(0, 0, self.layer.frame.size.width, self.layer.frame.size.height);
        bgLayer.delegate = [[RoundedCornerImageCBgLayerHelper alloc] init];
        [bgLayer setNeedsDisplay];
        [self.layer addSublayer:bgLayer];
    }
    return self;
}
@end
@implementation RoundedCornerImageCBgLayerHelper
- (void)drawLayer:(CALayer *)layer inContext:(CGContextRef)ctx
{
    CGFloat minx = CGRectGetMinX(layer.frame) , midx = CGRectGetMidX(layer.frame), maxx = CGRectGetMaxX(layer.frame) ;
    CGFloat miny = CGRectGetMinY(layer.frame) , maxy = CGRectGetMaxY(layer.frame)-5 ;
    
    CGMutablePathRef path = CGPathCreateMutable();
    CGPathMoveToPoint(path, NULL, minx, miny);
    CGPathAddArcToPoint(path, NULL, minx, maxy, midx, maxy, kRoundSize);
    CGPathAddArcToPoint(path, NULL, maxx, maxy, maxx, miny, kRoundSize);
    CGPathAddLineToPoint(path, NULL, maxx, miny);
    CGPathAddLineToPoint(path, NULL, minx, miny);
    CGPathCloseSubpath(path);
    
    CGContextSetShadowWithColor(ctx, CGSizeMake(0, 0), 5, [UIColor colorWithRed:0/255.0 green:0/255.0 blue:0/255.0 alpha:0.4].CGColor);
    CGContextAddPath(ctx, path);
    CGContextFillPath(ctx);
    
    CGContextAddPath(ctx, path);
    CGContextClip(ctx);
    UIImage *image = [UIImage imageNamed:@"pattern.png"];
    CGContextDrawTiledImage(ctx, CGRectMake(0, 0, image.size.width, image.size.height), image.CGImage);
    
    CGPathRelease(path);
}
@end
view rawRoundedCornerImageC.mThis Gist brought to you by GitHub.

这个方案比上个方案好了很多,不过细心的人可能会发现圆角的阴影有深色的锯齿,不够协调。原因是阴影是根据固定色生成的,在处理边界过度时是从固定色过度来的。

方案D

那有没有办法让阴影根据图片来生成呢?我想到了CGContextDrawImage方法。先在一个特殊的context生成平铺图,然后在当前的context使用CGContextDrawImage来绘制图片,这样绘制图片的同时会产生相应的阴影。

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667
//
// RoundedCornerImageD.m
// tutugogo
//
// Created by ohsc on 11-12-28.
// Copyright (c) 2011年 __MyCompanyName__. All rights reserved.
//
#import "RoundedCornerImageD.h"
#define kHeight 64
#define kRoundSize 10
@implementation RoundedCornerImageD
- (id)initWithFrame:(CGRect)frame
{
    CGRect aFrame = CGRectMake(frame.origin.x, frame.origin.y, frame.size.width, kHeight);
    self = [super initWithFrame:aFrame];
    if (self) {
        // Initialization code
        
        CALayer *bgLayer = [CALayer layer];
        bgLayer.frame = CGRectMake(0, 0, self.layer.frame.size.width, self.layer.frame.size.height);
        bgLayer.delegate = [[RoundedCornerImageDBgLayerHelper alloc] init];
        [bgLayer setNeedsDisplay];
        [self.layer addSublayer:bgLayer];
    }
    return self;
}
@end
@implementation RoundedCornerImageDBgLayerHelper
- (void)drawLayer:(CALayer *)layer inContext:(CGContextRef)ctx
{
    CGFloat minx = CGRectGetMinX(layer.frame) , midx = CGRectGetMidX(layer.frame), maxx = CGRectGetMaxX(layer.frame) ;
    CGFloat miny = CGRectGetMinY(layer.frame) , maxy = CGRectGetMaxY(layer.frame)-5 ;
    
    CGMutablePathRef path = CGPathCreateMutable();
    CGPathMoveToPoint(path, NULL, minx, miny);
    CGPathAddArcToPoint(path, NULL, minx, maxy, midx, maxy, kRoundSize);
    CGPathAddArcToPoint(path, NULL, maxx, maxy, maxx, miny, kRoundSize);
    CGPathAddLineToPoint(path, NULL, maxx, miny);
    CGPathAddLineToPoint(path, NULL, minx, miny);
    CGPathCloseSubpath(path);
    
    CGContextRef imgCtx = CGBitmapContextCreate(NULL, layer.frame.size.width, layer.frame.size.height, 8, 0, CGColorSpaceCreateDeviceRGB(), kCGImageAlphaPremultipliedLast);
    CGContextAddPath(imgCtx, path);
    CGContextClip(imgCtx);
    UIImage *image = [UIImage imageNamed:@"pattern.png"];
    CGContextDrawTiledImage(imgCtx, CGRectMake(0, 0, image.size.width, image.size.height), image.CGImage);
    
    CGImageRef img = CGBitmapContextCreateImage(imgCtx);
    
    CGContextSetShadowWithColor(ctx, CGSizeMake(0, 0), 5, [UIColor colorWithRed:0/255.0 green:0/255.0 blue:0/255.0 alpha:0.4].CGColor);
    CGContextDrawImage(ctx, layer.frame, img);
    
    CGPathRelease(path);
    CGContextRelease(imgCtx);
    CGImageRelease(img);
}
@end
view rawRoundedCornerImageD.mThis Gist brought to you by GitHub.

结语

作为初学者,对Quartz也只是一知半解。尝试过程中发现绘制的时候一定要注意顺序,否则会出现各种奇怪的问题。Quartz是个强大东西,要好好利用还需跟多的尝试。

另附上本文的源代码:https://github.com/ohsc/CodeBox-of-Cocoa/tree/master/RoundedCornerImageWithShadow

四个方案运行效果图:

运行结果图

原创粉丝点击