Android实现仿网易云音乐黑胶唱片效果

Android实现仿网易云音乐黑胶唱片效果

前言:

网易云音乐的黑胶唱片效果一直是很多音乐迷的最爱,之前实现过此效果,今天总结一下,直接上代码:

1.自定义圆形ImageView:

package com.example.vinylrecordplayer.view

import android.annotation.SuppressLint
import android.content.Context
import android.graphics.*
import android.util.AttributeSet
import androidx.appcompat.widget.AppCompatImageView
import com.example.vinylrecordplayer.R

/**
 * @author: njb
 * @date: 2023/5/21 22:56
 * @desc:
 */
@Suppress("NAME_SHADOWING")
class RoundImageView : AppCompatImageView {
    private val paint: Paint by lazy { Paint() }
    private var roundWidth = 10
    private var roundHeight = 10
    private val paint2: Paint by lazy { Paint() }

    constructor(context: Context, attrs: AttributeSet?, defStyle: Int) : super(
        context,
        attrs,
        defStyle
    ) {
        init(context, attrs)
    }

    constructor(context: Context, attrs: AttributeSet?) : super(context, attrs) {
        init(context, attrs)
    }

    constructor(context: Context) : super(context) {
        init(context, null)
    }

    @SuppressLint("Recycle")
    private fun init(context: Context, attrs: AttributeSet?) {
        if (attrs != null) {
            val attrs = context.obtainStyledAttributes(attrs, R.styleable.RoundImageView)
            roundWidth = attrs.getDimensionPixelSize(R.styleable.RoundImageView_x_radius, roundWidth)
            roundHeight = attrs.getDimensionPixelSize(R.styleable.RoundImageView_y_radius, roundHeight)
        } else {
            val density = context.resources.displayMetrics.density
            roundWidth = (roundWidth * density).toInt()
            roundHeight = (roundHeight * density).toInt()
        }
        paint.color = Color.WHITE
        paint.isAntiAlias = true
        paint.xfermode = PorterDuffXfermode(PorterDuff.Mode.DST_OUT)
    }

    override fun draw(canvas: Canvas) {
        val bitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888)
        val canvas2 = Canvas(bitmap)
        super.draw(canvas2)
        drawLiftUp(canvas2)
        drawLiftDown(canvas2)
        drawRightUp(canvas2)
        drawRightDown(canvas2)
        canvas.drawBitmap(bitmap, 0f, 0f, paint2)
        bitmap.recycle()
    }

    private fun drawLiftUp(canvas: Canvas) {
        val path = Path()
        path.moveTo(0f, roundHeight.toFloat())
        path.lineTo(0f, 0f)
        path.lineTo(roundWidth.toFloat(), 0f)
        path.arcTo(
            RectF(0f, 0f, (roundWidth * 2).toFloat(), (roundHeight * 2).toFloat()),
            -90f,
            -90f
        )
        path.close()
        canvas.drawPath(path, paint)
    }

    private fun drawLiftDown(canvas: Canvas) {
        val path = Path()
        path.moveTo(0f, (height - roundHeight).toFloat())
        path.lineTo(0f, height.toFloat())
        path.lineTo(roundWidth.toFloat(), height.toFloat())
        path.arcTo(
            RectF(
                0f,
                (height - roundHeight * 2).toFloat(),
                (roundWidth * 2).toFloat(),
                height.toFloat()
            ), 90f, 90f
        )
        path.close()
        canvas.drawPath(path, paint)
    }

    private fun drawRightDown(canvas: Canvas) {
        val path = Path()
        path.moveTo((width - roundWidth).toFloat(), height.toFloat())
        path.lineTo(width.toFloat(), height.toFloat())
        path.lineTo(width.toFloat(), (height - roundHeight).toFloat())
        path.arcTo(
            RectF(
                (width - roundWidth * 2).toFloat(),
                (height - roundHeight * 2).toFloat(),
                width.toFloat(),
                height.toFloat()
            ), -0f, 90f
        )
        path.close()
        canvas.drawPath(path, paint)
    }

    private fun drawRightUp(canvas: Canvas) {
        val path = Path()
        path.moveTo(width.toFloat(), roundHeight.toFloat())
        path.lineTo(width.toFloat(), 0f)
        path.lineTo((width - roundWidth).toFloat(), 0f)
        path.arcTo(
            RectF(
                (width - roundWidth * 2).toFloat(),
                0f,
                width.toFloat(),
                (roundHeight * 2).toFloat()
            ), -90f, 90f
        )
        path.close()
        canvas.drawPath(path, paint)
    }
}

2.自定义悬浮窗按钮:

package com.example.vinylrecordplayer.view

import android.content.Context
import android.util.AttributeSet
import android.view.Gravity
import android.view.MotionEvent
import android.view.View
import android.widget.LinearLayout
import com.example.vinylrecordplayer.R
import com.example.vinylrecordplayer.utils.DisplayUtils
import com.example.vinylrecordplayer.utils.DisplayUtils.dip2px
import kotlinx.android.synthetic.main.play_music_rotate.view.*
import kotlin.math.abs


/**
 * des 自定义音乐悬浮按钮
 * @date 2020/6/28
 * @author njb
 */
class FloatPlayLayout : LinearLayout {
    /**
     * content应该展示的宽度
     */
    private val contentWidth = dip2px(context, 180f)

    constructor(context: Context) : super(context) {
        initView(context)
    }

    constructor(context: Context, attrs: AttributeSet) : super(context, attrs) {
        initView(context)
    }

    constructor(context: Context, attrs: AttributeSet, defStyleAttr: Int) : super(
        context,
        attrs,
        defStyleAttr
    ) {
        initView(context)
    }

    override fun onInterceptTouchEvent(ev: MotionEvent): Boolean {
        when (ev.action) {
            MotionEvent.ACTION_DOWN -> {
                moveY = ev.y
            }
            MotionEvent.ACTION_MOVE -> {
                //兼容某些机型一碰就触发滑动
                if (abs(ev.y - moveY) > 100) {
                    return true
                }
            }
        }
        return false
    }

    private var moveY = 0f
    override fun onTouchEvent(event: MotionEvent): Boolean {
        when (event.action) {
            MotionEvent.ACTION_MOVE -> {
                var offsetY = translationY + (event.y - moveY)
                //因为translationY不管处于ViewGroup什么位置,初始值都为0,所以要买个top
                //最小值
                if (offsetY < -top + dip2px(context, 100f)) {
                    offsetY = -top.toFloat() + dip2px(context, 100f)
                }
                //最大值
                if (offsetY > DisplayUtils.getScreenHeight(context) - top - dip2px(context, 150f)) {
                    offsetY = DisplayUtils.getScreenHeight(context) - top.toFloat() - dip2px(
                        context,
                        150f
                    )
                }
                translationY = offsetY
                return true
            }
        }
        return super.onInterceptTouchEvent(event)
    }

    private fun initView(context: Context) {
        View.inflate(context, R.layout.play_music_rotate, this)
        //居中显示
        gravity = Gravity.CENTER
        //设置阴影
        click()
    }

    /**
     * 事件
     */
    private fun click() {

    }

    /**
     * 悬浮窗点击事件
     */
    fun rootClick(onClick: (View) -> Unit) {
        anchorLiveMusic.clickNoRepeat {
            onClick.invoke(it)
        }
    }
}

/**
 * 防止重复点击
 * @param interval 重复间隔
 * @param onClick  事件响应
 */
var lastTime = 0L
fun View.clickNoRepeat(interval: Long = 400, onClick: (View) -> Unit) {
    setOnClickListener {
        val currentTime = System.currentTimeMillis()
        if (lastTime != 0L && (currentTime - lastTime < interval)) {
            return@setOnClickListener
        }
        lastTime = currentTime
        onClick(it)
    }
}

3.自定义旋转的唱片view:

package com.example.vinylrecordplayer.view;

import android.animation.ObjectAnimator;
import android.animation.ValueAnimator;
import android.content.Context;
import android.util.AttributeSet;
import android.util.Log;
import android.view.MotionEvent;
import android.view.View;
import android.view.animation.LinearInterpolator;
import android.widget.FrameLayout;
import android.widget.ImageView;

import androidx.annotation.NonNull;
import androidx.annotation.Nullable;

import com.example.vinylrecordplayer.R;


/**
 * @author: njb
 * @date: 2021/5/25 16:34
 * @desc: 描述
 */
public class RotateAlbumView extends FrameLayout {
    private static final String TAG = "RotateAlbumView";

    private ImageView ivAlbumPic;
    private ObjectAnimator animator;

    public RotateAlbumView(@NonNull Context context) {
        this(context, null);
    }

    public RotateAlbumView(@NonNull Context context, @Nullable AttributeSet attrs) {
        this(context, attrs, 0);
    }

    public RotateAlbumView(@NonNull Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        init(context);
    }

    // 设置旋转动画(属性动画)
    private void init(Context context) {
        View.inflate(context, R.layout.view_rotate_album, this);
        ivAlbumPic = (ImageView) findViewById(R.id.view_pic);
        animator = ObjectAnimator.ofFloat(ivAlbumPic, "rotation", 0.0F, 360.0F);
        animator.setDuration(5 * 1000);
        animator.setInterpolator(new LinearInterpolator());
        animator.setRepeatCount(ObjectAnimator.INFINITE);
        animator.setRepeatMode(ValueAnimator.RESTART);
        setPlaying(true);
    }

    // 更新播放状态
    public void setPlaying(boolean isPlaying) {
        Log.d(TAG, "update RotateAlbumView: isPlaying = " + isPlaying);
        if (isPlaying) {
            if (!animator.isRunning()) {
                animator.start();
            } else {
                animator.resume();
            }
        } else {
            if (!animator.isStarted() || !animator.isRunning()) {
                animator.cancel();
            }
            animator.pause();
        }
    }


    @Override
    public boolean onTouchEvent(MotionEvent event) {
        return super.onTouchEvent(event);

    }

    @Override
    protected void onDetachedFromWindow() {
        super.onDetachedFromWindow();
        Log.d(TAG, "RotateAlbumView: onDetachedFromWindow");
        animator.cancel();
    }
}

4.自定义黑胶效果:

package com.example.vinylrecordplayer.view;

import android.content.Context;
import android.content.res.TypedArray;
import android.graphics.Canvas;
import android.graphics.Paint;
import android.graphics.SweepGradient;
import android.util.AttributeSet;
import android.view.View;

import androidx.annotation.ColorInt;
import androidx.annotation.ColorRes;
import androidx.annotation.Nullable;

import com.example.vinylrecordplayer.R;


/**
 * @author: njb
 * @date: 2021/5/25 16:35
 * @desc: 描述
 */
public class VinylRecordView extends View {
    private Paint paint;
    // 圆环半径
    private int ringWidth;
    // 渐变色
    private int[] colors;
    private SweepGradient gradient;
    // 圆线距圆环内边的距离
    private int[] ringLinesMarginOut = {
            dp2px(3.78F),
            dp2px(7.03F),
            dp2px(10.27F),
            dp2px(12.97F)
    };
    // 圆线高度
    private int ringLineWidth;

    public VinylRecordView(Context context) {
        this(context, null);
    }

    public VinylRecordView(Context context, @Nullable AttributeSet attrs) {
        this(context, attrs, 0);
    }

    public VinylRecordView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        init(context, attrs);
    }

    private void init(Context context, AttributeSet attrs) {
        paint = new Paint();
        paint.setAntiAlias(true);
        paint.setStyle(Paint.Style.STROKE);
        paint.setStrokeCap(Paint.Cap.ROUND);
        colors = new int[]{getColor(R.color.widget_album_ring_color1), getColor(R.color.widget_album_ring_color2),
                getColor(R.color.widget_album_ring_color1), getColor(R.color.widget_album_ring_color2),
                getColor(R.color.widget_album_ring_color1)};

        TypedArray typedArray = context.obtainStyledAttributes(attrs, R.styleable.VinylRecordView);
        ringWidth = (int) typedArray.getDimension(R.styleable.VinylRecordView_ring_width, getResources().getDimension(R.dimen.widget_album_ring_width));
        ringLineWidth = (int) typedArray.getDimension(R.styleable.VinylRecordView_ring_line_width, getResources().getDimension(R.dimen.widget_album_ring_line_width));
        typedArray.recycle();
    }

    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);
        paint.setStrokeWidth(ringWidth);
        paint.setColor(getColor(R.color.widget_album_ring_color1));
        if (gradient == null) {
            gradient = new SweepGradient(getWidth() * 0.5F, getHeight() * 0.5F, colors, new float[]{
                    0F, 0.25F, 0.5F, 0.75F, 1F
            });
        }
        paint.setShader(gradient);
        canvas.drawCircle(getWidth() * 0.5F, getHeight() * 0.5F, (getWidth() - ringWidth) * 0.5F, paint);
        paint.setShader(null);
        paint.setStrokeWidth(ringLineWidth);
        paint.setColor(getColor(R.color.widget_album_ring_line_color));
        for (int marginOut : ringLinesMarginOut) {
            canvas.drawCircle(getWidth() * 0.5F, getHeight() * 0.5F, getWidth() * 0.5F - marginOut - ringLineWidth * 0.5F, paint);
        }
    }

    private int dp2px(float dp) {
        float scale = getResources().getDisplayMetrics().density;
        return (int) (dp * scale + 0.5F);
    }

    @ColorInt
    private int getColor(@ColorRes int colorId) {
        return getResources().getColor(colorId, null);
    }
}

5.MainActivity使用:

package com.example.vinylrecordplayer

import android.os.Bundle
import android.widget.Toast
import androidx.appcompat.app.AppCompatActivity
import kotlinx.android.synthetic.main.activity_main.*

class MainActivity : AppCompatActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)
        initView()
    }

    private fun initView() {
        floatMusic.rootClick {
            Toast.makeText(this@MainActivity,"点击了悬浮窗",Toast.LENGTH_SHORT).show()
        }
    }
}

6.主界面代码:

<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".MainActivity">

    <com.example.vinylrecordplayer.view.FloatPlayLayout
        android:id="@+id/floatMusic"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        app:layout_constraintRight_toRightOf="parent"
        app:layout_constraintLeft_toLeftOf="parent"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintTop_toTopOf="parent" />

</androidx.constraintlayout.widget.ConstraintLayout>

7.实现效果如下:

在这里插入图片描述

8.源码地址:

https://gitee.com/jackning_admin/vinyl-record-demo

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值