我们知道
QQ 里面的联系人头像是圆形的。当我们发起多人聊天时,会自动生成一个讨论组。这个讨论组的头像图标是由组内人员头像自动组合生成的。比如:组内有两个人,就用两个人的头像组合成讨论组的头像图标。有三个就是用三个头像来组成,以此类推。最多5个。
本文演示如何实现这种组合头像的功能。
1,讨论组头像组件效果图
(1)根据初始化传入的图片数组中图片数量的不同(超过
5 张图片的话也只显示前
5 个),组件会自动设置内部图片的尺寸和位置。
(2)为了让显示效果更好,多张图片组合时不是简单地将每张图片裁剪成圆形。而会根据位置,向内凹陷一部分。
(3)组件默认背景是透明的,这里为了更好的演示将背景设置成灰色。
2,组件代码
(1)
GroupIconCell.swift(组件内部的圆形图标)
- 初始化参数中 isClip 表示图标是否要裁剪。
- 如果不裁剪,则生成圆形的图标。
- 如果裁剪,则根据 degrees 角度参数,在对应的位置掏一个弧形的缺口。(其实代码中的剪切路径是由两段圆弧组成。外面的大圆弧和内陷的圆弧)
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
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
|
import
UIKit
//组合图标内部小图标
class
GroupIconCell
:
CALayer
{
//使用的图片
var
image:
UIImage
!
//裁剪缺口位于圆上的位置(0~360度,0为y轴向下位置,顺时针旋转)
var
degrees:
Double
!
//是否裁剪
var
isClip:
Bool
?
//裁剪角度的一半(30即表示裁剪角度为60度,即圆弧上裁剪部分是1/6(60/360))
let
clipHalfAngle:
Double
= 30
//初始化
init
(image:
UIImage
, degrees:
Double
, isClip:
Bool
) {
super
.
init
()
//参数初始化
self
.degrees = degrees
self
.image = image
self
.isClip = isClip
//这个记得设置,否则图片在Retina设备上显示不准确,会模糊
self
.contentsScale =
UIScreen
.main.scale
}
required
init
?(coder aDecoder:
NSCoder
) {
fatalError(
"init(coder:) has not been implemented"
)
}
//绘制内容
override
func
draw(
in
ctx:
CGContext
) {
super
.draw(
in
: ctx)
let
bounds =
self
.bounds
//尺寸
let
size = bounds.size
//半径
let
radius = size.width/2
//中心点位置
let
center =
CGPoint
(x:bounds.midX, y:bounds.midY)
//为方便操作,先变换坐标系
var
transform =
CGAffineTransform
.identity
transform = transform.translatedBy(x: center.x, y: center.y)
transform = transform.rotated(by:
CGFloat
(radians(degrees:
self
.degrees)))
transform = transform.translatedBy(x: -center.x, y: -center.y)
let
path =
CGMutablePath
()
//判断是非裁剪
if
self
.isClip! {
//绘制大圆弧
let
angle1 = radians(degrees: (90.0-clipHalfAngle))
let
angle2 = radians(degrees: (90.0+clipHalfAngle))
path.addArc(center: center, radius: radius,
startAngle: angle1, endAngle: angle2,
clockwise:
true
, transform: transform)
//绘制小圆弧(形成缺口)
let
angle3 = radians(degrees: (clipHalfAngle))
let
tangent1End =
CGPoint
(x:radius,
y:radius+(radius*sin(angle1) -
radius*sin(angle3)*tan(angle3)))
let
tangent2End =
CGPoint
(x:radius+radius*sin(angle3),
y:radius+radius*sin(angle1))
path.addArc(tangent1End: tangent1End, tangent2End: tangent2End,
radius: radius, transform: transform)
}
else
{
//不裁剪的话直接画个圆
path.addEllipse(
in
: bounds)
}
//开始图片处理上下文(由于输出的图不会进行缩放,所以缩放因子等于屏幕的scale即可)
UIGraphicsBeginImageContextWithOptions
(
self
.frame.size,
false
, 0)
let
bezierPath =
UIBezierPath
(cgPath: path)
bezierPath.close()
//添加路径裁剪
bezierPath.addClip()
//图片绘制
self
.image.draw(
in
:
self
.bounds)
//获得处理后的图片
let
maskedImage =
UIGraphicsGetImageFromCurrentImageContext
()!
//结束上下文
UIGraphicsEndImageContext
()
//进入新状态
UIGraphicsPushContext
(ctx)
//绘制裁剪后的图像
maskedImage.draw(
in
:
self
.bounds)
//回到之前状态
UIGraphicsPopContext
()
}
}
//将角度转为弧度
func
radians(degrees:
Double
)->
CGFloat
{
return
CGFloat
(degrees/
Double
(180.0) *
M_PI
)
}
|
(2) GroupIcon.swift(组合头像组件)
- 组件初始化的时候会根据图片数组里图片数量,来调用相应的内部图片元素创建方法。
- 每种创建方法里的行为都差不多。即计算内部图标的尺寸,创建图片并放置到对应的位置。
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
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
|
import
UIKit
//组合图标
class
GroupIcon
:
UIView
{
//整个组合图标的长宽尺寸(两个相等)
var
wh:
CGFloat
!
//组合图标内部使用的图片
var
images:[
UIImage
]!
//初始化
init
(wh:
CGFloat
, images:[
UIImage
]) {
//初始化
super
.
init
(frame:
CGRect
(x:0, y:0, width:wh, height:wh))
self
.wh = wh
self
.images = images
//背景默认透明
self
.backgroundColor =
UIColor
.clear
//如果传入的图片数组为空,就不继续创建内部元素了
if
(
self
.images.count <= 0) {
return
}
//根据数量的不同,调用不同的创建方法
switch
images.count{
case
1:
self
.createCells1()
case
2:
self
.createCells2()
case
3:
self
.createCells3()
case
4:
self
.createCells4()
case
5:
//如果有5个或5个以上的图片的话,都只使用前5个图片
fallthrough
default
:
self
.createCells5()
break
}
}
required
init
?(coder aDecoder:
NSCoder
) {
fatalError(
"init(coder:) has not been implemented"
)
}
//创建内部图标元素(只有一个图标的情况)
func
createCells1(){
//内部小图标的直径
let
cellD =
self
.wh!
//内部小图标的半径
let
cellR = cellD / 2
//内部每个小图标的尺寸
let
cellSize =
CGSize
(width:cellD, height:cellD)
//第1个小图标
let
layer0 =
GroupIconCell
(image:images[0], degrees:0, isClip:
false
)
let
center0 =
CGPoint
(x:cellR, y:cellR)
layer0.frame = getRect(center: center0, size: cellSize)
self
.layer.addSublayer(layer0)
layer0.setNeedsDisplay()
}
//创建内部图标元素(有2个图标的情况)
func
createCells2(){
//内部小图标的直径
let
cellD = (
self
.wh+
self
.wh-
CGFloat
(sqrtf(2))*
self
.wh)
//内部小图标的半径
let
cellR = cellD / 2
//内部每个小图标的尺寸
let
cellSize =
CGSize
(width:cellD, height:cellD)
//第1个小图标
let
layer0 =
GroupIconCell
(image:images[0], degrees:0, isClip:
false
)
let
center0 =
CGPoint
(x:cellR, y:cellR)
layer0.frame = getRect(center: center0, size: cellSize)
self
.layer.addSublayer(layer0)
layer0.setNeedsDisplay()
//第2个小图标
let
layer1 =
GroupIconCell
(image:images[1], degrees:180 - 45, isClip:
true
)
let
center1 =
CGPoint
(x:cellR+
CGFloat
(sqrtf(2))*cellD/2,
y:cellR+
CGFloat
(sqrtf(2))*cellD/2)
layer1.frame = getRect(center: center1, size: cellSize)
self
.layer.addSublayer(layer1)
layer1.setNeedsDisplay()
}
//创建内部图标元素(有3个图标的情况)
func
createCells3(){
//内部小图标的直径
let
cellD =
self
.wh/2
//内部每个小图标的尺寸
let
cellSize =
CGSize
(width:cellD, height:cellD)
//第1个小图标
let
layer0 =
GroupIconCell
(image:images[0], degrees:30, isClip:
true
)
let
center0 =
CGPoint
(x:cellD, y:cellD/2)
layer0.frame = getRect(center: center0, size: cellSize)
self
.layer.addSublayer(layer0)
layer0.setNeedsDisplay()
//第2个小图标
let
layer1 =
GroupIconCell
(image:images[1], degrees:270, isClip:
true
)
let
center1 =
CGPoint
(x:center0.x-cellD*sin(radians(degrees: 30)),
y:cellD/2+cellD*cos(radians(degrees: 30)))
layer1.frame = getRect(center: center1, size: cellSize)
self
.layer.addSublayer(layer1)
layer1.setNeedsDisplay()
//第2个小图标
let
layer2 =
GroupIconCell
(image:images[2], degrees:180 - 30, isClip:
true
)
let
center2 =
CGPoint
(x:center1.x+cellD, y:center1.y)
layer2.frame = getRect(center: center2, size: cellSize)
self
.layer.addSublayer(layer2)
layer2.setNeedsDisplay()
}
//创建内部图标元素(有4个图标的情况)
func
createCells4(){
//内部小图标的直径
let
cellD =
self
.wh/2
//内部小图标的半径
let
cellR = cellD / 2
//内部每个小图标的尺寸
let
cellSize =
CGSize
(width:cellD, height:cellD)
//第1个小图标
let
layer0 =
GroupIconCell
(image:images[0], degrees:0, isClip:
true
)
let
center0 =
CGPoint
(x:cellR, y:cellR)
layer0.frame = getRect(center: center0, size: cellSize)
self
.layer.addSublayer(layer0)
layer0.setNeedsDisplay()
//第2个小图标
let
layer1 =
GroupIconCell
(image:images[1], degrees:270, isClip:
true
)
let
center1 =
CGPoint
(x:center0.x, y:center0.y+cellD)
layer1.frame = getRect(center: center1, size: cellSize)
self
.layer.addSublayer(layer1)
layer1.setNeedsDisplay()
//第3个小图标
let
layer2 =
GroupIconCell
(image:images[2], degrees:180, isClip:
true
)
let
center2 =
CGPoint
(x:center1.x+cellD, y:center1.y)
layer2.frame = getRect(center:center2, size: cellSize)
self
.layer.addSublayer(layer2)
layer2.setNeedsDisplay()
//第4个小图标
let
layer3 =
GroupIconCell
(image:images[3], degrees:90, isClip:
true
)
let
center3 =
CGPoint
(x:center2.x, y:center2.y-cellD)
layer3.frame = getRect(center:center3, size: cellSize)
self
.layer.addSublayer(layer3)
layer3.setNeedsDisplay()
}
//创建内部图标元素(有5个图标的情况)
func
createCells5(){
//内部小图标的半径
let
cellR =
self
.wh/2/(2*sin(radians(degrees: 54))+1)
//内部小图标的直径
let
cellD = cellR*2
//内部每个小图标的尺寸
let
cellSize =
CGSize
(width:cellD, height:cellD)
//第1个小图标
let
layer0 =
GroupIconCell
(image:images[0], degrees:54, isClip:
true
)
let
center0 =
CGPoint
(x:
self
.wh/2, y:cellR)
layer0.frame = getRect(center:center0, size: cellSize)
self
.layer.addSublayer(layer0)
layer0.setNeedsDisplay()
//第2个小图标
let
layer1 =
GroupIconCell
(image:images[1], degrees:270 + 72, isClip:
true
)
let
center1 =
CGPoint
(x:center0.x-cellD*sin(radians(degrees: 54)),
y:center0.y+cellD*cos(radians(degrees: 54)))
layer1.frame = getRect(center:center1, size: cellSize)
self
.layer.addSublayer(layer1)
layer1.setNeedsDisplay()
//第3个小图标
let
layer2 =
GroupIconCell
(image:images[2], degrees:270, isClip:
true
)
let
center2 =
CGPoint
(x:center1.x+cellD*cos(radians(degrees: 72)),
y:center1.y+cellD*sin(radians(degrees: 72)))
layer2.frame = getRect(center:center2, size: cellSize)
self
.layer.addSublayer(layer2)
layer2.setNeedsDisplay()
//第4个小图标
let
layer3 =
GroupIconCell
(image:images[3], degrees:180 + 18, isClip:
true
)
let
center3 =
CGPoint
(x:center2.x+cellD, y:center2.y)
layer3.frame = getRect(center:center3, size: cellSize)
self
.layer.addSublayer(layer3)
layer3.setNeedsDisplay()
//第5个小图标
let
layer4 =
GroupIconCell
(image:images[4], degrees:90 + 36, isClip:
true
)
let
center4 =
CGPoint
(x:center3.x+cellD*cos(radians(degrees: 72)),
y:center3.y-cellD*sin(radians(degrees: 72)))
layer4.frame = getRect(center:center4, size: cellSize)
self
.layer.addSublayer(layer4)
layer4.setNeedsDisplay()
}
//通过中心点坐标和Size尺寸,返回对应的CGRect
func
getRect(center:
CGPoint
, size:
CGSize
) ->
CGRect
{
return
CGRect
(x:center.x - size.width / 2, y:center.y - size.height / 2,
width:size.width, height:size.height)
}
}
|
3,使用样例
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
|
import
UIKit
class
ViewController
:
UIViewController
{
override
func
viewDidLoad() {
super
.viewDidLoad()
//讨论组图标尺寸(长宽一样)
let
viewWH:
CGFloat
= 140
//讨论组图标背景色(这里使用灰色,不设置的话则是透明的)
let
viewBgColor =
UIColor
(red: 0, green:0, blue: 0, alpha: 0.1)
//只由1张图片组成的讨论组图标
let
view1 =
GroupIcon
(wh: viewWH, images: [
UIImage
(named:
"0"
)!])
view1.center =
CGPoint
(x:85, y:90)
view1.backgroundColor = viewBgColor
self
.view.addSubview(view1)
//由2张图片组成的讨论组图标
let
view2 =
GroupIcon
(wh: viewWH, images: [
UIImage
(named:
"0"
)!,
UIImage
(named:
"1"
)!])
view2.center =
CGPoint
(x:235, y:90)
view2.backgroundColor = viewBgColor
self
.view.addSubview(view2)
//由3张图片组成的讨论组图标
let
view3 =
GroupIcon
(wh: viewWH, images: [
UIImage
(named:
"0"
)!,
UIImage
(named:
"1"
)!,
UIImage
(named:
"2"
)!])
view3.center =
CGPoint
(x:85, y:240)
view3.backgroundColor = viewBgColor
self
.view.addSubview(view3)
//由4张图片组成的讨论组图标
let
view4 =
GroupIcon
(wh: viewWH, images: [
UIImage
(named:
"0"
)!,
UIImage
(named:
"1"
)!,
UIImage
(named:
"2"
)!,
UIImage
(named:
"3"
)!])
view4.center =
CGPoint
(x:235, y:240)
view4.backgroundColor = viewBgColor
self
.view.addSubview(view4)
//由5张图片组成的讨论组图标
let
view5 =
GroupIcon
(wh: viewWH, images: [
UIImage
(named:
"0"
)!,
UIImage
(named:
"1"
)!,
UIImage
(named:
"2"
)!,
UIImage
(named:
"3"
)!,
UIImage
(named:
"4"
)!])
view5.center =
CGPoint
(x:85, y:390)
view5.backgroundColor = viewBgColor
self
.view.addSubview(view5)
}
override
func
didReceiveMemoryWarning() {
super
.didReceiveMemoryWarning()
}
}
|

原文出自: www.hangge.com 转载请保留原文链接: http://www.hangge.com/blog/cache/detail_1462.html