自定义view,模仿网易云播放唱片特效,鲸云特效,练习一下自定义view

本文介绍了一种使用Android平台实现类似网易云音乐唱片特效的方法。通过图片旋转动画及多个小圆模拟发散粒子效果,文章提供了完整的代码实现,包括初始化视图、设置动画等步骤。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

实现效果如图
首先分析一下思路:
1.中间的旋转可以用简单的图片加旋转动画实现
2.外面的发散粒子可以理解为多个小圆动画实现
具体实现代码如下:

package com.yyb.particle

import android.animation.ObjectAnimator
import android.animation.ObjectAnimator.ofFloat
import androidx.appcompat.app.AppCompatActivity
import android.os.Bundle
import android.view.View
import android.view.animation.LinearInterpolator
import com.bumptech.glide.Glide
import com.bumptech.glide.load.resource.bitmap.CircleCrop
import com.bumptech.glide.load.resource.drawable.DrawableTransitionOptions
import com.bumptech.glide.request.RequestOptions
import kotlinx.android.synthetic.main.activity_main.*

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

  }

  private fun initViewAndAnimation() {
    Glide.with(this)
      .load(R.mipmap.test)
      .circleCrop()
      .transition(DrawableTransitionOptions.withCrossFade(500))
      .into(iv_src)
    val animator = ObjectAnimator.ofFloat(iv_src, View.ROTATION, 0f, 360f)
    animator.apply {
      duration=6000
      repeatCount=-1
      interpolator= LinearInterpolator()
    }
    animator.start()
  }
}
package com.yyb.particle

/**
 *     author : 闫裕波
 *     e-mail : yyb@zlhopesun.com
 *     time   : 2020/10/20
 *     desc   : 定义坐标系
 */
class Coordinate (
    var x:Float,//X坐标
    var y:Float,//Y坐标
    var radius:Float,//半径
    var speed:Float,//速度
    var alpha: Int,//透明度
    var maxOffset:Float=300f,//最大移动距离
    var offset:Int,//当前移动距离
    var angle:Double,//粒子角度
)
package com.yyb.particle

import android.animation.ValueAnimator
import android.content.Context
import android.graphics.*
import android.util.AttributeSet
import android.util.Log
import android.view.View
import android.view.animation.LinearInterpolator
import java.lang.Math.*
import java.util.*
import kotlin.concurrent.thread
import kotlin.system.measureTimeMillis

/**
 *     author : 闫裕波
 *     e-mail : yyb@zlhopesun.com
 *     time   : 2020/10/20
 *     desc   : 模仿网易云音乐唱片特效
 */
class ParticleView @JvmOverloads constructor(
    context: Context, attrs: AttributeSet? = null, defStyleAttr: Int = 0
) : View(context, attrs, defStyleAttr) {
    //中心
    private var centerY: Float = 0f
    private var centerX: Float = 0f
    private var random = Random()
    private val path = Path()

    //圆的半径
    private var circleradius = 0f

    //星星的数量
    private var particleNum = 0
    private val pathMeasure = PathMeasure()//路径,用于测量扩散圆某一处的X,Y值
    private var pos = FloatArray(2) //扩散圆上某一点的x,y
    private val tan = FloatArray(2)//扩散圆上某一点切线

    //定义一个粒子的集合
    private var particleList = mutableListOf<Coordinate>()

    //定义画笔
    private var paint = Paint()

    //定义动画
    private var animator = ValueAnimator.ofFloat(0f, 1f)

    init {
        //初始化画笔
        paint.color = Color.WHITE
        paint.isAntiAlias = true
        //初始化值动画
        animator.duration = 2000
        animator.repeatCount = -1
        animator.interpolator = LinearInterpolator()
        animator.addUpdateListener {
            updateParticle(it.animatedValue as Float)
            invalidate()//重绘界面
        }
        val obtainStyledAttributes =
            context.obtainStyledAttributes(attrs, R.styleable.particleStyle)
        circleradius =
            obtainStyledAttributes.getDimension(
                R.styleable.particleStyle_circleradius,
                100f
            )
        particleNum = obtainStyledAttributes.getInteger(R.styleable.particleStyle_particleNum, 2000)
        obtainStyledAttributes.recycle()
    }

    private fun updateParticle(fl: Float) {
        particleList.forEach { particle ->
            //当偏移量大于最大的偏移量,重新归0
            if (particle.offset > particle.maxOffset) {
                particle.offset = 0
                particle.speed = (random.nextInt(3) + 1.5).toFloat()
            }
            particle.alpha = ((1f - particle.offset / particle.maxOffset) * 225f).toInt()
            //计算偏移的点X轴的坐标
            particle.x =
                (centerX + cos(particle.angle) * (circleradius + particle.offset)).toFloat()
            //计算Y轴偏移的点
            if (particle.y > centerY) {
                particle.y =
                    (sin(particle.angle) * (circleradius + particle.offset) + centerY).toFloat()
            } else {
                particle.y =
                    (centerY - sin(particle.angle) * (circleradius + particle.offset)).toFloat()
            }
            particle.offset += particle.speed.toInt()
        }
    }

    override fun onSizeChanged(w: Int, h: Int, oldw: Int, oldh: Int) {
        super.onSizeChanged(w, h, oldw, oldh)
        centerX = (w / 2).toFloat()
        centerY = (h / 2).toFloat()
        path.addCircle(centerX, centerY, circleradius, Path.Direction.CCW)
        //通过这个方法就可以获取到圆的路径上各个点的xy
        pathMeasure.setPath(path, false)
        //循环创建发散的星星各个坐标系统
        for (i in 0..2000) {
            pathMeasure.getPosTan(i / 2000f * pathMeasure.length, pos, tan)
            //取出xy的坐标
            val nextX = pos[0] + random.nextInt(6) - 3f
            val nextY = pos[1] + random.nextInt(6) - 3f
            //通过反余选函数获取角度
            val angle = acos(((pos[0] - centerX) / circleradius).toDouble())
            //设置星星的速度
            val speed = random.nextInt(2) + 2f
            val offSet = random.nextInt(200)
            val maxOffset = random.nextInt(200).toFloat()
            particleList.add(
                Coordinate(nextX, nextY, 2f, speed, 100, maxOffset, offSet, angle)
            )
        }
        animator.start()
    }

    override fun onDraw(canvas: Canvas?) {
        super.onDraw(canvas)
        particleList.forEachIndexed { index, particle ->
            if (particle.offset > 5f) {
                //根据偏移量和最大偏移量的比例计算渐变
                paint.alpha = ((1f - particle.offset / particle.maxOffset) * 0.8 * 225f).toInt()
                canvas?.drawCircle(particle.x, particle.y, particle.radius, paint)
            } else {
                paint.alpha = 225
            }
            canvas?.drawCircle(particle.x, particle.y, particle.radius, paint)

        }


    }
}

代码中需要注意的逻辑注释已经写的很清楚了。
源码传送门 https://github.com/yybDream/particle

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值