| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128 |
- //
- // LNVoiceWaveView.swift
- // Lanu
- //
- // Created by OneeChan on 2026/1/5.
- //
- import Foundation
- import UIKit
- import SnapKit
- class LNVoiceWaveView: UIView {
- var itemCount = 5
- var duration = 0.17
- var fillColor: UIColor = .fill
- var itemWidth: CGFloat = 1.7
- private let stackView = UIStackView()
- private var itemViews: [UIView] = []
-
- override init(frame: CGRect) {
- super.init(frame: frame)
-
- setupViews()
- }
-
- func build() {
- stackView.arrangedSubviews.forEach {
- stackView.removeArrangedSubview($0)
- $0.removeFromSuperview()
- }
- itemViews.removeAll()
-
- let mid: Double = Double(itemCount - 1) / 2.0
- for index in 0..<itemCount {
- let scale = 0.3 + (mid - abs(Double(index) - mid)) * (0.7 / mid)
- let line = UIView()
- line.layer.cornerRadius = itemWidth * 0.5
- line.backgroundColor = fillColor
- stackView.addArrangedSubview(line)
- line.snp.makeConstraints { make in
- make.width.equalTo(itemWidth)
- make.height.equalToSuperview().multipliedBy(scale)
- }
- itemViews.append(line)
- }
- }
-
- func startAnimate() {
- for (index, view) in itemViews.enumerated() {
- let scaleAnim = CABasicAnimation(keyPath: "transform.scale.y")
- scaleAnim.fromValue = 0.7
- scaleAnim.toValue = 1.0
- scaleAnim.duration = duration
- scaleAnim.autoreverses = true
- scaleAnim.fillMode = .both
-
- let animationGroup = CAAnimationGroup()
- animationGroup.animations = [scaleAnim]
- animationGroup.duration = duration + Double(itemCount) * duration
- animationGroup.repeatCount = .infinity
- animationGroup.isRemovedOnCompletion = false
- animationGroup.fillMode = .both
- animationGroup.beginTime = CACurrentMediaTime() + Double(index) * 0.1
-
- view.layer.add(animationGroup, forKey: "scaleAnimated")
- }
- }
-
- func stopAnimate() {
- itemViews.forEach { $0.layer.removeAllAnimations() }
- }
-
- required init?(coder: NSCoder) {
- fatalError("init(coder:) has not been implemented")
- }
- }
- extension LNVoiceWaveView {
- private func setupViews() {
- isUserInteractionEnabled = false
-
- stackView.axis = .horizontal
- stackView.distribution = .equalSpacing
- stackView.alignment = .center
- addSubview(stackView)
- stackView.snp.makeConstraints { make in
- make.edges.equalToSuperview()
- }
- }
- }
- #if DEBUG
- import SwiftUI
- struct LNVoiceWaveViewPreview: UIViewRepresentable {
- func makeUIView(context: Context) -> some UIView {
- let container = UIView()
- container.backgroundColor = .lightGray
-
- let view = LNVoiceWaveView()
- view.fillColor = .red
- view.itemWidth = 10
- container.addSubview(view)
- view.snp.makeConstraints { make in
- make.center.equalToSuperview()
- make.height.equalTo(100)
- make.width.equalTo(150)
- }
- view.build()
-
- view.onTap {
- view.startAnimate()
- }
-
- return container
- }
-
- func updateUIView(_ uiView: UIViewType, context: Context) { }
- }
- #Preview(body: {
- LNVoiceWaveViewPreview()
- })
- #endif
|