pull/32/head
parent
1d93529aef
commit
69947c426d
@ -0,0 +1,150 @@ |
|||||||
|
/* |
||||||
|
* Copyright (C) 2015 tyrantgit |
||||||
|
* |
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License"); |
||||||
|
* you may not use this file except in compliance with the License. |
||||||
|
* You may obtain a copy of the License at |
||||||
|
* |
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0 |
||||||
|
* |
||||||
|
* Unless required by applicable law or agreed to in writing, software |
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS, |
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
||||||
|
* See the License for the specific language governing permissions and |
||||||
|
* limitations under the License. |
||||||
|
*/ |
||||||
|
package io.legado.app.ui.widget.anima.explosion_field |
||||||
|
|
||||||
|
import android.animation.ValueAnimator |
||||||
|
import android.graphics.* |
||||||
|
import android.view.View |
||||||
|
import android.view.animation.AccelerateInterpolator |
||||||
|
import java.util.* |
||||||
|
|
||||||
|
class ExplosionAnimator(private val mContainer: View, bitmap: Bitmap, bound: Rect) : |
||||||
|
ValueAnimator() { |
||||||
|
private val mPaint: Paint |
||||||
|
private val mParticles: Array<Particle?> |
||||||
|
private val mBound: Rect |
||||||
|
|
||||||
|
init { |
||||||
|
mPaint = Paint() |
||||||
|
mBound = Rect(bound) |
||||||
|
val partLen = 15 |
||||||
|
mParticles = arrayOfNulls(partLen * partLen) |
||||||
|
val random = Random(System.currentTimeMillis()) |
||||||
|
val w = bitmap.width / (partLen + 2) |
||||||
|
val h = bitmap.height / (partLen + 2) |
||||||
|
for (i in 0 until partLen) { |
||||||
|
for (j in 0 until partLen) { |
||||||
|
mParticles[i * partLen + j] = |
||||||
|
generateParticle(bitmap.getPixel((j + 1) * w, (i + 1) * h), random) |
||||||
|
} |
||||||
|
} |
||||||
|
setFloatValues(0f, END_VALUE) |
||||||
|
interpolator = DEFAULT_INTERPOLATOR |
||||||
|
duration = DEFAULT_DURATION |
||||||
|
} |
||||||
|
|
||||||
|
private fun generateParticle(color: Int, random: Random): Particle { |
||||||
|
val particle = Particle() |
||||||
|
particle.color = color |
||||||
|
particle.radius = V |
||||||
|
if (random.nextFloat() < 0.2f) { |
||||||
|
particle.baseRadius = V + (X - V) * random.nextFloat() |
||||||
|
} else { |
||||||
|
particle.baseRadius = W + (V - W) * random.nextFloat() |
||||||
|
} |
||||||
|
val nextFloat = random.nextFloat() |
||||||
|
particle.top = mBound.height() * (0.18f * random.nextFloat() + 0.2f) |
||||||
|
particle.top = |
||||||
|
if (nextFloat < 0.2f) particle.top else particle.top + particle.top * 0.2f * random.nextFloat() |
||||||
|
particle.bottom = mBound.height() * (random.nextFloat() - 0.5f) * 1.8f |
||||||
|
var f = |
||||||
|
if (nextFloat < 0.2f) particle.bottom else if (nextFloat < 0.8f) particle.bottom * 0.6f else particle.bottom * 0.3f |
||||||
|
particle.bottom = f |
||||||
|
particle.mag = 4.0f * particle.top / particle.bottom |
||||||
|
particle.neg = -particle.mag / particle.bottom |
||||||
|
f = mBound.centerX() + Y * (random.nextFloat() - 0.5f) |
||||||
|
particle.baseCx = f |
||||||
|
particle.cx = f |
||||||
|
f = mBound.centerY() + Y * (random.nextFloat() - 0.5f) |
||||||
|
particle.baseCy = f |
||||||
|
particle.cy = f |
||||||
|
particle.life = END_VALUE / 10 * random.nextFloat() |
||||||
|
particle.overflow = 0.4f * random.nextFloat() |
||||||
|
particle.alpha = 1f |
||||||
|
return particle |
||||||
|
} |
||||||
|
|
||||||
|
fun draw(canvas: Canvas): Boolean { |
||||||
|
if (!isStarted) { |
||||||
|
return false |
||||||
|
} |
||||||
|
for (particle in mParticles) { |
||||||
|
particle?.let { |
||||||
|
particle.advance(animatedValue as Float) |
||||||
|
if (particle.alpha > 0f) { |
||||||
|
mPaint.color = particle.color |
||||||
|
mPaint.alpha = (Color.alpha(particle.color) * particle.alpha).toInt() |
||||||
|
canvas.drawCircle(particle.cx, particle.cy, particle.radius, mPaint) |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
mContainer.invalidate() |
||||||
|
return true |
||||||
|
} |
||||||
|
|
||||||
|
override fun start() { |
||||||
|
super.start() |
||||||
|
mContainer.invalidate(mBound) |
||||||
|
} |
||||||
|
|
||||||
|
private inner class Particle { |
||||||
|
internal var alpha: Float = 0.toFloat() |
||||||
|
internal var color: Int = 0 |
||||||
|
internal var cx: Float = 0.toFloat() |
||||||
|
internal var cy: Float = 0.toFloat() |
||||||
|
internal var radius: Float = 0.toFloat() |
||||||
|
internal var baseCx: Float = 0.toFloat() |
||||||
|
internal var baseCy: Float = 0.toFloat() |
||||||
|
internal var baseRadius: Float = 0.toFloat() |
||||||
|
internal var top: Float = 0.toFloat() |
||||||
|
internal var bottom: Float = 0.toFloat() |
||||||
|
internal var mag: Float = 0.toFloat() |
||||||
|
internal var neg: Float = 0.toFloat() |
||||||
|
internal var life: Float = 0.toFloat() |
||||||
|
internal var overflow: Float = 0.toFloat() |
||||||
|
|
||||||
|
|
||||||
|
fun advance(factor: Float) { |
||||||
|
var f = 0f |
||||||
|
var normalization = factor / END_VALUE |
||||||
|
if (normalization < life || normalization > 1f - overflow) { |
||||||
|
alpha = 0f |
||||||
|
return |
||||||
|
} |
||||||
|
normalization = (normalization - life) / (1f - life - overflow) |
||||||
|
val f2 = normalization * END_VALUE |
||||||
|
if (normalization >= 0.7f) { |
||||||
|
f = (normalization - 0.7f) / 0.3f |
||||||
|
} |
||||||
|
alpha = 1f - f |
||||||
|
f = bottom * f2 |
||||||
|
cx = baseCx + f |
||||||
|
cy = (baseCy - this.neg * Math.pow(f.toDouble(), 2.0)).toFloat() - f * mag |
||||||
|
radius = V + (baseRadius - V) * f2 |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
companion object { |
||||||
|
|
||||||
|
internal var DEFAULT_DURATION: Long = 0x400 |
||||||
|
private val DEFAULT_INTERPOLATOR = AccelerateInterpolator(0.6f) |
||||||
|
private val END_VALUE = 1.4f |
||||||
|
private val X = Utils.dp2Px(5).toFloat() |
||||||
|
private val Y = Utils.dp2Px(20).toFloat() |
||||||
|
private val V = Utils.dp2Px(2).toFloat() |
||||||
|
private val W = Utils.dp2Px(1).toFloat() |
||||||
|
} |
||||||
|
} |
@ -0,0 +1,191 @@ |
|||||||
|
/* |
||||||
|
* Copyright (C) 2015 tyrantgit |
||||||
|
* |
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License"); |
||||||
|
* you may not use this file except in compliance with the License. |
||||||
|
* You may obtain a copy of the License at |
||||||
|
* |
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0 |
||||||
|
* |
||||||
|
* Unless required by applicable law or agreed to in writing, software |
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS, |
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
||||||
|
* See the License for the specific language governing permissions and |
||||||
|
* limitations under the License. |
||||||
|
*/ |
||||||
|
package io.legado.app.ui.widget.anima.explosion_field |
||||||
|
|
||||||
|
import android.animation.Animator |
||||||
|
import android.animation.AnimatorListenerAdapter |
||||||
|
import android.animation.ValueAnimator |
||||||
|
import android.app.Activity |
||||||
|
import android.content.Context |
||||||
|
import android.graphics.Bitmap |
||||||
|
import android.graphics.Canvas |
||||||
|
import android.graphics.Rect |
||||||
|
import android.media.MediaPlayer |
||||||
|
import android.util.AttributeSet |
||||||
|
import android.util.Log |
||||||
|
import android.view.View |
||||||
|
import android.view.ViewGroup |
||||||
|
import android.view.Window |
||||||
|
import java.util.* |
||||||
|
|
||||||
|
|
||||||
|
class ExplosionField : View { |
||||||
|
|
||||||
|
private var customDuration = ExplosionAnimator.DEFAULT_DURATION |
||||||
|
private var idPlayAnimationEffect = 0 |
||||||
|
private var mZAnimatorListener: OnAnimatorListener? = null |
||||||
|
private var mOnClickListener: View.OnClickListener? = null |
||||||
|
|
||||||
|
private val mExplosions = ArrayList<ExplosionAnimator>() |
||||||
|
private val mExpandInset = IntArray(2) |
||||||
|
|
||||||
|
constructor(context: Context) : super(context) { |
||||||
|
init() |
||||||
|
} |
||||||
|
|
||||||
|
constructor(context: Context, attrs: AttributeSet) : super(context, attrs) { |
||||||
|
init() |
||||||
|
} |
||||||
|
|
||||||
|
constructor(context: Context, attrs: AttributeSet, defStyleAttr: Int) : super( |
||||||
|
context, |
||||||
|
attrs, |
||||||
|
defStyleAttr |
||||||
|
) { |
||||||
|
init() |
||||||
|
} |
||||||
|
|
||||||
|
private fun init() { |
||||||
|
|
||||||
|
Arrays.fill(mExpandInset, Utils.dp2Px(32)) |
||||||
|
} |
||||||
|
|
||||||
|
override fun onDraw(canvas: Canvas) { |
||||||
|
super.onDraw(canvas) |
||||||
|
for (explosion in mExplosions) { |
||||||
|
explosion.draw(canvas) |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
fun playSoundAnimationEffect(id: Int) { |
||||||
|
this.idPlayAnimationEffect = id |
||||||
|
} |
||||||
|
|
||||||
|
fun setCustomDuration(customDuration: Long) { |
||||||
|
this.customDuration = customDuration |
||||||
|
} |
||||||
|
|
||||||
|
fun addActionEvent(ievents: OnAnimatorListener) { |
||||||
|
this.mZAnimatorListener = ievents |
||||||
|
} |
||||||
|
|
||||||
|
|
||||||
|
fun expandExplosionBound(dx: Int, dy: Int) { |
||||||
|
mExpandInset[0] = dx |
||||||
|
mExpandInset[1] = dy |
||||||
|
} |
||||||
|
|
||||||
|
@JvmOverloads |
||||||
|
fun explode(bitmap: Bitmap?, bound: Rect, startDelay: Long, view: View? = null) { |
||||||
|
val currentDuration = customDuration |
||||||
|
val explosion = ExplosionAnimator(this, bitmap!!, bound) |
||||||
|
explosion.addListener(object : AnimatorListenerAdapter() { |
||||||
|
override fun onAnimationEnd(animation: Animator) { |
||||||
|
mExplosions.remove(animation) |
||||||
|
if (view != null) { |
||||||
|
view.scaleX = 1f |
||||||
|
view.scaleY = 1f |
||||||
|
view.alpha = 1f |
||||||
|
view.setOnClickListener(mOnClickListener)//set event |
||||||
|
|
||||||
|
} |
||||||
|
} |
||||||
|
}) |
||||||
|
explosion.startDelay = startDelay |
||||||
|
explosion.duration = currentDuration |
||||||
|
mExplosions.add(explosion) |
||||||
|
explosion.start() |
||||||
|
} |
||||||
|
|
||||||
|
@JvmOverloads |
||||||
|
fun explode(view: View, restartState: Boolean? = false) { |
||||||
|
|
||||||
|
val r = Rect() |
||||||
|
view.getGlobalVisibleRect(r) |
||||||
|
val location = IntArray(2) |
||||||
|
getLocationOnScreen(location) |
||||||
|
// getLocationInWindow(location); |
||||||
|
// view.getLocationInWindow(location); |
||||||
|
r.offset(-location[0], -location[1]) |
||||||
|
r.inset(-mExpandInset[0], -mExpandInset[1]) |
||||||
|
val startDelay = 100 |
||||||
|
val animator = ValueAnimator.ofFloat(0f, 1f).setDuration(150) |
||||||
|
animator.addUpdateListener(object : ValueAnimator.AnimatorUpdateListener { |
||||||
|
|
||||||
|
internal var random = Random() |
||||||
|
|
||||||
|
override fun onAnimationUpdate(animation: ValueAnimator) { |
||||||
|
view.translationX = (random.nextFloat() - 0.5f) * view.width.toFloat() * 0.05f |
||||||
|
view.translationY = (random.nextFloat() - 0.5f) * view.height.toFloat() * 0.05f |
||||||
|
} |
||||||
|
}) |
||||||
|
|
||||||
|
animator.addListener(object : Animator.AnimatorListener { |
||||||
|
override fun onAnimationStart(animator: Animator) { |
||||||
|
if (idPlayAnimationEffect != 0) |
||||||
|
MediaPlayer.create(context, idPlayAnimationEffect).start() |
||||||
|
} |
||||||
|
|
||||||
|
override fun onAnimationEnd(animator: Animator) { |
||||||
|
if (mZAnimatorListener != null) { |
||||||
|
mZAnimatorListener!!.onAnimationEnd(animator, this@ExplosionField) |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
override fun onAnimationCancel(animator: Animator) { |
||||||
|
Log.i("PRUEBA", "CANCEL") |
||||||
|
} |
||||||
|
|
||||||
|
override fun onAnimationRepeat(animator: Animator) { |
||||||
|
Log.i("PRUEBA", "REPEAT") |
||||||
|
} |
||||||
|
}) |
||||||
|
|
||||||
|
animator.start() |
||||||
|
view.animate().setDuration(150).setStartDelay(startDelay.toLong()).scaleX(0f).scaleY(0f) |
||||||
|
.alpha(0f).start() |
||||||
|
if (restartState!!) |
||||||
|
explode(Utils.createBitmapFromView(view), r, startDelay.toLong(), view) |
||||||
|
else |
||||||
|
explode(Utils.createBitmapFromView(view), r, startDelay.toLong()) |
||||||
|
|
||||||
|
} |
||||||
|
|
||||||
|
fun clear() { |
||||||
|
mExplosions.clear() |
||||||
|
invalidate() |
||||||
|
} |
||||||
|
|
||||||
|
override fun setOnClickListener(mOnClickListener: View.OnClickListener?) { |
||||||
|
this.mOnClickListener = mOnClickListener |
||||||
|
} |
||||||
|
|
||||||
|
companion object { |
||||||
|
|
||||||
|
fun attach2Window(activity: Activity): ExplosionField { |
||||||
|
val rootView = activity.findViewById<View>(Window.ID_ANDROID_CONTENT) as ViewGroup |
||||||
|
val explosionField = ExplosionField(activity) |
||||||
|
rootView.addView( |
||||||
|
explosionField, ViewGroup.LayoutParams( |
||||||
|
ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT |
||||||
|
) |
||||||
|
) |
||||||
|
return explosionField |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
|
||||||
|
} |
@ -0,0 +1,8 @@ |
|||||||
|
package io.legado.app.ui.widget.anima.explosion_field |
||||||
|
|
||||||
|
import android.animation.Animator |
||||||
|
import android.view.View |
||||||
|
|
||||||
|
interface OnAnimatorListener { |
||||||
|
fun onAnimationEnd(animator: Animator, view: View) |
||||||
|
} |
@ -0,0 +1,76 @@ |
|||||||
|
/* |
||||||
|
* Copyright (C) 2015 tyrantgit |
||||||
|
* |
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License"); |
||||||
|
* you may not use this file except in compliance with the License. |
||||||
|
* You may obtain a copy of the License at |
||||||
|
* |
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0 |
||||||
|
* |
||||||
|
* Unless required by applicable law or agreed to in writing, software |
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS, |
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
||||||
|
* See the License for the specific language governing permissions and |
||||||
|
* limitations under the License. |
||||||
|
*/ |
||||||
|
package io.legado.app.ui.widget.anima.explosion_field |
||||||
|
|
||||||
|
|
||||||
|
import android.content.res.Resources |
||||||
|
import android.graphics.Bitmap |
||||||
|
import android.graphics.Canvas |
||||||
|
import android.graphics.drawable.BitmapDrawable |
||||||
|
import android.view.View |
||||||
|
import android.widget.ImageView |
||||||
|
|
||||||
|
object Utils { |
||||||
|
|
||||||
|
private val DENSITY = Resources.getSystem().displayMetrics.density |
||||||
|
private val sCanvas = Canvas() |
||||||
|
|
||||||
|
fun dp2Px(dp: Int): Int { |
||||||
|
return Math.round(dp * DENSITY) |
||||||
|
} |
||||||
|
|
||||||
|
fun createBitmapFromView(view: View): Bitmap? { |
||||||
|
if (view is ImageView) { |
||||||
|
val drawable = view.drawable |
||||||
|
if (drawable != null && drawable is BitmapDrawable) { |
||||||
|
return drawable.bitmap |
||||||
|
} |
||||||
|
} |
||||||
|
view.clearFocus() |
||||||
|
val bitmap = createBitmapSafely( |
||||||
|
view.width, |
||||||
|
view.height, Bitmap.Config.ARGB_8888, 1 |
||||||
|
) |
||||||
|
if (bitmap != null) { |
||||||
|
synchronized(sCanvas) { |
||||||
|
val canvas = sCanvas |
||||||
|
canvas.setBitmap(bitmap) |
||||||
|
view.draw(canvas) |
||||||
|
canvas.setBitmap(null) |
||||||
|
} |
||||||
|
} |
||||||
|
return bitmap |
||||||
|
} |
||||||
|
|
||||||
|
fun createBitmapSafely( |
||||||
|
width: Int, |
||||||
|
height: Int, |
||||||
|
config: Bitmap.Config, |
||||||
|
retryCount: Int |
||||||
|
): Bitmap? { |
||||||
|
try { |
||||||
|
return Bitmap.createBitmap(width, height, config) |
||||||
|
} catch (e: OutOfMemoryError) { |
||||||
|
e.printStackTrace() |
||||||
|
if (retryCount > 0) { |
||||||
|
System.gc() |
||||||
|
return createBitmapSafely(width, height, config, retryCount - 1) |
||||||
|
} |
||||||
|
return null |
||||||
|
} |
||||||
|
|
||||||
|
} |
||||||
|
} |
Loading…
Reference in new issue