音乐播放器——实现后台播放、摇摇切歌等功能

前言

首先声明,小白一只,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));

以上是我使用到的信息,若想获取更多信息可以看看这个网站:
http://www.android-study.com/duomeitijishu/222.html

二.音乐后台播放

把音乐播放控件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资源的释放流程。

我的简书地址:https://www.jianshu.com/p/7e60fb0993c2

  • 4
    点赞
  • 17
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值