LNVoiceWaveView.swift 3.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128
  1. //
  2. // LNVoiceWaveView.swift
  3. // Lanu
  4. //
  5. // Created by OneeChan on 2026/1/5.
  6. //
  7. import Foundation
  8. import UIKit
  9. import SnapKit
  10. class LNVoiceWaveView: UIView {
  11. var itemCount = 5
  12. var duration = 0.17
  13. var fillColor: UIColor = .fill
  14. var itemWidth: CGFloat = 1.7
  15. private let stackView = UIStackView()
  16. private var itemViews: [UIView] = []
  17. override init(frame: CGRect) {
  18. super.init(frame: frame)
  19. setupViews()
  20. }
  21. func build() {
  22. stackView.arrangedSubviews.forEach {
  23. stackView.removeArrangedSubview($0)
  24. $0.removeFromSuperview()
  25. }
  26. itemViews.removeAll()
  27. let mid: Double = Double(itemCount - 1) / 2.0
  28. for index in 0..<itemCount {
  29. let scale = 0.3 + (mid - abs(Double(index) - mid)) * (0.7 / mid)
  30. let line = UIView()
  31. line.layer.cornerRadius = itemWidth * 0.5
  32. line.backgroundColor = fillColor
  33. stackView.addArrangedSubview(line)
  34. line.snp.makeConstraints { make in
  35. make.width.equalTo(itemWidth)
  36. make.height.equalToSuperview().multipliedBy(scale)
  37. }
  38. itemViews.append(line)
  39. }
  40. }
  41. func startAnimate() {
  42. for (index, view) in itemViews.enumerated() {
  43. let scaleAnim = CABasicAnimation(keyPath: "transform.scale.y")
  44. scaleAnim.fromValue = 0.7
  45. scaleAnim.toValue = 1.0
  46. scaleAnim.duration = duration
  47. scaleAnim.autoreverses = true
  48. scaleAnim.fillMode = .both
  49. let animationGroup = CAAnimationGroup()
  50. animationGroup.animations = [scaleAnim]
  51. animationGroup.duration = duration + Double(itemCount) * duration
  52. animationGroup.repeatCount = .infinity
  53. animationGroup.isRemovedOnCompletion = false
  54. animationGroup.fillMode = .both
  55. animationGroup.beginTime = CACurrentMediaTime() + Double(index) * 0.1
  56. view.layer.add(animationGroup, forKey: "scaleAnimated")
  57. }
  58. }
  59. func stopAnimate() {
  60. itemViews.forEach { $0.layer.removeAllAnimations() }
  61. }
  62. required init?(coder: NSCoder) {
  63. fatalError("init(coder:) has not been implemented")
  64. }
  65. }
  66. extension LNVoiceWaveView {
  67. private func setupViews() {
  68. isUserInteractionEnabled = false
  69. stackView.axis = .horizontal
  70. stackView.distribution = .equalSpacing
  71. stackView.alignment = .center
  72. addSubview(stackView)
  73. stackView.snp.makeConstraints { make in
  74. make.edges.equalToSuperview()
  75. }
  76. }
  77. }
  78. #if DEBUG
  79. import SwiftUI
  80. struct LNVoiceWaveViewPreview: UIViewRepresentable {
  81. func makeUIView(context: Context) -> some UIView {
  82. let container = UIView()
  83. container.backgroundColor = .lightGray
  84. let view = LNVoiceWaveView()
  85. view.fillColor = .red
  86. view.itemWidth = 10
  87. container.addSubview(view)
  88. view.snp.makeConstraints { make in
  89. make.center.equalToSuperview()
  90. make.height.equalTo(100)
  91. make.width.equalTo(150)
  92. }
  93. view.build()
  94. view.onTap {
  95. view.startAnimate()
  96. }
  97. return container
  98. }
  99. func updateUIView(_ uiView: UIViewType, context: Context) { }
  100. }
  101. #Preview(body: {
  102. LNVoiceWaveViewPreview()
  103. })
  104. #endif