最近我在学习在学习RecyclerView,跟着老司机玩转App,《Android App 开发入门与项目实战》很友好,比较适合新手入门,通过基础知识和案例相结合,慢慢掌握其中要点。 书中的音乐案例在我的Android Studio模拟器中运行,从媒体库MediaStore.Audio.Media.EXTERNAL_CONTENT_URI加载音频文件列表,显示只有两首歌曲,无论我怎么重启模拟器,结果还是如此。于是我选择从sdcard/Music下遍历文件,通过适配器加载到RecyclerView中。
[案例代码仓库](https://codechina.csdn.net/mirrors/aqi00/myapp) 推荐购买书籍来上手。
运行书中的案例,结果如下,显示模拟器中的两首歌曲,实际导入了9首。 一番操作之后,可以显示完整的音乐列表,除了最佳损友不能播放,或许是MediaPlayer不喜欢损友爱基友。
以下是具体的步骤: 1、AndroidManifest.xml加载存储卡读写权限:
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.MANAGE_EXTERNAL_STORAGE" />
运行在Android 10及更高版本手机,配置暂时停用分区存储。 <application android:requestLegacyExternalStorage=“true”
2、申请动态权限:方式一、用代码来申请权限;方式二、可以在手机设置中手动打开如下的权限。
3、写出每个音乐条目布局,没有点击播放时隐藏进度条,点击之后显示它。因为是progressBar,所以无法拖动播放进度。 item_audio.xml
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/ll_audio"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="40dp"
android:orientation="horizontal">
<TextView
android:id="@+id/tv_name"
android:layout_width="0dp"
android:layout_height="match_parent"
android:layout_weight="4"
android:gravity="left|center"
android:textColor="@color/black"
android:textSize="15sp" />
<TextView
android:id="@+id/tv_duration"
android:layout_width="0dp"
android:layout_height="match_parent"
android:layout_weight="1"
android:gravity="right|center"
android:textColor="@color/black"
android:textSize="15sp" />
</LinearLayout>
<LinearLayout
android:id="@+id/ll_progress"
android:layout_width="match_parent"
android:layout_height="30dp"
android:orientation="horizontal"
android:visibility="gone">
<ProgressBar
android:id="@+id/pb_audio"
style="?android:attr/progressBarStyleHorizontal"
android:layout_width="0dp"
android:layout_height="match_parent"
android:layout_weight="4" />
<TextView
android:id="@+id/tv_progress"
android:layout_width="0dp"
android:layout_height="match_parent"
android:layout_weight="1"
android:gravity="right|center"
android:textColor="@color/black"
android:textSize="15sp" />
</LinearLayout>
</LinearLayout>
4、主界面显示RecyclerView布局。 activity_audio_play.xml
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
android:padding="5dp">
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:gravity="center"
android:text="点击音频列表开始播放"
android:textColor="@color/black"
android:textSize="17sp" />
<LinearLayout
android:layout_width="match_parent"
android:layout_height="40dp"
android:layout_margin="2dp"
android:orientation="horizontal">
<TextView
android:layout_width="0dp"
android:layout_height="match_parent"
android:layout_weight="3"
android:gravity="left|center"
android:text="音频名称"
android:textColor="@color/black"
android:textSize="15sp" />
<TextView
android:layout_width="0dp"
android:layout_height="match_parent"
android:layout_weight="1"
android:gravity="right|center"
android:text="总时长"
android:textColor="@color/black"
android:textSize="15sp" />
</LinearLayout>
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/rv_audio"
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_weight="1" />
</LinearLayout>
5、创建bean的音乐信息类文件,提供信息给适配器,适配器作为沟通的桥梁,显示到RecyclerView。
public class AudioInfo {
private String title;
private int duration;
private String path;
private int progress = -1;
public String getTitle() {
return title;
}
public void setTitle(String title) {
this.title = title;
}
public int getDuration() {
return duration;
}
public void setDuration(int duration) {
this.duration = duration;
}
public String getPath() {
return path;
}
public void setPath(String path) {
this.path = path;
}
public int getProgress() {
return progress;
}
public void setProgress(int progress) {
this.progress = progress;
}
}
6、创建点击监听器接口
public class RecyclerExtras {
public interface OnItemClickListener {
void onItemClick(View view, int position);
}
}
7、创建适配器 循环视图有专门的循环适配器RecyclerView.Adapter,在setAdapter 方法之前,得先实现一个从 RecyclerView.Adapter 派生而来的适配器,用来定义列表项的界面布局及其控件操作。下面是实现循环适配器时有待重写的方法说明。 ●getItemCount: 获得列表项的数目。 ●onCreateViewHolder: 创建整个布局的视图持有者,可在该方法中指定列表项的布局文件。第二个输入参数为视图类型viewType,根据视图类型加载不同的布局,从而实现带头部的列表布局。 ●onBindViewHolder: 绑定列表项的视图持有者。可在该方法中操纵列表项的控件。 以上3种方法是必需的,每个自定义的循环适配器都要重写这3种方法。
●getltemViewType: 返回每项的视图类型。这里的类型与onCreateViewHolder 方法的viewType参数保持一致。 ●getItemld: 获得每个列表项的编号。 以上两种方法不是必需的,可以重写也可以不重写。 在我看来循环视图里的视图持有者是容纳视图,RecyclerView和adapter 在相互对话交流中创建了视图。先是RecyclerView调用getItemCount()询问adapter有多少条列表项的数目要显示,adapter告之具体的数目。接着RecyclerView开始创建ViewHolder,加载列表项的布局。然后adapter返回ViewHolder,每个ViewHolder容纳一个带item_audio.xml的布局。RecyclerView用onBindViewHolder: 绑定列表项的视图持有者对应的第一条列表项上,将数据绑定显示在ViewHolder上。之后同样的方式再绑定到第二条列表项上。… RecyclerView可以循环利用,超出屏幕的ViewHolder将会被回收利用,不会一直onCreateViewHolder(),但是会onBindViewHolder()直到你不滚动显示页面。如此可以节约资源,提高性能。
书中的内容:工作的步骤如下: 步骤1、在构造方法中传入消息列表。 步骤2、重写getItemCount方法,返回列表项的个数。 步骤3、定义一个由RecyclerView. ViewHolder派生而来的内部类,用作列表项的视图持有者。 步骤4、重写onCreateViewHolder 方法,根据指定的布局文件生成视图对象,并返回该视图对象对应的视图持有者。 步骤5、onBindViewHolder方法,从输入参数中的视图持有者获取各个控件实例,再操纵这些控件(设置文字、设置图片、设置点击监听器等)。
import android.content.Context;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.LinearLayout;
import android.widget.ProgressBar;
import android.widget.TextView;
import androidx.recyclerview.widget.RecyclerView;
import com.example.chapter13.R;
import com.example.chapter13.bean.AudioInfo;
import com.example.chapter13.util.MediaUtil;
import com.example.chapter13.widget.RecyclerExtras;
import java.util.List;
public class AudioRecycler extends RecyclerView.Adapter<RecyclerView.ViewHolder> {
private Context mContext;
private List<AudioInfo> audio_list ;
public AudioRecycler(Context context, List<AudioInfo> mp3audio_list) {
mContext = context;
audio_list = mp3audio_list;
}
public int getItemCount() {
return audio_list.size();
}
public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup vg, int viewType) {
View v = LayoutInflater.from(mContext).inflate(R.layout.item_audio, vg, false);
return new ItemHolder(v);
}
public void onBindViewHolder(RecyclerView.ViewHolder vh, final int position) {
ItemHolder holder = (ItemHolder) vh;
AudioInfo audio = audio_list.get(position);
holder.tv_name.setText(audio.getTitle());
holder.tv_duration.setText(MediaUtil.formatDuration(audio.getDuration()));
if (audio.getProgress() >= 0) {
holder.ll_progress.setVisibility(View.VISIBLE);
holder.pb_audio.setMax(audio.getDuration());
holder.pb_audio.setProgress(audio.getProgress());
holder.tv_progress.setText(MediaUtil.formatDuration(audio.getProgress()));
} else {
holder.ll_progress.setVisibility(View.GONE);
}
holder.ll_audio.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
if (mOnItemClickListener != null) {
mOnItemClickListener.onItemClick(v, position);
}
}
});
}
public class ItemHolder extends RecyclerView.ViewHolder {
public LinearLayout ll_audio;
public TextView tv_name;
public TextView tv_duration;
public LinearLayout ll_progress;
public ProgressBar pb_audio;
public TextView tv_progress;
public ItemHolder(View v) {
super(v);
ll_audio = v.findViewById(R.id.ll_audio);
tv_name = v.findViewById(R.id.tv_name);
tv_duration = v.findViewById(R.id.tv_duration);
ll_progress = v.findViewById(R.id.ll_progress);
pb_audio = v.findViewById(R.id.pb_audio);
tv_progress = v.findViewById(R.id.tv_progress);
}
}
private RecyclerExtras.OnItemClickListener mOnItemClickListener;
public void setOnItemClickListener(RecyclerExtras.OnItemClickListener listener) {
this.mOnItemClickListener = listener;
}
}
8、主程序AudioPlayer,MediaPlayer提供的方法虽多,基本的应用场景只有两个,一个是播放指定音频文件,另一个是退出页面时释放媒体资源。其中播放音频的场景需要历经下列步骤:重置播放器→设置媒体文件的路径→准备播放→开始播放。
import com.example.chapter13.widget.RecyclerExtras;
import androidx.appcompat.app.AppCompatActivity;
import androidx.recyclerview.widget.LinearLayoutManager;
import androidx.recyclerview.widget.RecyclerView;
import android.media.AudioManager;
import android.media.MediaPlayer;
import android.os.Bundle;
import android.os.Handler;
import android.os.Looper;
import android.os.Message;
import android.util.Log;
import android.view.View;
import com.example.chapter13.adapter.AudioRecycler;
import com.example.chapter13.bean.AudioInfo;
import java.io.File;
import java.util.ArrayList;
import java.util.List;
import java.util.Timer;
import java.util.TimerTask;
public class AudioPlayer extends AppCompatActivity implements RecyclerExtras.OnItemClickListener {
private RecyclerView rv_audio;
private List<AudioInfo> aAudio_list = new ArrayList<AudioInfo>();
private AudioRecycler mAdapter;
private MediaPlayer mMediaPlayer = new MediaPlayer();
private int mLastPosition = -1;
private Timer mTimer = new Timer();
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_audio_play);
rv_audio = findViewById(R.id.rv_audio);
rv_audio.addItemDecoration(new RecycleViewDivider(this, LinearLayoutManager.VERTICAL, R.drawable.divider_mileage));
File file=new File("/sdcard/Music");
final File[] files=file.listFiles();
if (files == null){
Log.e("Error","这是一个空目录");
}
for(int i =0;i<files.length;i++) {
AudioInfo audioInfo = new AudioInfo();
if (files[i].getName().endsWith(".mp3")) {
audioInfo.setTitle(files[i].getName().split(".mp3")[0]);
audioInfo.setPath(files[i].getAbsolutePath());
aAudio_list.add(audioInfo);
}
}
showAudioList();
}
private void showAudioList() {
LinearLayoutManager manager = new LinearLayoutManager(this, RecyclerView.VERTICAL, false);
rv_audio.setLayoutManager(manager);
mAdapter = new AudioRecycler(this, aAudio_list);
mAdapter.setOnItemClickListener((RecyclerExtras.OnItemClickListener) this);
rv_audio.setAdapter(mAdapter);
}
@Override
public void onItemClick(View view, final int position) {
if (mLastPosition!=-1 && mLastPosition!=position) {
AudioInfo last_audio = aAudio_list.get(mLastPosition);
last_audio.setProgress(-1);
aAudio_list.set(mLastPosition, last_audio);
mAdapter.notifyItemChanged(mLastPosition);
}
mLastPosition = position;
final AudioInfo audio = aAudio_list.get(position);
mTimer.cancel();
mMediaPlayer.reset();
mMediaPlayer.setAudioStreamType(AudioManager.STREAM_MUSIC);
try {
mMediaPlayer.setDataSource(audio.getPath());
mMediaPlayer.prepare();
audio.setDuration(mMediaPlayer.getDuration());
mMediaPlayer.start();
mMediaPlayer.setLooping(true);
} catch (Exception e) {
e.printStackTrace();
}
mTimer = new Timer();
mTimer.schedule(new TimerTask() {
@Override
public void run() {
audio.setProgress(mMediaPlayer.getCurrentPosition());
aAudio_list.set(position, audio);
mHandler.sendEmptyMessage(position);
}
}, 0, 1000);
}
private Handler mHandler = new Handler(Looper.myLooper()) {
@Override
public void handleMessage(Message msg) {
super.handleMessage(msg);
mAdapter.notifyItemChanged(msg.what);
}
};
}
|