语音条view 白色的一排竖线21个,通过setEffect();方法设置音量,第一个参数是说话的时候当时音量的值,第二个参数是音量的最大值;
java
在这里插入代码片
package com.example.view;
import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.RectF;
import android.util.AttributeSet;
import android.view.MotionEvent;
import android.view.View;
import java.util.Random;
public class LineSoundEffectView extends View {
private Paint paint;
// dp转px的工具,自己网上找个
public final int ticksWidth = DensityUtils.dip2px(getContext(), 3f);
public final int dividerWidth = DensityUtils.dip2px(getContext(), 5f);
public final int ticksMaxHeight = DensityUtils.dip2px(getContext(), 30f);
public final int ticksMinHeight = DensityUtils.dip2px(getContext(), 6f);
public final int ticksNumber = 21;
public final int fixOffset = (ticksMaxHeight - ticksMinHeight) / 2;
// 计算总宽度
public final int totalWidth = ticksWidth * ticksNumber + (dividerWidth * ticksNumber - 1);
public LineSoundEffectView(Context context) {
super(context);
init();
}
public LineSoundEffectView(Context context, AttributeSet attrs) {
super(context, attrs);
init();
}
public LineSoundEffectView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
init();
}
private void init() {
paint = new Paint();
paint.setColor(Color.WHITE);
paint.setAntiAlias(true);
paint.setDither(true);
offsetVertical = fixOffset;
invalidate();
}
@Override
protected void onSizeChanged(int w, int h, int oldw, int oldh) {
super.onSizeChanged(w, h, oldw, oldh);
}
private int offsetVertical = 0;
private int totalNum = 21;
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
int desiredWidth = totalWidth; // 假设我们想要设置的宽度为200px
int desiredHeight = ticksMaxHeight; // 假设我们想要设置的高度为200px
// 设置实际的宽度和高度
setMeasuredDimension(desiredWidth, desiredHeight);
}
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
for (int i = 0; i < totalNum; i++) {
int topY = offsetVertical;
int bottomY = ticksMaxHeight - offsetVertical < ticksMinHeight ? ticksMinHeight :
ticksMaxHeight - offsetVertical;
if (i < 4) {
topY = fixOffset;
bottomY = ticksMaxHeight - fixOffset;
paint.setAlpha((int) ((i + 1) * 255 / 6f));
} else if (i >= totalNum - 4) {
topY = fixOffset;
bottomY = ticksMaxHeight - fixOffset;
paint.setAlpha((int) ((totalNum - i) * 255 / 5f));
} else {
// random 越大,音波越小
int random = offsetVertical <= fixOffset ? i % 2 == 0 ? Math.abs(i < totalNum / 2 ? totalNum / 2 - i : i - totalNum / 2) : 0:Math.abs(i < totalNum / 2 ? totalNum / 2 - i : i - totalNum / 2)%5;
int i1 = offsetVertical + random > fixOffset ? fixOffset : offsetVertical + random;
topY = i1;
bottomY = ticksMaxHeight - i1;
paint.setAlpha(255);
}
canvas.drawRoundRect(new RectF((dividerWidth + ticksWidth) * i, topY, (dividerWidth + ticksWidth) * i + ticksWidth, bottomY), ticksWidth / 2, ticksWidth / 2, paint);
// }
}
}
@Override
public boolean onTouchEvent(MotionEvent event) {
if (event.getAction() == MotionEvent.ACTION_DOWN) {
// 重绘视图
invalidate();
return true;
}
return super.onTouchEvent(event);
}
public void setEffect(int value, int maxValue) {
offsetVertical = (int) (fixOffset * (1 - (value * 1f / maxValue)));
invalidate();
}
}
松手发送,上移取消代码
java
binding.tvChatVoice.setOnTouchListener((v, motionEvent) -> {
switch (motionEvent.getAction()) {
case MotionEvent.ACTION_DOWN:
downY = motionEvent.getY();
startVoice();
break;
case MotionEvent.ACTION_CANCEL: // 首次开权限时会走这里,录音取消
case MotionEvent.ACTION_UP:
if (mIat != null) {
if (isCanceledVoiceSendOp) {
mIat.cancel();
endVoice();
} else {
mIat.stopListening();
}
}
break;
case MotionEvent.ACTION_MOVE: // 滑动手指
float moveY = motionEvent.getY();
LogUtils.e("move-------downY - moveY=" + (downY - moveY));
if (downY != 0 && downY - moveY > 100) {
if (!isCanceledVoiceSendOp) {
// 震动反馈
binding.tvChatVoice.setHapticFeedbackEnabled(true);
binding.tvChatVoice.performHapticFeedback(HapticFeedbackConstants.LONG_PRESS);
}
isCanceledVoiceSendOp = true;
binding.llChatEffect.setBackgroundResource(R.drawable.shape_chat_voice_effect_cancel_bg);
binding.tvChatVoiceEffectTips.setText("松手取消");
binding.tvChatVoiceEffectTips.setTextColor(getResources().getColor(R.color.color_FE3750));
}
if (downY - moveY < 20) {
if (isCanceledVoiceSendOp) {
// 震动反馈
binding.tvChatVoice.setHapticFeedbackEnabled(true);
binding.tvChatVoice.performHapticFeedback(HapticFeedbackConstants.LONG_PRESS);
}
isCanceledVoiceSendOp = false;
binding.tvChatVoiceEffectTips.setText("松手发送,上移取消");
binding.tvChatVoiceEffectTips.setTextColor(getResources().getColor(R.color.color_8B8B8B));
binding.llChatEffect.setBackgroundResource(R.drawable.shape_chat_voice_effect_bg);
}
break;
}
return true;
}
);
java
/**
* 开始语音输入
*/
private void startVoice() {
if (!isAttachedActivityValid()) {
return;
}
binding.tvChatVoice.setHapticFeedbackEnabled(true);
binding.tvChatVoice.performHapticFeedback(HapticFeedbackConstants.LONG_PRESS);
// 取消上次请求
cancelPreQuestionRequest();
if (ContextCompat.checkSelfPermission(getActivity(), Manifest.permission.RECORD_AUDIO) != PackageManager.PERMISSION_GRANTED) {
requestPermissionLauncher.launch(Manifest.permission.RECORD_AUDIO);
return;
}
binding.tvChatVoiceEffectTips.setVisibility(View.VISIBLE);
binding.tvChatVoiceEffectTips.setText("松手发送,上移取消");
binding.tvChatVoiceEffectTips.setTextColor(getResources().getColor(R.color.color_8B8B8B));
binding.llChatEffect.setVisibility(View.VISIBLE);
binding.llFooter.setBackgroundResource(R.drawable.ic_chat_voice_effect_bottom_bg);
// binding.ivChatEffect.setImageResource(R.drawable.ic_chat_effect_1);
mVoiceTextBuffer.setLength(0);
// 不显示听写对话框
int ret = mIat.startListening(new RecognizerListener() {
@Override
public void onBeginOfSpeech() {
// 此回调表示:sdk内部录音机已经准备好了,用户可以开始语音输入
}
@Override
public void onError(SpeechError error) {
// Tips:
// 错误码:10118(您没有说话),可能是录音机权限被禁,需要提示用户打开应用的录音权限。
// Log.d(TAG, "onError " + error.getPlainDescription(true));
toastCenter(error.getPlainDescription(false));
endVoice();
}
@Override
public void onEndOfSpeech() {
// 此回调表示:检测到了语音的尾端点,已经进入识别过程,不再接受语音输入
// TODO 显示按住说话
endVoice();
}
@Override
public void onResult(RecognizerResult results, boolean isLast) {
String speechText = results.getResultString();
LogUtils.e(speechText);
if (isLast) {
LogUtils.e("aiChat-------onResult 结束");
endVoice();
}
mVoiceTextBuffer.append(speechText);
if (isLast) {
if (TextUtils.isEmpty(mVoiceTextBuffer.toString())) {
new CommonSureDialog(getActivity(), "听不清楚,请重新说一遍!", new View.OnClickListener() {
@Override
public void onClick(View v) {
}
}).show();
} else {
// 语音搜索
sendChatMsg(mVoiceTextBuffer.toString(), null);
}
}
}
@Override
public void onVolumeChanged(int volume, byte[] data) {
binding.lineSoundEffectView.setEffect(volume, 30);
LogUtils.e("aiChat-------当前正在说话,音量大小 = " + volume + " 返回音频数据 = " + data.length);
}
@Override
public void onEvent(int eventType, int arg1, int arg2, Bundle obj) {
}
});
if (ret != ErrorCode.SUCCESS) {
toastCenter("听写启用失败,错误码ret=" + ret);
}
}
/**
* 结束语音输入
*/
private void endVoice() {
binding.llChatVoice.setVisibility(View.VISIBLE);
binding.llChatEffect.setVisibility(View.GONE);
binding.llFooter.setBackgroundResource(R.drawable.ic_chat_bottom_bg);
binding.tvChatVoiceEffectTips.setVisibility(View.GONE);
binding.tvChatVoiceEffectTips.setText("松手发送,上移取消");
binding.tvChatVoiceEffectTips.setTextColor(getResources().getColor(R.color.color_8B8B8B));
binding.llChatEffect.setBackgroundResource(R.drawable.shape_chat_voice_effect_bg);
downY = 0;
}
// 语音听写对象
private SpeechRecognizer mIat;
// 初始化语音听写sdk
private void initVoiceSDK() {
// 初始化识别无UI识别对象
// 使用SpeechRecognizer对象,可根据回调消息自定义界面;
mIat = SpeechRecognizer.createRecognizer(getActivity(), new InitListener() {
@Override
public void onInit(int i) {
LogUtils.e("aiChat-------speechInit---i=" + i);
if (i == 0 && mIat != null) {
setParam();
}
}
});
}
/**
* 语音听写参数设置
*/
public void setParam() {
// 清空参数
mIat.setParameter(SpeechConstant.PARAMS, null);
// 设置听写引擎
mIat.setParameter(SpeechConstant.ENGINE_TYPE, SpeechConstant.TYPE_CLOUD);
// 设置返回结果格式
mIat.setParameter(SpeechConstant.RESULT_TYPE, "plain"); // plain / json
// 设置语言:中文
mIat.setParameter(SpeechConstant.LANGUAGE, "zh_cn");
// 设置语言区域:普通话
mIat.setParameter(SpeechConstant.ACCENT, "mandarin"); // mandarin:普通话
//此处用于设置dialog中不显示错误码信息
//mIat.setParameter("view_tips_plain","false");
// 设置语音前端点:静音超时时间,即用户多长时间不说话则当做超时处理
mIat.setParameter(SpeechConstant.VAD_BOS, "10000");
// 设置语音后端点:后端点静音检测时间,即用户停止说话多长时间内即认为不再输入, 自动停止录音
mIat.setParameter(SpeechConstant.VAD_EOS, "10000");
// 设置标点符号,设置为"0"返回结果无标点,设置为"1"返回结果有标点
mIat.setParameter(SpeechConstant.ASR_PTT, "1");
// 设置音频保存路径,保存音频格式支持pcm、wav.
mIat.setParameter(SpeechConstant.AUDIO_FORMAT, "wav");
mIat.setParameter(SpeechConstant.ASR_AUDIO_PATH,
getActivity().getCacheDir().getAbsolutePath() + "/msc/iat.wav");
}
xml布局 底部按住说话layout:
xml
<androidx.constraintlayout.widget.ConstraintLayout
android:id="@+id/ll_footer"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="@drawable/ic_chat_bottom_bg"
android:orientation="vertical"
android:padding="@dimen/dp_16"
app:layout_constraintBottom_toBottomOf="parent">
<TextView
android:id="@+id/tv_chat_voice_effect_tips"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginBottom="@dimen/dp_8"
android:text="松开发送,上移取消"
android:textColor="@color/color_8B8B8B"
android:textSize="@dimen/font_size_12"
android:translationZ="@dimen/dp_10"
android:visibility="gone"
app:layout_constraintBottom_toTopOf="@id/ll_chat_voice"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
tools:visibility="visible" />
<androidx.appcompat.widget.LinearLayoutCompat
android:id="@+id/ll_chat_text"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="@drawable/shape_item_block_bg_white_r16"
android:minHeight="@dimen/dp_46"
app:layout_constraintBottom_toBottomOf="parent">
<EditText
android:id="@+id/edt_chat_content"
android:layout_width="@dimen/dp_0"
android:layout_height="match_parent"
android:layout_gravity="top"
android:layout_weight="1"
android:background="@null"
android:hint="发消息..."
android:imeOptions="actionSend"
android:lines="1"
android:maxLength="200"
android:paddingHorizontal="@dimen/dp_16"
android:paddingVertical="@dimen/dp_12"
android:singleLine="true"
android:textColorHint="@color/color__999999"
android:textSize="@dimen/font_size_16" />
<View
android:id="@+id/line_send_text"
android:layout_gravity="center_vertical"
android:layout_width="@dimen/line_view_1"
android:visibility="gone"
android:background="@color/color_DADADA"
android:layout_height="@dimen/dp_16" />
<ImageView
android:id="@+id/iv_send_text"
android:padding="@dimen/dp_12"
android:visibility="gone"
android:src="@drawable/ic_ai_chat_send_text"
android:layout_width="wrap_content"
android:layout_height="wrap_content" />
<ImageView
android:id="@+id/iv_chat_voice"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:padding="@dimen/dp_12"
android:src="@drawable/ic_chat_voice" />
</androidx.appcompat.widget.LinearLayoutCompat>
<androidx.constraintlayout.widget.ConstraintLayout
android:id="@+id/ll_chat_voice"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="@drawable/shape_item_block_bg_white_r16"
android:visibility="gone"
app:layout_constraintBottom_toBottomOf="parent"
tools:visibility="gone">
<TextView
android:id="@+id/tv_chat_voice"
android:layout_width="@dimen/dp_0"
android:layout_height="match_parent"
android:layout_weight="1"
android:background="@null"
android:gravity="center"
android:paddingHorizontal="@dimen/dp_16"
android:paddingVertical="@dimen/dp_12"
android:text="按住 说话"
android:textColor="@color/color__333333"
android:textSize="@dimen/font_size_16"
android:textStyle="bold"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent" />
<ImageView
android:id="@+id/iv_chat_text"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:padding="@dimen/dp_12"
android:src="@drawable/ic_chat_text"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<androidx.constraintlayout.widget.ConstraintLayout
android:id="@+id/ll_chat_effect"
android:layout_width="@dimen/dp_0"
android:layout_height="@dimen/dp_0"
android:background="@drawable/shape_chat_voice_effect_bg"
android:visibility="gone"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
tools:visibility="visible">
<ImageView
android:id="@+id/iv_chat_effect"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:src="@drawable/ic_chat_effect_1"
android:visibility="gone"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<com.kshow.kxworkbench.utils.LineSoundEffectView
android:id="@+id/lineSoundEffectView"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
</androidx.constraintlayout.widget.ConstraintLayout>
</androidx.constraintlayout.widget.ConstraintLayout>
</androidx.constraintlayout.widget.ConstraintLayout>