iOS/Swift 头像轮播组件
在 iOS 开发中,使用轮播图展示头像是一种常见的 UI 设计方式。主要用于展示一个可循环滚动的头像轮播视图,并带尾随指示的小灰点。
主要功能
- 通过
UIImageView
依次排列头像,自动向左滚动,最左边变小消失,最右边变大出现 - 通过
UIView.animate
实现平滑滚动动画
代码解析
1. 初始化头像数据
swift
private lazy var avatarImages: [UIImage] = {
let imageNames = ["people-1", "people-2", "people-3", "people-4", "people-5", "people"]
return imageNames.compactMap { UIImage(named: $0) }
}()
这里 lazy var
避免了在 init
方法中立即解包,提高了效率。compactMap
过滤掉可能为空的 UIImage
,保证数据完整性。
2. 初始化小灰点
swift
private lazy var dotViews: [UIView] = (0..<4).map { _ in
let dotView = UIView()
dotView.backgroundColor = UIColor(hex: 0xEAEEF1)
dotView.layer.cornerRadius = 5
return dotView
}
创建了 4 个圆形小灰点,用来滚动。
3. 头像滚动逻辑
通过 UIView.animate
实现头像滚动效果。
swift
UIView.animate(withDuration: 0.3, animations: {
self.centerAvatar.forEach { $0.frame.origin.x -= self.avatarWidth }
}) { _ in
self.updateAvatarOrder()
}
这里 forEach
遍历所有头像,将其向左移动 avatarWidth
的距离。动画完成后,调用 updateAvatarOrder()
重新调整视图顺序。
最后附上完整实现代码
swift
// 头像图片数组 (使用 lazy var 避免立即解包)
private lazy var avatarImages: [UIImage] = {
let imageNames = ["people-1", "people-2", "people-3", "people-4", "people-5", "people"]
return imageNames.compactMap { UIImage(named: $0) }
}()
// 小灰点数组 (使用 map 简化初始化)
private lazy var dotViews: [UIView] = (0..<4).map { _ in
let dotView = UIView()
dotView.backgroundColor = UIColor(hex: 0xEAEEF1)
dotView.layer.cornerRadius = 5
return dotView
}
private var firstAvatar: UIImageView!
private var lastAvatar: UIImageView!
private var newAvatarView: UIImageView!
private var centerAvatar: [UIImageView] = []
private var index = 3
private func setup() {
backgroundColor = .clear
layer.masksToBounds = true
setupAvatars()
setupDots()
}
private func setupAvatars() {
// 添加头像视图
for (index, image) in avatarImages.enumerated() {
if index < 4 {
let avatarView = UIImageView()
avatarView.image = image
avatarView.contentMode = .scaleAspectFill
avatarView.frame = CGRect(x: CGFloat(index) * 60 - CGFloat(index) * 10, y: 0, width: 60, height: 60)
avatarView.addBorder(width: 2,borderColor: .white)
avatarView.layerCornerRadius = avatarView.width/2
addSubview(avatarView)
if index == 3 {
lastAvatar = avatarView
}
if index == 0 {
firstAvatar = avatarView
} else {
centerAvatar.append(avatarView)
}
}
}
}
private func setupDots() {
let dotSpacing: CGFloat = 5
let dotSize: CGFloat = 10
let dotY = (frame.height - dotSize)/2
for (index, dotView) in dotViews.enumerated() {
dotView.frame = CGRect(x: lastAvatar.right + CGFloat(index) * (dotSize + dotSpacing) + 5, y: dotY, width: dotSize, height: dotSize)
addSubview(dotView)
}
}
func createNewAvatarView() {
index += 1
// 添加右侧最左边点位置添加新头像
self.newAvatarView = MyCopyImageView()
self.newAvatarView.frame = self.dotViews[0].frame
self.newAvatarView.contentMode = .scaleAspectFill
self.newAvatarView.addBorder(width: 2,borderColor: .white)
self.newAvatarView.layerCornerRadius = self.newAvatarView.width/2
self.newAvatarView.alpha = 0.1
self.newAvatarView.image = avatarImages[index%avatarImages.count]
self.addSubview(self.newAvatarView)
}
func startAnimation() {
createNewAvatarView()
UIView.animate(withDuration: 0.4, delay: 0, options: [.curveLinear], animations: {
// 最左边头像变小消失
self.firstAvatar.transform = CGAffineTransform(scaleX: 0.1, y: 0.1)
// 移动头像和小灰点
for avatarView in self.centerAvatar {
avatarView.frame.origin.x -= 50
}
for dotView in self.dotViews {
dotView.frame.origin.x -= 15
}
// // 右侧最左边点位置添加新头像并变大出现
self.newAvatarView.frame = .init(x: self.lastAvatar.x+50, y: 0, width: 60, height: 60)
self.newAvatarView.layerCornerRadius = self.newAvatarView.width/2
self.newAvatarView.alpha = 1
}, completion: { _ in
// 动画完成后, 替换最左边头像
self.firstAvatar.removeFromSuperview()
self.firstAvatar = self.centerAvatar.first
self.centerAvatar.removeFirst()
self.lastAvatar = self.newAvatarView
self.centerAvatar.append(self.lastAvatar)
// 恢复小灰点位置
for dotView in self.dotViews {
dotView.frame.origin.x += 15
}
// 递归调用继续动画
Timer.after(0.6) {
self.startAnimation()
}
})
}