开发者

基于Android实现一个简易音乐播放器

目录
  • 1、简介
  • 2、Music: 音频对象
  • 3、BaseActivity:
  • 4、activity_main.XML:
  • 5、MainActivity:
    • 5.1、onCreate():
  • 6、MusicService
    • 7、MusicListActivity
      • 8、onResume()
        • 9、点击事件处理

          1、简介

          一个简易的音乐APP,主要练习对四大组件的应用。感兴趣的可以看看。

          播放界面如下:

          基于Android实现一个简易音乐播放器

          歌曲列表界面如下:

          基于Android实现一个简易音乐播放器

          项目结构如下:

          基于Android实现一个简易音乐播放器

          基于Android实现一个简易音乐播放器

          接下来将对代码做详细介绍:

          2、Music: 音频对象

          public class Music {
              private String name;//歌曲的名称
              private String author;//歌曲的作者(歌手)
              private long time;//歌曲的时长
              private String id;//歌曲的唯一Id  
              private String url;//歌曲的地址
          }
          

          特殊说明: 由于本APP没有使用数据库而是使用 List 去存储对象信息,所以没找到合适的属性值去唯一代表一个音频。此id用的是 name+author进行字符串拼接而成。

          这种做法很有可能会发生 id 碰撞。如有严格需求,请自行解决。

          3、BaseActivity:

          自定义Activity去继承AppCompatActivity。此Class主要用来存放一些全局都要访问的东西。

          public class BaseActivity extends AppCompatActivity {
          
              //用来存放音频对象。
              public static List<Music> musicList = null;
              
              //用来标志 当前播放的是第几首歌, 值代表在 musicList 中的下标。
              public static int currentOrder = -1;
              
              //不多解释,就看成一个解析音频文件的工具即可
              protected MediaMetadataRetriever retriever;
          
              @Override
              protected void onCreate(@Nullable Bundle savedInstanceState) {
                  super.onCreate(savedInstanceState);
                  retriever = new MediaMetadataRetriever();
              }
          
              @SuppressLint("Range")
              protected void initMusicList() {
                  //此处是有代码的,后面再具体讲解
              }
                 
          }
          

          4、activity_main.xml:

          主界面,这里主要是用了一个相对布局,没什么好讲的。

          后面会把整个项目代码放到资源里,免费使用。

          <?xml version="1.0" encoding="utf-8"?>
          <LinearLayout 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:orientation="vertical">
          
              <RelativeLayout
                  android:layout_width="match_parent"
                  android:lhttp://www.devze.comayout_height="match_parent"
                  tools:ignore="UselessParent">
          
                  <LinearLayout
                      android:id="@+id/title"
                      android:orientation="horizontal"
                      android:layout_width="match_parent"
                      android:layout_height="70sp"
                      android:layout_alignParentTop="true"
                      >
                      <TextView
                          android:layout_width="0dp"
                          android:layout_weight="5"
                          android:layout_height="match_parent"
                          android:layout_marginStart="5sp"
                          android:text="@string/app_name"
                          android:textSize="30sp"
                          android:textColor="#1295DA"
                          android:gravity="center|start"/>
                      <ImageButton
                          android:id="@+id/btn_list"
                          android:layout_width="0dp"
                          android:layout_weight="1"
                          android:layout_height="match_parent"
                          android:background="@drawable/list"
                          android:scaleType="fitCenter"/>
                  </LinearLayout>
          
          
                  <ImageButton
                      android:id="@+id/music"
                      android:layout_width="wrap_content"
                      android:layout_height="wrap_content"
                      android:background="@drawable/music"
                      android:layout_marginTop="70sp"
                      android:layout_centerInParent="true"
                      android:layout_below="@+id/title"
                      android:scaleType="fitCenter"/>
          
                  <LinearLayout
                      android:id="@+id/music_message"
                      android:layout_width="match_parent"
                      android:layout_height="wrap_content"
                      android:layout_marginTop="70sp"
                      android:layout_below="@+id/music"
                      android:orientation="vertical">
                      <TextView
                          android:id="@+id/tv_music_name"
                          android:layout_width="match_parent"
                          android:layout_height="match_parent"
                          android:layout_marginStart="10sp"
                          android:textSize="29sp"
                          android:textColor="#000000"
                          android:text="@string/default_music"/>
          
                      <TextView
                          android:id="@+id/tv_music_author"
                          android:layout_width="match_parent"
                          android:layout_height="match_parent"
                          android:layout_marginStart="10sp"
                          android:textSize="25sp"
                          android:text="@string/default_author"/>
          
                  </LinearLayout>
          
                  <SeekBar
                      android:id="@+id/seekBar"
                      android:layout_width="match_parent"
                      android:layout_height="wrap_content"
                      android:layout_marginTop="60sp"
                      android:layout_below="@+id/music_message"
                      />
          
                  <RelativeLayout
                      android:layout_below="@+id/seekBar"
                      android:layout_width="match_parent"
                      android:layout_height="wrap_content"
                      android:orientation="horizontal">
          
                      <TextView
                          android:id="@+id/tv_now_time"
                          android:layout_marginStart="10sp"
                          android:layout_width="wrap_content"
                          android:layout_height="wrap_content"
                          android:text="@string/default_music_time"/>
          
                      <TextView
                          android:id="@+id/tv_all_time"
                          android:layout_marginEnd="15sp"
                          android:layout_alignParentEnd="true"
                          android:layout_width="wrap_content"
                          android:layout_height="wrap_content"
                          android:text="@string/default_music_time"/>
                  </RelativeLayout>
          
          
          
                  <LinearLayout
                      android:layout_width="match_parent"
                      android:layout_height="80sp"
                      android:layout_alignParentBottom="true"
                      android:layout_marginBottom="20sp"
                      android:orientation="horizontal">
          
                      <ImageButton
                          android:id="@+id/btn_last"
                          android:layout_width="0dp"
                          android:layout_height="match_parent"
                          android:layout_marginEnd="1sp"
                          android:layout_weight="1"
                          android:background="@color/white"
                          android:scaleType="fitCenter"
                          android:src="@drawable/last" />
          
                      <ImageButton
                          android:id="@+id/btn_start"
                          android:layout_width="0dp"
                          android:layout_height="match_parent"
                          android:layout_weight="1"
                          android:background="@color/white"
                          android:scaleType="fitCenter"
                          android:src="@drawable/start" />
          
                      <ImageButton
                          android:id="@+id/btn_next"
                          android:layout_width="0dp"
                          android:layout_height="match_parent"
                          android:layout_weight="1"
                          android:background="@color/white"
                          android:scaleType="fitCenter"
                          android:src="@drawable/next" />
          
                  </LinearLayout>
                  
              </RelativeLayout>
          </LinearLayout>
          

          5、MainActivity:

          主Activity 。代码很长,分模块讲解。

          属性:

          protected static String CURRENT_ID = "-1";  //当前正在播放的歌曲id
          protected static Music currentMusic;
          protected static boolean isBind = false;
          protected ImageButton btn_list, btn_last, btn_start, btn_next;
          protected SeekBar seekBar;
          protected TextView tv_music_name, tv_music_author, tv_all_time, tv_now_time;
          protected static int Flag = 0; //当前的状态 1:正在播放 0:暂停
          protected MusicService.MusicBinder musicBinder;
          protected MusicServiceConnection musicServiceConnection;
          public static LocalBroadcastManager localBroadcastManager;
          private static final int REQ_READ_EXTERNAL_STORAGE = 1;
          private static Boolean IS_PERMISSION = false; //是否授予权限
          

          5.1、onCreate():

          protected void onCreate(Bundle savedInstanceState) {
                  ...
                  //省略一些属性赋值。
                  //获取权限
                  requestPermissionByHand();
                  //注册广播
                  registerBroadCast();
                  //绑定服务
                  startAndBindService();//启动服务
          
                  //进度条
                  seekBar.setOnSeekBarChangeListener(new SeekBar.OnSeekBarChangeListener() {
                      @Override
                      public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) {
                      }
          
                      @Override
                      public void onStartTrackingTouch(SeekBar seekBar) {
                          if (currentMusic == null) {
                              ToastUtil.toast(MainActivity.this, "未播放歌曲");
                          }
                      }
          
                      @Override
                      public void onStopTrackingTouch(SeekBar seekBar) {
                          int progress = seekBar.getProgress();
                          tv_now_time.setText(format(progress));
                          musicBinder.seekTo(progress);
                      }
                  });
          
                  oprSeekBar(false);//刚开始不允许操作
          
              }
          

          5.1.1、requestPermissionByHand(): 因为要读取音频文件,第一步肯定要先进行授权。代码就是很标准的权限获取流程。

            public void requestPermissionByHand() {
                  //检查有没有这个权限
                  int checkWriteStoragePermission = ContextCompat.checkSelfPermission(
                          MainActivity.this, Manifest.permission.READ_EXTERNAL_STORAGE);
                  //如果没有被授予
                  if (checkWriteStoragePermission != PackageManager.PERMISSION_GRANTED) {
                      //请求权限,此处可以同时申请多个权限
                      ActivityCompat.requestPermissions(MainActivity.this,
                              new String[]{Manifest.permission.READ_EXTERNAL_STORAGE},
                              REQ_READ_EXTERNAL_STORAGE);
                      //这里会根据授权的结果,去调用onRequestPermissionsResult 相应的操作。
                  } else {
                      //如果已经有权限了,把这个标识设为 true,后面讲为什么。
                      IS_PERMISSION = true;
                      initMusicList();
                  }
              }
              
              @Override
              public void onRequestPermissionsResult(int requestCode, final String[] permissions, int[] grantResults) {
                  super.onRequestPermissionsResult(requestCode, permissions, grantResults);
                  switch (requestCode) {
                      case REQ_READ_EXTERNAL_STORAGE:
                          // 如果请求被取消了,那么结果数组就是空的
                          if (grantResults.length > 0 && grantResults[0] == PackageManager.PERMISSION_GRANTED) {
                              // 权限被授予了
                              initMusicList();//初始化数据
                              IS_PERMISSION = true;
                          } else {
                              //拒绝了权限请求,弹出提示,然后退出程序。
                              ToastUtil.toast(MainActivity.this, "请前往设置授予权限");
                          }
                          break;
                      default:
                          break;
                  }
              }
          

          ==注意:==当我们安装完应用后第一次启动时如果拒绝了权限请求。那么再次启动应用时,它会默认为禁止此权限,且 ActivityCompat.requestPermissions()将不会再弹出权限授予框进行选择。如果想获取权限,只能手动去手机应用设置处授权。

          IS_PERMISSION: 这玩意是干啥用的?

          主要是考虑到下列情景:

          如果第一次授权被拒绝了,程序虽然自动结束了,但我发现其实它仍在后台进行(才疏学浅,没找到彻底杀死进程的方法)。这个时候我们去手动授权结束后,再次打开APP(),其实是执行了 onStop()->onRestart()->onResume()这样一个流程(activity的生命周期)。那我们这时应该再去判断一次,是否授权。如果缺少这次判断,那么应用将会一直退出。(虽然我们手动授权了,但是app自己不知道,必须告诉它一声)。

          @Override
          protected void onRestart() {
              super.onRestart();
              if (!IS_PERMISSION) {//当从后台进入时,判断应用是否已经有权限了 ,没有就去申请
                  requestPermissionByHand();
              }
          }
          

          为什么不放在 onResume()里面呢? 这个主要是会出现重复授权请求的情况(可以自己思考一下哈)。

          仔细留意可以看到,我们在授权完成后,其实是去执行了 BaeActivity.initMusicList()方法。

          5.1.2 initMusicList(): 初始化音频数据

          @SuppressLint("Range")
          protected void initMusicList() {
              musicList = new ArrayList<>();
              ContentResolver contentResolver = getContentResolver(); //系统提供的内容提供者,可以通过去去访问一些数据。
              Cursor cursor = null;
          编程客栈
              //读取sd卡
              //这一部分直接用就行
              try {
                  cursor = contentResolver.query(MediaStore.Audio.Media.EXTERNAL_CONTENT_URI,
                                                 null, null, null, null);
                  if (cursor != null) {
                      while (cursor.moveToNext()) {
                          //是否是音频
                          int isMusic = cursor.getInt(cursor.getColumnIndex(MediaStore.Audio.Media.IS_MUSIC));
                          //时长
                          long duration = cursor.getLong(cursor.getColumnIndex(MediaStore.Audio.Media.DURATION));
                          //是音乐并且时长大于1分钟
                          if (isMusic != 0 && duration >= 60 * 1000) {
                              //歌名
                              String musicName = cursor.getString(cursor.getColumnIndexOrThrow(MediaStore.Audio.Media.TITLE));
                              //歌手
                              String musicAuthor = cursor.getString(cursor.getColumnIndexOrThrow(MediaStore.Audio.Media.ARTIST));
                              //文件路径
                              String musicPath = cursor.getString(cursor.getColumnIndexOrThrow(MediaStore.Audio.Media.DATA));
                              //歌名,歌手,时长,专辑,图标,文件路径,sequence number of list in listview
                              Music music = new Music(musicName, musicAuthor, duration, musicName + musicAuthor, musicPath);
                              musicList.add(music);
                          }
                      }
                  }
          
              } catch (Exception e) {
                  e.printStackTrace();
              } finally {
                  if (cursor != null)
                      cursor.close();//用完要关闭
              }
              
          
              //主要是这一部分
              //这一部分是可有可无,上面一部分是读取的本地的音频文件
              //这一部分主要是 将两个音频文件塞进了app内部,进行测试系统功能,可删除
              //在上面系统结构图中可以看到 ,我在 /res/raw 下放了两首 MP3
              // 由于没找到具体去直接遍历的操作,所以这里使用了暴力去解决,即把文件名设置成有规律的,如:m1,m2这样。
              // 如果有好方法可以提出来。
              try {
                  for (int i = 1; i <= 2; i++) {
                      Uri uri = Uri.parse("android.resource://" + getPackageName() + "/raw/m" + i);
                      retriever.setDataSource(this,uri);
                      String musicName = retriever.extractMetadata(MediaMetadataRetriever.METADATA_KEY_TITLE);
                      if(musicName == null) musicName = "music"+i;
                      //歌手
                      String musicAuthor = retriever.extractMetadata(MediaMetadataRetriever.METADATA_KEY_ARTIST);
                      if(musicAuthor == null) musicAuthor = "网络歌手";
                      //文件路径
                      String musicPath = "android.resource://" + getPackageName() + "/raw/m" + i;
                      //时长
                      String duration = retriever.extractMetadata(MediaMetadataRetriever.METADATA_KEY_DURATION);
                      //歌名,歌手,时长,专辑,图标,文件路径,sequence number of list in listview
                      Music music = new Music(musicName, musicAuthor, Long.parseLong(duration), musicName + musicAuthor, musicPath);
                      musicList.add(music);
                  }
              }catch (Exception e){
                  e.printStackTrace();
              }finally {
                  if(retriever != null) retriever.release();
              }
          }
          

          到这里 requestPermissionByHand()就结束了,就是 授权+读文件

          5.1.3、registerBroadCast();

          注册广播: 这里采用的是 本地广播 + 动态注册

          private void registerBroadCast() {
              localBroadcastManager = LocalBroadcastManager.getInstance(this);
              MusicReceiver musicReceiver = new MusicReceiver();
              IntentFilter intentFilter = new IntentFilter();
              intentFilter.addAction("com.xhy.musicRunning");
              localBroadcastManager.registerReceiver(musicReceiver, intentFilter);
          }
          
          class MusicReceiver extends BroadcastReceiver {
              @Override
              public void onReceive(Context context, Intent intent) {
                  Bundle bundle = intent.getBundleExtra("bundle");
                  int currentPosition = bundle.getInt("currentPosition");
                  seekBar.setProgress(currentPosition);
                  tv_now_time.setText(format(currentPosition));
                  if (format(currentPosition).equals(format(seekBar.getMax())) && Flag == 1) {
                      handleEnd();
                  }
              }
          }
          

          ok,先到这里,后面再讲 MusicReceiver的操作。

          5.1.4、startAndBindService()

          private void startAndBindService() {
              Intent intent = new Intent(MainActivity.this, MusicService.class);
              musicServiceConnection = new MusicServiceConnection();
              startService(intent);
              bindService(intent, musicServiceConnection, BIND_AUTO_CREATE);
          }
          
          class MusicServiceConnection implements ServiceConnection {
              @Override
              public void onServiceConnected(ComponentName name, IBinder service) {
                  musicBinder = (MusicService.MusicBinder) service;
                  isBind = true;
              }
              @Override
              public void onServiceDisconnected(ComponentName name) {}
          }
          

          这就是很标准的服务绑定流程。

          5.1.5、seekBar.setOnSeekBarChangeListener()

          这种都比较好理解,不多讲。

          seekBar.setOnSeekBarChangeListener(new SeekBar.OnSeekBarChangeListener() {
              @Override
              public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) {
              }
          
              @Override
              public void onStartTrackingTouch(SeekBar seekBar) {
                  if (currentMusic == null) {
                      ToastUtil.toast(MainActivity.this, "未播放歌曲");
                  }
              }
              //主要看这个
              //当我们滑动或者点击进度条时,会跟随改变歌曲的进度。
              @Override
              public void onStopTrackingTouch(SeekBar seekBar) {
                  int progress = seekBar.getProgress(); // progress就是代表当前进度条的数据
                  tv_now_time.setText(format(progress)); //修改展示的当前时间(歌曲的进度)
                  musicBinder.seekTo(progress);
              }
          });
          
          

          format() : 将 ms 转化成 mm:ss 的格式

          private String format(long time) {
              int minute = 0;
              int second = 0;
              minute = (int) (time / (1000 * 60)) % 60;
              second = (int) (time / 1000) % 60;
              return String.format("%02d", minute) + ":" + String.format("%02d", second);
          }
          

          5.1.6、oprSeekBar():

          刚开始,seekBar处于不可点击状态。本应用启动时是不会主动播放歌曲的,也就是处于 暂无歌曲状态。seekBar此时应处于不可用状态(因为有监听点击事件,会导致一些错误)。

          private void oprSeekBar(Boolean clickable) { //禁止拖动
              seekBar.setClickable(clickable);
              seekBar.setEnabled(clickable);
              seekBar.setFocusable(clickable);
          }
          

          onCreate() 到这里就暂时先结束,我们要先去看服务。

          6、MusicService

          public class MusicService extends Service {
          
              //用来控制音乐的播放与暂停。系统自带的
              protected MediaPlayer mediaPlayer;
              
              //定时器
              protected Timer timer;
              
              //广播管理器
              //用的是 MainActivity中的
              public static LocalBroadcastManager localBroadcastManager; 
          
              public MusicService() {
              }
          
              @Override
              public void onCreate() {
                  super.onCreate();
                  mediaPlayer = new MediaPlayer();
                  localBroadcastManager = MainActivity.localBroadcastManager;
              }
          
              private void createTimer() {
                  if (timer == null) {
                      timer = new Timer();
                      TimerTask timerTask = new TimerTask() { //定时任务
                          @Override
                          public void run() {
                              //还没有播放器的时候,就直接退出。
                              if(mediaPlayer == null) return;
                              
                              //当前进度, mediaPlayer 自带API,获取当前音频播放到哪里了
                              int currentPosition = mediaPlayer.getCurrentPosition();
          
                              //携带数据
                              Bundle bundle=new Bundle();
                              bundle.putInt("currentPosition",currentPosition);
          
                              Intent intent = new Intent();
                              intent.setAction("com.xhy.musicRunning");
                              intent.setClassName("com.xhy.musicplayer","MainActivity&MusicReceiver");
                              intent.putExtra("bundle",bundle);
                              //发送广播
                              localBroadcastManager.sendBroadcast(intent);
                          }
                      };
                      timer.schedule(timerTask,1,1000); // 1ms后,每1000ms执行 一次 TimerTask;
                      //总结下来就是,只要有 mediaPlay的存在,就把当前歌曲播放的具体时长 以广播的形式发送,由MainActivity进行捕获与响应
                  }
              }
          
              @Override
              public IBinder onBind(Intent intent) {
                  return new MusicBinder();
              }
          
              //用来绑定服务,这样可以通过Activity 与服务进行交互了
              public class MusicBinder extends Binder {
                  public void play(String url){//String path
                      Uri uri= Uri.parse(url);
                      try{
                          //重置音乐播放器
                          mediaPlayer.reset();
                          //加载多媒体文件
                          mediaPlayer=MediaPlayer.create(getApplicationContext(),uri);
                          mediaPlayer.start();//播放音乐
                          createTimer();//添加计时器
                      }catch(Exception e){
                          e.printStackTrace();
                      }
                  }
                  //下面的暂停继续和退出方法全部调用的是MediaPlayer自带的方法
                  public void pausePlay(){
                      mediaPlayer.pause();//暂停播放音乐
                  }
                  pub编程lic void continuePlay(){
                      mediaPlayer.start();//继续播放音乐
                  }
                  public void seekTo(int progress){
                      mediaPlayer.seekTo(progress);//设置音乐的播放位置
                  }
                  
                 //播放下一首
                  public void nextPlay(){
                      //当前的下标加1,
                      BaseActivity.currentOrder +=1;
                      //确定下一首歌的坐标
                      if(BaseActivity.currentOrder == BaseActivity.musicList.size()) BaseActivity.currentOrder = 0;
                      //获取下一首歌的对象
                      Music nextMusic = BaseActivity.musicList.get(BaseActivity.currentOrder);
                      //播放
                      play(nextMusic.getUrl());
                  }
                  
                  //播放上一首
                  public void lastPlay(){
                      BaseActivity.currentOrder -=1;
                      if(BaseActivity.currentOrder == -1) BaseActivity.currentOrder = 0;
                      Music lastMusic = BaseActivity.musicList.get(BaseActivity.currentOrder);
                      play(lastMusic.getUrl());
                  }
              }
          
              @Override
              public void onDestroy() { //当服务被销毁就 销毁 mediaPlayer,释放资源
                  super.onDestroy();
                  if(mediaPlayer==null) return;
                  if(mediaPlayer.isPlaying()) mediaPlayer.stop();//停止播放音乐
                  mediaPlayer.release();//释放占用的资源
            android      mediaPlayer=null;//将player置为空
                  if(timer != null) timer = null;
              }
          }
          

          ok,此时我们回去看一下,广播接收器干了什么。

          class MusicReceiver extends BroadcastReceiver {
              @Override
              public void onReceive(Context context, Intent intent) {
                  Bundle bundle = intent.getBundleExtra("bundle");
                  int currentPosition = bundle.getInt("currentPosition");
                  seekBar.setProgress(currentPosition);//调整进度条
                  tv_now_time.setText(format(currentPosition)); //设置当前的播放时间
                  if (format(currentPosition).equals(format(seekBar.getMax())) && Flag == 1) {//如果进度条已经到头了
                      handleEnd();
                  }
              }
          }
          
          private void handleEnd() {
              //歌曲放完了,相当于触发一次下一首
              Flag = 0;//先暂停这一首,然后执行下一首
              btn_start.setImageResource(R.drawable.start);
              ToastUtil.toast(MainActivity.this, "即将播放下一首");
              //延迟2.5s,播放下一首
              new Handler().postDelayed(new Runnable() {
                  @Override
                  public void run() {
                      btn_next.performClick();
                      Log.d("TestRecycler", "发送消息");
                      //如果此时是在歌曲列表界面,发个消息
                      if (MusicListActivity.musicHandler != null) {
                          Message message = new Message();
                          message.what = MusicListActivity.UPDATE_TEXT;
                          MusicListActivity.musicHandler.sendMessage(message);
                      }
                  }
              }, 2500);
          }
          

          总结来说:MusicReceiver 就复杂监听音乐的播放,动态的去更新 界面上时间及进度条的显示。

          if (format(currentPosition).equals(format(seekBar.getMax())) && Flag == 1) {//如果进度条已经到头了
              handleEnd();
          }
          

          ==提示:==这里简单的提一下,为什么要判断 format 之后的 字符串 而不是直接比较 currentPositionseekBar.getMax()

          因为我们接受的是广播,且广播一秒才发一次,再加上传播产生的时间,在 ms 时间级内, currentPosition和seekBar.getMax()。大概不不会出现相等。所以这里比较的是格式化后的 s 级内。

          MusicService就到这里

          7、MusicListActivity

          歌曲列表界面。这里采用的是 RecyclerView 布局去展示。

          public class MusicListActivity extends BaseActivity {
              protected ImageButton btn_back;
              public static Handler musicHandler;
              public static final int UPDATE_TEXT = 1;
          
              @Override
              protected void onCreate(Bundle savedInstanceState) {
                  super.onCreate(savedInstanceState);
                  setContentView(R.layout.activity_music_list);
          
                  RecyclerView recyclerView = findViewById(R.id.recycle_view);
                  LinearLayoutManager layoutManager = new LinearLayoutManager(this);
                  recyclerView.setLayoutManager(layoutManager);
                  MusicAdapter musicAdapter = new MusicAdapter(musicList, currentOrder == -1 ? "-1":musicList.get(currentOrder).getId());
                  musicAdapter.setOnItemClickListener(new OnItemClickListener() { //给我们的 item 设置点击事件,代表选中这首歌
                      @Override
                      public void onItemClick(View view, int position) {
                          Music music = musicList.get(position);
                          if (music != null) {
                              Intent intent = new Intent(MusicListActivity.this, MainActivity.class);
                              currentOrder = position; //更新选中的小标,
                              startActivity(intent); // 回到 MainActivity ,
                          }
          
                      }
                  });
                  recyclerView.setAdapter(musicAdapter);
          
                  musicHandler = new Handler(new Handler.Callback() {
                      @Override
                      public boolean handleMessage(@NonNull Message msg) {
                          if (msg.what == UPDATE_TEXT){
                              //刷新 recycler
                              musicAdapter.setCurrentId(musicList.get(currentOrder).getId());
                              recyclerView.setAdapter(null);
                              recyclerView.setAdapter(musicAdapter);
                          }
                          return true;
                      }
                  });
          
                  btn_back = findViewById(R.id.btn_back);
                  btn_back.setOnClickListener(new View.OnClickListener() {
                      @Override
                      public void onClick(View v) {
                          finish();
                      }
                  });
              }
          }
          

          这里主要有两个部分需要注意。

          1、

          MusicAdapter musicAdapter = new MusicAdapter(musicList, currentOrder == -1 ? "-1":musicList.get(currentOrder).getId());
          

          我们在这里传了当前正在播放歌曲的 id 。因为我们要对这个做特殊处理。MusicAdapter 做的大部分都是标准的流程化处理

          public class MusicAdapter extends RecyclerView.Adapter<MusicAdapter.ViewHolder> {
              protected List<Music> myMusicList;
              protected  OnItemClickListener myItemListener;
              public String currentId;
              private static final String CHOOSE_COLOR = "#7FE67F";
          
              public  void setCurrentId(String id){
                  currentId = id;
              }
          
              public MusicAdapter(List<Music> musicList, String currentId) {
                  myMusicList = musicList;
                  this.currentId = currentId;
              }
          
              public void setOnItemClickListener(OnItemClickListener listener){
                  this.myItemListener = listener;
              }
          
              @NonNull
              @Override
              public ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
                  View view = LayoutInflater.from(parent.getContext()).inflate(R.layout.music_list, parent, false);
                  return new ViewHolder(view,myItemListener);
              }
          
              //在这里
              @Override
              public void onBindViewHolder(@NonNull ViewHolder holder, int position) {
                  Log.d("TestRecycler","会执行几次呢");
                  Music music = myMusicList.get(position);
                  holder.musicName.setText(music.getName());
                  holder.musicAuthor.setText(music.getAuthor());
                  //检测是否是正在播放的歌曲
                  //对于正在播放的歌曲要加绿处理。
                  if(currentId.equalsIgnoreCase(music.getId())){
                      Log.d("TestRecycler","匹配成功--"+music.getName());
                      holder.chooseFlag.setText("正在播放");
                      holder.musicName.setTextColor(Color.parseColor(CHOOSE_COLOR));
                      holder.musicAuthor.setTextColor(Color.parseColor(CHOOSE_COLOR));
                      holder.point.setTextColor(Color.parseColor(CHOOSE_COLOR));
                      holder.chooseFlag.setTextColor(Color.parseColor(CHOOSE_COLOR));
                  }
              }
          
              @Override
              public int getItemCount() {
                  return myMusicList.size();
              }
          
              class ViewHolder extends RecyclerView.ViewHolder imple编程ments View.OnClickListener {
                  TextView musicName;
                  TextView musicAuthor;
                  TextView point;
                  TextView chooseFlag;
          
                  public ViewHolder(View view, OnItemClickListener onItemClickListener) {
                      super(view);
                      myItemListener = onItemClickListener;
                      view.setOnClickListener(this);
                      musicName = view.findViewById(R.id.tv_list_name);
                      musicAuthor = view.findViewById(R.id.tv_list_author);
                      point = view.findViewById(R.id.point);
                      chooseFlag  = view.findViewById(R.id.tv_choose);
                  }
                  
                  @Override
                  public void onClick(View v) {
                      myItemListener.onItemClick(v,getPosition());
                  }
              }
          }
          
          

          2、

          musicHandler = new Handler(new Handler.Callback() {
              @Override
              public boolean handleMessage(@NonNull Message msg) {
                  if (msg.what == UPDATE_TEXT){
                      //刷新 recycler
                      musicAdapter.setCurrentId(musicList.get(currentOrder).getId());
                      recyclerView.setAdapter(null);
                      recyclerView.setAdapter(musicAdapter);
                  }
                  return true;
              }
          });
          

          不知道还记不记得,前面有个地方发了一个消息。当歌曲播放完成后,如果我们正处于 MusicListActivity界面。会发送一条消息。然后 MusicListActivity就会接受这条消息,然后刷新当前页面(主要就是为了更新 绿色的正在播放)。这里我先是用了notifyItemRangeChanged()去测试,但是发现如果一直待在这个界面,有绿色状态的会变的不唯一,也是DeBug很久,没解决,就用了这种 重置适配器 的暴力方法(大数据时不可取)。如果有别的方法,还请多多指教。

          private void handleEnd() {
              //歌曲放完了,相当于触发一次下一首
              Flag = 0;//先暂停这一首,然后执行下一首
              btn_start.setImageResource(R.drawable.start);
              ToastUtil.toast(MainActivity.this, "即将播放下一首");
              //延迟2.5s,播放下一首
              new Handler().postDelayed(new Runnable() {
                  @Override
                  public void run() {
                      btn_next.performClick();
                      Log.d("TestRecycler", "发送消息");
                      //如果此时是在歌曲列表界面,发个消息
                      if (MusicListActivity.musicHandler != null) {
                          Message message = new Message();
                          message.what = MusicListActivity.UPDATE_TEXT;
                          MusicListActivity.musicHandler.sendMessage(message);
                      }
                  }
              }, 2500);
          }
          

          这个Activity功能较少。让我们继续回到MainActivity

          8、onResume()

          @Override
          protected void onResume() {
              super.onResume();
              Intent intent = getIntent();
              //这个判断是为了区别时初始化还是从 MusicListActivity 返回来的。
              if (intent != null && currentOrder != -1) {
                  //从歌曲列表返回来时,更新正在播放的音频对象
                  currentMusic = musicList.get(currentOrder);//这个更新不会影响到播放,因为播放是 mediaPlayer 控制的
                  //如果我们点击的是正在播放的歌曲,那么我们就不会进行任何操作
                  //如果歌曲不一样,就会进行更新
                  if (currentMusic != null && !CURRENT_ID.equalsIgnoreCase(currentMusic.getId())) {
                      initMusicMessage();//更新展示界面
                      btn_start.performClick(); //这个意思是 触发一次 btn_start的点击事件。后面再讲,这里主要是理清是否需要切歌的逻辑。
                  }
              }
          }
          
          private void initMusicMessage() { //更新展示界面
              currentMusic = musicList.get(currentOrder);
              seekBar.setMax((int) currentMusic.getTime());
              seekBar.setProgress(0);
              tv_music_name.setText(currentMusic.getName());
              tv_music_author.setText(currentMusic.getAuthor());
              tv_all_time.setText(format(currentMusic.getTime()));
              tv_now_time.setText(R.string.default_music_time);
          }
          

          9、点击事件处理

          坚持住,就要结束了!

          btn_list : 点击后跳转到 歌曲列表。

          case R.id.btn_list: //展示歌曲列表
          if (IS_PERMISSION) {
              Intent intent = new Intent(this, MusicListActivity.class);
              startActivity(intent);
          } else {
              ToastUtil.toast(MainActivity.this, "请先前往授权");
          }
          break;
          

          btn_start: 情况最多的点击

          case R.id.btn_start:
          /*
           *三种情况会触发。
           * 1、刚进入界面,还没有选择任何歌曲
           * 2、歌曲播放中,点击按钮
           * 3、选歌界面返回后,触发
          */
          
          //1、刚进入界面,没有选择任何歌曲
          if (currentOrder == -1) {
              startFirstMusic();//选中第一首歌进行播放
              break;
          }
          //如果二者不相等,说明发生了切歌
          //什么时候不相等?还记的 onResume() 触发了一次点击事件不,就在这里
          if (!CURRENT_ID.equalsIgnoreCase(currentMusic.getId())) { //在歌曲列表选择了不同的歌曲
              if (Flag == 0) { //如果是暂停装填,则修改一下图标
                  btn_start.setImageResource(R.drawable.pause);
              }
              CURRENT_ID = currentMusic.getId();
              initMusicMessage();//初始化歌曲信息
              musicBinder.play(currentMusic.getUrl());//播放
          } else {
              //相等就是单纯的暂停与播放
              if (Flag == 1) { //处于播放状态,点击后暂停
                  btn_start.setImageResource(R.drawable.start);
                  musicBinder.pausePlay();
              } else {
                  btn_start.setImageResource(R.drawable.pause);
                  //这个地方要判断下 是还没有播放,还是继续播放
                  // play()是会从头开始重新播放的,所以不能乱用
                  if (seekBar.getProgress() == 0) {
                      musicBinder.play(currentMusic.getUrl());
                  } else {
                      musicBinder.continuePlay();
                  }
              }
              Flag = Flag == 1 ? 0 : 1;
          }
          break;
          

          btn_nextbtn_last 二者差不多

          case R.id.btn_last:
                          nextAndLast(false);
                          break;
          case R.id.btn_next:
                          nextAndLast(true);
                          break;
          
          private void nextAndLast(Boolean nextFlag) {
              if (currentOrder == -1) { //与开始按钮一样,最开始的时候,点击三个中的任意一个,都会选中第一首歌进行播放
                  startFirstMusic();
                  return;
              }
              if (Flag == 0) { //如果此时处于暂停状态
                  Flag = 1;  //更新状态
                  btn_start.setImageResource(R.drawable.pause); // 更新下图标
              }
              if (nextFlag) {
                  musicBinder.nextPlay(); //执行下一首
              } else {
                  musicBinder.lastPlay(); //执行上一首
              }
              initMusicMessage(); //更新界面
              CURRENT_ID = currentMusic.getId(); //跟新 CURRNET_ID 的值,供后续使用
          }
          

          还有最后一个函数

          private void startFirstMusic() {
              if (!IS_PERMISSION) { //如果没有授权,点击任何一个按钮,都会弹出提示,然后什么也不干
                  ToastUtil.toast(MainActivity.this, "请先前往授权");
                  return;
              }
              if (BaseActivity.musicList.isEmpty()) { //授权了,但是没有歌曲,也是弹出提示,然后啥也不干
                  ToastUtil.toast(MainActivity.this, "暂无曲目");
                  return;
              }
              //有歌曲就播放第一首
              currentOrder = 0;
              currentMusic = musicList.get(currentOrder);
              CURRENT_ID = currentMusic.getId();
              initMusicMessage();
              btn_start.setImageResource(R.drawable.pause);
              Flag = 1;
              musicBinder.play(musicList.get(currentOrder).getUrl());
              oprSeekBar(true)//设置我们的进度条可以进行点击、滑动。
          }
          

          以上就是基于Android实现一个简易音乐播放器的详细内容,更多关于Android音乐播放器的资料请关注编程客栈(www.devze.com)其它相关文章!

          0

          上一篇:

          下一篇:

          精彩评论

          暂无评论...
          验证码 换一张
          取 消

          最新开发

          开发排行榜