2025-02-17

我們用瞭6篇文章的篇幅做瞭鋪墊,終於到瞭真正的應用程序瞭。這部分還是一如既往的簡單。
有關應用的類有兩個,一個是LiryicMain,一個是SelectFileActivity。都是差不多最低限度的內容,沒有任何華麗的內容。
先看看這兩個類在整個軟件中的位置。從圖中可以看出LyricMain是軟件全體的控制者。SelectFileActivity也為LyricMain提供服務。

SelectFileActivity太過簡單,本文中就不再說明瞭。我們集中篇幅說明一下LyricMain。
首先是數據成員。一個是LyricPlayerServiceProxy,歌詞播放服務的代理,一個是用來保存歌詞結束位置的List。
private LyricPlayerServiceProxy mProxy = new LyricPlayerServiceProxy(this); 
private ArrayList<Integer> mLyricEndList = new ArrayList<Integer>();
LyricPlayerServiceProxy是前面已經介紹過的內容,在這裡就不在重復瞭。mLyricEndList需要說明一下。在這個軟件中我們將所有歌詞都表示在一個TextEditView中,為瞭能夠表示當前播放中的歌詞,我們將每一句歌詞的位置保存在mLyricEndList中,這樣當播放中的歌詞發生變化時,隻要將這句歌詞設為選中狀態就可以瞭。
 接下來是LyricMediaInfoProvider的最簡單實現,提供瞭固定的歌名和歌曲文件的位置信息。如果需要切換歌曲,需要再復雜一些。
private class LyricMediaInfoProvider implements MediaPlayerService.MediaInfoProvider{ 
    String mUrl; 
    String mTitle; 
     
    LyricMediaInfoProvider(String url, String title){ 
        mUrl = url; 
        mTitle = title; 
    } 
     
    @Override
    public boolean moveToPrev() { 
        // TODO Auto-generated method stub 
        return false; 
    } 
     
    @Override
    public boolean moveToNext() { 
        // TODO Auto-generated method stub 
        return false; 
    } 
     
    @Override
    public String getUrl() { 
        return mUrl; 
    } 
     
    @Override
    public String getTitle() { 
        // TODO Auto-generated method stub 
        return mTitle; 
    } 
}
接下來是onCreate方法。主要做瞭幾件事
1.建立和LyricPlayerServiceProxy之間的聯系。
2.提供瞭的實現NotificationProvider(詳細信息請參照: Android歌詞秀設計思路(4)通用的音樂播放服務(下) )
3.設置ImageButton的尺寸。
/** Called when the activity is first created. */
   @Override
   public void onCreate(Bundle savedInstanceState) { 
       super.onCreate(savedInstanceState); 
       setContentView(R.layout.main); 
        
       mLyricEdit = (EditText)this.findViewById(R.id.editLyric); 
        
       mProxy.setConnectionListener(this); 
       mProxy.setLyricPlayerListener(this); 
       mProxy.setNotificationProvider(new MediaPlayerService.NotificationProvider(){ 
        @Override
        public Notification createNotification(Context context) { 
            Notification notification = new Notification(R.drawable.button_blue_play, mProxy.getTitle(), System.currentTimeMillis()); 
            // The PendingIntent to launch our activity if the user selects this notification 
            PendingIntent contentIntent = PendingIntent.getActivity(context, 0, new Intent(context, LyricMain.class), 0);  
            // Set the info for the views that show in the notification panel. 
            notification.setLatestEventInfo(context, getText(R.string.media_player_label), mProxy.getTitle(), contentIntent); 
            return notification; 
        } 
    }); 
       mProxy.startAndBindService(); 
       mLyricEndList.clear(); 
        
       DisplayMetrics metrics = new DisplayMetrics(); 
       getWindowManager().getDefaultDisplay().getMetrics(metrics); 
                
       int btnId[] = {R.id.buttonPrev, R.id.buttonStop, R.id.buttonPlay, R.id.buttonPause, R.id.buttonNext}; 
       int btnSize = Math.min(metrics.widthPixels, metrics.heightPixels) / (btnId.length + 1); 
        
       //調整按鍵尺寸。 
       for(int i = 0; i < btnId.length; ++i){ 
        ImageButton ib = (ImageButton)this.findViewById(btnId[i]); 
        ib.setAdjustViewBounds(true); 
        ib.setMaxHeight(btnSize); 
        ib.setMaxWidth(btnSize); 
       } 
       ImageButton selectFile = (ImageButton)this.findViewById(R.id.buttonSelectFile); 
       selectFile.setAdjustViewBounds(true); 
       selectFile.setMaxHeight(btnSize*2/3); 
       selectFile.setMaxWidth(btnSize*2/3); 
     
       updateButtonState(); 
   }
再下來是onDestroy方法,如果音樂在播放中,就接觸和播放服務之間的關系,退出程序,這是歌曲播放會繼續。如果播放出於停止或暫停狀態,就連同播放服務一起關閉,完全退出程序。
@Override
    protected void onDestroy() { 
        super.onDestroy(); 
        mProxy.setConnectionListener(null); 
        mProxy.setLyricPlayerListener(null); 
        if(!mProxy.isPlaying()){ 
            mProxy.stopService(); 
        } 
    }
啟動選擇文件的SelectFileActivity
public void OnSelectFile(View v){ 
        Intent i = new Intent(this, SelectFileActivity.class); 
        startActivityForResult(i, 0); 
    }
SelectFileActivity關閉,取得選中的媒體文件的信息並通知的LyricPlayerServiceProxy
接下來是按鍵處理
public void OnOperationButtonClick(View v){ 
        switch(v.getId()){ 
        case R.id.buttonPrev: 
            mProxy.seekToPrevLyric(); 
            break; 
        case R.id.buttonStop: 
            if(mProxy.isPlaying() || mProxy.isPausing()){ 
                mProxy.stop(); 
            } 
            break; 
        case R.id.buttonPlay: 
            if(!mProxy.isPlaying()){ 
                mProxy.start(); 
            } 
            break; 
        case R.id.buttonPause: 
            if(mProxy.isPlaying()){ 
                mProxy.pause(); 
            } 
            break; 
        case R.id.buttonNext: 
            mProxy.seekToNextLyric(); 
            break; 
        } 
     }
根據播放狀態更新各個按鍵的狀態。
protected void updateButtonState(){ 
        ((ImageButton)this.findViewById(R.id.buttonPrev)).setEnabled(mProxy.isPlaying() || mProxy.isPausing()); 
        ((ImageButton)this.findViewById(R.id.buttonStop)).setEnabled(mProxy.isPlaying() || mProxy.isPausing()); 
        ((ImageButton)this.findViewById(R.id.buttonPlay)).setEnabled(mProxy.getDataSource()!= null && (!mProxy.isPlaying() || mProxy.isPausing())); 
        ((ImageButton)this.findViewById(R.id.buttonPause)).setEnabled(mProxy.isPlaying()); 
        ((ImageButton)this.findViewById(R.id.buttonNext)).setEnabled(mProxy.isPlaying() || mProxy.isPausing()); 
    }
如果是程序啟動時已經有歌曲在播放,就更新一下文件標題和按鈕狀態。
//implement of LyricPlayerServiceProxy.ServiceConnectionListener 
    public void onServiceConnected(){ 
        String title = mProxy.getTitle(); 
        if(title != null){ 
            TextView tv = (TextView)this.findViewById(R.id.fileTitle); 
            tv.setText(title); 
        } 
        updateButtonState(); 
    } 
    public void onServiceDisconnected(){ 
         
    }
實現LyricPlayerListener的代碼,負責處理歌詞播放服務的各種通知。
//implement of LyricPlayerService.LyricPlayerListener 
    public void onLyricLoaded(){ 
        mLyricEndList.clear(); 
        String lyric = new String(); 
        for(int i = 0; i < mProxy.getLyricCount(); ++i){ 
            lyric += mProxy.getLyric(i); 
            lyric += "\r\n"; 
            mLyricEndList.add(new Integer(lyric.length())); 
        } 
        mLyricEdit.setText(lyric); 
     } 
     
    public void onStateChanged(){ 
        updateButtonState(); 
    } 
     
    public void onPositionChanged(long position){ 
         
    } 
     
    public void onLyricChanged(int lyric_index){ 
        int lyricStart = 0; 
        if(lyric_index > 0){ 
            lyricStart = mLyricEndList.get(lyric_index – 1); 
        } 
        int lyricEnd = mLyricEndList.get(lyric_index); 
        mLyricEdit.setSelection(lyricStart, lyricEnd); 
        mLyricEdit.invalidate(); 
        Log.i(TAG, String.format("lyric= %d, setSelection(%d, %d)", lyric_index, lyricStart, lyricEnd)); 
    }
在歌詞讀入時,將所有歌詞練成一個長字符串,並記住每一句歌詞在字符串中的位置。
在播放服務的狀態發生變化時,更新按鈕的狀態。
在當前歌詞發生變化時,根據前面保存的位置信息將當前歌詞設置成高亮。
最後是跳到選定歌詞的代碼,還是一樣的簡單。
public void OnLyricClick(View v){ 
        EditText et = (EditText)v; 
        int sel_start = et.getSelectionStart(); 
        for(int i = 0; i < mLyricEndList.size(); ++i){ 
            if(sel_start < mLyricEndList.get(i)) 
            { 
                mProxy.seekToLyric(i); 
                break; 
            } 
        } 
    }
結合選中的位置,和保存的歌詞位置信息,找到歌詞的序號,讓播放服務跳到那句就行瞭。
作者 “來自大連”

發佈留言

發佈留言必須填寫的電子郵件地址不會公開。 必填欄位標示為 *