Skip to content

Instantly share code, notes, and snippets.

@Skyyo
Last active August 16, 2020 11:22
Show Gist options
  • Save Skyyo/adbc9f30f1f4a50bc587958ccd442dff to your computer and use it in GitHub Desktop.
Save Skyyo/adbc9f30f1f4a50bc587958ccd442dff to your computer and use it in GitHub Desktop.
SnowFlake effect/view. SnowFlakesEffect.kt credits to https://github.com/DrKLO/Telegram #view #snow
import android.graphics.Canvas
import android.graphics.Color
import android.graphics.Paint
import android.view.View
import android.view.animation.AccelerateInterpolator
import android.view.animation.DecelerateInterpolator
import java.security.SecureRandom
import java.util.*
class SnowFlakesEffect {
private val particlePaint: Paint = Paint(Paint.ANTI_ALIAS_FLAG)
private val particleThinPaint: Paint
private var lastAnimationTime: Long = 0
private val angleDiff = (Math.PI / 180 * 60).toFloat()
private val particles = ArrayList<Particle>()
private val freeParticles = ArrayList<Particle>()
private var color = 0
private var marginTop = 0
private inner class Particle {
var x = 0f
var y = 0f
var vx = 0f
var vy = 0f
var velocity = 0f
var alpha = 0f
var lifeTime = 0f
var currentTime = 0f
var scale = 0f
var type = 0
fun draw(canvas: Canvas) {
when (type) {
0 -> {
particlePaint.alpha = (255 * alpha).toInt()
canvas.drawPoint(x, y, particlePaint)
}
1 -> {
particleThinPaint.alpha = (255 * alpha).toInt()
var angle = (-Math.PI).toFloat() / 2
val px = (2.0F.dp) * 2 * scale
val px1 = -(0.57F.dp) * 2 * scale
val py1 = (1.55F.dp) * 2 * scale
var a = 0
while (a < 6) {
var x1 =
Math.cos(angle.toDouble()).toFloat() * px
var y1 =
Math.sin(angle.toDouble()).toFloat() * px
val cx = x1 * 0.66f
val cy = y1 * 0.66f
canvas.drawLine(x, y, x + x1, y + y1, particleThinPaint)
val angle2 = (angle - Math.PI / 2).toFloat()
x1 =
(Math.cos(angle2.toDouble()) * px1 - Math.sin(angle2.toDouble()) * py1).toFloat()
y1 =
(Math.sin(angle2.toDouble()) * px1 + Math.cos(angle2.toDouble()) * py1).toFloat()
canvas.drawLine(x + cx, y + cy, x + x1, y + y1, particleThinPaint)
x1 = (
-Math.cos(angle2.toDouble()) * px1 - Math.sin(
angle2.toDouble()
) * py1
).toFloat()
y1 = (
-Math.sin(angle2.toDouble()) * px1 + Math.cos(
angle2.toDouble()
) * py1
).toFloat()
canvas.drawLine(x + cx, y + cy, x + x1, y + y1, particleThinPaint)
angle += angleDiff
a++
}
}
else -> {
particleThinPaint.alpha = (255 * alpha).toInt()
var angle = (-Math.PI).toFloat() / 2
val px = (2.0F.dp) * 2 * scale
val px1 = -(0.57F.dp) * 2 * scale
val py1 = (1.55F.dp) * 2 * scale
var a = 0
while (a < 6) {
var x1 =
Math.cos(angle.toDouble()).toFloat() * px
var y1 =
Math.sin(angle.toDouble()).toFloat() * px
val cx = x1 * 0.66f
val cy = y1 * 0.66f
canvas.drawLine(x, y, x + x1, y + y1, particleThinPaint)
val angle2 = (angle - Math.PI / 2).toFloat()
x1 =
(Math.cos(angle2.toDouble()) * px1 - Math.sin(angle2.toDouble()) * py1).toFloat()
y1 =
(Math.sin(angle2.toDouble()) * px1 + Math.cos(angle2.toDouble()) * py1).toFloat()
canvas.drawLine(x + cx, y + cy, x + x1, y + y1, particleThinPaint)
x1 = (
-Math.cos(angle2.toDouble()) * px1 - Math.sin(
angle2.toDouble()
) * py1
).toFloat()
y1 = (
-Math.sin(angle2.toDouble()) * px1 + Math.cos(
angle2.toDouble()
) * py1
).toFloat()
canvas.drawLine(x + cx, y + cy, x + x1, y + y1, particleThinPaint)
angle += angleDiff
a++
}
}
}
}
}
private fun updateParticles(dt: Long) {
var count = particles.size
var a = 0
while (a < count) {
val particle = particles[a]
if (particle.currentTime >= particle.lifeTime) {
if (freeParticles.size < 40) {
freeParticles.add(particle)
}
particles.removeAt(a)
a--
count--
a++
continue
}
if (particle.currentTime < 200.0f) {
particle.alpha =
accelerateInterpolator.getInterpolation(particle.currentTime / 200.0f)
} else {
particle.alpha =
1.0f - decelerateInterpolator.getInterpolation((particle.currentTime - 200.0f) / (particle.lifeTime - 200.0f))
}
particle.x += particle.vx * particle.velocity * dt / 500.0f
particle.y += particle.vy * particle.velocity * dt / 500.0f
particle.currentTime += dt.toFloat()
a++
}
}
fun onDraw(parent: View?, canvas: Canvas?) {
if (parent == null || canvas == null) {
return
}
val count = particles.size
for (a in 0 until count) {
val particle = particles[a]
particle.draw(canvas)
}
if (random.nextFloat() > 0.7f && particles.size < 100) {
val cx = random.nextFloat() * parent.measuredWidth
val cy = marginTop + random.nextFloat() * (parent.measuredHeight - (20F.dp) - marginTop)
val angle = random.nextInt(40) - 20 + 90
val vx = Math.cos(Math.PI / 180.0 * angle).toFloat()
val vy = Math.sin(Math.PI / 180.0 * angle).toFloat()
val newParticle: Particle
if (!freeParticles.isEmpty()) {
newParticle = freeParticles[0]
freeParticles.removeAt(0)
} else {
newParticle = Particle()
}
newParticle.x = cx
newParticle.y = cy
newParticle.vx = vx
newParticle.vy = vy
newParticle.alpha = 0.0f
newParticle.currentTime = 0f
newParticle.scale = random.nextFloat() * 1.2f
newParticle.type = random.nextInt(2)
newParticle.lifeTime = 2000 + random.nextInt(100).toFloat()
newParticle.velocity = 20.0f + random.nextFloat() * 4.0f
particles.add(newParticle)
}
val newTime = System.currentTimeMillis()
val dt = Math.min(17, newTime - lastAnimationTime)
updateParticles(dt)
lastAnimationTime = newTime
parent.invalidate()
}
fun updateValues(color: Int = Color.WHITE, marginTop: Int = 0) {
this.marginTop = marginTop
val color = color and -0x19191a
if (this.color != color) {
this.color = color
particlePaint.color = color
particleThinPaint.color = color
}
}
companion object {
private val random = SecureRandom()
var decelerateInterpolator = DecelerateInterpolator()
var accelerateInterpolator = AccelerateInterpolator()
}
init {
particlePaint.strokeWidth = (1.5F.dp).toFloat()
particlePaint.strokeCap = Paint.Cap.ROUND
particlePaint.style = Paint.Style.STROKE
particleThinPaint = Paint(Paint.ANTI_ALIAS_FLAG)
particleThinPaint.strokeWidth = (0.5F.dp).toFloat()
particleThinPaint.strokeCap = Paint.Cap.ROUND
particleThinPaint.style = Paint.Style.STROKE
updateValues()
for (a in 0..19) {
freeParticles.add(Particle())
}
}
}
val Float.dp: Int
get() = TypedValue.applyDimension(
TypedValue.COMPLEX_UNIT_DIP,
this,
Resources.getSystem().displayMetrics
).toInt()
import android.content.Context
import android.graphics.Canvas
import android.util.AttributeSet
import android.view.View
class SnowFlakeView @JvmOverloads constructor(
context: Context,
attrs: AttributeSet? = null,
defStyleAttr: Int = 0
) : View(context, attrs, defStyleAttr) {
private val snowFlakesEffect =
SnowFlakesEffect()
init {
snowFlakesEffect.updateValues()
}
override fun onDraw(canvas: Canvas) {
super.onDraw(canvas)
snowFlakesEffect.onDraw(this, canvas)
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment