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