【Android】_MediaServer_仿网易云音乐播放器1(指针和唱片)

目录

  • 一、MyServer的用法
  • 二、仿网易云音乐播放器设计思路
    • (一)网易云播放器构成分析
    • (二)MusicDemo实现功能
      • a. MediaServer播放音乐
      • b. Seekbar滑动条及时间进度
      • c. 唱针旋转和碟片旋转
      • d.隐藏上边框
  • 三、具体编码
    • 附:本次项目源码

一、MyServer的用法

(一)原理

Service
是android四大组件之一,主要用于在后台处理一些耗时的逻辑,或者去执行某些需要长期运行的任务。必要的时候我们甚至可以在程序退出的情况下,让Service在后台继续保持运行状态。而Service是运行在主线程里的,如果直接在Service中处理一些耗时的逻辑,就会导致程序ANR,所以需要另外开启子线程来处理。
两种模式: startService()/bindService()
基本用法:
新建MyService继承自Service;
不要忘了在AndroidManifest.xml中注册
MyService Service启动后如果没有StopService操作即使activity被销毁任然会在后台运行
【Android】_MediaServer_仿网易云音乐播放器1(指针和唱片)_第1张图片

(二)用法
方法一:在java包下,右键选择Server新建MediaServer,这样在AndroidManifest下会自动生成服务;

【Android】_MediaServer_仿网易云音乐播放器1(指针和唱片)_第2张图片
【Android】_MediaServer_仿网易云音乐播放器1(指针和唱片)_第3张图片
【Android】_MediaServer_仿网易云音乐播放器1(指针和唱片)_第4张图片

方法二:自定义MediaServer.java类,继承Server,然后在AndroidManifest里添加:

.....略
     activity>

        <service
            android:name=".MediaService"
            android:enabled="true"
            android:exported="true" />

    application>
略.....

二、仿网易云音乐播放器设计思路

(一)网易云播放器构成分析

主要包含:
1.播放组键功能,2.歌曲进度条功能, 3.唱片特效功能,4.隐藏上边框

原网易云效果图和我的仿网易云音乐播放器Demo中的对比图:
【Android】_MediaServer_仿网易云音乐播放器1(指针和唱片)_第5张图片 【Android】_MediaServer_仿网易云音乐播放器1(指针和唱片)_第6张图片

(二)MusicDemo实现功能

a. MediaServer播放音乐

【Android】_MediaServer_仿网易云音乐播放器1(指针和唱片)_第7张图片
main.java部分函数截图:

  • 定义及声明:
    【Android】_MediaServer_仿网易云音乐播放器1(指针和唱片)_第8张图片
  • 在这里插入图片描述的点击事件
    【Android】_MediaServer_仿网易云音乐播放器1(指针和唱片)_第9张图片【Android】_MediaServer_仿网易云音乐播放器1(指针和唱片)_第10张图片

b. Seekbar滑动条及时间进度

绑定Server的前提下,run()函数定义seekbar的滚动及test时间值,调用方法放在对button的监听Onclick函数中;

【Android】_MediaServer_仿网易云音乐播放器1(指针和唱片)_第11张图片

  • seekbar及textSwitcher的定义:
    【Android】_MediaServer_仿网易云音乐播放器1(指针和唱片)_第12张图片
  • Oncreate函数中的相关部分声明:
    【Android】_MediaServer_仿网易云音乐播放器1(指针和唱片)_第13张图片
    自定义run()函数,控制播放到当前音乐的滑动条时间设置:
    【Android】_MediaServer_仿网易云音乐播放器1(指针和唱片)_第14张图片

c. 唱针旋转和碟片旋转

【Android】_MediaServer_仿网易云音乐播放器1(指针和唱片)_第15张图片
【Android】_MediaServer_仿网易云音乐播放器1(指针和唱片)_第16张图片

d.隐藏上边框

自定义styles,在main函数调用

main函数中:

    //设置透明栏
        if(Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
            Window window = getWindow();
            window.clearFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS
                    | WindowManager.LayoutParams.FLAG_TRANSLUCENT_NAVIGATION);
            window.getDecorView().setSystemUiVisibility(
                    View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN
                            | View.SYSTEM_UI_FLAG_LAYOUT_STABLE
            );
            window.addFlags(WindowManager.LayoutParams.FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS);
            window.setStatusBarColor(Color.TRANSPARENT);
        }

【Android】_MediaServer_仿网易云音乐播放器1(指针和唱片)_第17张图片
自定义styles.xml:

<resources>
    <style name="AppTheme.Base" parent="Theme.AppCompat.Light">
        "windowActionBar">false
        "windowNoTitle">true
    style>

    <style name="BaseAppTheme" parent="AppTheme.Base">
        
        "colorPrimary">@color/colorPrimary
        "colorPrimaryDark">@color/colorPrimaryDark
        "colorAccent">@color/colorAccent
    style>
    
    <style name="AppTheme" parent="BaseAppTheme">
        "android:windowTranslucentStatus">true
    style>
resources>

【Android】_MediaServer_仿网易云音乐播放器1(指针和唱片)_第18张图片

三、具体编码

项目结构图:
【Android】_MediaServer_仿网易云音乐播放器1(指针和唱片)_第19张图片
注意:因为drawable不支持mp3格式,所以要把mp3文件放在如图新建的raw文件夹中:
【Android】_MediaServer_仿网易云音乐播放器1(指针和唱片)_第20张图片

准备文件:
图片:上一首,下一首,播放键,暂停键,唱针,唱片,模糊背景图
【Android】_MediaServer_仿网易云音乐播放器1(指针和唱片)_第21张图片

源码:
main.xml:
【Android】_MediaServer_仿网易云音乐播放器1(指针和唱片)_第22张图片


<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:id="@+id/activity_main"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:background="@drawable/music_play_background">
    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="60dp"
        android:layout_marginTop="10dp"
        android:layout_alignParentTop="true"
        android:id="@+id/title"
        android:orientation="horizontal">
        <TextView
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:layout_marginTop="15dp"
            android:layout_marginBottom="3dp"
            android:text="Quit Inside"
            android:textSize="25dp"
            android:gravity="center"
            android:textColor="#ffffff"/>
    LinearLayout>
    <TextView
        android:layout_width="match_parent"
        android:layout_height="0.5dp"
        android:background="#afafaf"
        android:layout_below="@+id/title"/>
    <ImageView
        android:id="@+id/disc"
        android:layout_width="280dp"
        android:layout_height="280dp"
        android:layout_centerHorizontal="true"
        android:layout_below="@+id/title"
        android:layout_marginTop="50dp"
        android:src="@drawable/play_album" />
    <ImageView
        android:id="@+id/needle"
        android:layout_width="120dp"
        android:layout_height="120dp"
        android:layout_below="@+id/title"
        android:src="@drawable/play_needle"

        android:layout_marginLeft="150dp"/>
    <RelativeLayout
        android:id="@+id/music1"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_above="@+id/rl"
        android:layout_marginTop="20dp"
        android:layout_marginBottom="10dp"
        android:gravity="center">
        <SeekBar
            android:id="@+id/music_seek_bar"
            android:layout_width="240dp"
            android:layout_height="wrap_content"/>
        <TextSwitcher
            android:id="@+id/text_switcher"
            android:layout_width="80dp"
            android:layout_height="50dp"
            android:layout_toRightOf="@+id/music_seek_bar">
            <TextView
                android:layout_width="match_parent"
                android:layout_height="wrap_content"
                android:text="00:00/2:00"
                android:textColor="@color/colorAccent"/>
        TextSwitcher>
    RelativeLayout>

    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="70dp"
        android:gravity="center"
        android:id="@+id/rl"
        android:layout_marginBottom="20dp"
        android:layout_alignParentBottom="true"
        android:orientation="horizontal">

        <ImageView
            android:id="@+id/playing_pre"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_gravity="center_vertical"
            android:src="@drawable/music_previous" />

        <ImageView
            android:id="@+id/playing_play"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_gravity="center_vertical"
            android:src="@drawable/music_pause" />

        <ImageView
            android:id="@+id/playing_next"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_gravity="center_vertical"
            android:src="@drawable/music_next" />
    LinearLayout>
RelativeLayout>

MediaServer.java:

按照方法一新建MediaServer.java内容:

package com.example.cungu.musicdemo;

import android.app.Service;
import android.content.Intent;
import android.media.MediaPlayer;
import android.os.Binder;
import android.os.Environment;
import android.os.IBinder;
import android.support.annotation.Nullable;
import android.util.Log;

import java.io.IOException;


public class MediaService extends Service {
    private MediaPlayer mPlayer;
    /*
     * 绑定服务的实现流程:
     * 1.服务 onCreate, onBind, onDestroy 方法
     * 2.onBind 方法需要返回一个 IBinder 对象
     * 3.如果 Activity 绑定,Activity 就可以取到 IBinder 对象,可以直接调用对象的方法
     */

    // 相同应用内部不同组件绑定,可以使用内部类以及Binder对象来返回。
    public class MusicController extends Binder {
        public void play() {
            mPlayer.start();//开启音乐
        }

        public void pause() {
            mPlayer.pause();//暂停音乐
        }

        public long getMusicDuration() {
            return mPlayer.getDuration();//获取文件的总长度
        }

        public long getPosition() {
            return mPlayer.getCurrentPosition();//获取当前播放进度
        }

        public void setPosition (int position) {
            mPlayer.seekTo(position);//重新设定播放进度
        }
    }

    /**
     * 当绑定服务的时候,自动回调这个方法
     * 返回的对象可以直接操作Service内部的内容
     * @param intent
     * @return
     */
    @Override
    public IBinder onBind(Intent intent) {
        return new MusicController();
    }

    @Override
    public void onCreate() {
        super.onCreate();
        mPlayer = MediaPlayer.create(this, R.raw.music1);
    }

    /**
     * 任意一次unbindService()方法,都会触发这个方法
     * 用于释放一些绑定时使用的资源
     * @param intent
     * @return
     */
    @Override
    public boolean onUnbind(Intent intent) {
        return super.onUnbind(intent);
    }

    @Override
    public void onDestroy() {
        if (mPlayer.isPlaying()) {
            mPlayer.stop();
        }
        mPlayer.release();
        mPlayer = null;
        super.onDestroy();
    }
}

main.java:

package com.example.cungu.musicdemo;


import android.animation.ObjectAnimator;
import android.animation.ValueAnimator;
import android.content.ComponentName;
import android.content.Intent;
import android.content.ServiceConnection;
import android.graphics.Bitmap;
import android.graphics.Color;
import android.os.Build;
import android.os.IBinder;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.view.View;
import android.view.Window;
import android.view.WindowManager;
import android.view.animation.LinearInterpolator;
import android.widget.Button;
import android.widget.ImageView;
import android.widget.SeekBar;
import android.widget.TextSwitcher;

import java.text.SimpleDateFormat;
import java.util.Date;


public class MainActivity extends AppCompatActivity implements View.OnClickListener,
        Runnable, ServiceConnection, SeekBar.OnSeekBarChangeListener {
    private ImageView disc,needle,playingPre,playingPlay,playingNext;
    private ObjectAnimator discAnimation,needleAnimation;//自定义指针和唱盘
    private boolean isPlaying = true;//0,1 判断是否处于播放状态
    //声明服务
    private static final String TAG = MainActivity.class.getSimpleName();
    private MediaService.MusicController mMusicController;
    //使用方法:mMusicController.play();播放   mMusicController.pause();暂停
    private boolean running;
    private TextSwitcher mSwitcher;
    private SeekBar mSeekBar;
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        //设置透明栏
        if(Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
            Window window = getWindow();
            window.clearFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS
                    | WindowManager.LayoutParams.FLAG_TRANSLUCENT_NAVIGATION);
            window.getDecorView().setSystemUiVisibility(
                    View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN
                            | View.SYSTEM_UI_FLAG_LAYOUT_STABLE
            );
            window.addFlags(WindowManager.LayoutParams.FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS);
            window.setStatusBarColor(Color.TRANSPARENT);
        }

        //滑动条部分
        mSeekBar = (SeekBar) findViewById(R.id.music_seek_bar);
        mSeekBar.setOnSeekBarChangeListener(this);
        mSwitcher = (TextSwitcher) findViewById(R.id.text_switcher);
        mSwitcher.setInAnimation(this, android.R.anim.fade_in);
        mSwitcher.setOutAnimation(this, android.R.anim.fade_out);
        Intent intent = new Intent(this, MediaService.class);
        //增加StartService,来增加后台播放功能
        startService(intent);
        // 绑定服务,使用context来绑定
        // 那个界面需要绑定 就用哪个 Activity
        // 参数1:Intent               代表需要绑定哪一个Service
        // 参数2:ServiceConnection    回调接口,可以接收到Service连接成功和断开的回调,成功就可以取到对象。
        // 绑定服务 参数2就是服务和指定的对象绑定在一起
        bindService(intent, this, BIND_AUTO_CREATE);

        //指针和唱片部分
        initViews();//定义背景图
        setAnimations();
    }

    private void initViews() {
        playingPre = (ImageView) findViewById(R.id.playing_pre);
        playingPlay = (ImageView) findViewById(R.id.playing_play);
        playingNext = (ImageView) findViewById(R.id.playing_next);
        disc = (ImageView) findViewById(R.id.disc);
        needle = (ImageView) findViewById(R.id.needle);
        playingPre.setOnClickListener(this);
        playingPlay.setOnClickListener(this);
        playingNext.setOnClickListener(this);
    }

    //动画设置
    private void setAnimations() {

        discAnimation = ObjectAnimator.ofFloat(disc, "rotation", 0, 360);
        discAnimation.setDuration(20000);
        discAnimation.setInterpolator(new LinearInterpolator());
        discAnimation.setRepeatCount(ValueAnimator.INFINITE);

        needleAnimation = ObjectAnimator.ofFloat(needle, "rotation", 0, 25);
        needle.setPivotX(0);
        needle.setPivotY(0);
        needleAnimation.setDuration(800);
        needleAnimation.setInterpolator(new LinearInterpolator());
    }

    @Override
    public void onClick(View v) {
        int id = v.getId();
        switch (id) {
            //前一曲
            case R.id.playing_pre:
                if (discAnimation != null) {
                    discAnimation.end();
                    playing();
                }
                break;
            //播放中
            case R.id.playing_play:
                if (isPlaying){
                    playing();
                }else {
                    if (needleAnimation != null) {
                        needleAnimation.reverse();
                        needleAnimation.end();
                        mMusicController.pause();
                    }
                    if (discAnimation != null && discAnimation.isRunning()) {
                        discAnimation.cancel();
                        mMusicController.pause();
                        float valueAvatar = (float) discAnimation.getAnimatedValue();
                        discAnimation.setFloatValues(valueAvatar, 360f + valueAvatar);
                    }
                    playingPlay.setImageResource(R.drawable.music_play);
                    isPlaying = true;
                }
                break;
            //下一曲
            case R.id.playing_next:
                if (discAnimation != null) {
                    discAnimation.end();

                    playing();
                }
                break;
            default:
                break;
        }
    }

    //播放时动画设置和图片切换
    private void playing(){
        needleAnimation.start();
        discAnimation.start();
        playingPlay.setImageResource(R.drawable.music_pause);
        mMusicController.play();//播放
        isPlaying = false;
    }
//===================================歌曲播放服务================================================
    @Override
    protected void onStart() {
        super.onStart();
        Thread thread = new Thread(this);
        thread.start();
    }

    @Override
    protected void onStop() {
        running = false;
        super.onStop();
    }

    @Override
    protected void onDestroy() {
        // 解除绑定
        unbindService(this);
        super.onDestroy();
    }

    //-----------播放到当前音乐的滑动条及时间设置-------------
    @Override
    public void run() {
        running = true;
        try {
            while (running) {
                if (mMusicController != null) {
                    long musicDuration = mMusicController.getMusicDuration();
                    final long position = mMusicController.getPosition();
                    final Date dateTotal = new Date(musicDuration);
                    final SimpleDateFormat sb = new SimpleDateFormat("mm:ss");
                    mSeekBar.setMax((int) musicDuration);
                    mSeekBar.setProgress((int) position);
                    mSwitcher.post(
                            new Runnable() {
                                @Override
                                public void run() {
                                    Date date = new Date(position);
                                    String time = sb.format(date) + "/" + sb.format(dateTotal);
                                    mSwitcher.setCurrentText(time);
                                }
                            }
                    );
                }

                Thread.sleep(500);
            }
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }

    //-----------------------------
    //服务绑定与解除绑定的回调

    /**
     * 当服务与当前绑定对象,绑定成功,服务onBind方法调用并且返回之后
     * 回调给这个方法
     *
     * @param name
     * @param service IBinder 就是服务 onBind 返回的对象
     */
    @Override
    public void onServiceConnected(ComponentName name, IBinder service) {
        mMusicController = ((MediaService.MusicController) service);
    }

    @Override
    public void onServiceDisconnected(ComponentName name) {
        mMusicController = null;
    }

    public void btnStopService(View view) {
        Intent intent = new Intent(this, MediaService.class);
        stopService(intent);
    }

    @Override
    public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) {
    }

    @Override
    public void onStartTrackingTouch(SeekBar seekBar) {
    }

    @Override
    public void onStopTrackingTouch(SeekBar seekBar) {
        mMusicController.setPosition(seekBar.getProgress());
    }
}

大功告成!

附:本次项目源码

下载地址:https://github.com/cungudafa/MusicDemo


注:
1、我在MediaServer里面只放了一首歌曲,可以完善读取手机内存的歌曲,还可以做一个listView界面来显示全部歌曲(需要创建对数据库的调用),like this;
【Android】_MediaServer_仿网易云音乐播放器1(指针和唱片)_第23张图片
2、真正的网易云模糊背景用了高斯模糊函数,对唱片中图像进行处理,我这里未实现,机智如我,一张模糊背景就处理掉,like this,不够效果不好;
【Android】_MediaServer_仿网易云音乐播放器1(指针和唱片)_第24张图片 【Android】_MediaServer_仿网易云音乐播放器1(指针和唱片)_第25张图片 【Android】_MediaServer_仿网易云音乐播放器1(指针和唱片)_第26张图片
3、还有唱片是我用绘图工具将专辑封面合成的,like this,真正网易云是用图片叠加函数,绘出来的,可以略过手工绘图,我功力不够啊!希望大佬可尝试~
【Android】_MediaServer_仿网易云音乐播放器1(指针和唱片)_第27张图片
我Ps合成图片:【Android】_MediaServer_仿网易云音乐播放器1(指针和唱片)_第28张图片可还行~

若有大佬完善注释功能,请受我一拜!直接去网易云上班吧!

你可能感兴趣的:(Android,Androidstudio学习)