欧美bbbwbbbw肥妇,免费乱码人妻系列日韩,一级黄片

基于Android實現(xiàn)一個簡易音樂播放器

 更新時間:2024年08月26日 09:13:23   作者:本科學的寫bug  
在Android平臺上開發(fā)一個音樂播放器是一項常見的任務,這涉及到對音頻文件的處理、用戶界面設計以及多媒體框架的運用,本項目基于樣例代碼進行擴展,雖然功能相對簡單,但包含了Android音樂播放器開發(fā)的核心知識點,需要的朋友可以參考下

1、簡介

一個簡易的音樂APP,主要練習對四大組件的應用。感興趣的可以看看。

播放界面如下:

在這里插入圖片描述

歌曲列表界面如下:

在這里插入圖片描述

項目結構如下:

在這里插入圖片描述

在這里插入圖片描述

接下來將對代碼做詳細介紹:

2、Music: 音頻對象

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

特殊說明: 由于本APP沒有使用數(shù)據(jù)庫而是使用 List 去存儲對象信息,所以沒找到合適的屬性值去唯一代表一個音頻。此id用的是 name+author進行字符串拼接而成。

這種做法很有可能會發(fā)生 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:layout_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; //當前的狀態(tài) 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);
            //這里會根據(jù)授權的結果,去調用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:
                // 如果請求被取消了,那么結果數(shù)組就是空的
                if (grantResults.length > 0 && grantResults[0] == PackageManager.PERMISSION_GRANTED) {
                    // 權限被授予了
                    initMusicList();//初始化數(shù)據(jù)
                    IS_PERMISSION = true;
                } else {
                    //拒絕了權限請求,彈出提示,然后退出程序。
                    ToastUtil.toast(MainActivity.this, "請前往設置授予權限");
                }
                break;
            default:
                break;
        }
    }

==注意:==當我們安裝完應用后第一次啟動時如果拒絕了權限請求。那么再次啟動應用時,它會默認為禁止此權限,且 ActivityCompat.requestPermissions()將不會再彈出權限授予框進行選擇。如果想獲取權限,只能手動去手機應用設置處授權。

IS_PERMISSION: 這玩意是干啥用的?

主要是考慮到下列情景:

如果第一次授權被拒絕了,程序雖然自動結束了,但我發(fā)現(xiàn)其實它仍在后臺進行(才疏學淺,沒找到徹底殺死進程的方法)。這個時候我們去手動授權結束后,再次打開APP(),其實是執(zhí)行了 onStop()->onRestart()->onResume()這樣一個流程(activity的生命周期)。那我們這時應該再去判斷一次,是否授權。如果缺少這次判斷,那么應用將會一直退出。(雖然我們手動授權了,但是app自己不知道,必須告訴它一聲)。

@Override
protected void onRestart() {
    super.onRestart();
    if (!IS_PERMISSION) {//當從后臺進入時,判斷應用是否已經有權限了 ,沒有就去申請
        requestPermissionByHand();
    }
}

為什么不放在 onResume()里面呢? 這個主要是會出現(xiàn)重復授權請求的情況(可以自己思考一下哈)。

仔細留意可以看到,我們在授權完成后,其實是去執(zhí)行了 BaeActivity.initMusicList()方法。

5.1.2 initMusicList(): 初始化音頻數(shù)據(jù)

@SuppressLint("Range")
protected void initMusicList() {
    musicList = new ArrayList<>();
    ContentResolver contentResolver = getContentResolver(); //系統(tǒng)提供的內容提供者,可以通過去去訪問一些數(shù)據(jù)。
    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內部,進行測試系統(tǒng)功能,可刪除
    //在上面系統(tǒng)結構圖中可以看到 ,我在 /res/raw 下放了兩首 MP3
    // 由于沒找到具體去直接遍歷的操作,所以這里使用了暴力去解決,即把文件名設置成有規(guī)律的,如: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 = "網(wǎng)絡歌手";
            //文件路徑
            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();

注冊廣播: 這里采用的是 本地廣播 + 動態(tài)注冊

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就是代表當前進度條的數(shù)據(jù)
        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處于不可點擊狀態(tài)。本應用啟動時是不會主動播放歌曲的,也就是處于 暫無歌曲狀態(tài)。seekBar此時應處于不可用狀態(tài)(因為有監(jiān)聽點擊事件,會導致一些錯誤)。

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

onCreate() 到這里就暫時先結束,我們要先去看服務。

6、MusicService

public class MusicService extends Service {

    //用來控制音樂的播放與暫停。系統(tǒng)自帶的
    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();

                    //攜帶數(shù)據(jù)
                    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);
                    //發(fā)送廣播
                    localBroadcastManager.sendBroadcast(intent);
                }
            };
            timer.schedule(timerTask,1,1000); // 1ms后,每1000ms執(zhí)行 一次 TimerTask;
            //總結下來就是,只要有 mediaPlay的存在,就把當前歌曲播放的具體時長 以廣播的形式發(fā)送,由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();
            }
        }
        //下面的暫停繼續(xù)和退出方法全部調用的是MediaPlayer自帶的方法
        public void pausePlay(){
            mediaPlayer.pause();//暫停播放音樂
        }
        public void continuePlay(){
            mediaPlayer.start();//繼續(xù)播放音樂
        }
        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();//釋放占用的資源
        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() {
    //歌曲放完了,相當于觸發(fā)一次下一首
    Flag = 0;//先暫停這一首,然后執(zhí)行下一首
    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", "發(fā)送消息");
            //如果此時是在歌曲列表界面,發(fā)個消息
            if (MusicListActivity.musicHandler != null) {
                Message message = new Message();
                message.what = MusicListActivity.UPDATE_TEXT;
                MusicListActivity.musicHandler.sendMessage(message);
            }
        }
    }, 2500);
}

總結來說:MusicReceiver 就復雜監(jiān)聽音樂的播放,動態(tài)的去更新 界面上時間及進度條的顯示。

if (format(currentPosition).equals(format(seekBar.getMax())) && Flag == 1) {//如果進度條已經到頭了
    handleEnd();
}

==提示:==這里簡單的提一下,為什么要判斷 format 之后的 字符串 而不是直接比較 currentPositionseekBar.getMax()。

因為我們接受的是廣播,且廣播一秒才發(fā)一次,再加上傳播產生的時間,在 ms 時間級內, currentPosition和seekBar.getMax()。大概不不會出現(xiàn)相等。所以這里比較的是格式化后的 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","會執(zhí)行幾次呢");
        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 implements 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;
    }
});

不知道還記不記得,前面有個地方發(fā)了一個消息。當歌曲播放完成后,如果我們正處于 MusicListActivity界面。會發(fā)送一條消息。然后 MusicListActivity就會接受這條消息,然后刷新當前頁面(主要就是為了更新 綠色的正在播放)。這里我先是用了notifyItemRangeChanged()去測試,但是發(fā)現(xiàn)如果一直待在這個界面,有綠色狀態(tài)的會變的不唯一,也是DeBug很久,沒解決,就用了這種 重置適配器 的暴力方法(大數(shù)據(jù)時不可取)。如果有別的方法,還請多多指教。

private void handleEnd() {
    //歌曲放完了,相當于觸發(fā)一次下一首
    Flag = 0;//先暫停這一首,然后執(zhí)行下一首
    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", "發(fā)送消息");
            //如果此時是在歌曲列表界面,發(fā)個消息
            if (MusicListActivity.musicHandler != null) {
                Message message = new Message();
                message.what = MusicListActivity.UPDATE_TEXT;
                MusicListActivity.musicHandler.sendMessage(message);
            }
        }
    }, 2500);
}

這個Activity功能較少。讓我們繼續(xù)回到MainActivity

8、onResume()

@Override
protected void onResume() {
    super.onResume();
    Intent intent = getIntent();
    //這個判斷是為了區(qū)別時初始化還是從 MusicListActivity 返回來的。
    if (intent != null && currentOrder != -1) {
        //從歌曲列表返回來時,更新正在播放的音頻對象
        currentMusic = musicList.get(currentOrder);//這個更新不會影響到播放,因為播放是 mediaPlayer 控制的
        //如果我們點擊的是正在播放的歌曲,那么我們就不會進行任何操作
        //如果歌曲不一樣,就會進行更新
        if (currentMusic != null && !CURRENT_ID.equalsIgnoreCase(currentMusic.getId())) {
            initMusicMessage();//更新展示界面
            btn_start.performClick(); //這個意思是 觸發(fā)一次 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:
/*
 *三種情況會觸發(fā)。
 * 1、剛進入界面,還沒有選擇任何歌曲
 * 2、歌曲播放中,點擊按鈕
 * 3、選歌界面返回后,觸發(fā)
*/

//1、剛進入界面,沒有選擇任何歌曲
if (currentOrder == -1) {
    startFirstMusic();//選中第一首歌進行播放
    break;
}
//如果二者不相等,說明發(fā)生了切歌
//什么時候不相等?還記的 onResume() 觸發(fā)了一次點擊事件不,就在這里
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) { //處于播放狀態(tài),點擊后暫停
        btn_start.setImageResource(R.drawable.start);
        musicBinder.pausePlay();
    } else {
        btn_start.setImageResource(R.drawable.pause);
        //這個地方要判斷下 是還沒有播放,還是繼續(xù)播放
        // 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) { //如果此時處于暫停狀態(tài)
        Flag = 1;  //更新狀態(tài)
        btn_start.setImageResource(R.drawable.pause); // 更新下圖標
    }
    if (nextFlag) {
        musicBinder.nextPlay(); //執(zhí)行下一首
    } else {
        musicBinder.lastPlay(); //執(zhí)行上一首
    }
    initMusicMessage(); //更新界面
    CURRENT_ID = currentMusic.getId(); //跟新 CURRNET_ID 的值,供后續(xù)使用
}

還有最后一個函數(shù)

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實現(xiàn)一個簡易音樂播放器的詳細內容,更多關于Android音樂播放器的資料請關注腳本之家其它相關文章!

相關文章

最新評論