美文网首页
音乐播放器——麦响音乐盒

音乐播放器——麦响音乐盒

作者: 麦香菌 | 来源:发表于2018-05-19 14:28 被阅读0次

前言

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

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资源的释放流程。

结语

本想做成网络音乐播放器,但苦于能力有限,找不到网络音乐接口,只能做做本地音乐了。
由于是新手,APP主体部分花了两个星期时间完成,当时感觉还不错,但当我准备给APP添些小功能时却发现要花大量时间去完成这些看似微小的工作。而当我把APP给室友试用时却出现了好几个BUG,改起来也是伤透了脑筋,导致现在十分害怕室友打开我的APP。
究其原因还是因为自己在写这个APP之前并没有做什么准备,并不知道具体要实现什么功能,都是写一步看一步,边写边学,且我的代码里用到了大量的本地广播而非监听器,导致代码混乱。而当我准备添些新功能进去时,代码混乱便演变成逻辑混乱,每出现一个BUG就改一个BUG,改错了还会出现新BUG,有点东墙补西墙的感觉。
还有我的所有图标都是用AE自己画的,其实好些图标可以直接用代码实现,这样还能减少安装包的大小。
最后也是最关键的自定义歌词界面,现在使用的歌词界面什么都好,就是滚动歌词时,滚动距离太短,想修改源码却因为作者不断地更改尺寸单位导致我该如何下手修改这个滚动距离,头疼的要死。

相关文章

  • 音乐播放器——麦响音乐盒

    前言 首先声明,小白一只,android完全自学,若代码中有不妥或更简便的方法求指教(大佬带带我)。。。 麦响音乐...

  • 《你当像鸟飞往你的山》随笔笔记

    一、 人生大多如此,世界也大多如此,都像极了上满发条的音乐盒,规律的运行着。有的音乐盒奏出哀乐,有的音乐盒奏出欢快...

  • 麦田里的音乐盒

    麦田里有一个音乐盒, 谁也没打开过那个音乐盒, 有一天,一个老师来到这里, 发现了这个音乐盒, ...

  • 2019-11-09

    diy音乐盒

  • 音乐盒

    从商店买来一个音乐盒, 这个音乐盒上是一个独角兽, 健壮的兽身,蓝色的尾巴, 整个独角兽看得十分高贵。 转下音乐盒...

  • 一份生日礼物,一份纯粹的快乐

    记得小时候很喜欢听音乐盒的声音。 每次转动音乐盒的发条,看着音乐盒转动,觉得很有趣。 小时候很喜欢做手工,每次手工...

  • 用HTML5audio组件实现一个音乐播放器

    心血来潮就用h5的新标签写了个简单的音乐播放器,实现的过程还是很简单的,对于前端来说最难的部分应该是让音乐盒歌词同...

  • Android应用-MusicBox实现

    前言 音乐播放器在目前手机应用中非常普遍,安卓开发中对于音乐盒的实现方法进行掌握也十分重要,今天我将总结一下播放的...

  • 音乐盒

    加纳人的棺材 奇形怪状 因为它们是按照 逝者的喜好 制定的 一辆劳斯莱斯形棺材的主人 生前一定是个豪车迷 当然了 ...

  • 音乐盒

    剪影里轮廓好看 这些鲜明故事 还是小时候 双手有守候 有温度 等我回来 错过了几首音乐 又跳跃几下 才跟上脚步 才...

网友评论

      本文标题:音乐播放器——麦响音乐盒

      本文链接:https://www.haomeiwen.com/subject/mgemdftx.html