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
相关产品推荐
相关产品推荐

