【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>
相关推荐
CV资深专家4 小时前
在 Android 框架中,接口的可见性规则
android
daifgFuture8 小时前
Android 3D球形水平圆形旋转,旋转动态更换图片
android·3d
二流小码农9 小时前
鸿蒙开发:loading动画的几种实现方式
android·ios·harmonyos
爱吃西红柿!10 小时前
fastadmin fildList 动态下拉框默认选中
android·前端·javascript
悠哉清闲10 小时前
工厂模式与多态结合
android·java
大耳猫11 小时前
Android SharedFlow 详解
android·kotlin·sharedflow
火柴就是我12 小时前
升级 Android Studio 后报错 Error loading build artifacts from redirect.txt
android
androidwork13 小时前
掌握 MotionLayout:交互动画开发
android·kotlin·交互
奔跑吧 android13 小时前
【android bluetooth 协议分析 14】【HFP详解 1】【案例一: 手机侧显示来电,但车机侧没有显示来电: 讲解AT+CLCC命令】
android·hfp·aosp13·telecom·ag·hf·headsetclient
Chenyu_31014 小时前
09.MySQL内外连接
android·数据库·mysql