使用Core Graphics绘画一个山寨微信icon

之前文章格式有问题,更新一下,原文在这

先看最终效果:



绘画这个纯属周末雨天无聊,这里使用的都是Core Graphics上很基本的几个方法,对新手(我也是新手)来说还是有帮助的。下面说下这个绘制过程。

  1. 设置背景色
  2. 绘制绿色椭圆
  3. 绘制绿色三角形
  4. 绘制眼睛
  5. 绘制白色椭圆
  6. 绘制白色三角形
  7. 绘制眼睛
  8. 就这么简单

那么,省去创建project的步骤之后我们要做的就是自定义个UIView子类,我们就将它命名为WechatView,然后在storyboard上拖动一个UIView到视图控制器上。

接着,为了在绘制过程中能不需要每次都编译,我们可以使用iOS7之后的一个新特性 IB_DESIGNABLE 来时时看到我们的绘画过程。说到 IB_DESIGNABLE 就自然的会联想到 IBInspectable,先浅陋的介绍一下这两个的作用。首先是 IB_DESIGNABLE,如刚才所说,它可以让我们时时的看到我们的绘制过程,大大的省去了我们编译的时间,是iOS7上一个超赞的新特性。然后是 IBInspectable,它是添加在属性上的,在属性上添加上它我们就可以在Xcode的右边的Show the Attributes inspector上看到,并可以自定义这些值(当然,并不是所有的类习惯都支持的,具体有哪些,需要的时候试试便知哈)。
回到正题,现在我们只需要将 IB_DESIGNABLE 添加到WechatView 的头文件上即可(当然,你想添加到.m文件上编译器也不反对)。然后,在这里就添加一个用处不大的属性bkColor作为背景颜色,如下代码:

IB_DESIGNABLE
@interface WechatView : UIView
@property(nonatomic, assign)IBInspectable UIColor *bkColor;
@end

这样,我们就可以在点击storyboard后,选择Show the Identity inspector上看到下图:

Show the Attributes inspector上看到下图:

现在改变一下bkColor,将它改为灰色。

改后当然是看不到视图改变的,我们得真正的为 backgoundColor 这个属性赋值;或者使用Core Graphices来填充背景。方法一中如果想时时看到改变效果,我们可以添加如下代码:

- (void)prepareForInterfaceBuilder {
  [super prepareForInterfaceBuilder];
  self.backgroundColor = self.bkColor;
}

代码刚上,效果即可见哈,爽


prepareForInterfaceBuilder 这个方法是iOS8之后才有的,它就是提供给我们试试查看效果的,但是它只是在当你的view准备被绘制在Interface Builder时被执行,换句话说就是程序运行时它是不被调用的。==command+r==运行下程序我们就可以发现,这是的view背景并不是刚才所看到的灰色。说明prepareForInterfaceBuilder确实没有被调用。(我们可以通过这个方法,省去编译时间来看我们的绘制效果,然后再将代码移到我们所要的位置)。这个方法就说到这。

填充背景

还有方法二,也是接下来我们要用到的方法:使用Core Graphices来填充背景。将前面的代码注释掉,然后添加如下代码:

- (void)drawRect:(CGRect)rect {
  CGContextRef context =UIGraphicsGetCurrentContext();
  /*0. 填充背景*/
  //将当前context的颜色填充为bkColor
  [self.bkColor setFill];
  //填充颜色到当前的context上,大小为rect
  CGContextFillRect(context, rect);
}

同样,可以看到WechatView的背景变成了灰色。这样就算开始了第0步绘画。

绘制绿色椭圆

第1步是开始绘制绿色椭圆,在以上的代码基础上继续添加如下代码:

//绘制的最小宽度
CGFloat minWidth = MAX(160, rect.size.width);
//定义绿色椭圆位置和大小
CGFloat greenX = 10;
CGFloat greenY = 10;
CGFloat greenCircleWidth = minWidth/1.5;
CGFloat greenCircleHeight = 21.0/24 * greenCircleWidth;
CGFloat gcW = greenCircleWidth;
CGFloat gcH = greenCircleHeight;
//1. 绘制绿色椭圆
UIColor *greenColor = RGB(125, 225, 73, 1);
//将当前context的颜色填充为greenColor
[greenColor setFill];
//绘制并填充椭圆
CGContextFillEllipseInRect(context, CGRectMake(greenX, greenY, greenCircleWidth, greenCircleHeight));

完成了第1步,效果见图:


绘制绿色三角形

第2步:绘制绿色三角形。通过观察微信的icon,计算,微调,我们可以添加以下的代码来绘制这个三角形:

//2. 画三角形
//椭圆左边焦点
CGFloat greenCircleFocusLeft = gcW/2-sqrt(pow(gcW/2, 2)-pow(gcH/2, 2));
//椭圆右边焦点
CGFloat greenCircleFocusRight = gcW/2+sqrt(pow(gcW/2, 2)-pow(gcH/2, 2));
CGFloat gcFL = greenCircleFocusLeft;
CGFloat gcFR = greenCircleFocusRight;
//眼睛大小
CGFloat eyesWidth1 = gcW/7.5;
//2. 画三角形
CGPoint points[] = {
CGPointMake(gcFL-eyesWidth1, greenY + gcH + (gcFL-eyesWidth1)/12-10),
CGPointMake(greenX+gcW/2+40, 1.2*(greenX+gcW/2) + greenY),
CGPointMake(gcFL+10, 1.8*gcFL + greenY)};
CGContextAddLines(context, points, 3);
CGContextClosePath(context);
//CGContextStrokePath(context);
CGContextFillPath(context);

三角形的三个点的计算花了我不少时间,但是这不是重点,重点是其实很简单的问题我却傻X的花了很长时间哈,而且最终还只是凑合的,不是最优解。取消CGContextStrokePath(context);这句的注释,我们可以看到下图:

注释掉后即可看到:

绘制眼睛

第3步:绘制眼睛。眼睛的位置取的是椭圆焦点的位置,而且绘制后看起来还挺对的。眼睛无非是两个黑色的圆形。如下:

//3. 画眼睛(圆)
[RGB(45, 49, 32, 1) setFill];
CGContextFillEllipseInRect(context, CGRectMake(greenX + gcFL, greenY+gcH/4, eyesWidth1, eyesWidth1));
CGContextFillEllipseInRect(context, CGRectMake(greenX + gcFR-eyesWidth1, greenY+gcH/4, eyesWidth1, eyesWidth1));

然后就是这样了:

绘制白色icon

第4,5,6步跟1,2,3步基本是一样的,只是位置不同而已。

//4. 画白色椭圆
CGFloat wcX = greenX + gcW / 2;
CGFloat wcY = greenY + gcH / 2;
CGFloat wcW = gcW * 0.8;
CGFloat wcH = gcH * 0.8;
CGFloat whiteCircleFocusLeft = wcW/2-sqrt(pow(wcW/2, 2)-pow(wcH/2, 2));
CGFloat whiteCircleFocusRight = wcW/2+sqrt(pow(wcW/2, 2)-pow(wcH/2, 2));
CGFloat wcFL = whiteCircleFocusLeft;
CGFloat wcFR = whiteCircleFocusRight;
CGFloat eyesWidth2 = wcW/7.5;
UIColor *whiteColor = RGB(251, 251, 251, 1);
[whiteColor setFill];
CGContextFillEllipseInRect(context, CGRectMake(wcX, wcY, wcW, wcH));
//5. 画白色三角形
CGPoint whiteTrianglePoints[] = {
CGPointMake(wcFR+eyesWidth2/4+wcX, wcY + wcH + (wcFL-eyesWidth2/4)/4),
CGPointMake(wcX+wcW/2, 0.9*(wcX+wcW/2)),
CGPointMake(wcFR+eyesWidth2, 0.9*wcFR + wcY)};
CGContextAddLines(context, whiteTrianglePoints, 3);
CGContextClosePath(context);
CGContextFillPath(context);
//6. 画眼睛(圆)
[RGB(60, 64, 49, 1) setFill];
CGContextFillEllipseInRect(context, CGRectMake(wcX + wcFL, wcY + wcH/4, eyesWidth2, eyesWidth2));
CGContextFillEllipseInRect(context, CGRectMake(wcX + wcFR - eyesWidth2, wcY + wcH/4, eyesWidth2, eyesWidth2));

OK,现在就长这样了,咋一看还是挺像的哈。


添加阴影

下面我们再添加一些细节上的东西。比如我们发现少了阴影,还有微信的icon上的三角形是圆角的。首先是阴影:在CGFloat minWidth = MAX(160, rect.size.width);这句之上加上下面的代码:

//阴影
CGContextSetShadowWithColor(context, CGSizeMake(-0.5, 0.5), 6, [UIColor blackColor].CGColor);

阴影是加上了,但是并不是我们想要的效果。怎样才能让它只是在边缘上加阴影呢?也很简单,继续在刚才添加的语句下添加:

CGContextBeginTransparencyLayer(context, nil);

搞定,感觉还行哈,不过眼睛的阴影好像也没了。试试用同样的方法看能不能解决:在第3步画眼睛之前添加:

CGContextEndTransparencyLayer(context);
//为绿色椭圆的眼睛添加阴影
CGContextSetShadowWithColor(context, CGSizeMake(-0.5, 0.5), 2, [UIColor blackColor].CGColor);

然后在第3步画完眼睛之后添加:

CGContextBeginTransparencyLayer(context, nil);

再然后在第6步画眼睛之前添加上:

//为白色椭圆的眼睛添加阴影
CGContextSetShadowWithColor(context, CGSizeMake(-0.5, 0.5), 2, [UIColor blackColor].CGColor);

这样说有点乱,注意到了:CGContextBeginTransparencyLayer(context, nil)CGContextEndTransparencyLayer(context) 是成对出现的,从函数名称可以知道它们的作用分别是开始一个透明的layer和结束一个透明的layer。而夹在它们中间的绘图操作在指定的context上被合成到一个完全透明的背景(在context中作为一个分离的目标缓冲区)。这个操作会保持context原有的裁剪区域。调用了Begin之后除了1.全局的透明度被设置为1;2.阴影被屏蔽外,其他的都不变。这样解释后,上面的代码应该可以理解了。现在我们的icon看起来是这样的:

圆角三角形

最后,解决下圆角三角形的问题:回到第2步,将CGContextFillPath(context);注释,并添加以下代码:

CGContextSetLineJoin(context, kCGLineJoinRound);
CGContextSetLineWidth(context, 4);
CGContextSetStrokeColorWithColor(context, greenColor.CGColor);
CGContextDrawPath(context, kCGPathFillStroke);

然后同理在第5步画白色三角形上注释CGContextFillPath(context);,并添加以下代码:

CGContextSetLineJoin(context, kCGLineJoinRound);
CGContextSetLineWidth(context, 3);
CGContextSetStrokeColorWithColor(context, whiteColor.CGColor);
CGContextDrawPath(context, kCGPathFillStroke);

好了,废话说完了,也算是完成了,最终效果:

实际上,微信的icon的颜色还是一个渐变的,在这里就不继续研究了,要实现渐变可以用*CGContextDrawLinearGradient(CGContextRef context, CGGradientRef gradient, CGPoint startPoint, CGPoint endPoint, CGGradientDrawingOptions options)**CGContextDrawRadialGradient(CGContextRef context, CGGradientRef gradient, CGPoint startCenter, CGFloat startRadius, CGPoint endCenter, CGFloat endRadius, CGGradientDrawingOptions options)*这两个函数。
bug 修复
command+r 编译+运行以下(现在才需要编译,这也太好了吧),然后发现成功的crash了。

什么原因?好吧,我把属性定义成 assign 了(低级错误),改成strong ,问题解决。

结束语

以上就是我绘制的整个编码过程,绘制微信icon实际上就用了core graphics的几个基本功能,希望能对初学者有所帮助,说错的地方也希望能得到指点哈~~
源码地址:https://github.com/linshaolie/blog/tree/master/DrawWechatIcon

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 202,607评论 5 476
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,047评论 2 379
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 149,496评论 0 335
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,405评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,400评论 5 364
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,479评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,883评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,535评论 0 256
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,743评论 1 295
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,544评论 2 319
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,612评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,309评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,881评论 3 306
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,891评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,136评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,783评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,316评论 2 342

推荐阅读更多精彩内容