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

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

需求效果图

方案A

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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
           
           
//
// 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

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

方案B

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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
           
           
//
// 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

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

方案C

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

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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
           
           
//
// 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

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

方案D

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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
           
           
//
// 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

结语

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

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

四个方案运行效果图:

运行结果图

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值