【android 安卓 模仿豆包语音条效果】

语音条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>
相关推荐
千里马学框架17 小时前
一起学 Android 14:ShellTransition 屏幕旋转过程深度剖析
android·智能手机·性能优化·framework·性能·屏幕旋转·rotation
美狐美颜SDK开放平台17 小时前
开发直播APP时如何接入视频美颜SDK?开发流程与注意事项
android·人工智能·计算机视觉·音视频·直播美颜sdk
AFinalStone18 小时前
Android7 SystemUI源码解析(七)Keyguard锁屏模块深度解析
android·systemui
致远ccc18 小时前
Google Play 上架前如何测试 App?多国家 Android 环境测试
android·app测试·googleplay·多国家应用测试
ttyyttemo20 小时前
Kotlin 协程中的 Job 结构化并发与取消
android
sun00770020 小时前
tbox 4g/5g切换,导致wan ip 改变,导致车机旧网络不可用。需要重启车机才行
android
其实防守也摸鱼21 小时前
内网穿透与反向代理:原理、工具与实战指南
android·大数据·运维·安全·网络安全·自动化·渗透
AFinalStone1 天前
Android7 SystemUI 源码解析(四)NavigationBar 导航栏与 SystemBars
android·systemui
JMchen1 天前
属性动画原理与高级动画实现
android·kotlin·canvas
AFinalStone1 天前
Android7 SystemUI 源码解析(二)启动流程深度解析
android·systemui