From d2d23312dfbb13bd47673b65e156e5e50ea714b7 Mon Sep 17 00:00:00 2001 From: lzan13 Date: Wed, 31 Jan 2024 14:15:49 +0800 Subject: [PATCH] =?UTF-8?q?=E4=BF=AE=E6=94=B9=E5=BD=95=E9=9F=B3=E6=8C=89?= =?UTF-8?q?=E4=B8=8B=E5=8A=A8=E7=94=BB=EF=BC=8C=E6=B7=BB=E5=8A=A0=E5=80=92?= =?UTF-8?q?=E8=AE=A1=E6=97=B6=E6=8F=90=E7=A4=BA?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../layout/activity_demo_view_recorder.xml | 21 ++- .../vmloft/develop/plugin/config/VMConfig.kt | 4 +- .../tools/voice/recorder/VMAudioRecorder.kt | 2 - .../tools/voice/recorder/VMRecorderManager.kt | 4 +- .../tools/voice/recorder/VMRecorderView.kt | 147 +++++++++++++----- vmtools/src/main/res/values/attr.xml | 16 +- 6 files changed, 137 insertions(+), 57 deletions(-) diff --git a/example/src/main/res/layout/activity_demo_view_recorder.xml b/example/src/main/res/layout/activity_demo_view_recorder.xml index 08e06cc6..3836324d 100644 --- a/example/src/main/res/layout/activity_demo_view_recorder.xml +++ b/example/src/main/res/layout/activity_demo_view_recorder.xml @@ -66,7 +66,7 @@ android:layout_height="@dimen/vm_dimen_192" app:layout_constraintTop_toBottomOf="@+id/testTV" - app:vm_bg_color="#fafafb" + app:vm_bg_color="@color/app_bg_display" app:vm_cancel_color="#f2f2f4" app:vm_cancel_color_activate="#f0578e" @@ -74,19 +74,24 @@ app:vm_cancel_icon_activate="@drawable/ic_close_white" app:vm_cancel_size="@dimen/vm_dimen_56" + app:vm_count_down_desc="%d秒后即将发送" + app:vm_desc_cancel="松开 取消" app:vm_desc_color="#44407a" app:vm_desc_font_size="@dimen/vm_size_14" app:vm_desc_normal="按下 说话" - app:vm_inner_color="#6457f0" - app:vm_inner_color_cancel="#c7c6d4" - app:vm_inner_icon="@drawable/ic_voice_record_mic_white" - app:vm_inner_icon_cancel="@drawable/ic_voice_record_mic_black" - app:vm_inner_size="@dimen/vm_dimen_64" + app:vm_inner_color="#89ffffff" + app:vm_inner_size="@dimen/vm_dimen_128" + + app:vm_outer_color="#40ffffff" + app:vm_outer_size="@dimen/vm_dimen_220" - app:vm_outer_color="#ffffff" - app:vm_outer_size="@dimen/vm_dimen_128" + app:vm_touch_color="#6457f0" + app:vm_touch_color_cancel="#c7c6d4" + app:vm_touch_icon="@drawable/ic_voice_record_mic_white" + app:vm_touch_icon_cancel="@drawable/ic_voice_record_mic_black" + app:vm_touch_size="@dimen/vm_dimen_64" app:vm_simple_time="100" diff --git a/vmconfig/config/src/main/kotlin/com/vmloft/develop/plugin/config/VMConfig.kt b/vmconfig/config/src/main/kotlin/com/vmloft/develop/plugin/config/VMConfig.kt index 6d6122a7..bece6950 100644 --- a/vmconfig/config/src/main/kotlin/com/vmloft/develop/plugin/config/VMConfig.kt +++ b/vmconfig/config/src/main/kotlin/com/vmloft/develop/plugin/config/VMConfig.kt @@ -13,8 +13,8 @@ object VMConfig { const val compileSdk = 34 const val minSdk = 21 const val targetSdk = 34 - const val versionCode = 182 - const val versionName = "1.8.2" + const val versionCode = 183 + const val versionName = "1.8.3" // 发布到 jitpack 仓库所需配置,一般项目不需要 const val publishGroup = "com.github.lzan13" diff --git a/vmtools/src/main/kotlin/com/vmloft/develop/library/tools/voice/recorder/VMAudioRecorder.kt b/vmtools/src/main/kotlin/com/vmloft/develop/library/tools/voice/recorder/VMAudioRecorder.kt index 1ffffda8..3859b08c 100644 --- a/vmtools/src/main/kotlin/com/vmloft/develop/library/tools/voice/recorder/VMAudioRecorder.kt +++ b/vmtools/src/main/kotlin/com/vmloft/develop/library/tools/voice/recorder/VMAudioRecorder.kt @@ -33,8 +33,6 @@ class VMAudioRecorder : VMRecorderEngine() { private var bufferSize = 0 // 缓冲区大小 lateinit var bufferData: ShortArray // 缓冲区数据 - private var maxDuration = 60 * 60 * 1000 // 录音最大持续时间 60 分钟 - // 缓冲区大小 // private var bufferSize = AudioRecord.getMinBufferSize(samplingRate, channel, encodeFormat) diff --git a/vmtools/src/main/kotlin/com/vmloft/develop/library/tools/voice/recorder/VMRecorderManager.kt b/vmtools/src/main/kotlin/com/vmloft/develop/library/tools/voice/recorder/VMRecorderManager.kt index 39ea11bc..9f6d4b9a 100644 --- a/vmtools/src/main/kotlin/com/vmloft/develop/library/tools/voice/recorder/VMRecorderManager.kt +++ b/vmtools/src/main/kotlin/com/vmloft/develop/library/tools/voice/recorder/VMRecorderManager.kt @@ -7,6 +7,7 @@ package com.vmloft.develop.library.tools.voice.recorder object VMRecorderManager { // 声音采样时间间隔 var sampleTime: Long = 100L + // 触摸动画时间 var touchAnimTime: Long = 1000L @@ -20,7 +21,8 @@ object VMRecorderManager { const val errorCancel = 4 // 录音取消 const val errorShort = 5 // 录音时间过短 - var maxDuration = 60 * 60 * 1000 // 录音最大持续时间 60 分钟 + var maxDuration = 60 * 1000 // 录音最大持续时间 60 秒 +// var maxDuration = 10 * 1000 // 录音最大持续时间 60 秒 private var recorderEngine: VMRecorderEngine? = null diff --git a/vmtools/src/main/kotlin/com/vmloft/develop/library/tools/voice/recorder/VMRecorderView.kt b/vmtools/src/main/kotlin/com/vmloft/develop/library/tools/voice/recorder/VMRecorderView.kt index 449eb606..3f512c22 100644 --- a/vmtools/src/main/kotlin/com/vmloft/develop/library/tools/voice/recorder/VMRecorderView.kt +++ b/vmtools/src/main/kotlin/com/vmloft/develop/library/tools/voice/recorder/VMRecorderView.kt @@ -52,25 +52,34 @@ class VMRecorderView @JvmOverloads constructor(context: Context, attrs: Attribut private var mCancelSize = VMDimen.dp2px(48) private var mCancelMargin = VMDimen.dp2px(48) + // 倒计时文案 + private var mCountDownDesc: String = "%d秒后停止" + // 触摸区域提示文本 - private var mDescNormal: String = "按下 说话" + private var mDescNormal: String = "按住 说话" private var mDescCancel: String = "松开 取消" private var mDescColor = 0x44407a private var mDescFontSize = VMDimen.dp2px(14) - // 内圈录音按钮的颜色、大小 - private var mInnerSize = VMDimen.dp2px(60) - private var mInnerColor = 0x6457f0 - private var mInnerColorCancel = 0xc7c6d4 - private var mInnerIcon = R.drawable.ic_voice_record_mic_white - private var mInnerIconCancel = R.drawable.ic_voice_record_mic_black + // 内圈颜色、大小 + private var mInnerSize = VMDimen.dp2px(128) + private var mInnerColor = 0xffffff + private var mInnerAnimSize = 0 + private var mInnerAnimAlpha = 100 // 外圈的颜色、大小 private var mOuterColor = 0xffffff - private var mOuterSize = VMDimen.dp2px(128) + private var mOuterSize = VMDimen.dp2px(220) private var mOuterAnimSize = 0 private var mOuterAnimAlpha = 100 + // 录音按钮的颜色、大小 + private var mTouchSize = VMDimen.dp2px(60) + private var mTouchColor = 0x6457f0 + private var mTouchColorCancel = 0xc7c6d4 + private var mTouchIcon = R.drawable.ic_voice_record_mic_white + private var mTouchIconCancel = R.drawable.ic_voice_record_mic_black + // 时间字体的大小、颜色 private var mTimeColor = 0x44407a private var mTimeFontSize = VMDimen.dp2px(14) @@ -80,6 +89,8 @@ class VMRecorderView @JvmOverloads constructor(context: Context, attrs: Attribut private var durationTime: Int = 0 // 录制持续时间 private var voiceDecibel = 1 // 声音分贝 + private var countDownTime: Int = 0 // 录制倒计时 + // 录音声音分贝集合 private var decibelList = mutableListOf() private var decibelCount: Int = 0 // 声音分贝总数,用来计算抽样 @@ -127,27 +138,26 @@ class VMRecorderView @JvmOverloads constructor(context: Context, attrs: Attribut mCancelSize = array.getDimensionPixelOffset(styleable.VMVoiceView_vm_cancel_size, mCancelSize) mCancelMargin = array.getDimensionPixelOffset(styleable.VMVoiceView_vm_cancel_margin, mCancelMargin) - mOuterColor = array.getColor(styleable.VMVoiceView_vm_outer_color, mOuterColor) - mOuterSize = array.getDimensionPixelOffset(styleable.VMVoiceView_vm_outer_size, mOuterSize) - mOuterAnimSize = (mOuterSize * 1.5f).toInt() + mCountDownDesc = array.getString(styleable.VMVoiceView_vm_count_down_desc) ?: mCountDownDesc + mDescNormal = array.getString(styleable.VMVoiceView_vm_desc_normal) ?: mDescCancel + mDescCancel = array.getString(styleable.VMVoiceView_vm_desc_cancel) ?: mDescCancel + + mDescColor = array.getColor(styleable.VMVoiceView_vm_desc_color, mDescColor) + mDescFontSize = array.getDimensionPixelOffset(styleable.VMVoiceView_vm_desc_font_size, mDescFontSize) mInnerColor = array.getColor(styleable.VMVoiceView_vm_inner_color, mInnerColor) - mInnerColorCancel = array.getColor(styleable.VMVoiceView_vm_inner_color_cancel, mInnerColorCancel) mInnerSize = array.getDimensionPixelOffset(styleable.VMVoiceView_vm_inner_size, mInnerSize) - mInnerIcon = array.getResourceId(styleable.VMVoiceView_vm_inner_icon, mCancelIcon) - mInnerIconCancel = array.getResourceId(styleable.VMVoiceView_vm_inner_icon_cancel, mCancelIconActivate) + mOuterColor = array.getColor(styleable.VMVoiceView_vm_outer_color, mOuterColor) + mOuterSize = array.getDimensionPixelOffset(styleable.VMVoiceView_vm_outer_size, mOuterSize) + + mTouchColor = array.getColor(styleable.VMVoiceView_vm_touch_color, mTouchColor) + mTouchColorCancel = array.getColor(styleable.VMVoiceView_vm_touch_color_cancel, mTouchColorCancel) + mTouchSize = array.getDimensionPixelOffset(styleable.VMVoiceView_vm_touch_size, mTouchSize) + + mTouchIcon = array.getResourceId(styleable.VMVoiceView_vm_touch_icon, mCancelIcon) + mTouchIconCancel = array.getResourceId(styleable.VMVoiceView_vm_touch_icon_cancel, mCancelIconActivate) - mDescNormal = array.getString(styleable.VMVoiceView_vm_desc_normal) ?: mDescCancel - mDescCancel = array.getString(styleable.VMVoiceView_vm_desc_cancel) ?: mDescCancel - if (mDescNormal.isEmpty()) { - mDescNormal = "触摸录音" - } - if (mDescCancel.isEmpty()) { - mDescCancel = "松开取消" - } - mDescColor = array.getColor(styleable.VMVoiceView_vm_desc_color, mDescColor) - mDescFontSize = array.getDimensionPixelOffset(styleable.VMVoiceView_vm_desc_font_size, mDescFontSize) mTimeColor = array.getColor(styleable.VMVoiceView_vm_time_color, mTimeColor) mTimeFontSize = array.getDimensionPixelOffset(styleable.VMVoiceView_vm_time_font_size, mTimeFontSize) mTimeMargin = array.getDimensionPixelOffset(styleable.VMVoiceView_vm_time_margin, mTimeMargin) @@ -215,7 +225,7 @@ class VMRecorderView @JvmOverloads constructor(context: Context, attrs: Attribut mPaint.textSize = mDescFontSize.toFloat() val tWidth = VMDimen.getTextWidth(mPaint, mUnusableDesc) val tHeight = VMDimen.getTextHeight(mPaint, mUnusableDesc) - val centerY = mHeight / 2 - mInnerSize / 2 - tHeight * 2 + val centerY = mHeight / 2 - mTouchSize / 2 - tHeight * 2 canvas.drawText(mUnusableDesc, mWidth / 2 - tWidth / 2, centerY, mPaint) } @@ -223,30 +233,39 @@ class VMRecorderView @JvmOverloads constructor(context: Context, attrs: Attribut * 绘制录制按钮动画 */ private fun drawRecordAnim(canvas: Canvas) { - if (isReadyCancel) return + if (!isStart || isReadyCancel) return // 绘制外圈 mPaint.color = mOuterColor // 设置透明度,这里透明度要放在设置颜色之后 mPaint.alpha = mOuterAnimAlpha canvas.drawCircle(mWidth / 2.0f, mHeight / 2.0f, mOuterAnimSize / 2.0f, mPaint) + + // 绘制内圈 + mPaint.color = mInnerColor + // 设置透明度,这里透明度要放在设置颜色之后 + mPaint.alpha = mInnerAnimAlpha + canvas.drawCircle(mWidth / 2.0f, mHeight / 2.0f, mInnerAnimSize / 2.0f, mPaint) } /** * 绘制录制按钮 */ private fun drawRecordBtn(canvas: Canvas) { - if (!isReadyCancel) { - // 绘制外圈 + if (!isStart && !isReadyCancel) { + // 绘制内外圈 mPaint.color = mOuterColor canvas.drawCircle(mWidth / 2.0f, mHeight / 2.0f, mOuterSize / 2.0f, mPaint) + + mPaint.color = mInnerColor + canvas.drawCircle(mWidth / 2.0f, mHeight / 2.0f, mInnerSize / 2.0f, mPaint) } // 绘制触摸区域 - mPaint.color = if (isReadyCancel) mInnerColorCancel else mInnerColor - canvas.drawCircle(mWidth / 2.0f, mHeight / 2.0f, mInnerSize / 2.0f, mPaint) + mPaint.color = if (isReadyCancel) mTouchColorCancel else mTouchColor + canvas.drawCircle(mWidth / 2.0f, mHeight / 2.0f, mTouchSize / 2.0f, mPaint) // 绘制麦克风图标 - val bitmap = context.resources.getDrawable(if (isReadyCancel) mInnerIconCancel else mInnerIcon).toBitmap() + val bitmap = context.resources.getDrawable(if (isReadyCancel) mTouchIconCancel else mTouchIcon).toBitmap() canvas.drawBitmap(bitmap, mWidth / 2.0f - bitmap.width / 2, mHeight / 2.0f - bitmap.height / 2, mPaint) } @@ -277,6 +296,20 @@ class VMRecorderView @JvmOverloads constructor(context: Context, attrs: Attribut * 绘制文本 */ private fun drawDesc(canvas: Canvas) { + // 倒计时,倒计时优先级最高 + if (countDownTime < 5 * 1000 && countDownTime > 0) { + mPaint.color = mCancelColorActivate + + val desc = VMStr.byArgs(mCountDownDesc, countDownTime / 1000) + + mPaint.textSize = mDescFontSize.toFloat() + val tWidth = VMDimen.getTextWidth(mPaint, desc) + val tHeight = VMDimen.getTextHeight(mPaint, desc) + val centerY = mHeight / 2 - mTouchSize / 2 - tHeight * 2 + canvas.drawText(desc, mWidth / 2 - tWidth / 2, centerY, mPaint) + return + } + if (isStart && !isReadyCancel) return val descColor: Int @@ -293,7 +326,7 @@ class VMRecorderView @JvmOverloads constructor(context: Context, attrs: Attribut mPaint.textSize = mDescFontSize.toFloat() val tWidth = VMDimen.getTextWidth(mPaint, desc) val tHeight = VMDimen.getTextHeight(mPaint, desc) - val centerY = mHeight / 2 - mInnerSize / 2 - tHeight * 2 + val centerY = mHeight / 2 - mTouchSize / 2 - tHeight * 2 canvas.drawText(desc, mWidth / 2 - tWidth / 2, centerY, mPaint) } @@ -318,12 +351,34 @@ class VMRecorderView @JvmOverloads constructor(context: Context, attrs: Attribut canvas.drawText(time, centerX, mHeight / 2 + tHeight / 2, mPaint) } + /** + * 内圈动画 + */ + private fun startInnerAnim() { + VMSystem.runInUIThread({ + val mAnimator = ValueAnimator.ofInt(mTouchSize, mInnerSize * 2) + mAnimator.duration = VMRecorderManager.touchAnimTime + mAnimator.repeatCount = 0 + mAnimator.interpolator = LinearInterpolator() + mAnimator.addUpdateListener { a: ValueAnimator -> + if (isStart) { + // 动画大小根据回调变化 + mInnerAnimSize = a.animatedValue as Int + mInnerAnimAlpha = (mInnerSize * 2 - mInnerAnimSize) * 100 / (mInnerSize * 2 - mTouchSize) + + invalidate() + } + } + mAnimator.start() + }) + } + /** * 外圈动画 */ private fun startOuterAnim() { VMSystem.runInUIThread({ - val mAnimator = ValueAnimator.ofInt(mOuterSize, mOuterSize * 2) + val mAnimator = ValueAnimator.ofInt(mInnerSize, mOuterSize * 2) mAnimator.duration = VMRecorderManager.touchAnimTime mAnimator.repeatCount = 0 mAnimator.interpolator = LinearInterpolator() @@ -331,9 +386,8 @@ class VMRecorderView @JvmOverloads constructor(context: Context, attrs: Attribut if (isStart) { // 动画大小根据回调变化 mOuterAnimSize = a.animatedValue as Int - mOuterAnimAlpha = (mOuterSize * 2 - mOuterAnimSize) * 100 / mOuterSize + mOuterAnimAlpha = (mOuterSize * 2 - mOuterAnimSize) * 100 / (mOuterSize * 2 - mInnerSize) -// VMLog.i("alpha $mOuterAnimAlpha") invalidate() } } @@ -352,9 +406,11 @@ class VMRecorderView @JvmOverloads constructor(context: Context, attrs: Attribut val task: TimerTask = object : TimerTask() { override fun run() { durationTime = (System.currentTimeMillis() - startTime).toInt() + countDownTime = VMRecorderManager.maxDuration - durationTime + voiceDecibel = recorderEngine.decibel() if (durationTime > VMRecorderManager.maxDuration) { - stopRecord(false) + VMSystem.runInUIThread({ stopRecord(false) }) } // 将声音分贝添加到集合,这里防止过大,进行抽样保存 if (decibelCount % 2 == 1) { @@ -362,7 +418,9 @@ class VMRecorderView @JvmOverloads constructor(context: Context, attrs: Attribut } onRecordDecibel(voiceDecibel) - if ((decibelCount % (VMRecorderManager.touchAnimTime / VMRecorderManager.sampleTime)).toInt() == 0) { + val simple = decibelCount % (VMRecorderManager.touchAnimTime / VMRecorderManager.sampleTime).toInt() + if (simple == 0) { + startInnerAnim() startOuterAnim() } @@ -370,7 +428,7 @@ class VMRecorderView @JvmOverloads constructor(context: Context, attrs: Attribut postInvalidate() } } - timer?.scheduleAtFixedRate(task, 300, VMRecorderManager.sampleTime) + timer?.scheduleAtFixedRate(task, 10, VMRecorderManager.sampleTime) } /** @@ -427,7 +485,12 @@ class VMRecorderView @JvmOverloads constructor(context: Context, attrs: Attribut val x = event.x val y = event.y when (action) { - MotionEvent.ACTION_DOWN -> if (isUsable && x > mWidth / 2 - mInnerSize / 2 && x < mWidth / 2 + mInnerSize / 2 && y > mHeight / 2 - mInnerSize / 2 && y < mHeight / 2 + mInnerSize / 2) { + MotionEvent.ACTION_DOWN -> if (isUsable + && x > mWidth / 2 - mTouchSize / 2 + && x < mWidth / 2 + mTouchSize / 2 + && y > mHeight / 2 - mTouchSize / 2 + && y < mHeight / 2 + mTouchSize / 2 + ) { startRecord() } @@ -471,11 +534,15 @@ class VMRecorderView @JvmOverloads constructor(context: Context, attrs: Attribut isStart = false isReadyCancel = false - mOuterAnimSize = (mOuterSize * 1.5).toInt() + mOuterAnimSize = 0 mOuterAnimAlpha = 100 + mInnerAnimSize = 0 + mInnerAnimAlpha = 100 + startTime = 0L durationTime = 0 + countDownTime = 0 decibelList.clear() } diff --git a/vmtools/src/main/res/values/attr.xml b/vmtools/src/main/res/values/attr.xml index 068b8f25..a894400a 100644 --- a/vmtools/src/main/res/values/attr.xml +++ b/vmtools/src/main/res/values/attr.xml @@ -143,19 +143,27 @@ + + + + + - - + + - - + + + + +