-
-
Notifications
You must be signed in to change notification settings - Fork 1.3k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #984 from divadsn/snow
#MoarSnow
- Loading branch information
Showing
20 changed files
with
405 additions
and
1 deletion.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
38 changes: 38 additions & 0 deletions
38
app/src/main/java/ch/deletescape/lawnchair/snow/Drawables.kt
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,38 @@ | ||
package ch.deletescape.lawnchair.snow | ||
|
||
import android.annotation.SuppressLint | ||
import android.annotation.TargetApi | ||
import android.graphics.Bitmap | ||
import android.graphics.Canvas | ||
import android.graphics.Color | ||
import android.graphics.drawable.BitmapDrawable | ||
import android.graphics.drawable.Drawable | ||
import android.graphics.drawable.VectorDrawable | ||
import android.os.Build | ||
|
||
import ch.deletescape.lawnchair.util.Randomizer | ||
|
||
/** | ||
* Copyright (C) 2016 JetRadar, licensed under Apache License 2.0 | ||
* https://github.com/JetradarMobile/android-snowfall/ | ||
*/ | ||
|
||
@SuppressLint("NewApi") | ||
internal fun Drawable.toBitmap(rotation: Float): Bitmap { | ||
return when (this) { | ||
is BitmapDrawable -> bitmap | ||
is VectorDrawable -> toBitmap(rotation) | ||
else -> throw IllegalArgumentException("Unsupported drawable type") | ||
} | ||
} | ||
|
||
@TargetApi(Build.VERSION_CODES.LOLLIPOP) | ||
internal fun VectorDrawable.toBitmap(rotation: Float): Bitmap { | ||
val bitmap = Bitmap.createBitmap(intrinsicWidth, intrinsicHeight, Bitmap.Config.ARGB_8888) | ||
val canvas = Canvas(bitmap) | ||
canvas.rotate(rotation, intrinsicWidth / 2f, intrinsicHeight / 2f); | ||
setBounds(0, 0, canvas.width, canvas.height) | ||
setTint(Color.WHITE) | ||
draw(canvas) | ||
return bitmap | ||
} |
127 changes: 127 additions & 0 deletions
127
app/src/main/java/ch/deletescape/lawnchair/snow/SnowfallView.kt
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,127 @@ | ||
package ch.deletescape.lawnchair.snow | ||
|
||
import android.content.Context | ||
import android.graphics.Bitmap | ||
import android.graphics.Canvas | ||
import android.os.Handler | ||
import android.os.HandlerThread | ||
import android.util.AttributeSet | ||
import android.view.View | ||
|
||
import ch.deletescape.lawnchair.R | ||
import java.util.* | ||
|
||
/** | ||
* Copyright (C) 2016 JetRadar, licensed under Apache License 2.0 | ||
* https://github.com/JetradarMobile/android-snowfall/ | ||
*/ | ||
class SnowfallView(context: Context, attrs: AttributeSet) : View(context, attrs) { | ||
private val DEFAULT_SNOWFLAKES_NUM = 200 | ||
private val DEFAULT_SNOWFLAKE_ALPHA_MIN = 150 | ||
private val DEFAULT_SNOWFLAKE_ALPHA_MAX = 250 | ||
private val DEFAULT_SNOWFLAKE_ANGLE_MAX = 10 | ||
private val DEFAULT_SNOWFLAKE_SIZE_MIN_IN_DP = 2 | ||
private val DEFAULT_SNOWFLAKE_SIZE_MAX_IN_DP = 8 | ||
private val DEFAULT_SNOWFLAKE_SPEED_MIN = 2 | ||
private val DEFAULT_SNOWFLAKE_SPEED_MAX = 8 | ||
private val DEFAULT_SNOWFLAKES_FADING_ENABLED = false | ||
private val DEFAULT_SNOWFLAKES_ALREADY_FALLING = false | ||
|
||
private val snowflakesNum: Int | ||
private val snowflakeImage: Bitmap? | ||
private val snowflakeAlphaMin: Int | ||
private val snowflakeAlphaMax: Int | ||
private val snowflakeAngleMax: Int | ||
private val snowflakeSizeMinInPx: Int | ||
private val snowflakeSizeMaxInPx: Int | ||
private val snowflakeSpeedMin: Int | ||
private val snowflakeSpeedMax: Int | ||
private val snowflakesFadingEnabled: Boolean | ||
private val snowflakesAlreadyFalling: Boolean | ||
|
||
private val updateSnowflakesThread: UpdateSnowflakesThread | ||
private var snowflakes: Array<Snowflake>? = null | ||
|
||
private var rotationAngles = floatArrayOf(45f, 135f, 225f, 315f) | ||
|
||
init { | ||
val attrs = context.obtainStyledAttributes(attrs, R.styleable.SnowfallView) | ||
val rotation = rotationAngles[Random().nextInt(4)] | ||
|
||
try { | ||
snowflakesNum = attrs.getInt(R.styleable.SnowfallView_snowflakesNum, DEFAULT_SNOWFLAKES_NUM) | ||
snowflakeImage = attrs.getDrawable(R.styleable.SnowfallView_snowflakeImage)?.toBitmap(rotation) | ||
snowflakeAlphaMin = attrs.getInt(R.styleable.SnowfallView_snowflakeAlphaMin, DEFAULT_SNOWFLAKE_ALPHA_MIN) | ||
snowflakeAlphaMax = attrs.getInt(R.styleable.SnowfallView_snowflakeAlphaMax, DEFAULT_SNOWFLAKE_ALPHA_MAX) | ||
snowflakeAngleMax = attrs.getInt(R.styleable.SnowfallView_snowflakeAngleMax, DEFAULT_SNOWFLAKE_ANGLE_MAX) | ||
snowflakeSizeMinInPx = attrs.getDimensionPixelSize(R.styleable.SnowfallView_snowflakeSizeMin, dpToPx(DEFAULT_SNOWFLAKE_SIZE_MIN_IN_DP)) | ||
snowflakeSizeMaxInPx = attrs.getDimensionPixelSize(R.styleable.SnowfallView_snowflakeSizeMax, dpToPx(DEFAULT_SNOWFLAKE_SIZE_MAX_IN_DP)) | ||
snowflakeSpeedMin = attrs.getInt(R.styleable.SnowfallView_snowflakeSpeedMin, DEFAULT_SNOWFLAKE_SPEED_MIN) | ||
snowflakeSpeedMax = attrs.getInt(R.styleable.SnowfallView_snowflakeSpeedMax, DEFAULT_SNOWFLAKE_SPEED_MAX) | ||
snowflakesFadingEnabled = attrs.getBoolean(R.styleable.SnowfallView_snowflakesFadingEnabled, DEFAULT_SNOWFLAKES_FADING_ENABLED) | ||
snowflakesAlreadyFalling = attrs.getBoolean(R.styleable.SnowfallView_snowflakesAlreadyFalling, DEFAULT_SNOWFLAKES_ALREADY_FALLING) | ||
} finally { | ||
attrs.recycle() | ||
} | ||
|
||
updateSnowflakesThread = UpdateSnowflakesThread() | ||
} | ||
|
||
private fun dpToPx(dp: Int): Int { | ||
return (dp * resources.displayMetrics.density).toInt() | ||
} | ||
|
||
override fun onSizeChanged(w: Int, h: Int, oldw: Int, oldh: Int) { | ||
super.onSizeChanged(w, h, oldw, oldh) | ||
snowflakes = createSnowflakes() | ||
} | ||
|
||
override fun onVisibilityChanged(changedView: View, visibility: Int) { | ||
super.onVisibilityChanged(changedView, visibility) | ||
if (changedView === this && visibility == GONE) { | ||
snowflakes?.forEach { it.reset() } | ||
} | ||
} | ||
|
||
override fun onDraw(canvas: Canvas) { | ||
super.onDraw(canvas) | ||
if (isInEditMode) { | ||
return | ||
} | ||
|
||
snowflakes?.forEach { it.draw(canvas) } | ||
updateSnowflakes() | ||
} | ||
|
||
private fun createSnowflakes(): Array<Snowflake> { | ||
val snowflakeParams = Snowflake.Params( | ||
parentWidth = width, | ||
parentHeight = height, | ||
image = snowflakeImage, | ||
alphaMin = snowflakeAlphaMin, | ||
alphaMax = snowflakeAlphaMax, | ||
angleMax = snowflakeAngleMax, | ||
sizeMinInPx = snowflakeSizeMinInPx, | ||
sizeMaxInPx = snowflakeSizeMaxInPx, | ||
speedMin = snowflakeSpeedMin, | ||
speedMax = snowflakeSpeedMax, | ||
fadingEnabled = snowflakesFadingEnabled, | ||
alreadyFalling = snowflakesAlreadyFalling) | ||
return Array(snowflakesNum, { Snowflake(snowflakeParams) }) | ||
} | ||
|
||
private fun updateSnowflakes() { | ||
updateSnowflakesThread.handler.post { | ||
snowflakes?.forEach { it.update() } | ||
postInvalidateOnAnimation() | ||
} | ||
} | ||
|
||
private inner class UpdateSnowflakesThread : HandlerThread("SnowflakesComputations") { | ||
val handler by lazy { Handler(looper) } | ||
|
||
init { | ||
start() | ||
} | ||
} | ||
} |
100 changes: 100 additions & 0 deletions
100
app/src/main/java/ch/deletescape/lawnchair/snow/Snowflake.kt
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,100 @@ | ||
package ch.deletescape.lawnchair.snow | ||
|
||
import android.graphics.Bitmap | ||
import android.graphics.Canvas | ||
import android.graphics.Color | ||
import android.graphics.Paint | ||
import android.graphics.Paint.Style | ||
|
||
import ch.deletescape.lawnchair.util.Randomizer | ||
|
||
import java.lang.Math.cos | ||
import java.lang.Math.sin | ||
import java.lang.Math.toRadians | ||
|
||
/** | ||
* Copyright (C) 2016 JetRadar, licensed under Apache License 2.0 | ||
* https://github.com/JetradarMobile/android-snowfall/ | ||
*/ | ||
internal class Snowflake(val params: Params) { | ||
private var size: Int = 0 | ||
private var alpha: Int = 255 | ||
private var bitmap: Bitmap? = null | ||
private var speedX: Double = 0.0 | ||
private var speedY: Double = 0.0 | ||
private var positionX: Double = 0.0 | ||
private var positionY: Double = 0.0 | ||
|
||
private val paint by lazy { | ||
Paint(Paint.ANTI_ALIAS_FLAG).apply { | ||
color = Color.rgb(255, 255, 255) | ||
style = Style.FILL | ||
} | ||
} | ||
|
||
private val randomizer by lazy { Randomizer() } | ||
|
||
init { | ||
reset() | ||
} | ||
|
||
internal fun reset(positionY: Double? = null) { | ||
size = randomizer.randomInt(params.sizeMinInPx, params.sizeMaxInPx, gaussian = true) | ||
if (params.image != null) { | ||
bitmap = Bitmap.createScaledBitmap(params.image, size, size, false) | ||
} | ||
|
||
val speed = ((size - params.sizeMinInPx).toFloat() / (params.sizeMaxInPx - params.sizeMinInPx) * (params.speedMax - params.speedMin) + params.speedMin) | ||
val angle = toRadians(randomizer.randomDouble(params.angleMax) * randomizer.randomSignum()) | ||
speedX = speed * sin(angle) | ||
speedY = speed * cos(angle) | ||
|
||
alpha = randomizer.randomInt(params.alphaMin, params.alphaMax) | ||
paint.alpha = alpha | ||
|
||
positionX = randomizer.randomDouble(params.parentWidth) | ||
if (positionY != null) { | ||
this.positionY = positionY | ||
} else { | ||
this.positionY = randomizer.randomDouble(params.parentHeight) | ||
if (!params.alreadyFalling) { | ||
this.positionY = this.positionY - params.parentHeight - size | ||
} | ||
} | ||
} | ||
|
||
fun update() { | ||
positionX += speedX | ||
positionY += speedY | ||
|
||
if (positionY > params.parentHeight) { | ||
reset(positionY = -size.toDouble()) | ||
} | ||
|
||
if (params.fadingEnabled) { | ||
paint.alpha = (alpha * ((params.parentHeight - positionY).toFloat() / params.parentHeight)).toInt() | ||
} | ||
} | ||
|
||
fun draw(canvas: Canvas) { | ||
if (bitmap != null) { | ||
canvas.drawBitmap(bitmap, positionX.toFloat(), positionY.toFloat(), paint) | ||
} else { | ||
canvas.drawCircle(positionX.toFloat(), positionY.toFloat(), size.toFloat(), paint) | ||
} | ||
} | ||
|
||
data class Params( | ||
val parentWidth: Int, | ||
val parentHeight: Int, | ||
val image: Bitmap?, | ||
val alphaMin: Int, | ||
val alphaMax: Int, | ||
val angleMax: Int, | ||
val sizeMinInPx: Int, | ||
val sizeMaxInPx: Int, | ||
val speedMin: Int, | ||
val speedMax: Int, | ||
val fadingEnabled: Boolean, | ||
val alreadyFalling: Boolean) | ||
} |
Oops, something went wrong.