引言
在如今的直播平台和社交应用中,用户互动不仅限于文字和语音,更多的创意动画和特效被引入来提升用户的沉浸感和参与感。特别是在直播场景中,动态效果如屏幕飘心、点赞、烟花等互动特效,已成为增强直播氛围、吸引观众注意力的重要手段。
本篇博客将重点探讨如何在直播间内实现一个经典的“飘心”动画效果。当用户点击屏幕时,心形图案会随机飘动,增加直播互动的趣味性。在这篇文章中,我将介绍如何通过自定义动画来实现这一效果,解决如何控制每次点击的心形数量、轨迹、随机性等问题,并通过实际代码演示如何构建这个互动效果。
通过这个例子,我们可以进一步了解核心动画相关知识在实际项目开发中的应用,深入理解如何精细地控制动画的路径、持续时间、数量等参数,从而提升用于体验。
需求分析
本需求的核心是:当用户点击直播间的屏幕时,屏幕上会随机生成一个心形图案,并沿着一个随机轨迹向上飘动。每次点击时,系统能够处理并发的多个点击事件,确保每个心形的动画都能够流畅显示,并在适当的时间消失。
为了实现这一效果,关键点如下:
- 点击屏幕生成多种图案:每次点击屏幕时,都要生成随机的图片。
- 轨迹随机:飘心的图案要颜色以及的轨迹飘动。为了避免所有心形朝着相同方向飘动,可以对每个心形设置不同的随机偏移量,控制其飘动方向和路径。
- 动画效果控制:每个图案的飘动轨迹需要时平滑的,并带有一定的随机性,以避免路径单一化。图案的透明度也会随着时间变化直到消失。
- 性能考虑:由于可能存在较高的并发,我们需要确保每次生成的心形动画不会导致界面卡顿或资源浪费。
通过解决上述需求,我们能够为直播间的观众带来一个生动的互动效果,同时增强用户在观看直播过程中的参与感。
实现方案
我们使用Demo先来实现一个简易版,因为在直播间的中的实现,需要处理各种消息和一次显示多个飘心动画等许多复杂情况。
准备工作
在简易版本中,我们实现定义飘心动画执行的最大个数,以及正在显示的个数,来控制最大数量。
class MWHeartFloatView: UIView, CAAnimationDelegate {
/// 动画最大堆积个数
private let maxCount = 1000
/// 正在显示个数
private var showingCount = 0
override init(frame: CGRect) {
super.init(frame: frame)
self.backgroundColor = .red
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
showHeartFloatAnimation()
}
...
}
- 定义动画执行的最大个数。
- 定义变量记录正在显示的个数。
- 重写屏幕点击方法,点击后执行飘心动画。
创建内容图层
为了实现屏幕飘心的效果,我们选择使用 CALayer 来显示心形的图片内容。相比于 UIImageView,CALayer 更加轻量和高效,特别适合用于需要频繁创建和销毁的场景。在这个需求中,我们可能会同时处理多个心形的生成与动画,因此 CALayer 的优势尤为突出。它不会像UIImageView 那样占用过多内存和资源,而且能更高效地与图形渲染引擎配合,确保动画流畅执行。
CALayer 不仅支持图像渲染,还能直接与 Core Animation 中的动画框架结合,提供更细粒度的动画控制。通过使用 CALayer,我们可以为每个心形图层创建个性化的动画路径,控制其位置、透明度、旋转等属性,而无需依赖视图层级结构的复杂操作。这使得在处理大量动画时,性能得到了显著提升,避免了频繁创建视图导致的渲染负担。
通过这种方式,我们不仅可以保证每个心形的渲染和动画效果流畅,还能够最大化地减少内存消耗和CPU开销,从而优化用户体验,尤其是在并发点击的高频率场景下,CALayer 展现出其出色的性能优势。
private func showHeartFloatAnimation() {
showingCount = showingCount + 1
if showingCount > maxCount {
return
}
let name = "like_heart="
let imageName = name + String(format: "%02d", arc4random() % 20 + 1)
let image = UIImage(named: imageName)
let layer = CALayer()
layer.contents = image?.cgImage
let width = 24.0 + CGFloat(arc4random() % 5)
let x = MW_SCREEN_WIDTH - 24.0
let y = MW_SCREEN_HEIGHT - MW_BOTTOM_SAFE_HEIGHT - 45.0
layer.frame = CGRect(x: x, y: y, width: width, height: width)
layer.contentsScale = UIScreen.main.scale
self.layer.addSublayer(layer)
....
}
- 修改显示数量,来控制飘心动画最大同时显示个数。
- 创建图层并设置寄宿图内容添加到指定位置。
添加动画
由于动画内容具有随机性,并且涉及多个维度的变化,我们选择采用动画组(CAAnimationGroup)的方式,将多个动画效果结合在一起,从而实现更加丰富和复杂的飘心效果。
具体来说,除了位置动画外,我们还将缩放和透明度的变化与位置的动画同步执行。通过这种方式,每个心形在飘动的过程中不仅会随机沿不同轨迹飘动,还会随着动画的进行逐渐放大或缩小,透明度也会随之变化,增强动态感和自然感。例如,心形可能会在飘动时稍微放大,模拟真实的浮动效果,而在飘动过程中透明度逐渐减少,最终消失,从而更贴合现实中的物理表现。
为了实现位置的随机性,我们在动画中加入了随机路径的功能,使每个心形沿着不同的轨迹飘动。通过为每个心形创建独立的路径并加入随机的起始位置和控制点,确保每个心形的飘动路线都不完全相同。随机性不仅增加了视觉上的多样性,还能使每次用户点击时,屏幕上的心形效果都呈现出不同的轨迹,避免了重复和单调的动画效果。
这些动画通过 CAAnimationGroup 组合在一起,使得多个动画能够同时执行,同时又不互相干扰,保证了效果的流畅性与同步性。利用动画组的强大功能,我们能够确保每个心形在飘动的过程中,位置、透明度和缩放的变化能够无缝衔接,最终呈现出一个生动、自然的动画效果。
private func showHeartFloatAnimation() {
....
// 1.缩放动画,由小变大
let scaleAnimation = CABasicAnimation(keyPath: "transform.scale")
scaleAnimation.fromValue = 0.1
scaleAnimation.toValue = 1.0
scaleAnimation.duration = 0.3
scaleAnimation.timingFunction = CAMediaTimingFunction(name: .easeInEaseOut)
//2.透明度动画,由不透明到透明
let opacityAnimation = CABasicAnimation(keyPath: "opacity")
opacityAnimation.fromValue = 1.0
opacityAnimation.toValue = 0.0
opacityAnimation.duration = 2.0
opacityAnimation.isRemovedOnCompletion = true
//3.轨迹动画,随机 贝塞尔曲线
let pathAnimation = CAKeyframeAnimation(keyPath: "position")
pathAnimation.calculationMode = .paced
pathAnimation.fillMode = .forwards
pathAnimation.isRemovedOnCompletion = false
let path = UIBezierPath()
let startPoint = layer.position
path.move(to: startPoint)
// 随机生成目标点
let endX = x - 30 + CGFloat(arc4random() % 60) // 随机左右偏移
let endY = y - 200 - CGFloat(arc4random() % 20) // 向上偏移200,再加上随机的微小偏移
let endPoint = CGPoint(x: endX, y: endY)
// 改为三次贝塞尔曲线,使用多个控制点来让路径更平滑
let controlPoint1 = CGPoint(x: startPoint.x + CGFloat(arc4random() % 100) - 50, y: startPoint.y - 100)
let controlPoint2 = CGPoint(x: endX + CGFloat(arc4random() % 100) - 50, y: endY - 50)
path.addCurve(to: endPoint, controlPoint1: controlPoint1, controlPoint2: controlPoint2)
pathAnimation.path = path.cgPath
pathAnimation.duration = 2.0
// 组合动画
let animationGroup = CAAnimationGroup()
animationGroup.duration = 2.0
animationGroup.animations = [scaleAnimation, opacityAnimation, pathAnimation]
animationGroup.delegate = self
animationGroup.isRemovedOnCompletion = false
animationGroup.fillMode = .forwards
layer.add(animationGroup, forKey: nil)
animationGroup.setValue(layer, forKey: "heartLayer")
}
- 缩放动画,用于飘心图案一出现的动画,由小变大。
- 透明度动画,用于飘心逐渐消失的效果。
- 位置动画,通过随机的结束位置,控制点1,控制点2, 构建三次贝塞尔曲线。
- 将三个动画组合并添加到指定图层。
动画结束
动画结束后,首先需要修改正在显示的动画图层个数,并移除图层的动画,以及从父图层移除图层。释放整个飘心图案的图层。
func animationDidStop(_ anim: CAAnimation, finished flag: Bool) {
showingCount = showingCount - 1
if let layer = anim.value(forKey: "heartLayer") as? CALayer {
layer.removeAllAnimations()
layer.removeFromSuperlayer()
}
}
结语
通过本篇博客的介绍,我们实现了一个简单而有趣的飘心动画效果,展示了如何使用 CALayer 和 CAAnimationGroup 结合多个动画来提升用户体验。在实际应用中,通过合理的路径随机化、动画维度的组合和性能优化,我们能够为直播间或社交应用带来更加生动的互动效果。希望通过这个示例,读者能够更深入理解如何在 iOS 中灵活使用动画技术,同时激发更多创意,在应用中实现丰富的动态效果。