263 lines
7.5 KiB
Swift
263 lines
7.5 KiB
Swift
//
|
|
// VoiceRecordView.swift
|
|
// QuickLocation
|
|
//
|
|
// Created by 八条 on 2026/6/6.
|
|
//
|
|
|
|
import UIKit
|
|
import Lottie
|
|
|
|
final class VoiceRecordView: UIView {
|
|
|
|
enum State {
|
|
case recording
|
|
case canceling
|
|
}
|
|
|
|
var state: State = .recording {
|
|
didSet {
|
|
updateUI()
|
|
}
|
|
}
|
|
|
|
private func updateUI() {
|
|
switch state {
|
|
case .recording:
|
|
tipsLab.text = "松开发送"
|
|
startRotating()
|
|
case .canceling:
|
|
tipsLab.text = "松开取消"
|
|
}
|
|
}
|
|
|
|
lazy var bgView: UIView = {
|
|
let v = UIView()
|
|
v.backgroundColor = .clear
|
|
v.layer.shadowColor = UIColor(red: 0, green: 0, blue: 0, alpha: 0.1).cgColor
|
|
v.layer.shadowOffset = CGSize(width: 0, height: 0)
|
|
v.layer.shadowOpacity = 1
|
|
v.layer.shadowRadius = 8
|
|
return v
|
|
}()
|
|
|
|
lazy var topContainer: UIView = {
|
|
let v = UIView()
|
|
v.backgroundColor = .white
|
|
return v
|
|
}()
|
|
|
|
lazy var keyboardBtn: UIButton = {
|
|
let btn = UIButton(type: .custom)
|
|
btn.setImage(UIImage(named: "IM/keyboard"), for: .normal)
|
|
return btn
|
|
}()
|
|
|
|
lazy var titleView: UIView = {
|
|
let v = UIView()
|
|
v.layer.shadowColor = UIColor(red: 0.06, green: 0.16, blue: 0.27, alpha: 0.1).cgColor
|
|
v.layer.shadowOffset = CGSize(width: 0, height: 0)
|
|
v.layer.shadowOpacity = 1
|
|
v.layer.shadowRadius = 9
|
|
|
|
let cornerView = UIView()
|
|
cornerView.backgroundColor = .white
|
|
cornerView.cornerRadius = 19
|
|
|
|
let label = UILabel()
|
|
label.text = "正在录入语音..."
|
|
label.font = .systemFont(ofSize: 12)
|
|
label.textColor = ThemeManager.shared.color.subTitleColor
|
|
label.textAlignment = .center
|
|
|
|
v.addSubview(cornerView)
|
|
v.addSubview(label)
|
|
|
|
cornerView.layoutChain.edges()
|
|
label.layoutChain.centerX().centerY()
|
|
|
|
return v
|
|
}()
|
|
|
|
lazy var emojiBtn: UIButton = {
|
|
let btn = UIButton(type: .custom)
|
|
btn.setImage(UIImage(named: "IM/emoji_off"), for: .normal)
|
|
btn.extendEdgeInsets = UIEdgeInsets(top: 15, left: 15, bottom: 15, right: 0)
|
|
return btn
|
|
}()
|
|
|
|
lazy var addBtn: UIButton = {
|
|
let btn = UIButton(type: .custom)
|
|
btn.setImage(UIImage(named: "IM/add_off"), for: .normal)
|
|
btn.extendEdgeInsets = UIEdgeInsets(top: 15, left: 0, bottom: 15, right: 15)
|
|
return btn
|
|
}()
|
|
|
|
lazy var containerView: UIView = {
|
|
let view = UIView()
|
|
view.backgroundColor = .white
|
|
return view
|
|
}()
|
|
|
|
lazy var tipsLab: UILabel = {
|
|
let label = UILabel()
|
|
label.text = " "
|
|
label.font = .systemFont(ofSize: 12, weight: .medium)
|
|
label.textColor = ThemeManager.shared.color.titleAuxColor
|
|
label.textAlignment = .center
|
|
return label
|
|
}()
|
|
|
|
lazy var speakingImgView: UIImageView = {
|
|
let view = UIImageView(image: UIImage(named: "IM/speaking"))
|
|
view.isHidden = true
|
|
return view
|
|
}()
|
|
|
|
lazy var speakBtn: UIView = {
|
|
let view = UIView()
|
|
view.backgroundColor = UIColor(hexStr: "#E7F7FF")
|
|
view.cornerRadius = 48
|
|
|
|
let icon = UIImageView(image: UIImage(named: "IM/speak"))
|
|
view.addSubview(icon)
|
|
icon.layoutChain
|
|
.centerX()
|
|
.centerY()
|
|
|
|
return view
|
|
}()
|
|
|
|
lazy var cancelLab: UILabel = {
|
|
let label = UILabel()
|
|
label.text = "左滑取消"
|
|
label.font = .systemFont(ofSize: 12, weight: .regular)
|
|
label.textColor = ThemeManager.shared.color.contentColor
|
|
return label
|
|
}()
|
|
|
|
lazy var cancelBtn: UIButton = {
|
|
let btn = UIButton(type: .custom)
|
|
btn.setImage(UIImage(named: "IM/cancel_off"), for: .normal)
|
|
btn.setImage(UIImage(named: "IM/cancel"), for: .selected)
|
|
btn.isUserInteractionEnabled = false
|
|
return btn
|
|
}()
|
|
|
|
// MARK: - Init
|
|
override init(frame: CGRect) {
|
|
super.init(frame: frame)
|
|
backgroundColor = .clear
|
|
|
|
addSubview(bgView)
|
|
bgView.addSubview(topContainer)
|
|
topContainer.addSubview(keyboardBtn)
|
|
topContainer.addSubview(titleView)
|
|
topContainer.addSubview(emojiBtn)
|
|
topContainer.addSubview(addBtn)
|
|
|
|
bgView.addSubview(containerView)
|
|
containerView.addSubview(tipsLab)
|
|
containerView.addSubview(speakBtn)
|
|
containerView.addSubview(speakingImgView)
|
|
containerView.addSubview(cancelLab)
|
|
containerView.addSubview(cancelBtn)
|
|
|
|
bgView.layoutChain.edges()
|
|
|
|
topContainer.layoutChain
|
|
.edges(excludingEdge: .bottom)
|
|
.height(58)
|
|
|
|
keyboardBtn.layoutChain
|
|
.left(22)
|
|
.centerY()
|
|
.width(36)
|
|
.height(36)
|
|
|
|
addBtn.layoutChain
|
|
.right(22)
|
|
.centerY()
|
|
.width(24)
|
|
.height(24)
|
|
|
|
emojiBtn.layoutChain
|
|
.rightToLeftOfView(addBtn, offset: -8)
|
|
.centerY()
|
|
.width(24)
|
|
.height(24)
|
|
|
|
titleView.layoutChain
|
|
.top(10)
|
|
.leftToRightOfView(keyboardBtn, offset: 7)
|
|
.rightToLeftOfView(emojiBtn, offset: -12)
|
|
.bottom(10)
|
|
|
|
containerView.layoutChain
|
|
.topToBottomOfView(topContainer, offset: 0)
|
|
.edges(excludingEdge: .top)
|
|
|
|
tipsLab.layoutChain
|
|
.top(18)
|
|
.centerX()
|
|
|
|
speakBtn.layoutChain
|
|
.height(96)
|
|
.widthToHeight(1)
|
|
.centerY()
|
|
.centerX()
|
|
|
|
speakingImgView.layoutChain
|
|
.topToView(speakBtn, offset: -6)
|
|
.leftToView(speakBtn, offset: -6)
|
|
.rightToView(speakBtn, offset: 6)
|
|
.bottomToView(speakBtn, offset: 6)
|
|
|
|
cancelBtn.layoutChain
|
|
.rightToLeftOfView(speakBtn, offset: -47)
|
|
.centerY(speakBtn)
|
|
.width(41)
|
|
.heightToWidth(1)
|
|
|
|
cancelLab.layoutChain
|
|
.bottomToTopOfView(cancelBtn, offset: -7)
|
|
.centerX(cancelBtn)
|
|
}
|
|
|
|
required init?(coder: NSCoder) {
|
|
fatalError("init(coder:) has not been implemented")
|
|
}
|
|
|
|
override func layoutSubviews() {
|
|
super.layoutSubviews()
|
|
topContainer.setNeedsLayout()
|
|
topContainer.layoutIfNeeded()
|
|
topContainer.setCornerRadius(corners: [.topRight, .topLeft], withCornerRadii: CGSize(width: 20, height: 20))
|
|
}
|
|
}
|
|
|
|
extension VoiceRecordView {
|
|
func startRotating() {
|
|
guard speakingImgView.isHidden == true else { return }
|
|
speakingImgView.isHidden = false
|
|
// 避免重复添加动画
|
|
speakingImgView.layer.removeAnimation(forKey: "rotationAnimation")
|
|
|
|
let rotationAnimation = CABasicAnimation(keyPath: "transform.rotation.z")
|
|
rotationAnimation.fromValue = 0
|
|
rotationAnimation.toValue = Double.pi * 2 // 360°
|
|
rotationAnimation.duration = 1.0
|
|
rotationAnimation.repeatCount = .infinity // 无限循环
|
|
rotationAnimation.isRemovedOnCompletion = false
|
|
rotationAnimation.fillMode = .forwards
|
|
|
|
speakingImgView.layer.add(rotationAnimation, forKey: "rotationAnimation")
|
|
}
|
|
|
|
func stopRotating() {
|
|
tipsLab.text = " "
|
|
speakingImgView.isHidden = true
|
|
speakingImgView.layer.removeAnimation(forKey: "rotationAnimation")
|
|
}
|
|
}
|