1. 程式人生 > >音樂播放器——實現後臺播放、搖搖切歌等功能

音樂播放器——實現後臺播放、搖搖切歌等功能

前言

首先宣告,小白一隻,android完全自學,若程式碼中有不妥或更簡便的方法求指教(大佬帶帶我)。。。

APP

這裡寫圖片描述

歡迎介面

歡迎介面

主介面

本地音樂 我的音樂 側邊欄 播放列表

音樂介面

封面 歌詞

實現功能

1.遍歷本地音樂 2.音樂後臺播放 3.音樂封面之黑礁唱片旋轉效果 4.歌詞的顯示與切換(還未能實現平滑滾動效果) 5.搖搖切歌 6.本地天氣資訊的獲取與顯示 7.自定義音量控制元件

一.遍歷本地音樂

1.本地音樂查詢介面:

cursor=getContentResolver().query(MediaStore.Audio.Media.EXTERNAL_CONTENT_URI, 
null, null, null, MediaStore.Audio
.Media.DEFAULT_SORT_ORDER);

2.本地音樂資訊:

//歌曲的名稱:MediaStore.Audio.Media.TITLE
String tilte = cursor.getString(cursor.getColumnIndexOrThrow(MediaStore.Audio.Media.TITLE));
//歌曲的歌手名:MediaStore.Audio.Media.ARTIST
String artist = cursor.getString(cursor.getColumnIndexOrThrow(MediaStore.Audio.Media.ARTIST));
//歌曲檔案的路徑:MediaStore.Audio.Media.DATA String url = cursor.getString(cursor.getColumnIndexOrThrow(MediaStore.Audio.Media.DATA)); //歌曲的總播放時長:MediaStore.Audio.Media.DURATION int duration = cursor.getInt(cursor.getColumnIndexOrThrow(MediaStore.Audio.Media.DURATION));

二.音樂後臺播放

把音樂播放控制元件MediaPlayer及音樂播放暫停等函式寫在Service裡,讓Activity連線Service操控音樂。

//音樂播放控制元件
public MediaPlayer mediaPlayer;
//音樂的播放
public void star(){
    if(mediaPlayer!=null){
            mediaPlayer.start();
            MusicActivity.STATE = "PLAY";
    }
}
//音樂的暫停
public void pause(){
    if(mediaPlayer!=null){
            mediaPlayer.pause();
            MusicActivity.STATE = "PAUSE";
    }
}

三.音樂封面之黑礁唱片旋轉效果

圖片旋轉函式

imageView.setRotation((imageView.getRotation() + 0.5f) % 360);

開啟非同步通訊迴圈更新UI

private Handler mhandler = new Handler() {
        @Override
        public void handleMessage(Message msg) {
            switch(msg.what){
                case 0:
                   //若當前音樂在播放,更新圖片的rotation,令其隨著音樂播放旋轉
                   imageView.setRotation((imageView.getRotation() + 0.5f) % 360);
                   //每隔10ms迴圈更新一次UI執行緒
                   mhandler.sendEmptyMessageDelayed(0, 10);
                   break;
            }
        }
}

四.歌詞的顯示與切換

實現方式:自定義View——LrcView

private ILrcView mLrcView;

解析的歌詞資訊存在List中

private List<LrcRow> mLrcRows;

關鍵變數:

private int mLrcFontSize = 35;//字型大小
private int mHignlightRowColor = Color.WHITE;//歌詞顏色
//獲取歌詞介面的寬高
final int height = getHeight(); 
final int width = getWidth(); 

LrcView繪製歌詞主要函式:

//繪製歌詞
String highlightText = mLrcRows.get(mHignlightRow).content;//獲取播放行歌詞
final int rowX = width / 2;//設定播放行歌詞在LrcView的X軸位置
int highlightRowY = height / 2 - dropDistance*dropTime;//設定播放行歌詞在LrcView的Y軸位置
mPaint.setColor(mHignlightRowColor);//設定播放行歌詞的顏色
mPaint.setTextSize(mLrcFontSize);//設定播放行歌詞的字型大小
mPaint.setTextAlign(Align.CENTER);//歌詞居中顯示
canvas.drawText(highlightText, rowX, highlightRowY, mPaint);//繪製歌詞

五.搖搖切歌

寫個監聽函式ShakeListener監聽手機的重力感應與加速度,並在Service裡向Activity傳送本地廣播實現切歌效果。

關鍵定義:

//速度閾值
private static final int SPEED_SHAKEHOLD=3000;
//檢測時間間隔
private static final int UPTATE_INTERVAL_TIME = 70;
//感測器管理器
private SensorManager sensorManager;
//感測器
private Sensor sensor;
//重力感應監聽器
private OnShakeListener onShakeListener;
// 上下文
private Context mContext;
// 手機上一個位置時重力感應座標
private float lastX;
private float lastY;
private float lastZ;
// 上次檢測時間
private long lastUpdateTime;

關鍵函式

//重力感應到變化
    @Override
    public void onSensorChanged(SensorEvent event) {
        // TODO Auto-generated method stub
        long currentUpdateTime=System.currentTimeMillis();//當前時間

        long timeInterval=currentUpdateTime-lastUpdateTime;//獲取時間間隔

        if(timeInterval<UPTATE_INTERVAL_TIME){
            return;
        }

        lastUpdateTime=currentUpdateTime;

        float x=event.values[0];
        float y=event.values[1];
        float z=event.values[2];

        float deltaX=x-lastX;
        float deltaY=y-lastY;
        float deltaZ=z-lastZ;

        lastX=x;
        lastY=y;
        lastZ=z;

        double speed=Math.sqrt(deltaX*deltaX+deltaY*deltaY*deltaZ*deltaZ)/timeInterval*10000;//獲得速度


        if(speed>SPEED_SHAKEHOLD){
            onShakeListener.onShake();//開啟搖搖切歌
        }
    }

六.本地天氣資訊的獲取與顯示

先通過百度地圖API定位手機位置,再通過定位資訊獲取對應地區天氣資訊。 1.獲取定位許可權

2.獲得許可權後在百度地圖監聽器裡獲得定位資訊,再通過定位資訊獲得對應weatherId

public class MyLocationListener implements BDLocationListener {//定位監聽
        public void onReceiveLocation(BDLocation bdLocation) {
            //獲取省市縣資料
            location=bdLocation.getProvince()+"-" +bdLocation.getCity()+"-"+bdLocation.getDistrict();
            Log.d("TAG",location);
            province=bdLocation.getProvince().substring(0,bdLocation.getProvince().length()-1);
            city=bdLocation.getCity().substring(0,bdLocation.getProvince().length()-1);
            county=bdLocation.getDistrict().substring(0,bdLocation.getProvince().length()-1);

            //獲取天氣資訊
            if((province!=null)&&(city!=null)&&(county!=null)){
                queryProvinces();
                while (queryFlag){}
                Log.d("query","ProvinceFlag1:"+queryFlag+"");
                for(Province p:provincesList){
                    Log.d("TAG",p.getProvinceName());
                    if(p.getProvinceName().equals(province)){
                        selectedProvince=p;
                        Log.d("TAG","select:"+selectedProvince.getProvinceName());
                        break;
                    }
                    Log.d("query","ProvinceFlag:"+queryFlag+"");
                    queryFlag=true;
                    Log.d("query","ProvinceFlag:"+queryFlag+"");
                }
                queryCities();
                while (queryFlag){}
                Log.d("query","CityFlag1:"+queryFlag+"");
                for(City c:cityList){
                    Log.d("TAG","city:"+c.getCityName());
                    if(c.getCityName().equals(city)){
                        selectedCity=c;
                        Log.d("TAG","select:"+selectedCity.getCityName());
                        break;
                    }
                    queryFlag=true;
                    Log.d("query","CityFlag:"+queryFlag+"");
                }
                queryCounties();
                while (queryFlag){}
                for(County co:countyList){
                    Log.d("TAG",co.getCountyName());
                    if(co.getCountyName().equals(county)){
                        Log.d("TAG","select:"+co.getCountyName());
                        SharedPreferences.Editor editor=
                                getSharedPreferences("Weather",MODE_PRIVATE).edit();
                        editor.putString("weatherId",co.getWeatherId());
                        Log.d("TAG","co.id:"+co.getWeatherId());
                        editor.apply();
                        break;
                    }
                }
                initWeather();
            }
        }
    }

省市縣資料介面:

//全國所有省份
String address="http://guolin.tech/api/china";
//省份對應所有城市
String address="http://guolin.tech/api/china/" + selectedProvince.getProvinceCode();
//城市對應所有縣城
String address="http://guolin.tech/api/china/"+selectedProvince.getProvinceCode()
                    +"/"+selectedCity.getCityCode();

天氣介面(郭林的和風天氣介面):

String weatherUrl="http://guolin.tech/api/weather?cityid="+weatherId+
                "&key=63660528a9dc4f968243a7***********";

3.向介面獲取資料

requestWeather(weatherId);

若成功獲取資料,則把資料顯示在側邊欄

private void showWeather(Weather weather){
        String degree=weather.now.temperature+"℃";//溫度
        String pm25=weather.aqi.city.pm25;//pm2.5
        String weatherInfo=weather.now.more.info;//天氣資訊
        String weatherCode=weather.now.more.code;//天氣資訊icon的code
        Log.d("MusicTAG","degree:"+degree);
        Log.d("MusicTAG","pm25:"+pm25);

        weather_temp.setText(degree);
        weather_location.setText(location);
        weather_info.setText(weatherInfo);
        weather_pm25.setText("PM2.5 "+pm25);
        setWeatherIcon(weatherCode);
    }

    private void setWeatherIcon(String weatherCode){//獲得code對應icon資源
        String code="weather_"+weatherCode;
        weather_icon.setImageResource(getResource(code));
        Log.d("TAG","id:"+getResource(code));
    }

    public int getResource(String imageName){//將String轉為對應的資源ID
        Context ctx=getBaseContext();
        int resId = getResources().getIdentifier(imageName, "drawable", ctx.getPackageName());
        //如果沒有在"drawable"下找到imageName,將會返回0
        return resId;
    }

七.自定義音量控制元件

用Seekbar做音量控制元件

SeekBar seekBar_volume=(SeekBar)findViewById(R.id.seekBar_volume);
初始化音量控制元件

private void initVolume(){//初始化音量控制元件
        audioManager=(AudioManager)getSystemService(AUDIO_SERVICE);//獲取音量服務
        MaxSound=audioManager.getStreamMaxVolume(AudioManager.STREAM_MUSIC);//獲取系統音量最大值
        seekBar_volume.setMax(MaxSound);
        int currentSount=audioManager.getStreamVolume(AudioManager.STREAM_MUSIC);//獲取當前音量
        seekBar_volume.setProgress(currentSount);
    }

音量調的監聽

seekBar_volume.setOnSeekBarChangeListener(new SeekBar.OnSeekBarChangeListener() {//音量條的監聽
            @Override
            public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) {
                if(fromUser){// 判斷是否來自使用者
                    int seekPosition=seekBar_volume.getProgress();
                    audioManager.setStreamVolume(AudioManager.STREAM_MUSIC, seekPosition, 0);
                }
            }

            @Override
            public void onStartTrackingTouch(SeekBar seekBar) {

            }

            @Override
            public void onStopTrackingTouch(SeekBar seekBar) {

            }
        });

碰到的問題

問題:無法通過點選實現封面與歌詞介面的切換 分析:未在歌詞介面LrcView設定點選事件。 解決:在歌詞介面LrcView重寫onTouchEvent,並通過設定監聽器,讓音樂播放介面執行封面與歌詞介面的切換。判斷點選的方法是若使用者手指接觸與離開螢幕時的位置未發生變化則被認為點選事件。

問題:在APP裡執行退出應用操作時,APP的資源未被釋放。 分析:finish()的退出操作並不會執行onDestroy()裡的操作,而我把APP資源的釋放全寫在onDestroy()裡。 解決:在APP退出函式裡再寫一遍APP資源的釋放流程。