android 蓝牙语音转换成pcm文件,进行播放暂停停止操作

需求描述

最近在做蓝牙与android之间互联通信,有个需求,是通过指令,控制蓝牙开启录音,结束录音,录音过程中,将蓝牙传回的数据,转换成pcm文件,然后再做个文件列表,点击播放pcm,包括暂停,重新播放之类的操作。

蓝牙数据转pcm

蓝牙传输的数据,先转换成Byte[],然后再转换成pcm文件,保存到本地。

我们蓝牙协议的真正数据是从字节第4位开始

java 复制代码
public class ByteKit {

    private BitOperator bitOperator;
    private BCD8421Operater bcd8421Operater;
    private static final ByteKit mInstance = new ByteKit();


    public static ByteKit getInstance() {
        return mInstance;
    }
    private ByteKit(){
        bitOperator = new BitOperator();
        bcd8421Operater = new BCD8421Operater();
    }


    public float parseFloatFromBytes(byte[] data, int startIndex, int length) {
        return this.parseFloatFromBytes(data, startIndex, length, 0f);
    }

    private float parseFloatFromBytes(byte[] data, int startIndex, int length, float defaultVal) {
        try {
            // 字节数大于4,从起始索引开始向后处理4个字节,其余超出部分丢弃
            final int len = length > 4 ? 4 : length;
            byte[] tmp = new byte[len];
            System.arraycopy(data, startIndex, tmp, 0, len);
            return bitOperator.byte2Float(tmp);
        } catch (Exception e) {
            e.printStackTrace();
            return defaultVal;
        }
    }


    public String parseHexStringFormBytes(byte[] data, int startIndex, int length) {
        return this.parseHexStringFormBytes(data, startIndex, length, null);
    }

    private String parseHexStringFormBytes(byte[] data, int startIndex, int length, String defaultValue) {
        try {
            byte[] tmp = new byte[length];
            System.arraycopy(data, startIndex, tmp, 0, length);
            return ConvertUtils.bytes2HexString(tmp);
        } catch (Exception e) {
            e.printStackTrace();
            return defaultValue;
        }
    }

    public String parseStringFromBytes(byte[] data, int startIndex, int lenth) {
        return this.parseStringFromBytes(data, startIndex, lenth, null);
    }

    private String parseStringFromBytes(byte[] data, int startIndex, int lenth, String defaultVal) {
        try {
            byte[] tmp = new byte[lenth];
            System.arraycopy(data, startIndex, tmp, 0, lenth);
            return new String(tmp, "gbk");
        } catch (Exception e) {
            e.printStackTrace();
            return defaultVal;
        }
    }

    public String parseBcdStringFromBytes(byte[] data, int startIndex, int lenth) {
        return this.parseBcdStringFromBytes(data, startIndex, lenth, null);
    }

    private String parseBcdStringFromBytes(byte[] data, int startIndex, int lenth, String defaultVal) {
        try {
            byte[] tmp = new byte[lenth];
            System.arraycopy(data, startIndex, tmp, 0, lenth);
            return this.bcd8421Operater.bcd2String(tmp);
        } catch (Exception e) {
            e.printStackTrace();
            return defaultVal;
        }
    }


    public int parseIntFromBytes(byte[] data, int startIndex, int length) {
        return this.parseIntFromBytes(data, startIndex, length, 0);
    }

    private int parseIntFromBytes(byte[] data, int startIndex, int length, int defaultVal) {
        try {
            // 字节数大于4,从起始索引开始向后处理4个字节,其余超出部分丢弃
            final int len = length > 4 ? 4 : length;
            byte[] tmp = new byte[len];
            System.arraycopy(data, startIndex, tmp, 0, len);
            return bitOperator.byteToInteger(tmp);
        } catch (Exception e) {
            e.printStackTrace();
            return defaultVal;
        }
    }

    public Long parseLongFromBytes(byte[] data, int startIndex, int length) {
        try {
            // 字节数大于4,从起始索引开始向后处理4个字节,其余超出部分丢弃
            final int len = length > 4 ? 4 : length;
            byte[] tmp = new byte[len];
            System.arraycopy(data, startIndex, tmp, 0, len);
            return bitOperator.fourBytesToLong(tmp);
        } catch (Exception e) {
            e.printStackTrace();
            return 0L;
        }
    }

    public byte[] str2Bcd(String asc) {
        int len = asc.length();
        int mod = len % 2;

        if (mod != 0) {
            asc = "0" + asc;
            len = asc.length();
        }

        byte abt[] = new byte[len];
        if (len >= 2) {
            len = len / 2;
        }

        byte bbt[] = new byte[len];
        abt = asc.getBytes();
        int j, k;

        for (int p = 0; p < asc.length()/2; p++) {
            if ( (abt[2 * p] >= '0') && (abt[2 * p] <= '9')) {
                j = abt[2 * p] - '0';
            } else if ( (abt[2 * p] >= 'a') && (abt[2 * p] <= 'z')) {
                j = abt[2 * p] - 'a' + 0x0a;
            } else {
                j = abt[2 * p] - 'A' + 0x0a;
            }

            if ( (abt[2 * p + 1] >= '0') && (abt[2 * p + 1] <= '9')) {
                k = abt[2 * p + 1] - '0';
            } else if ( (abt[2 * p + 1] >= 'a') && (abt[2 * p + 1] <= 'z')) {
                k = abt[2 * p + 1] - 'a' + 0x0a;
            }else {
                k = abt[2 * p + 1] - 'A' + 0x0a;
            }

            int a = (j << 4) + k;
            byte b = (byte) a;
            bbt[p] = b;
        }
        return bbt;
    }
}

语音协议的数据是从第10位开始,数据长度是4,5两位,做如下处理

java 复制代码
  int dataLength = ByteKit.getInstance().parseIntFromBytes(data, 4, 2);
                    byte[] result = new byte[dataLength];
                    System.arraycopy(data, 10, result, 0, result.length);

                    ibleResponse.CONTROL_AUDIO(result);

byte[]转pcm文件

java 复制代码
public class ByteToPcmConverter {

    public static void convertByteToPcm(byte[] bytes, String filePath) throws IOException {

        File file = new File(filePath);
        FileOutputStream outputStream = new FileOutputStream(file,true);
        outputStream.write(bytes);
        outputStream.close();
    }

//    // 使用示例
//    public static void main(String[] args) {
//        byte[] bytes = {/* 这里填入你的byte数组 */};
//        String pcmFilePath = "/path/to/your/pcmfile.pcm";
//        String outputPath= FileUtil.getSDPath(App.getInstance(),"");
//        try {
//            convertByteToPcm(bytes, pcmFilePath);
//        } catch (IOException e) {
//            e.printStackTrace();
//        }
//    }


}

在获取数据之前,比如在点击录音按钮的时候,需要生产文件,文件保存到应用私密空间,在android高版本上,需要特殊处理

java 复制代码
outputPath= FileUtil.getSDPath(App.getInstance(),name+".pcm");
      File file=new File(outputPath);
        if(!file.exists()){
            try {
                file.createNewFile();

            } catch (IOException e) {
                throw new RuntimeException(e);
            }
        }

接收数据,并转换,我这还做了个页面提示

java 复制代码
 @Override
    public void CONTROL_AUDIO(byte[] bytes) {

            try {

                ByteToPcmConverter.convertByteToPcm(bytes, outputPath);
                File file = new File(outputPath);
                if (file.exists() && file.isFile()) {
                    long length = file.length();
                    double mb = (double) length / (1024 * 1024);
                    String format = String.format("%.4f", mb);
                    tv_write_progress.setText("录音成功,文件大小"+ format +"mb");
                }

            } catch (IOException e) {
                e.printStackTrace();

            }
    }
java 复制代码
    /**
     * 获取SDCard文件路径
     * @param ctx
     * @return
     */
    public static String getSDPath(Context ctx,String fileName) {
       return getSDPath(ctx,"",fileName);
    }
    public static String getSDPath(Context ctx,String path,String fileName) {
        File sdDir = null;
        boolean sdCardExist = Environment.getExternalStorageState().equals(
                Environment.MEDIA_MOUNTED);// 判断sd卡是否存在
        if (sdCardExist) {
            if (Build.VERSION.SDK_INT >= 29) {
                //Android10之后
                sdDir = ctx.getExternalFilesDir(null);
//                sdDir=  ctx.getExternalCacheDir();
            } else {
                sdDir= Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS);
            }
        } else {
            sdDir = Environment.getRootDirectory();// 获取跟目录
        }
        if(!TextUtils.isEmpty(fileName)){
            if(!TextUtils.isEmpty(path))
                return sdDir.toString()+path+"/"+fileName;
            return sdDir.toString()+"/"+fileName;
        }
        if(!TextUtils.isEmpty(path))
            return sdDir.toString()+path;
        return sdDir.toString();
    }

录音文件页面播放,暂停和文件切换播放

重点是播放pcm文件

java 复制代码
public class PcmAudioUtils {

    private AudioTrack audioTrack;

    private String filePath;
    private File file;
    private  int bufferSizeInBytes;
    private Thread audioTrackThread;
    private int currentPosition;
    private double lastTime;
    private double elapsedSeconds;//已播放时间
    private double audioDurationInSeconds;//总时长
    int sampleRateInHz = 8000; // 采样率
    int channelConfig = AudioFormat.CHANNEL_OUT_MONO; // 声道配置
    int audioFormat = AudioFormat.ENCODING_PCM_16BIT; // 音频格式
    boolean pauseTag=false;
    private IAudioProgress iAudioProgress;
    private String  playState="none";


    public PcmAudioUtils(String filePath) {
        file = new File(filePath);
        init();
    }

    /**
     * 初始化
     */
    public void init() {

        if (!file.exists()) return;

        int streamType = AudioManager.STREAM_MUSIC; // 音频流类型

         bufferSizeInBytes = AudioTrack.getMinBufferSize(sampleRateInHz, channelConfig, audioFormat); // 缓冲区大小

        /**
         * 设置音频信息属性
         * 1.设置支持多媒体属性,比如audio,video
         * 2.设置音频格式,比如 music
         */
        AudioAttributes attributes = new AudioAttributes.Builder()
                .setUsage(AudioAttributes.USAGE_MEDIA)
                .setContentType(AudioAttributes.CONTENT_TYPE_MUSIC)
                .build();
        /**
         * 设置音频格式
         * 1. 设置采样率
         * 2. 设置采样位数
         * 3. 设置声道
         */
        AudioFormat format = new AudioFormat.Builder()
                .setSampleRate(sampleRateInHz)
                .setEncoding(AudioFormat.ENCODING_PCM_16BIT)
                .setChannelMask(channelConfig)
                .build();
        audioTrack = new AudioTrack(attributes,format,bufferSizeInBytes,AudioTrack.MODE_STREAM, AudioManager.AUDIO_SESSION_ID_GENERATE);

    }

    /**
     * 停止播放录音,并释放资源
     */
    public void stopPlay() {
        init();
        playState="stopPlay";
        if (audioTrack != null) {
            audioTrack.release();
        }
    }

    /**
     * 暂停
     */
    public void pausePlay() {
        init();
        if (audioTrack != null) {
            audioTrack.pause();
        }
//        if(audioTrackThread!=null){
//            audioTrackThread.interrupt();
//        }
        pauseTag=true;
        playState="pausePlay";
    }


    public void play(){
        init();
        long currentTimeMillis = SystemClock.elapsedRealtime();
        pauseTag=false;

        if(audioTrack==null){
            return;
        }
        audioTrack.play();
        playState="play";
        audioTrackThread=   new Thread(() -> {
            FileInputStream fileInputStream = null;
            lastTime=(double)currentTimeMillis / 1000;
            try {
                fileInputStream = new FileInputStream(file);
                byte[] buffer = new byte[bufferSizeInBytes];
                Log.i("PcmAudioUtils", "playAudio: "+bufferSizeInBytes);
                if (currentPosition > 0) {
                    fileInputStream.skip(currentPosition);
                }
                int read = 0;
                while (read != -1&&!pauseTag) {
                    read = fileInputStream.read(buffer);
                    //将缓冲区buffer写入audioTrack进行播放
                    int write = audioTrack.write(buffer, 0, buffer.length);
                    currentPosition+=write;
//                    if(iAudioProgress!=null){
//                        iAudioProgress.playProgress();
//                    }
                    elapsedSeconds+=( (double) SystemClock.elapsedRealtime() /1000-lastTime);

                   // Log.d("AudioTrack", "已播放时间:" +elapsedSeconds + "秒");
                }
                if(!pauseTag){
                    audioTrack.stop();
                    audioTrack.release();
                    if(iAudioProgress!=null){
                        iAudioProgress.setStop();
                    }
                    currentPosition=0;
                }

            } catch (Throwable e) {

            }

        });
        audioTrackThread.start();
    }

    public void setiAudioProgress(IAudioProgress iAudioProgress) {
        this.iAudioProgress = iAudioProgress;
    }

    public interface IAudioProgress{
        void playProgress(String progress);
        void setStop();
    }

    public void stop() {
        init();
        playState="none";
        audioTrack.stop();
        audioTrack.release();

    }

    public String getPlayState() {
        return playState;
    }
}

每次在播放或者暂停的时候,都需要调用一下init()方法,要不然回报未初始化的错误,这块正常播放应该没事,就是切换播放状态,会报错,不知道有没有其他处理办法。

剩下的就简单了,大概逻辑就是:

读取本地存储的pcm文件,在列表上展示,然后点击播放,再次点击暂停,再点击继续播放,播放完成,播放按钮变成未播放,播放途中,点击其他文件播放,正在播放的文件停止。长按文件可以删除

activity_voice_recording.xml

html 复制代码
<?xml version="1.0" encoding="utf-8"?>
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:background="#F7F7F7"
>

<LinearLayout
    android:layout_width="match_parent"
    android:layout_height="match_parent"

    android:orientation="vertical"
    android:gravity="center"
   >


    <include layout="@layout/layout_toolbar" />


    <androidx.recyclerview.widget.RecyclerView
        android:id="@+id/recycler_view"
        android:layout_width="match_parent"
        android:layout_height="0dp"
        android:layout_weight="1"
        android:padding="@dimen/dp_10" />


    <ImageView
        android:id="@+id/iv_audio"
        android:layout_width="@dimen/dp_70"
        android:layout_height="@dimen/dp_70"
        android:layout_marginBottom="20dp"
        android:src="@mipmap/icon_start_recording"/>
    <TextView
        android:id="@+id/tv_write_progress"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:textSize="15sp"
        android:layout_marginLeft="@dimen/dp_10"
        android:layout_marginRight="@dimen/dp_10"
        android:textColor="@color/text_gray"
        tools:text="录音已写入"
        />

    <TextView
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:textSize="15sp"
        android:layout_margin="@dimen/dp_10"
        android:textColor="@color/text_gray"
        android:text="*点击列表左侧图标进行播放,长按进行删除"
      />
</LinearLayout>

   <RelativeLayout
       android:id="@+id/rl_recording"
       android:layout_width="match_parent"
       android:layout_height="match_parent">
       <ImageView
           android:id="@+id/iv_recording"
           android:layout_width="@dimen/dp_90"
           android:layout_height="@dimen/dp_90"
           android:layout_marginBottom="220dp"
           android:visibility="gone"
           android:src="@drawable/audio_anim_image"
           android:layout_centerHorizontal="true"
           android:layout_alignParentBottom="true"
           />
   </RelativeLayout>
</FrameLayout>

VoiceRecordingActivity.java

java 复制代码
public class VoiceRecordingActivity extends BaseActivity  {

    RecyclerView recycler_view;
    ImageView iv_audio;
    ImageView iv_recording;
    TextView tv_write_progress;
    RelativeLayout rl_recording;

    private List<AudioBean> dataEntityList = new ArrayList<>();
    private AudioAdapter adapter;
    private  String folderName="BLEAUDIO";
    private AnimationDrawable animationDrawable;
    private String outputPath;
    private String platFilePath;//播放音乐的文件地址
    private PcmAudioUtils audioPlay = null;
    private int selectItemPosition=-1;//已选中的列表项
    @Override
    public int getLayoutId() {
        return R.layout.activity_voice_recording;
    }

    @Override
    public void initViews(Bundle savedInstanceState) {
        iv_audio = findViewById(R.id.iv_audio);
        iv_recording = findViewById(R.id.iv_recording);
        recycler_view = findViewById(R.id.recycler_view);
        tv_write_progress= findViewById(R.id.tv_write_progress);
        setToolBarInfo("语音录制", true);


        // 初始化列表
        LinearLayoutManager linearLayoutManager = new LinearLayoutManager(this);
        recycler_view.setLayoutManager(linearLayoutManager);
        recycler_view.setHasFixedSize(true);
        recycler_view.setItemAnimator(new DefaultItemAnimator());
        recycler_view.addItemDecoration(new SpacesItemDecoration(ConvertUtils.dp2px(this, 5)));
        adapter = new AudioAdapter(dataEntityList);
        recycler_view.setAdapter(adapter);
        adapter.setOnLongClickListener(new OnItemLongClickListener() {
            @Override
            public void OnLongClickListener(Object o, int position) {
                AudioBean audioBean=(AudioBean)o;
                new XPopup.Builder(VoiceRecordingActivity.this).asConfirm(getRsString(R.string.hint), getString(R.string.notice_delete),
                        new OnConfirmListener() {
                            @Override
                            public void onConfirm() {
                                if(audioPlay!=null){
                                    audioPlay.stopPlay();
                                    audioPlay=null;
                                }

                               String  outputPath= FileUtil.getSDPath(App.getInstance(),audioBean.getAudioInfo());
                                File file=new File(outputPath);
                                if(file.exists()){
                                    try {
                                        file.delete();
                                        getFileList();
                                        ToastUtils.show(R.string.operate_successfully);
                                    } catch (Exception e) {
                                        throw new RuntimeException(e);
                                    }
                                }
                            }
                        }).show();
            }
        });

        adapter.setOnItemClickListener(new OnItemClickListener() {
            @Override
            public void onItemClick(Object o, int position) {
                AudioBean audioBean=(AudioBean)o;
                String filePath=FileUtil.getSDPath(App.getInstance(),audioBean.getAudioInfo());
                if(!filePath.equals(platFilePath)){
                    if(audioPlay!=null){
                        audioPlay.stop();
                    }
                    audioPlay=null;

                }
                platFilePath =filePath;
                if(audioPlay==null){
                    audioPlay = new PcmAudioUtils(platFilePath);
                }
                if(selectItemPosition==-1){//初始阶段,没有选中播放,直接播放选中的音频
                    audioPlay.play();
                    selectItemPosition=position;
                }else if(selectItemPosition==position){//如果点击的是上一次点击过的文件
                    if(audioPlay.getPlayState().equals("none")){//已经停止播放,重新播放
                        audioPlay.play();
                        selectItemPosition=position;
                    }else if(audioPlay.getPlayState().equals("play")){//已经在播放了,暂停播放
                        audioPlay.pausePlay();
                        selectItemPosition=-1;//播放停止,还原成未播放状态
                    }else if(audioPlay.getPlayState().equals("pausePlay")){//已经暂停播放,继续播放
                        audioPlay.play();
                        selectItemPosition=position;
                    }

                }else{//选中的是其他列表项,把正在播放的列表项停止,变成未播放状态,播放选中列表项的文件
                    audioPlay.stop();
                    audioPlay=null;
                    audioPlay = new PcmAudioUtils(platFilePath);
                    audioPlay.play();
                    selectItemPosition=position;
                }
                dataEntityList.get(position).setAudioProgress("");
                adapter.setPlayPosition(selectItemPosition);
                audioPlay.setiAudioProgress(new PcmAudioUtils.IAudioProgress() {
                    @Override
                    public void playProgress(String progress) {

                    }

                    @Override
                    public void setStop() {
                        final Handler mainHandler = new Handler(Looper.getMainLooper());
                        // 在子线程中更新数据并刷新RecyclerView
                        new Thread(new Runnable() {
                            @Override
                            public void run() {
                                // 这里进行数据的更新操作
                                // updateYourData();

                                // 使用Handler将刷新操作切换到UI线程
                                mainHandler.post(new Runnable() {
                                    @Override
                                    public void run() {
                                        // 在UI线程中刷新RecyclerView的适配器
                                        selectItemPosition = -1;//播放停止,还原成未播放状态
                                        adapter.setPlayPosition(selectItemPosition);
                                        audioPlay=null;
                                    }
                                });
                            }
                        }).start();

                    }
                });
            }
        });


        getFileList();
        iv_audio.setTag(R.mipmap.icon_start_recording);
        iv_audio.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                if(!BLEUtils.isGetToken()){
                    ToastUtils.show(R.string.ble_break_retry);
                    iv_audio.setTag(R.mipmap.icon_start_recording);
                    iv_audio.setImageResource(R.mipmap.icon_start_recording);
                    stopAudio();
                    return;
                }
                Integer resourceId = (Integer) iv_audio.getTag();
                if(resourceId==R.mipmap.icon_start_recording){
                    iv_audio.setTag(R.mipmap.icon_end_recording);
                    iv_audio.setImageResource(R.mipmap.icon_end_recording);
                    startAudio();
                }else{
                    iv_audio.setTag(R.mipmap.icon_start_recording);
                    iv_audio.setImageResource(R.mipmap.icon_start_recording);
                    stopAudio();
                }
            }
        });

    }
    private void startAudio(){
        iv_recording.setVisibility(View.VISIBLE);
        tv_write_progress.setVisibility(View.VISIBLE);
         animationDrawable = (AnimationDrawable) iv_recording.getDrawable();
        //直接就开始执行
        animationDrawable.start();

        String address = PreferencesUtils.getString("address");
        String name= address+"--"+ DateUtils.getCurrentTime();

        //保存到本地
        outputPath= com.smart.bing.utils.FileUtil.getSDPath(App.getInstance(),name+".pcm");
        File file=new File(outputPath);
        if(!file.exists()){
            try {
                file.createNewFile();

            } catch (IOException e) {
                throw new RuntimeException(e);
            }
        }

        if(BLEUtils.isGetToken()){
            LmAPI.CONTROL_AUDIO((byte) 0x1);
        }

    }

    private void stopAudio(){
        iv_recording.setVisibility(View.GONE);
        tv_write_progress.setVisibility(View.GONE);
        if(animationDrawable!=null){
            animationDrawable.stop();
            animationDrawable=null;
        }

        if(BLEUtils.isGetToken()){
            LmAPI.CONTROL_AUDIO((byte) 0x0);
        }
        getFileList();
    }

    public void getFileList() {
        dataEntityList.clear();
        String outputPath=FileUtil.getSDPath(App.getInstance(),"");
        File folder = new File(outputPath);
        File[] files = folder.listFiles();
        if (files != null) {
            Arrays.sort(files, new Comparator<File>() {
                @Override
                public int compare(File file1, File file2) {
                    return Long.compare(file2.lastModified(), file1.lastModified());
                }
            });

            for (File file : files) {

                String fileName = file.getName();
                String filePath = file.getAbsolutePath();
                // 处理文件逻辑,比如打印文件名和文件路径
                Log.d("File", "File Name: " + fileName + ", File Path: " + filePath);
                String fileExtension = file.getName().substring(file.getName().lastIndexOf(".") + 1);
                if("pcm".equals(fileExtension)){
                    AudioBean audioBean=new AudioBean();
                    audioBean.setAudioInfo(fileName);
                    audioBean.setAudioProgress("");
                    dataEntityList.add(audioBean);
                }

            }
        }
        adapter.notifyDataSetChanged();
    }


    @Override
    protected void onDestroy() {
        super.onDestroy();
       //关闭录音
        if(BLEUtils.isGetToken()){
            LmAPI.CONTROL_AUDIO((byte) 0x0);
        }
    }

    @Override
    protected void onPause() {
        super.onPause();
        if(audioPlay!=null){
            audioPlay.stopPlay();
            audioPlay=null;
        }
    }

    @Override
    public void CONTROL_AUDIO(byte[] bytes) {

            try {

                ByteToPcmConverter.convertByteToPcm(bytes, outputPath);
                File file = new File(outputPath);
                if (file.exists() && file.isFile()) {
                    long length = file.length();
                    double mb = (double) length / (1024 * 1024);
                    String format = String.format("%.4f", mb);
                    tv_write_progress.setText("录音成功,文件大小"+ format +"mb");
                }

            } catch (IOException e) {
                e.printStackTrace();

            }
    }

这一块有个坑,是在子线程更新adapter,直接更新会无效,需要实现子线程到主线程的转换。重点代码是:

java 复制代码
final Handler mainHandler = new Handler(Looper.getMainLooper());
                        // 在子线程中更新数据并刷新RecyclerView
                        new Thread(new Runnable() {
                            @Override
                            public void run() {
                                // 这里进行数据的更新操作
                                // updateYourData();

                                // 使用Handler将刷新操作切换到UI线程
                                mainHandler.post(new Runnable() {
                                    @Override
                                    public void run() {
                                        // 在UI线程中刷新RecyclerView的适配器
                                        selectItemPosition = -1;//播放停止,还原成未播放状态
                                        adapter.setPlayPosition(selectItemPosition);
                                        audioPlay=null;
                                    }
                                });
                            }
                        }).start();

AudioAdapter.java

java 复制代码
public class AudioAdapter extends RecyclerView.Adapter<AudioAdapter.StrokeHolder> {

    private OnItemClickListener onItemClickListener;
    private OnItemLongClickListener onLongClickListener;
    private List<AudioBean> dataEntityList = new ArrayList<>();

    private int playPosition=-1;//显示播放按钮的列表项
    public AudioAdapter(List<AudioBean> dataEntityList) {
        this.dataEntityList = dataEntityList;
    }

    @Override
    public StrokeHolder onCreateViewHolder(ViewGroup parent, int viewType) {
        View view = View.inflate(parent.getContext(), R.layout.item_audio_layout, null);

        return new StrokeHolder(view, onItemClickListener);
    }

    @Override
    public void onBindViewHolder(StrokeHolder holder, int position) {
        AudioBean resultEntity = dataEntityList.get(position);
        holder.setData(resultEntity);
        if(playPosition==position){
            holder.ivAudioStart.setImageResource(R.mipmap.icon_end);
            holder.ivAudioStart.setTag(R.mipmap.icon_end); // 存储资源 ID
        }else{
            holder.ivAudioStart.setImageResource(R.mipmap.icon_start);
            holder.ivAudioStart.setTag(R.mipmap.icon_start); // 存储资源 ID
        }
    }

    @Override
    public int getItemCount() {
        return dataEntityList.size();
    }

    @SuppressLint("NotifyDataSetChanged")
    public void setPlayPosition(int position){
        playPosition=position;
        notifyDataSetChanged();
    }
    public AudioBean getItemBean(int position) {
        return dataEntityList.get(position);
    }

    public void setOnItemClickListener(OnItemClickListener onItemClickListener) {
        this.onItemClickListener = onItemClickListener;
    }

    public void setOnLongClickListener(OnItemLongClickListener onLongClickListener) {
        this.onLongClickListener = onLongClickListener;
    }

    public void updateData(AudioBean audioBean) {
        if (!dataEntityList.contains(audioBean)) {
            dataEntityList.add(audioBean);
            notifyDataSetChanged();
        }
    }

    public void clearData() {
        dataEntityList.clear();
        notifyDataSetChanged();
    }

    class StrokeHolder extends RecyclerView.ViewHolder implements View.OnClickListener ,View.OnLongClickListener{
        TextView tvAudioInfo;
        TextView tvAudioProgress;
        ImageView ivAudioStart;
        OnItemClickListener onItemClick;
        public StrokeHolder(View itemView, OnItemClickListener onItemClickListener) {
            super(itemView);
            tvAudioInfo = (TextView) itemView.findViewById(R.id.tv_audio_info);
            tvAudioProgress = (TextView) itemView.findViewById(R.id.tv_audio_progress);
            ivAudioStart = itemView.findViewById(R.id.iv_audio_start);
            itemView.setLayoutParams(new RecyclerView.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT));

            this.onItemClick = onItemClickListener;
            itemView.setOnClickListener(this);
            itemView.setOnLongClickListener(this);



        }

        public void setData(AudioBean resultEntity) {
            tvAudioInfo.setText(resultEntity.getAudioInfo());
            tvAudioProgress.setText(resultEntity.getAudioProgress());

        }
        @Override
        public void onClick(View v) {
            if (onItemClick != null) {
                onItemClick.onItemClick(dataEntityList.get(getPosition()), getPosition());
            }
        }

        @Override
        public boolean onLongClick(View v) {
            if(onLongClickListener !=null){
                onLongClickListener.OnLongClickListener(dataEntityList.get(getPosition()), getPosition());
            }
            return true;
        }
    }
}
相关推荐
selt79111 小时前
Redisson之RedissonLock源码完全解析
android·java·javascript
Yao_YongChao12 小时前
Android MVI处理副作用(Side Effect)
android·mvi·mvi副作用
非凡ghost13 小时前
JRiver Media Center(媒体管理软件)
android·学习·智能手机·媒体·软件需求
席卷全城13 小时前
Android 推箱子实现(引流文章)
android
齊家治國平天下13 小时前
Android 14 系统中 Tombstone 深度分析与解决指南
android·crash·系统服务·tombstone·android 14
maycho12315 小时前
MATLAB环境下基于双向长短时记忆网络的时间序列预测探索
android
思成不止于此15 小时前
【MySQL 零基础入门】MySQL 函数精讲(二):日期函数与流程控制函数篇
android·数据库·笔记·sql·学习·mysql
brave_zhao16 小时前
达梦数据库(DM8)支持全文索引功能,但并不直接兼容 MySQL 的 FULLTEXT 索引语法
android·adb
sheji341616 小时前
【开题答辩全过程】以 基于Android的网上订餐系统为例,包含答辩的问题和答案
android
easyboot16 小时前
C#使用SqlSugar操作mysql数据库
android·sqlsugar