1. 程式人生 > >Android 自定義音樂播放器實現

Android 自定義音樂播放器實現

Android自定義音樂播放器

一:首先介紹用了哪些Android的知識點:

1 MediaPlayer工具來播放音樂

2 Handle。因為存在定時任務(歌詞切換,動畫,歌詞進度條變換等)需要由Handle來處理Ui相關內容

3 動態許可權申請(該應用程式讀取本地歌曲,並且設定音質相關屬性)且這兩個許可權在Android6.0後都需要動態申請

4 手勢控制 (左劃和右劃需要滿足一定條件後可以進行切歌)

5 Service服務 (啟動Service 繫結Service 前臺Service)

6 BroadcastReceiver 廣播,Service與Activity,Activity與Activity存在動態互動,需要廣播實現

7 基本的重寫View能力和Intent互動資料的能力

8 Animation動畫 圖片旋轉 歌詞更新

二:實現過程(簡要步驟,下面會詳細講解)

1 先編寫MusicInfo工具類。因為我們是從手機記憶體中去讀取音樂的相關資訊,那麼讀出的資料該儲存到MusicInfo工具類集合中。

2 先申請許可權,然後再去手機記憶體將音樂及其相關的資訊讀出來 ,用一個ListView容器去裝載所有的本地音樂

PS:到了這裡基本的音樂資訊列表已經有了   這也是我們的主介面(音樂列表介面) 即: 展示音樂/歌曲列表;


3 這時候先不方去實現播放這類的功能,我們先去處理歌詞

這裡說明一下。一個歌詞檔案(.Lrc)裡面內容格式如下

(張衛健--真英雄)

可以發現他由時間戳和歌詞內容兩部分組成。有了這個資訊後。編寫LrcContent工具類,用於記錄歌詞內容和歌詞時間。然後去手機裡面尋找歌曲對應的歌詞檔案,將其編碼,讀出,裝載為LrcContent集合。

4 編寫Service類,Service主要用來處理:音樂播放,前臺服務。在播放狀態改變的時候與播放音樂的Activity進行通訊。該Service由主介面啟動,後面的Activity繫結即可

5 編寫播放音樂的Activity類(MusicPlay)。當我們從主介面(音樂列表介面)點選了一首具體的音樂時,就會調轉到該Activity,所有首先,主介面Activity需要傳遞一些資訊給該Activity。

(1) MusicInfo工具類集合。即手機中所有的音樂資訊

(2)當前點選的歌曲,傳遞位置(position)即可

好,現在我們播放音樂的Activity有了所有的音樂資訊,還有當前需要播放的歌曲位置。因為需要前臺服務,所以我們把音樂播放的控制權交給Service,我們去繫結服務,然後把所有的音樂資訊,還有當前需要播放的歌曲位置都傳遞給Serivce,Service來控制播放音樂。

6 好了,現在我們的程式可以播放音樂了,我們再來一步步完善細節,歌詞同步,該功能自定義View實現,最後顯現在播放音樂的Activity中。注意Mediaplayer有一個重要屬性:

mediaPlayer.getCurrentPosition()。該方法會返回當前播放時間,不過返回的時候時毫秒(重)。

自定義View(LrcView 顯示歌詞),該View中除了傳統的自定義View需要的OnDraw之類的方法外,還需要獲取第三步中的LrcContent集合,有了這個我們就有了所有的歌詞內容和歌詞相應的時間,那麼同步如何實現呢?音樂最終該View要顯示在播放音樂的Activity(MusicPlay)中,我們去MusicPlay的佈局檔案申明該View,然後在MusicPlay中編寫一個定時器,可以設定每一秒啟動一次,定時器傳送訊息,在Handle中接受訊息,處理訊息。Handle中我們需要:獲取歌曲當前播放時間,根據當前播放時間去LrcContent集合中尋求匹配的歌詞。用invalidate()方法,通知自定義View重繪,來同步更新歌詞

7 我們的音樂播放器還差一個重要的東西,音樂控制器部分。這部分需要來控制播放上一首,下一首,播放/暫停,音樂進度拖動,音量設定。

該部分不難,所以再這裡不詳細講。

三:效果圖  因為完整錄製的GIF太大,傳不上來所以分批處理



四:程式碼精講 程式碼量也不很大,但是全貼出來挨著講又影響閱讀。所以部分節選和重要知識點一併講解。

PS:原始碼中含有大量System.out.println("XXXX");語句。個人比較偏愛的一種測試方式。。應該不干擾閱讀,忘見諒

1 許可權獲取,我們要做的第一件事就是去記憶體讀取音樂相關資訊,那麼我們就需要獲得相關的許可權,從Android6.0開始部分許可權不僅需要在AndroidManifest.xml檔案中宣告,還需要在執行程式的時候動態獲取.這裡以讀取儲存許可權為例:

首先在AndroidManifest.xml中定義

<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />

在Java業務程式碼中先判斷是否已經有許可權,有許可權就不再申請,沒有就申請許可權

//首先檢查自身是否已經擁有相關許可權,擁有則不再重複申請
int check = checkSelfPermission(Manifest.permission.WRITE_EXTERNAL_STORAGE) ;
    //沒有相關許可權
if (check != PackageManager.PERMISSION_GRANTED)
    {
        //申請許可權  STORGE_REQUEST = 1ActivityCompat.requestPermissions(this , new String[] {Manifest.permission.WRITE_EXTERNAL_STORAGE} ,STORGE_REQUEST);
    }else {
    //已有許可權的情況下可以直接初始化程式
init();
}

當我們申請許可權後,去判斷使用者是否給與了相關許可權,如果賦予了就可以做我們的事情了

/*
申請許可權處理結果
 */
@Override
public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
     super.onRequestPermissionsResult(requestCode, permissions, grantResults);
     switch (requestCode)
     {
         case STORGE_REQUEST :
             if (grantResults.length > 0 && grantResults[0] == PackageManager.PERMISSION_GRANTED)
             {
                 //完成程式的初始化
init();
                 System.out.println("程式申請許可權成功,完成初始化") ;
             }
             else {
                 System.out.println("程式沒有獲得相關許可權,請處理");
             }
             break ;
     }

}

關於許可權這部分:有興趣的可以看下參考下這篇部落格

2 有了許可權,我們就可以從手機中去讀取音樂相關資訊了,這裡講解下如何去讀取資訊

(1) 我們需要先建立一個儲存音樂資訊MusicInfo工具類  

注意:程式碼沒貼完,還有屬性的get和set方法沒列出

public class MusicInfo implements Serializable {
    private  int _id = - 1;       //音樂標識碼
    private  int duration = -1 ;   //音樂時常
    private String artist = null ;    //音樂作者
    private String musicName= null ; //音樂名字
    private String album = null ;   //音樂檔案專輯
    private String title = null ;  //音樂檔案標題
    private int size  ;   //音樂檔案的大小  返回byte大小
    private String data ;  //獲取檔案的完整路徑
    private String album_id ; //實際儲存為音樂專輯團片
}   

(2)讀取音樂資訊。

Android中的音樂資訊儲存再手機資料庫中,那麼我們就需要去訪問資料庫。這裡介紹下如何訪問資料庫

首先要知道基本資料庫查詢操作,返回Cursor物件

public final Cursor query(Uri uri , //查詢路徑

                            String[] projection , //查詢指定的列

                            String selection, //查詢條件

                            String[] selectionArgs, //查詢引數

                           String sortOrder } //查詢結果的排序方式

 那麼就有了我們查詢音樂的操作。

//申明ContentResolver物件,用於訪問系統資料庫
private ContentResolver contentResolver ;
//用於裝載MusicInfo物件
private List<MusicInfo> musicInfos ;
//獲取系統的ContentResolver
contentResolver = getContentResolver() ;

//從資料庫中獲取指定列的資訊
mCursor = contentResolver.query(MediaStore.Audio.Media.EXTERNAL_CONTENT_URI ,
        new String[] {MediaStore.Audio.Media._ID ,
                MediaStore.Audio.Media.TITLE , MediaStore.Audio.Media.ALBUM ,
                MediaStore.Audio.Media.ARTIST , MediaStore.Audio.Media.DURATION ,
                MediaStore.Audio.Media.DISPLAY_NAME , MediaStore.Audio.Media.SIZE ,
                MediaStore.Audio.Media.DATA , MediaStore.Audio.Media.ALBUM_ID } , null ,null ,null) ;

我們已經查詢出了所有的音樂資訊,並且儲存在mCursor物件中,現在我們需要把音樂資訊裝載到musicInfos工具類集合中。

但是注意,音樂專輯圖片需要特殊處理:我們這是隻查詢出了音樂專輯id   MediaStore.Audio.Media.ALBUM_ID 我們需要根據音樂專輯id再去查詢音樂專輯圖片,用該圖片來作為音樂圖片。

/*
    獲取本地音樂專輯的圖片
 */
private String getAlbumArt(int album_id)
{
    String UriAlbum = "content://media/external/audio/albums" ;
    String projecttion[] =  new String[] {"album_art"} ;
    Cursor cursor = contentResolver.query(Uri.parse(UriAlbum + File.separator +Integer.toString(album_id)) ,
            projecttion , null , null , null);
    String album = null ;
    if (cursor.getCount() > 0 && cursor.getColumnCount() > 0)
    {
        cursor.moveToNext() ;
        album = cursor.getString(0) ;
    }
    //關閉資源資料
cursor.close();
    return album ;
}

這時還可能出現一個問題,可能存在本地專輯音樂圖片不存在,所以我們要進行判斷,沒有專輯圖片就使用預設的圖片。好了,現在我們就可以裝載音樂資訊了

musicInfos = new ArrayList<>() ;
for (int i = 0 ; i < mCursor.getCount() ; i++)
{
    Map<String , String> map = new HashMap<>() ;
    MusicInfo musicInfo = new MusicInfo() ;

    //列表移動
mCursor.moveToNext() ;

    //將資料裝載到List<MusicInfo>中
musicInfo.set_id(mCursor.getInt(0));
    musicInfo.setTitle(mCursor.getString(1));
    musicInfo.setAlbum(mCursor.getString(2));
    musicInfo.setArtist(mCursor.getString(3));
    musicInfo.setDuration(mCursor.getInt(4));
    musicInfo.setMusicName(mCursor.getString(5));
    musicInfo.setSize(mCursor.getInt(6));
    musicInfo.setData(mCursor.getString(7));
      //將資料裝載到List<Map<String ,String>>中
    //獲取本地音樂專輯圖片
String MusicImage = getAlbumArt(mCursor.getInt(8)) ;
    //判斷本地專輯的圖片是否為空
if (MusicImage == null)
    {
        //為空,用預設圖片
map.put("image" , String.valueOf(R.mipmap.timg)) ;
        musicInfo.setAlbum_id(String.valueOf(R.mipmap.timg));
    }else
{
        //不為空,設定專輯圖片為音樂顯示的圖片
map.put("image" , MusicImage) ;
        musicInfo.setAlbum_id(MusicImage);
    }
   // musicInfo.setAlbum_id(mCursor.getInt(8));
musicInfos.add(musicInfo) ;

3 MediaPlayer重要方法:MediaPlayer用來指定音樂檔案和播放相關動作,所以我們需要掌握與音樂播放相關的方法。

方法: getCurrentPosition() 

解釋:返回 Int, 得到當前播放位置  毫秒單位時間

方法: getDuration() 

解釋:返回 Int,得到檔案的時間 

方法:isLooping() 

解釋:返回 boolean ,是否迴圈播放

方法:isPlaying() 

解釋:返回 boolean,是否正在播放

方法:pause() 

解釋:無返回值 ,暫停

方法:release() 

解釋:無返回值,釋放 MediaPlayer 物件

方法:reset() 

解釋:無返回值,重置 MediaPlayer 物件

方法:seekTo(int msec) 

解釋:無返回值,指定播放的位置(以毫秒為單位的時間) 

方法:setDataSource(String path) 

解釋:無返回值,設定多媒體資料來源【根據 路徑】

方法:setLooping(boolean looping) 

解釋:無返回值,設定是否迴圈播放 

方法:start() 

解釋:無返回值,開始播放

方法:stop() 

解釋:無返回值,停止播放

方法:setVolume(float leftVolume, float rightVolume) 

解釋:無返回值,設定音量 

事件:setOnCompletionListener(MediaPlayer.OnCompletionListener listener) 

解釋:監聽事件,網路流媒體播放結束監聽

事件:setOnBufferingUpdateListener(MediaPlayer.OnBufferingUpdateListener listener) 

解釋:監聽事件,網路流媒體的緩衝監聽

事件:setOnErrorListener(MediaPlayer.OnErrorListener listener) 
解釋:監聽事件,設定錯誤資訊監聽

4 歌詞處理  說下思路把:首先根據歌曲去記憶體尋找對應的歌詞檔案,找到後解碼為歌詞內容和歌詞時間,並且把歌詞內容和個時間裝載到歌詞資訊類集合中。然後提供一個外部訪問的方法用於向外部輸出歌詞資訊

// 注:LrcContent是歌詞處理類 其中包含兩個引數,歌詞時間和歌詞內容
public class LrcProcess {

    //所有需要處理的歌詞物件
    private List<LrcContent> lrcList;
    //一個歌詞物件
    private LrcContent mLrcContent;

    /*
    建構函式完成物件的初始化
     */
    public LrcProcess() {
        lrcList = new ArrayList<>();
    }
     /*
    *從記憶體中讀取歌詞檔案,並轉換為String物件輸出
    */
     public String readLrc(String path)
     {
         //用StringBuild來儲存歌詞內容
         StringBuilder sb = new StringBuilder() ;
         //獲取歌詞檔案  因為傳入的檔案為MusicInfo類中的Data內容,其檔案為mp3,需要更換為lrc檔案
         File f = new File(path.replace(".mp3" , ".lrc")) ;
         try{
             //通過檔案流物件來獲取檔案內容並且匯入歌詞內容物件集合中(lrcList)
             FileInputStream inputStream = new FileInputStream(f) ;
             InputStreamReader streamReader = new InputStreamReader(inputStream , "utf-8") ;
             BufferedReader bufferedReader = new BufferedReader(streamReader) ;
             String tempStr = "" ;
             while((tempStr = bufferedReader.readLine()) != null)
             {
                 //實現字元替換
                 tempStr = tempStr.replace("[" , "") ;
                 tempStr = tempStr.replace("]" , "@") ;

                 //根據@分號對檔案分離
                 String[] splitData = tempStr.split("@") ;

               //  System.out.println("THE TEMP STR IS " + splitData[0]) ;

                 if (splitData.length > 1)
                 {
                     //新建歌詞內容物件
                     mLrcContent = new LrcContent();
                     //設定歌詞文字內容
                     mLrcContent.setLrcStr(splitData[1]);
                     //設定歌詞時間
                     int lrcTime = timeToStr(splitData[0]) ;
                     mLrcContent.setLrcTime(lrcTime);
                     //新增到列表
                     lrcList.add(mLrcContent);
                   //  System.out.println("錄入歌詞成功") ;
                 }else {
                    // System.out.println("錄入歌詞失敗") ;

                 }
             }
         }catch (FileNotFoundException e)
         {
             sb.append("木有歌詞檔案,趕緊去下載!...");
         } catch (UnsupportedEncodingException e) {
             e.printStackTrace();
             sb.append("木有讀取到歌詞哦!");
         } catch (IOException e) {
             e.printStackTrace();
             sb.append("木有讀取到歌詞哦!");
         }
         return sb.toString() ;

     }
     /*
     * 對歌詞檔案lrc中的時間內容進行轉碼
     * [00:02.32]陳奕迅   時間分別代表分,秒,毫秒
     * [00:03.43]好久不見
      */
     public int timeToStr(String timeStr)
     {
         timeStr = timeStr.replace(":" , ".") ;
         timeStr = timeStr.replace("." , "@") ;
         String []splitTime = timeStr.split("@") ;

         //分離出分, 秒, 毫秒 
         int minute = Integer.parseInt(splitTime[0]) ;
         int second = Integer.parseInt(splitTime[1]) ;
         int millisSecond = Integer.parseInt(splitTime[2]) ;
         int time = (minute * 60 + second) * 1000 + millisSecond ;
         return time ;
     }
     /*
     提供一個外界方法歌詞物件集合的方法
      */
     public  List<LrcContent> getLrcList()
     {
         return lrcList ;
     }


}
//例項化歌詞處理物件   Activity中呼叫方法  
  //musicInfosList為封裝好的所有音樂資訊類,get(position)可以獲取當前音樂資訊,getData獲取音樂路徑     
LrcProcess  mLrcProcess = new LrcProcess() ;
mLrcProcess.readLrc(musicInfosList.get(position).getData()) ;

這裡再補一個內容:通常經常下載的歌曲都是沒有歌詞檔案的,所以我們需要自己去下載歌詞檔案,注意該程式中你需要把歌詞檔案和音樂檔案放在一個地方,並且歌詞檔案和音樂檔案字首相同只是將.mp3改為了.lrc。我用的是網易雲音樂,這裡順便推薦一個網易雲下載歌詞的網址,大神們各種推薦供你選擇

5 歌曲播放介面

看圖分析:

(1)標題動畫


這兩個TextView的佈局程式碼就不再列出了。動畫效果主要是繼承了Animation類自定實現。

public class TextAnimation extends Animation {

    private float currentX ;  //指定X
private float currentY ;  //指定Y
    //定義持續時間
private int duration ;
    //設定Camera
private Camera camera = new Camera() ;

    public TextAnimation(float x , float y ,int duration)
    {
        currentX = x ;
        currentY = y ;
        this.duration = duration ;
        System.out.println("THE X IS " + currentX + "\nTHE Y IS " + currentY) ;
    }

    @Override
public void initialize(int width, int height, int parentWidth, int parentHeight) {
        super.initialize(width, height, parentWidth, parentHeight);

        //設定持續時間
setDuration(duration);
        //設定動畫結束後保留
setFillAfter(true);
        //設定變換速度
setInterpolator(new AccelerateDecelerateInterpolator());  //減速
        //setInterpolator(new AccelerateInterpolator());        //加速 預設情況
        //setInterpolator(new LinearInterpolator());          //勻速
}

    @Override
protected void applyTransformation(float interpolatedTime, Transformation t) {
        super.applyTransformation(interpolatedTime, t);
        /*
        儲存
         */
camera.save();
        //根據interpolatedTime來控制X Y Z上的偏移
camera.translate(10.0f - 10.0f * interpolatedTime ,
                30.0f  - 30.0f * interpolatedTime ,
                80.0f - 80.0f * interpolatedTime);
        //根據interpolatedTime在X軸做角度變換
camera.rotateX(360 * interpolatedTime);
        //根據interpolatedTime在Y軸做角度變換
camera.rotateY(360 * interpolatedTime);
        //獲取Transformation引數封裝的matrix物件
camera.getMatrix(t.getMatrix());
        t.getMatrix().preTranslate(-currentX / 4 , -currentY / 4) ;
        t.getMatrix().postTranslate(currentX , currentY) ;
        /*
        如果存在儲存的狀態,就恢復
         */
camera.restore();
    }

}
 initialize(int width, int height, int parentWidth, int parentHeight)中,width和height代表指定播放動畫的View空間寬高,parentWidth和parentHeight代表該View控制元件所在的父控制元件寬高。可以在該方法中獲取View的寬和高,但是我在這裡面主要是完成的初始化

applyTransformation()方法是動畫具體的實現方法,在系統繪製動畫時會反覆呼叫這個方法,每呼叫一次applyTransformation()方法,其中的interpolatedTime引數都會改變一次,值從0到1遞增,當interpolatedTime的值為1時則動畫結束。Transformatio類是一個變換的矩陣,通過改變該矩陣就可以實現各種複雜的效果。PS:更多的相關的動畫知識可以閱讀官方文件,或者其它博文,書籍,網路視訊獲取。
Activity呼叫該動畫方法:
/*
獲取並且設定音樂的標題和歌手。並新增動畫來顯示
 */
MusicArtist = (TextView) findViewById(R.id.MusicArtist) ;
MusicName = (TextView) findViewById(R.id.MusicName) ;
MusicName.setText(musicInfosList.get(position).getTitle());
MusicArtist.setText(musicInfosList.get(position).getArtist());
//動畫效果
MusicArtist.setAnimation(new TextAnimation(0 , 0 , 2000));
MusicName.setAnimation(new TextAnimation(0 , 0 , 2000));
(2)音樂專輯圖片處理



1,圓形圖片

 需要用到Shader

 Shader的使用步驟:
1. 構建Shader物件
2. 通過Paint的setShader方法設定渲染物件
3.設定渲染物件
4.繪製時使用這個Paint物件
public class XCRoundImageView extends android.support.v7.widget.AppCompatImageView{
    private Paint mPaintBitmap = new Paint(Paint.ANTI_ALIAS_FLAG);
    private Bitmap mRawBitmap;
    private BitmapShader mShader;
    private Matrix mMatrix = new Matrix();

    public XCRoundImageView(Context context, AttributeSet attrs) {
        super(context, attrs);
    }

    @Override
protected void onDraw(Canvas canvas) {
        //獲取原生Bitmap點陣圖
Bitmap rawBitmap = getBitmap(getDrawable());
        if (rawBitmap != null){
            //獲取圖片寬和高
int viewWidth = getWidth();
            int viewHeight = getHeight();
            //由於要變換為圓形,在變換過程中取邊長相對小的為基準
int viewMinSize = Math.min(viewWidth, viewHeight);
            float dstWidth = viewMinSize;
            float dstHeight = viewMinSize;
            //如果是第一次繪製
if (mShader == null || !rawBitmap.equals(mRawBitmap)){
                mRawBitmap = rawBitmap;
                /*
                BitmapShader是Shader的子類,可以通過Paint.setShader(Shader shader)進行設定、
                這裡我們只關注BitmapShader,構造方法:
                mBitmapShader = new BitmapShader(bitmap, TileMode.CLAMP, TileMode.CLAMP);
                引數1:bitmap
                引數2,引數3:TileMode;
                TileMode的取值有三種:
                CLAMP 拉伸
                REPEAT 重複
                MIRROR 映象
                如果大家給電腦螢幕設定屏保的時候,如果圖片太小,可以選擇重複、拉伸、映象;
                重複:就是橫向、縱向不斷重複這個bitmap
                映象:橫向不斷翻轉重複,縱向不斷翻轉重複;
                拉伸:這個和電腦屏保的模式應該有些不同,這個拉伸的是圖片最後的那一個畫素;橫向的最後一個橫行畫素,不斷的重複,縱項的那一列畫素,不斷的重複;
                */
mShader = new BitmapShader(mRawBitmap, Shader.TileMode.CLAMP, Shader.TileMode.CLAMP);
            }
            if (mShader != null){
                /*
                 void setLocalMatrix(Matrix localM);
                 設定shader的本地矩陣,如果localM為空將重置shader的本地矩陣。
                 */
mMatrix.setScale(dstWidth / rawBitmap.getWidth(), dstHeight / rawBitmap.getHeight());

                mShader.setLocalMatrix(mMatrix);
            }
            mPaintBitmap.setShader(mShader);
            float radius = viewMinSize / 2.0f;
            canvas.drawCircle(radius, radius, radius, mPaintBitmap);
        } else {
            super.onDraw(canvas);
        }
    }

    private Bitmap getBitmap(Drawable drawable){
        if (drawable instanceof BitmapDrawable){
            return ((BitmapDrawable)drawable).getBitmap();
        } else if (drawable instanceof ColorDrawable){
            Rect rect = drawable.getBounds();
            int width = rect.right - rect.left;
            int height = rect.bottom - rect.top;
            int color = ((ColorDrawable)drawable).getColor();
            Bitmap bitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888);
            Canvas canvas = new Canvas(bitmap);
            canvas.drawARGB(Color.alpha(color), Color.red(color), Color.green(color), Color.blue(color));
            return bitmap;
        } else {
            return null;
        }
    }
}

PS:作者當時也是搬運的,只是補充了部分註釋。內容可能有點複雜,需要多實踐,建議參考資料文件

自定義控制元件三部曲之繪圖篇(十八)——BitmapShader與望遠鏡效果2,圖片旋轉

首先定義旋轉動畫 :image_rotate.xml

<?xml version="1.0" encoding="utf-8"?>
<set xmlns:android="http://schemas.android.com/apk/res/android"

    android:interpolator="@android:anim/linear_interpolator">

    <rotate
        android:fromDegrees="0"
        android:toDegrees="359"
        android:pivotX="50%"
        android:pivotY="50%"
        android:duration="8000"

        />
</set>

然後在Java業務程式碼中設定定時器來啟動動畫

/*
該Timer用於實現:音樂播放介面圖片旋轉動畫
 */
MyHandle2 handle2 = new MyHandle2() ;
new Timer().schedule(new TimerTask() {
    @Override
public void run() {
        handle2.sendEmptyMessage(0x112) ;
    }
}, 0 ,8000);

最後再Handle中接收訊息,並且啟動動畫

public class MyHandle2 extends  Handler
{
    @Override
public void handleMessage(Message msg) {
        if ((msg.what == 0x112))
        {
            //設定圖片旋轉
MusicImage.setAnimation(AnimationUtils.loadAnimation(MusicPlay.this , R.anim.image_rotate));

        }
    }
}

五:下載地址github

六:參考資料

七:總結

第一次寫這麼長文的部落格,也沒想過邀功。不過真心希望您再閱讀了這篇博文,並且有所建議和收穫後留下您寶貴的評論。謝謝。

PS:轉載請註明 https://blog.csdn.net/qq_29989087/article/details/80206290