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

it2023-07-02  78

首先分析一下思路: 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

最新回复(0)