You need to enable JavaScript to run this app. Android开发如何实现读取本地存储音频的自定义ListView
优惠活动
大模型
产品
解决方案
定价
更多

Android开发如何实现读取本地存储音频的自定义ListView

自定义ListView绑定自定义单条目布局实现方案

现有代码适配核心问题

  • 当前使用的系统默认ArrayAdapter仅支持向单TextView的简单布局填充内容,无法匹配你编写的含播放图标、独立标题、独立日期的三控件单条目布局
  • 原有音频日期转换逻辑存在Bug:代码中把Cursor列索引的int值直接传入日期格式化方法,无法得到正确的文件添加时间
  • 原有数据存储逻辑把标题和日期拼接为单个字符串,无法拆分绑定到布局内的两个独立TextView控件

实现步骤

1. 定义音频数据实体类

新建AudioInfo类替代原来的拼接字符串,单独存储每条音频的标题、格式化后的添加时间:

public class AudioInfo {
    private String audioTitle;
    private String addedDate;

    public AudioInfo(String audioTitle, String addedDate) {
        this.audioTitle = audioTitle;
        this.addedDate = addedDate;
    }

    public String getAudioTitle() {
        return audioTitle;
    }

    public String getAddedDate() {
        return addedDate;
    }
}

2. 编写自定义列表Adapter

继承ArrayAdapter,重写getView方法手动加载你编写的single_list_item.xml布局,绑定控件数据,同时通过ViewHolder机制优化列表滑动性能:

import android.content.Context;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ArrayAdapter;
import android.widget.TextView;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import java.util.List;

public class AudioListAdapter extends ArrayAdapter<AudioInfo> {
    private final LayoutInflater inflater;
    private final int itemLayoutResId;

    // 缓存条目控件,避免重复调用findViewById造成性能损耗
    private static class ViewHolder {
        TextView titleTv;
        TextView dateTv;
    }

    public AudioListAdapter(@NonNull Context context, @NonNull List<AudioInfo> dataList) {
        super(context, 0, dataList);
        this.inflater = LayoutInflater.from(context);
        this.itemLayoutResId = R.layout.single_list_item;
    }

    @NonNull
    @Override
    public View getView(int position, @Nullable View convertView, @NonNull ViewGroup parent) {
        ViewHolder holder;
        // 复用已有布局视图,减少inflate开销
        if (convertView == null) {
            convertView = inflater.inflate(itemLayoutResId, parent, false);
            holder = new ViewHolder();
            holder.titleTv = convertView.findViewById(R.id.list_title);
            holder.dateTv = convertView.findViewById(R.id.list_date);
            convertView.setTag(holder);
        } else {
            holder = (ViewHolder) convertView.getTag();
        }

        // 绑定当前位置的音频数据到控件
        AudioInfo currentAudio = getItem(position);
        if (currentAudio != null) {
            holder.titleTv.setText(currentAudio.getAudioTitle());
            holder.dateTv.setText(currentAudio.getAddedDate());
        }
        // 单条目布局内的播放图标已写死在XML中,无需动态赋值
        return convertView;
    }
}

3. 替换Activity内原有逻辑

  • 将原有的字符串列表、系统默认Adapter替换为实体类列表、自定义Adapter
  • 修复getAudio()方法中的日期转换Bug
// 替换原有变量声明
private ArrayList<AudioInfo> audioDataList;
private AudioListAdapter customAdapter;

// 替换原有upload()方法
private void upload() {
    audioDataList = new ArrayList<>();
    getAudio();
    customAdapter = new AudioListAdapter(this, audioDataList);
    listView.setAdapter(customAdapter);

    listView.setOnItemClickListener(new AdapterView.OnItemClickListener() {
        @Override
        public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
            // 通过position获取选中的音频对象,实现后续播放逻辑
            AudioInfo selectedAudio = audioDataList.get(position);
        }
    });
}

// 修复原有getAudio()的日期转换Bug
public void getAudio() {
    ContentResolver contentResolver = getContentResolver();
    Uri audioUri = MediaStore.Audio.Media.EXTERNAL_CONTENT_URI;
    SimpleDateFormat formatter = new SimpleDateFormat("dd/MM/yyyy hh:mm:ss", Locale.getDefault());
    Cursor audioCursor = contentResolver.query(audioUri, null, null, null, null);

    if(audioCursor != null && audioCursor.moveToFirst()) {
        int titleColIndex = audioCursor.getColumnIndex(MediaStore.Audio.Media.TITLE);
        int dateColIndex = audioCursor.getColumnIndex(MediaStore.Audio.Media.DATE_ADDED);

        do {
            String currentTitle = audioCursor.getString(titleColIndex);
            // 直接读取秒级时间戳,转毫秒后格式化
            long dateTimestamp = audioCursor.getLong(dateColIndex);
            String formattedDate = formatter.format(new Date(dateTimestamp * 1000));
            audioDataList.add(new AudioInfo(currentTitle, formattedDate));
        }
        while (audioCursor.moveToNext());
        // 关闭Cursor避免内存泄漏
        audioCursor.close();
    }
}

额外适配提醒

  • Android 13(API 33)及以上版本,READ_EXTERNAL_STORAGE权限不再生效,需替换为Manifest.permission.READ_MEDIA_AUDIO申请音频读取权限
  • 原代码中upload()方法内重复为listView赋值的冗余代码可以删除,onCreate中已完成控件初始化

内容的提问来源于stack exchange,提问作者Mike

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.11.10 07:24:04