安卓实现仿网易云音乐播放界面

网易云播放界面

想要在安卓上实现类似于网易云的播放界面,工作不算复杂,简单分析一下需要完成的工作:

  1. 上拉全屏弹窗Activity
  2. 毛玻璃背景
  3. 播放暂停旋转动画

其实也就这些内容,所以实现起来也很简单,下面逐一完成。

1. 上拉全屏弹窗Activity

实际上就是改变一下Activity的入场和出场动画,额外需要增加的是下拉关闭功能

1.1 出入场动画

只需要调用方法overridePendingTransition()即可:

//入场
startActivity(new Intent(activity, MusicPlayerActivity.class))
overridePendingTransition(R.anim.slide_down_in, R.anim.slide_down_out)

//出场
finish();
overridePendingTransition(R.anim.slide_up_in, R.anim.slide_up_out);
java

其中用到的四个动画代码文件放置在资源文件夹res/anim下(非必须,能找到就可以),代码如下:

  • res/anim/slide_down_in.xml
<?xml version="1.0" encoding="utf-8"?>
<set xmlns:android="http://schemas.android.com/apk/res/android"
    android:duration="300">
    <translate
        android:fromYDelta="100%"
        android:interpolator="@android:anim/decelerate_interpolator"
        android:toYDelta="0"/>
</set>
xml
  • res/anim/slide_down_out.xml
<?xml version="1.0" encoding="utf-8"?>
<set xmlns:android="http://schemas.android.com/apk/res/android"
    android:duration="300">
    <translate
        android:fromYDelta="0"
        android:interpolator="@android:anim/decelerate_interpolator"
        android:toYDelta="-100%" />
</set>
xml
  • res/anim/slide_up_in.xml
<?xml version="1.0" encoding="utf-8"?>
<set xmlns:android="http://schemas.android.com/apk/res/android"
    android:duration="200">
    <translate
        android:fromYDelta="-100%"
        android:interpolator="@android:anim/decelerate_interpolator"
        android:toYDelta="0" />
</set>
xml
  • res/anim/slide_up_out.xml
<?xml version="1.0" encoding="utf-8"?>
<set xmlns:android="http://schemas.android.com/apk/res/android"
    android:duration="200">
    <translate
        android:fromYDelta="0"
        android:interpolator="@android:anim/accelerate_interpolator"
        android:toYDelta="100%" />
</set>
xml

我们定义一个静态方法show()用于封装startActivity()

public static void show(Activity activity) {
    if (activity == null) return;
    activity.startActivity(new Intent(activity, MusicPlayerActivity.class));
    activity.overridePendingTransition(R.anim.slide_down_in, R.anim.slide_down_out);
}
java

同时重写一下finish()方法:

@Override
public void finish() {
    super.finish();
    overridePendingTransition(R.anim.slide_up_in, R.anim.slide_up_out);
}
java

这样其它Activity想要启动该Activity只需要调用静态方法show()即可,关闭的时候正常调用finish.

1.2 下拉关闭

我们需要重写touchEvent,根据Action来决定Activity的行为:

当按下时,记录Y方向的坐标信息,为下拉事件判定作准备

当移动时,计算Y方向坐标变化,然后改变Activity的位置

当松开/取消时,判断Y方向向下移动距离是否超过临界距离,超过则关闭Activity,否则Activity归位

float touchDownY = 0;
float layoutY = 0;
float closeY = (float) (Resources.getSystem().getDisplayMetrics().heightPixels * 0.3);
boolean isTouched = false;
@Override
public boolean onTouchEvent(MotionEvent event) {
    switch (event.getAction()) {
        case MotionEvent.ACTION_DOWN:
            Log.d(TAG, "onTouchEvent: ACTION_DOWN....");
            touchDownY = event.getY();
            isTouched = true;
            layoutY = mLayout.getY();
            break;
        case MotionEvent.ACTION_MOVE:
            if (isTouched) {
                float aimY = layoutY + event.getY() - touchDownY;
                if (aimY < 0) aimY = 0;
                Log.d(TAG, "onTouchEvent: " + aimY);
                mLayout.setY(aimY);
            }
            break;
        case MotionEvent.ACTION_UP:
            Log.d(TAG, "onTouchEvent: ACTION_UP....");
        case MotionEvent.ACTION_CANCEL:
            Log.d(TAG, "onTouchEvent: ACTION_CANCEL....");
            isTouched = false;
            if (mLayout.getY() < closeY) {
                ObjectAnimator enterAnim = ObjectAnimator.ofFloat(mLayout, "y", mLayout.getY(), 0);
                enterAnim.setDuration(300);
                enterAnim.start();
            } else {
                finish();
            }
            break;
    }
    return false;
}
java

1.3 透明通知栏

为了更好的显示效果,需要将通知栏透明:

public void setNavAndStatusBarTransparent(Activity activity) {
    if (activity == null) {
        return;
    }
    try {
        Window window = activity.getWindow();
        if (window != null) {
            View decorView = window.getDecorView();
            //两个 flag 要结合使用,表示让应用的主体内容占用系统状态栏的空间
            int option = View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN
                    | View.SYSTEM_UI_FLAG_LAYOUT_STABLE;
            decorView.setSystemUiVisibility(option);
            window.addFlags(WindowManager.LayoutParams.FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS);
            window.setStatusBarColor(Color.TRANSPARENT);
            //导航栏颜色也可以正常设置
            window.setNavigationBarColor(Color.TRANSPARENT);
        }
    } catch (Exception e) {
        e.printStackTrace();
    }
}
java

然后在OnCreate里调用:

@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_music_player);
    setNavAndStatusBarTransparent(this);
}
private void initView() {
    WindowManager.LayoutParams lp = getWindow().getAttributes();
    lp.width = WindowManager.LayoutParams.MATCH_PARENT;
    lp.height = WindowManager.LayoutParams.MATCH_PARENT;
    getWindow().setAttributes(lp);
    setNavAndStatusBarTransparent(this);
}
java

1.4 全屏显示

@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_music_player);
    WindowManager.LayoutParams lp = getWindow().getAttributes();
    lp.width = WindowManager.LayoutParams.MATCH_PARENT;
    lp.height = WindowManager.LayoutParams.MATCH_PARENT;
    getWindow().setAttributes(lp);
}
java

我们封装好的Activity完整代码如下:

  • MusicPlayerActivity.class
package cool.hyz.musicplayer;

import android.animation.ObjectAnimator;
import android.app.Activity;
import android.content.Intent;
import android.content.res.Resources;
import android.graphics.Color;
import android.os.Bundle;
import android.util.Log;
import android.view.MotionEvent;
import android.view.View;
import android.view.Window;
import android.view.WindowManager;
import android.widget.RelativeLayout;

import androidx.annotation.Nullable;
import androidx.appcompat.app.AppCompatActivity;

public class MusicPlayerActivity extends AppCompatActivity {

    private static final String TAG = "MusicPlayerActivity";

    private RelativeLayout mLayout;

    public static void show(Activity activity) {
        if (activity == null) return;
        activity.startActivity(new Intent(activity, MusicPlayerActivity.class));
        activity.overridePendingTransition(R.anim.slide_down_in, R.anim.slide_down_out);
    }

    @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_music_player);
        initView();
    }

    private void initView() {
        WindowManager.LayoutParams lp = getWindow().getAttributes();
        lp.width = WindowManager.LayoutParams.MATCH_PARENT;
        lp.height = WindowManager.LayoutParams.MATCH_PARENT;
        getWindow().setAttributes(lp);
        setNavAndStatusBarTransparent(this);
        mLayout = findViewById(R.id.media_player_layout);
    }

    public void setNavAndStatusBarTransparent(Activity activity) {
        if (activity == null) {
            return;
        }
        try {
            Window window = activity.getWindow();
            if (window != null) {
                View decorView = window.getDecorView();
                //两个 flag 要结合使用,表示让应用的主体内容占用系统状态栏的空间
                int option = View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN
                        | View.SYSTEM_UI_FLAG_LAYOUT_STABLE;
                decorView.setSystemUiVisibility(option);
                window.addFlags(WindowManager.LayoutParams.FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS);
                window.setStatusBarColor(Color.TRANSPARENT);
                //导航栏颜色也可以正常设置
                window.setNavigationBarColor(Color.TRANSPARENT);
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
    }


    float touchDownY = 0;
    float layoutY = 0;
    float closeY = (float) (Resources.getSystem().getDisplayMetrics().heightPixels * 0.3);
    boolean isTouched = false;

    @Override
    public boolean onTouchEvent(MotionEvent event) {
        switch (event.getAction()) {
            case MotionEvent.ACTION_DOWN:
                Log.d(TAG, "onTouchEvent: ACTION_DOWN....");
                touchDownY = event.getY();
                isTouched = true;
                layoutY = mLayout.getY();
                break;
            case MotionEvent.ACTION_MOVE:
                if (isTouched) {
                    float aimY = layoutY + event.getY() - touchDownY;
                    if (aimY < 0) aimY = 0;
                    Log.d(TAG, "onTouchEvent: " + aimY);
                    mLayout.setY(aimY);
                }
                break;
            case MotionEvent.ACTION_UP:
                Log.d(TAG, "onTouchEvent: ACTION_UP....");
            case MotionEvent.ACTION_CANCEL:
                Log.d(TAG, "onTouchEvent: ACTION_CANCEL....");
                isTouched = false;
                if (mLayout.getY() < closeY) {
                    ObjectAnimator enterAnim = ObjectAnimator.ofFloat(mLayout, "y", mLayout.getY(), 0);
                    enterAnim.setDuration(300);
                    enterAnim.start();
                } else {
                    finish();
                }
                break;
        }
        return false;
    }

    @Override
    public void finish() {
        super.finish();
        overridePendingTransition(R.anim.slide_up_in, R.anim.slide_up_out);
    }

}
java

1.5 布局文件

首先准备几个图标,这里我们用vector:

  • 播放:drawable/ic_music_play.xml
<vector xmlns:android="http://schemas.android.com/apk/res/android"
    android:width="200dp"
    android:height="200dp"
    android:viewportWidth="1024"
    android:viewportHeight="1024">

    <group
        android:rotation="180"
        android:translateX="1024"
        android:translateY="1024">
        <path
            android:fillColor="#CD1C1C1C"
            android:pathData="M511.3 98.3c-228 0-413.5 185.5-413.5 413.5 0 23.8 2.6 48.1 6.5 70.8 0 0.1 0.2 0.1 0.3 0.2 1.6 5.8 6.7 10.1 13 10.1 7.6 0 13.8-6.2 13.8-13.8 0-0.5-0.2-0.9-0.3-1.3l0.2-0.1c-3.7-21.4-6-43.3-6-65.8 0-213.1 172.8-385.9 385.9-385.9s385.9 172.8 385.9 385.9-172.6 385.9-385.8 385.9c-142.4 0-266.5-77.4-333.4-192.2 0-0.1-0.1-0.1-0.1-0.2l-0.6-0.9c-2.3-4.3-6.7-7.4-12-7.4-7.6 0-13.8 6.2-13.8 13.8 0 3 1.2 5.6 2.8 7.8l-0.2 0.1C225.7 842 358.8 925.2 511.3 925.2c228 0 413.5-185.5 413.5-413.5S739.3 98.3 511.3 98.3z" />
    </group>
    <path
        android:fillColor="#CD1C1C1C"
        android:pathData="M432.1 672.1c-7.6 0-13.8-6.2-13.8-13.8V365.4c0-7.6 6.2-13.8 13.8-13.8 7.6 0 13.8 6.2 13.8 13.8v292.9c0 7.6-6.2 13.8-13.8 13.8zM590.6 672.1c-7.6 0-13.8-6.2-13.8-13.8V365.4c0-7.6 6.2-13.8 13.8-13.8 7.6 0 13.8 6.2 13.8 13.8v292.9c0 7.6-6.2 13.8-13.8 13.8z" />
    <path
        android:fillColor="#CD1C1C1C"
        android:pathData="m886,387c-7.18232,0 -13,-5.81768 -13,-13c0,-7.18232 5.81768,-13 13,-13c7.18232,0 13,5.81768 13,13c0,7.18232 -5.81768,13 -13,13z" />
</vector>
xml
  • 暂停:drawable/ic_music_pause.xml
<vector xmlns:android="http://schemas.android.com/apk/res/android"
    android:width="200dp"
    android:height="200dp"
    android:viewportWidth="1024"
    android:viewportHeight="1024">
    <path
        android:fillColor="#CD1C1C1C"
        android:pathData="M511.3 98.3c-228 0-413.5 185.5-413.5 413.5 0 23.8 2.6 48.1 6.5 70.8 0 0.1 0.2 0.1 0.3 0.2 1.6 5.8 6.7 10.1 13 10.1 7.6 0 13.8-6.2 13.8-13.8 0-0.5-0.2-0.9-0.3-1.3l0.2-0.1c-3.7-21.4-6-43.3-6-65.8 0-213.1 172.8-385.9 385.9-385.9s385.9 172.8 385.9 385.9-172.6 385.9-385.8 385.9c-142.4 0-266.5-77.4-333.4-192.2 0-0.1-0.1-0.1-0.1-0.2l-0.6-0.9c-2.3-4.3-6.7-7.4-12-7.4-7.6 0-13.8 6.2-13.8 13.8 0 3 1.2 5.6 2.8 7.8l-0.2 0.1C225.7 842 358.8 925.2 511.3 925.2c228 0 413.5-185.5 413.5-413.5S739.3 98.3 511.3 98.3z" />
    <path
        android:fillColor="#CD1C1C1C"
        android:pathData="M446.9 659.6c-7 0-14.1-1.8-20.5-5.5-12.8-7.3-20.6-20.5-20.9-35.2L402 406.1c-0.2-14.9 7.3-28.5 20.1-36.2 12.8-7.7 28.3-7.9 41.3-0.7l191.3 106.4c13.2 7.4 21.2 20.8 21.3 36 0.1 15.1-7.8 28.6-21 36.1L467.3 654.2c-6.4 3.6-13.4 5.4-20.4 5.4z m-17.4-254l3.6 212.8c0.1 7.1 4.9 10.6 7 11.7 2 1.2 7.5 3.6 13.6 0l187.7-106.4c6.3-3.6 7-9.6 7-12.1 0-2.4-0.7-8.5-7.1-12L450 393.3c-6.2-3.4-11.7-1-13.8 0.2-2 1.3-6.8 4.9-6.7 12.1z" />

    <group
        android:rotation="180"
        android:translateX="1024"
        android:translateY="1024">
        <path
            android:fillColor="#CD1C1C1C"
            android:pathData="m886,387c-7.18232,0 -13,-5.81768 -13,-13c0,-7.18232 5.81768,-13 13,-13c7.18232,0 13,5.81768 13,13c0,7.18232 -5.81768,13 -13,13z" />
    </group>
</vector>

xml
  • 上一曲:drawable/ic_music_pre.xml
<vector xmlns:android="http://schemas.android.com/apk/res/android"
    android:width="200dp"
    android:height="200dp"
    android:viewportWidth="1024"
    android:viewportHeight="1024">
    <path
        android:fillColor="#CD1C1C1C"
        android:pathData="M356.77 605.22L701.8 849.53c69.8 49.42 166.29-0.49 166.29-86.01V261.25c0-85.52-96.49-135.43-166.29-86.01L356.77 419.55c-64.02 45.34-64.02 140.34 0 185.67zM210.42 154.36c30.38 0 55 24.62 55 55v606.07c0 30.38-24.62 55-55 55s-55-24.62-55-55V209.36c0-30.38 24.62-55 55-55z" />
</vector>
xml
  • 下一曲:drawable/ic_music_next.xml
<vector xmlns:android="http://schemas.android.com/apk/res/android"
    android:width="200dp"
    android:height="200dp"
    android:viewportWidth="1024"
    android:viewportHeight="1024">
    <path
        android:fillColor="#CD1C1C1C"
        android:pathData="M665.47 417.65l-345.03-244.3c-69.8-49.42-166.29 0.49-166.29 86.01v502.27c0 85.52 96.49 135.43 166.29 86.01l345.03-244.31c64.02-45.34 64.02-140.34 0-185.68zM811.82 868.52c-30.38 0-55-24.62-55-55V207.46c0-30.38 24.62-55 55-55s55 24.62 55 55v606.07c0 30.37-24.62 54.99-55 54.99z" />
</vector>
xml
  • 随机播放:drawable/ic_music_random.xml
<vector xmlns:android="http://schemas.android.com/apk/res/android"
    android:width="200dp"
    android:height="200dp"
    android:viewportWidth="1024"
    android:viewportHeight="1024">
    <path
        android:fillColor="#CD1C1C1C"
        android:pathData="M747.093333 596.48a21.333333 21.333333 0 0 1 12.586667 4.096l133.888 97.792a21.333333 21.333333 0 0 1-0.106667 34.56l-133.909333 96.405333a21.333333 21.333333 0 0 1-33.813333-17.322666v-53.482667c-82.858667-0.789333-133.397333-24.896-178.709334-81.088 16.810667-24.192 32.597333-51.029333 47.808-80.554667 36.032 56.213333 68.885333 75.690667 130.901334 76.288V617.813333a21.333333 21.333333 0 0 1 21.333333-21.333333zM746.88 169.472a21.333333 21.333333 0 0 1 12.458667 4.010667l134.058666 96.298666a21.333333 21.333333 0 0 1 0.149334 34.56l-134.08 98.026667a21.333333 21.333333 0 0 1-33.92-17.237333v-55.296c-86.592 0.789333-118.784 37.824-175.381334 161.365333-6.357333 13.930667-9.749333 21.333333-12.906666 28.16-75.946667 164.16-153.642667 236.608-323.541334 239.232H128v-85.76h85.077333c129.152-1.984 182.784-51.989333 246.4-189.482667 3.072-6.656 6.357333-13.866667 12.757334-27.861333 69.141333-150.912 122.88-210.197333 253.333333-211.413333V190.805333a21.333333 21.333333 0 0 1 21.333333-21.333333zM213.76 244.074667c102.442667 1.578667 171.370667 28.544 226.602667 85.717333a981.12 981.12 0 0 0-42.432 82.133333c-44.693333-57.450667-97.493333-80.746667-184.832-82.112H128v-85.76h85.738667z" />
</vector>
xml
  • 循环播放:drawable/ic_music_loop.xml
<vector xmlns:android="http://schemas.android.com/apk/res/android"
    android:width="200dp"
    android:height="200dp"
    android:viewportWidth="1024"
    android:viewportHeight="1024">
    <path
        android:fillColor="#CD1C1C1C"
        android:pathData="M203 768a31.85 31.85 0 0 1-20.11-7.13A320 320 0 0 1 384 192h210.75l-9.38-9.37a32 32 0 0 1 45.26-45.26l64 64a32 32 0 0 1 0 45.26l-64 64a32 32 0 0 1-45.26-45.26l9.38-9.37H384c-141.16 0-256 114.84-256 256a255.07 255.07 0 0 0 95.13 199.15A32 32 0 0 1 203 768z m235.63 118.63a32 32 0 0 0 0-45.26l-9.38-9.37H640a320 320 0 0 0 201.12-568.91 32 32 0 1 0-40.25 49.76A255.07 255.07 0 0 1 896 512c0 141.16-114.84 256-256 256H429.25l9.38-9.37a32 32 0 0 0-45.26-45.26l-64 64a32 32 0 0 0 0 45.26l64 64a32 32 0 0 0 45.26 0zM448 544a32 32 0 1 1-32-32 32 32 0 0 1 32 32z m96-32a32 32 0 1 0 32 32 32 32 0 0 0-32-32z m128 0a32 32 0 1 0 32 32 32 32 0 0 0-32-32z"
/></vector>
xml
  • 单曲循环播放:drawable/ic_music_loop_single.xml
<vector xmlns:android="http://schemas.android.com/apk/res/android"
    android:width="200dp"
    android:height="200dp"
    android:viewportWidth="1024"
    android:viewportHeight="1024">
    <path
        android:fillColor="#CD1C1C1C"
        android:pathData=
            "M361.5 727.8c-119.1 0-215.9-96.9-215.9-215.9 0-119.1 96.9-215.9 215.9-215.9 2.3 0 4.6-0.2 6.8-0.6v58.3c0 12.3 14 19.4 23.9 12.1l132.6-97.6c8.1-6 8.1-18.2 0-24.2l-132.6-97.6c-9.9-7.3-23.9-0.2-23.9 12.1v58.1c-2.2-0.4-4.5-0.6-6.8-0.6-39.8 0-78.5 7.9-115 23.4-35.2 15-66.8 36.3-94 63.5s-48.6 58.8-63.5 94c-15.5 36.5-23.4 75.2-23.4 115s7.9 78.5 23.4 115c15 35.2 36.3 66.8 63.5 94s58.8 48.6 94 63.5c36.5 15.5 75.2 23.4 115 23.4 22.1 0 40-17.9 40-40s-17.9-40-40-40zM938.2 396.9c-15-35.2-36.3-66.8-63.5-94s-58.8-48.6-94-63.5c-36.5-15.5-75.2-23.4-115-23.4-22.1 0-40 17.9-40 40s17.9 40 40 40c119.1 0 215.9 96.9 215.9 215.9 0 119.1-96.9 215.9-215.9 215.9-4.1 0-8.1 0.6-11.8 1.8v-60.8c0-12.3-14-19.4-23.9-12.1l-132.6 97.6c-8.1 6-8.1 18.2 0 24.2L629.9 876c9.9 7.3 23.9 0.2 23.9-12.1V806c3.7 1.2 7.7 1.8 11.8 1.8 39.8 0 78.5-7.9 115-23.4 35.2-15 66.8-36.3 94-63.5s48.6-58.8 63.5-94c15.5-36.5 23.4-75.2 23.4-115s-7.8-78.5-23.3-115z"
/>
    <path
        android:fillColor="#CD1C1C1C"
        android:pathData=
            "M512.8 660.6c22.1-0.1 39.9-18.1 39.8-40.2l-1.2-214.1c-0.1-22-18-39.8-40-39.8h-0.2c-22.1 0.1-39.9 18.1-39.8 40.2l1.2 214.1c0.1 22 18 39.8 40 39.8h0.2z"
/>

</vector>
xml
  • 顺序播放:drawable/ic_music_in_order.xml
<vector xmlns:android="http://schemas.android.com/apk/res/android"
    android:width="200dp"
    android:height="200dp"
    android:viewportWidth="1024"
    android:viewportHeight="1024">
    <path
        android:fillColor="#CD1C1C1C"
        android:pathData="M560.70379009 320.16991061H198.25332639c-21.68104205 0-39.27725009-17.59620803-39.27725009-39.27725007s17.59620803-39.27725009 39.27725009-39.27725005H560.70379009c21.68104205 0 39.27725009 17.59620803 39.27725007 39.27725005s-17.59620803 39.27725009-39.27725007 39.27725007zM560.70379009 555.67630209H198.25332639c-21.68104205 0-39.27725009-17.59620803-39.27725009-39.27725008s17.59620803-39.27725009 39.27725009-39.27725008H560.70379009c21.68104205 0 39.27725009 17.59620803 39.27725007 39.27725008-0.15710901 21.68104205-17.59620803 39.27725009-39.27725007 39.27725008zM560.70379009 791.02558454H198.25332639c-21.68104205 0-39.27725009-17.59620803-39.27725009-39.27725007s17.59620803-39.27725009 39.27725009-39.27725006H560.70379009c21.68104205 0 39.27725009 17.59620803 39.27725007 39.27725006s-17.59620803 39.27725009-39.27725007 39.27725007zM721.42629742 826.21800061c-6.441469 0-13.04004703-1.57108999-18.85308003-4.87037899-12.56872002-6.91279601-20.26706103-20.10995203-20.26706105-34.40687108V245.07180847c0-21.68104205 17.59620803-39.27725009 39.27725007-39.27725008s39.27725009 17.59620803 39.27725008 39.27725008v470.69856494l48.54668111-30.63625506c18.22464404-11.46895703 42.57653909-6.127251 54.20260509 12.25450201 11.62606602 18.38175303 6.127251 42.57653909-12.25450202 54.20260511l-108.71942822 68.65663314c-6.59857803 3.92772502-13.98270102 5.97014202-21.20971503 5.970142z"
/></vector>
xml
  • 播放列表:drawable/ic_music_list.xml
<vector xmlns:android="http://schemas.android.com/apk/res/android"
    android:width="200dp"
    android:height="200dp"
    android:viewportWidth="1024"
    android:viewportHeight="1024">
    <path
        android:fillColor="#CD1C1C1C"
        android:pathData="M221.58 319.89h-48c-35.9 0-65-29.1-65-65s29.1-65 65-65h48c35.9 0 65 29.1 65 65s-29.1 65-65 65zM718.58 319.89h-322c-35.9 0-65-29.1-65-65s29.1-65 65-65h322c35.9 0 65 29.1 65 65s-29.1 65-65 65zM407.58 579.89h-234c-35.9 0-65-29.1-65-65s29.1-65 65-65h234c35.9 0 65 29.1 65 65s-29.1 65-65 65zM256.58 838.89h-83c-35.9 0-65-29.1-65-65s29.1-65 65-65h83c35.9 0 65 29.1 65 65s-29.1 65-65 65zM429.58 838.89h-1c-35.9 0-65-29.1-65-65s29.1-65 65-65h1c35.9 0 65 29.1 65 65s-29.1 65-65 65zM713.96 396.72c-122.04 0-220.97 98.93-220.97 220.97s98.93 220.97 220.97 220.97 220.97-98.93 220.97-220.97S836 396.72 713.96 396.72zM837.9 561.91L689.88 715.26a30.078 30.078 0 0 1-21.38 9.19h-0.27c-7.97 0-15.63-3.17-21.27-8.81l-56.57-56.57c-11.75-11.75-11.75-30.79 0-42.54s30.79-11.75 42.54 0l34.92 34.91 126.76-131.32c11.54-11.96 30.58-12.29 42.54-0.75 11.95 11.54 12.29 30.58 0.75 42.54z" />
</vector>

xml

以及两个图片文件

  • drawable/icon_music_player_bar.webp
    icon_music_player_bar.webp

  • drawable/icon_music_player_pan.webp

icon_music_player_pan.webp
需要的只需要右键图片然后保存图片复制到资源目录res/drawable下就可以了

完整的布局文件代码如下:

  • layout/activity_music_player.xml
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    android:id="@+id/media_player_layout"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:background="@color/black">

    <ImageView
        android:id="@+id/song_background"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:scaleType="centerCrop"
        android:scaleX="1.5"
        android:scaleY="1.5" />


    <LinearLayout
        android:id="@+id/player_tab"
        android:layout_width="match_parent"
        android:layout_height="90dp"
        android:layout_alignParentTop="true"
        android:background="#90707070"
        android:paddingTop="40dp">


        <View
            android:layout_width="0dp"
            android:layout_height="2dp"
            android:layout_weight="1.5" />

        <TextView
            android:layout_width="0dp"
            android:layout_height="wrap_content"
            android:layout_weight="1"
            android:text="详情"
            android:textAlignment="center"
            android:textColor="#E8E8E8" />

        <TextView
            android:layout_width="0dp"
            android:layout_height="wrap_content"
            android:layout_weight="1"
            android:text="|"
            android:textAlignment="center" />

        <TextView
            android:layout_width="0dp"
            android:layout_height="wrap_content"
            android:layout_weight="1"
            android:text="歌曲"
            android:textAlignment="center"
            android:textColor="@color/white" />

        <TextView
            android:layout_width="0dp"
            android:layout_height="wrap_content"
            android:layout_weight="1"
            android:text="|"
            android:textAlignment="center" />

        <TextView
            android:layout_width="0dp"
            android:layout_height="wrap_content"
            android:layout_weight="1"
            android:text="歌词"
            android:textAlignment="center"
            android:textColor="#E8E8E8" />

        <View
            android:layout_width="0dp"
            android:layout_height="2dp"
            android:layout_weight="1.5" />


    </LinearLayout>

    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:layout_below="@id/player_tab"
        android:background="#90707070"
        android:orientation="vertical"
        android:padding="30dp">


        <RelativeLayout
            android:id="@+id/player"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:layout_marginTop="26dp">

            <ImageView
                android:id="@+id/song_cover"
                android:layout_width="200dp"
                android:layout_height="200dp"
                android:layout_centerInParent="true"
                android:scaleType="centerCrop"
                android:src="@mipmap/ic_launcher" />

            <ImageView
                android:layout_width="320dp"
                android:layout_height="320dp"
                android:layout_centerInParent="true"
                android:scaleType="centerCrop"
                android:src="@drawable/icon_music_player_pan" />

        </RelativeLayout>

        <TextView
            android:id="@+id/song_title"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_marginTop="20dp"
            android:text="续加仪"
            android:textAlignment="center"
            android:textColor="@color/white"
            android:textSize="25sp" />

        <TextView
            android:id="@+id/song_singer"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_marginTop="20dp"
            android:text="续加仪"
            android:textAlignment="center"
            android:textColor="#E8E8E8"
            android:textSize="15sp" />


    </LinearLayout>


    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_alignParentBottom="true"
        android:gravity="center"
        android:orientation="vertical"
        android:paddingStart="30dp"
        android:paddingEnd="30dp">

        <SeekBar
            android:id="@+id/song_progress"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:progress="0"
            android:progressTint="@color/white"
            android:thumbTint="@color/white" />

        <LinearLayout
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:gravity="center"
            android:paddingStart="15dp"
            android:paddingEnd="15dp"
            android:orientation="horizontal">

            <TextView
                android:id="@+id/song_current"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:text="00:00"
                android:textAlignment="center"
                android:textColor="#E8E8E8"
                android:textSize="12sp" />

            <View
                android:layout_width="0dp"
                android:layout_height="2dp"
                android:layout_weight="1" />

            <TextView
                android:id="@+id/song_duration"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:text="05:30"
                android:textAlignment="center"
                android:textColor="#E8E8E8"
                android:textSize="12sp" />
        </LinearLayout>

        <LinearLayout
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:gravity="center"
            android:orientation="horizontal">


            <View
                android:layout_width="0dp"
                android:layout_height="2dp"
                android:layout_weight="1" />

            <ImageView
                android:id="@+id/song_play_mode"
                android:layout_width="30dp"
                android:layout_height="30dp"
                android:layout_marginStart="5dp"
                android:layout_marginEnd="5dp"
                android:clickable="true"
                android:focusable="true"
                android:src="@drawable/ic_music_random"
                app:tint="@color/selector_color_music_icon_button_white" />

            <View
                android:layout_width="0dp"
                android:layout_height="2dp"
                android:layout_weight="1" />

            <ImageView
                android:id="@+id/song_pre"
                android:layout_width="30dp"
                android:layout_height="30dp"
                android:layout_marginStart="5dp"
                android:layout_marginEnd="5dp"
                android:clickable="true"
                android:focusable="true"
                android:src="@drawable/ic_music_pre"
                app:tint="@color/selector_color_music_icon_button_white" />

            <View
                android:layout_width="0dp"
                android:layout_height="2dp"
                android:layout_weight="1" />

            <ImageView
                android:id="@+id/song_play_or_pause"
                android:layout_width="80dp"
                android:layout_height="80dp"
                android:layout_marginStart="5dp"
                android:layout_marginEnd="5dp"
                android:clickable="true"
                android:focusable="true"
                android:src="@drawable/ic_music_pause"
                app:tint="@color/selector_color_music_icon_button_white" />

            <View
                android:layout_width="0dp"
                android:layout_height="2dp"
                android:layout_weight="1" />

            <ImageView
                android:id="@+id/song_next"
                android:layout_width="30dp"
                android:layout_height="30dp"
                android:layout_marginStart="5dp"
                android:layout_marginEnd="5dp"
                android:clickable="true"
                android:focusable="true"
                android:src="@drawable/ic_music_next"
                app:tint="@color/selector_color_music_icon_button_white" />

            <View
                android:layout_width="0dp"
                android:layout_height="2dp"
                android:layout_weight="1" />

            <ImageView
                android:id="@+id/song_play_list"
                android:layout_width="30dp"
                android:layout_height="30dp"
                android:layout_marginStart="5dp"
                android:layout_marginEnd="5dp"
                android:clickable="true"
                android:focusable="true"
                android:src="@drawable/ic_music_list"
                app:tint="@color/selector_color_music_icon_button_white" />

            <View
                android:layout_width="0dp"
                android:layout_height="2dp"
                android:layout_weight="1" />
        </LinearLayout>
    </LinearLayout>

    <ImageView
        android:id="@+id/player_bar"
        android:layout_width="180dp"
        android:layout_height="wrap_content"
        android:layout_below="@id/player_tab"
        android:layout_centerHorizontal="true"
        android:layout_marginTop="-19.5dp"
        android:adjustViewBounds="true"
        android:rotation="-30"
        android:src="@drawable/icon_music_player_bar"
        android:transformPivotX="90dp"
        android:transformPivotY="19dp" />

</RelativeLayout>
xml

效果:

image.png

1.6 主题设置

该实现效果会出现上拉的Activity把前一个Activity顶上去的问题,需要配置主题:

  • res/values/themes.xml
<resources xmlns:tools="http://schemas.android.com/tools">
    <style name="FullScreenActivity" parent="Theme.AppCompat.Light.NoActionBar">
        <!--设置Activity的背景-->
        <item name="android:windowBackground">@android:color/transparent</item>
        <!--设置Activity的windowFrame框为无-->
        <item name="android:windowFrame">@null</item>
        <!--设置无标题-->
        <item name="android:windowNoTitle">true</item>
        <!--是否浮现在activity之上-->
        <item name="android:windowIsFloating">true</item>
        <!--设置窗口内容不覆盖-->
        <item name="android:windowContentOverlay">@null</item>
        <!--背景是否模糊显示-->
        <item name="android:backgroundDimEnabled">false</item>
    </style>
</resources>
xml

并在Manifest中配置:

  • AndroidManifest.xml
<activity android:name="cool.hyz.musicplayer.MusicPlayerActivity"  android:theme="@style/FullScreenActivity"/>
xml

2. 毛玻璃背景

先放一张需要用到的素材

  • drawable/lover_cover.png

lover_cover

需要的可以右键保存复制到资源目录res/drawable

毛玻璃背景很好实现,只需要使用Gilde自带的功能即可。

其中,Glide的依赖为:

//imageLoader
implementation 'com.github.bumptech.glide:glide:4.11.0'
implementation 'jp.wasabeef:glide-transformations:4.3.0'

然后实现方式为:

Glide.with(this).load(R.drawable.lover_cover)
     .apply(RequestOptions.bitmapTransform(new BlurTransformation(25, 3)))
     .into(mBackground);
java

3.播放暂停旋转动画

播放暂停旋转动画是一种仿留声机的唱片旋转动画,只需要完成两个动作:

  • 唱片的旋转
  • 唱头的摆动

首先我们先给音乐的基本信息显示出来,包括歌手、歌名、封面,这里需要圆角图片,同样使用Glide完成:

mTitle.setText("恋人");
mSinger.setText("李荣浩");
Glide.with(this).load(R.drawable.lover_cover)
     .apply(RequestOptions.circleCropTransform())
     .into(mCover);
java

3.1 唱片的旋转

我们需要在播放的时候,唱片顺时针无限循环旋转;暂停的时候停止旋转,但角度保持;继续播放的时候不归位而继续旋转

RelativeLayout mPlayer = findViewById(R.id.player);

MediaPlayer mMediaPlayer;
ObjectAnimator mPlayerRotationAnim;

if (mPlayerRotationAnim != null) {
    mPlayerRotationAnim.pause();
}

if (mMediaPlayer.isPlaying()) {
    mPlayerBar.clearAnimation();
    mPlayerBar.setAnimation(mPlayerBarRotationAnimEnter);
    if (mPlayerRotationAnim == null) {
        mPlayerRotationAnim = ObjectAnimator.ofFloat(mPlayer, "rotation", mPlayer.getRotation(), 360.0f);
        mPlayerRotationAnim.setDuration(5000);
        mPlayerRotationAnim.setRepeatCount(Animation.INFINITE);
        mPlayerRotationAnim.setInterpolator(new LinearInterpolator());
        mPlayerRotationAnim.setRepeatMode(ObjectAnimator.RESTART);// 循环模式
        mPlayerRotationAnim.start();
    } else
        mPlayerRotationAnim.resume();
}
java

3.2 唱头的摆动

我们需要在播放的时候,唱头位于唱片之上;暂停的时候远离唱片;中间过程为摆动的动画

这里使用RotateAnimation

ImageView mPlayerBar = findViewById(R.id.player_bar);

MediaPlayer mMediaPlayer;
RotateAnimation mPlayerBarRotationAnimEnter;
RotateAnimation mPlayerBarRotationAnimExit;

if (mPlayerBarRotationAnimEnter != null) mPlayerBarRotationAnimEnter.reset();
mPlayerBarRotationAnimEnter = new RotateAnimation(0.0f, 25.0f, mPlayerBar.getPivotX(), mPlayerBar.getPivotY());
mPlayerBarRotationAnimEnter.setInterpolator(new LinearInterpolator());
mPlayerBarRotationAnimEnter.setFillEnabled(true);
mPlayerBarRotationAnimEnter.setFillAfter(true);
mPlayerBarRotationAnimEnter.setDuration(300);

if (mPlayerBarRotationAnimExit != null) mPlayerBarRotationAnimExit .reset();
mPlayerBarRotationAnimExit = new RotateAnimation(25.0f, 0.f, mPlayerBar.getPivotX(), mPlayerBar.getPivotY());
mPlayerBarRotationAnimExit.setInterpolator(new LinearInterpolator());
mPlayerBarRotationAnimExit.setFillEnabled(true);
mPlayerBarRotationAnimExit.setFillAfter(true);
mPlayerBarRotationAnimExit.setDuration(300);

if (mMediaPlayer.isPlaying()) {
    mPlayerBar.clearAnimation();
    mPlayerBar.setAnimation(mPlayerBarRotationAnimEnter);
} else {
    mPlayerBar.clearAnimation();
    mPlayerBar.setAnimation(mPlayerBarRotationAnimExit);
}
java

将上面的内容封装为一个方法initAnim(),在播放/暂停时调用即可:

private ObjectAnimator mPlayerRotationAnim;
private RotateAnimation mPlayerBarRotationAnimEnter;
private RotateAnimation mPlayerBarRotationAnimExit;

private void initAnim() {
    if (mPlayerRotationAnim != null) {
        mPlayerRotationAnim.pause();
    }
    
    if (mPlayerBarRotationAnimEnter != null) mPlayerBarRotationAnimEnter.reset();
    mPlayerBarRotationAnimEnter = new RotateAnimation(0.0f, 25.0f, mPlayerBar.getPivotX(), 
    mPlayerBar.getPivotY());
    mPlayerBarRotationAnimEnter.setInterpolator(new LinearInterpolator());
    mPlayerBarRotationAnimEnter.setFillEnabled(true);
    mPlayerBarRotationAnimEnter.setFillAfter(true);
    mPlayerBarRotationAnimEnter.setDuration(300);

    if (mPlayerBarRotationAnimExit != null) mPlayerBarRotationAnimExit .reset();
    mPlayerBarRotationAnimExit = new RotateAnimation(25.0f, 0.f, mPlayerBar.getPivotX(), 
    mPlayerBar.getPivotY());
    mPlayerBarRotationAnimExit.setInterpolator(new LinearInterpolator());
    mPlayerBarRotationAnimExit.setFillEnabled(true);
    mPlayerBarRotationAnimExit.setFillAfter(true);
    mPlayerBarRotationAnimExit.setDuration(300);

    if (mMediaPlayer.isPlaying()) {
        mPlayerBar.clearAnimation();
        mPlayerBar.setAnimation(mPlayerBarRotationAnimEnter);
        if (mPlayerRotationAnim == null) {
            mPlayerRotationAnim = ObjectAnimator.ofFloat(mPlayer, "rotation", mPlayer.getRotation(), 360.0f);
            mPlayerRotationAnim.setDuration(5000);
            mPlayerRotationAnim.setRepeatCount(Animation.INFINITE);
            mPlayerRotationAnim.setInterpolator(new LinearInterpolator());
            mPlayerRotationAnim.setRepeatMode(ObjectAnimator.RESTART);// 循环模式
            mPlayerRotationAnim.start();
        } else
            mPlayerRotationAnim.resume();
    } else {
        mPlayerBar.clearAnimation();
        mPlayerBar.setAnimation(mPlayerBarRotationAnimExit);
    }
}
java

4. 音乐播放

4.1 MediaPlayer初始化

使用MediaPlayer完成播放功能

MediaPlayer mMediaPlayer;

...

if (mMediaPlayer == null) {
    mMediaPlayer = MediaPlayer.create(this, R.raw.ronghaoli_lover);
}
java

这里用到的音频文件如下,右键保存复制到资源文件res/raw下面即可:

恋人-李荣浩

4.2 进度更新

需要用到Timer定时器:

private Timer timer;
private SeekBar mProgress;//需要初始化

...

private void initTimer() {
    boolean playing = mMediaPlayer.isPlaying();
    if (timer != null) timer.cancel();
    if (!playing) return;
    timer = new Timer();
    mProgress.setMax(mMediaPlayer.getDuration());
    mDuration.setText(DateUtils.formatTime(mMediaPlayer.getDuration()));
    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
        mProgress.setMin(0);
    }
    mProgress.setProgress(mMediaPlayer.getCurrentPosition());
    BaseApplication.getHandler().post(() -> {
        timer.schedule(new TimerTask() {
            @Override
            public void run() {
                MusicPlayerActivity.this.runOnUiThread(() -> {
                    long currentPosition = mMediaPlayer.getCurrentPosition();
                    mProgress.setProgress((int) currentPosition, true);
                    mCurrent.setText(DateUtils.formatTime(currentPosition));
                });
            }
        }, 0, 1000);
    });
}
java

其中用到的事件格式化工具类:

package cool.hyz.musicplayer.utils;

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

/**
 * @author xujiayi
 * @date 2024/7/11
 * 我只是个自由的主!
 */
public class DateUtils {
    public static String getDateStr(String time) {
        Date date = null;
        try {
            date = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss").parse(time);
        } catch (Exception ignored) {}

        if (date == null) return "最近";
        Calendar calendar= Calendar.getInstance();
        calendar.setTime(date);

        long now = new Date().getTime() / 1000;
        long dTime = now - date.getTime() / 1000;
        if (dTime < 10) return "刚刚";
        else if (dTime < 60) return dTime + "秒前";
        else if (dTime < 60 * 60) return (dTime / 60) + "分钟前";
        else if (dTime < 60 * 60 * 24) return (dTime / 60 / 60) + "小时前";
        else if (dTime < 60 * 60 * 24 * 10) return (dTime / 60 / 60 / 24) + "天前";
        else return calendar.get(Calendar.YEAR) + "年" + (calendar.get(Calendar.MONTH) + 1) + "月" +calendar.get(Calendar.DATE)  + "日";
    }
    public static Date getDate(String time) {
        Date date = null;
        try {
            date = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss").parse(time);
        } catch (Exception ignored) {}
        return date;
    }

    public static  String formatTime(double time) {
        String min = ((int) time / 1000 / 60) + "";
        min = (min.length() == 1 ? "0" : "") + min;
        String sec = ((int) time / 1000 % 60) + "";
        sec = (sec.length() == 1 ? "0" : "") + sec;
        return min + ":" + sec;
    }
}
java

以及BaseApplication类:

package cool.hyz.musicplayer.base;

import android.app.Application;
import android.os.Handler;


/**
 * @author xujiayi
 * @date 2024/7/11
 * 我只是个自由的主!
 */
public class BaseApplication extends Application {

    private static BaseApplication sInstance;

    public static BaseApplication getInstance() {
        return sInstance;
    }

    private static Handler sHandler;

    public  static Handler getHandler(){
        if (sHandler ==null) {
            synchronized (Handler.class){
                if(sHandler==null){
                    sHandler = new Handler();
                }
            }
        }
        return sHandler;
    }

    @Override
    public void onCreate() {
        super.onCreate();
        sInstance = this;
    }
}

java

同时在AndroidManifest中添加:

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools">

    <application
        android:name=".base.BaseApplication"
        android:allowBackup="true"
        android:dataExtractionRules="@xml/data_extraction_rules"
        android:fullBackupContent="@xml/backup_rules"
        android:icon="@mipmap/ic_launcher"
        android:label="@string/app_name"
        android:roundIcon="@mipmap/ic_launcher"
        android:supportsRtl="true"
        android:theme="@style/Theme.Musicplayer"
        tools:targetApi="31" >
        <activity android:name="cool.hyz.musicplayer.MainActivity"
            android:exported="true">
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />
                <category android:name="android.intent.category.LAUNCHER"/>
            </intent-filter>
        </activity>
        <activity android:name="cool.hyz.musicplayer.MusicPlayerActivity"  android:theme="@style/FullScreenActivity"/>
    </application>
</manifest>
xml

注意需要在Activity销毁时,结束定时任务:

@Override
protected void onDestroy() {
    super.onDestroy();
    if (mMediaPlayer != null) {
        mMediaPlayer.stop();
        mMediaPlayer.release();
        if (timer != null) {
            timer.cancel();
        }
    }
}
java

最终效果

项目地址

Github : Music Player Activity

打赏
  • 微信
  • 支付宝
评论
来发评论吧~
···

歌手: