pull/32/head
GKF 6 years ago
parent 1d93529aef
commit 69947c426d
  1. 15
      app/src/main/java/io/legado/app/ui/widget/anima/RotateLoading.kt
  2. 150
      app/src/main/java/io/legado/app/ui/widget/anima/explosion_field/ExplosionAnimator.kt
  3. 191
      app/src/main/java/io/legado/app/ui/widget/anima/explosion_field/ExplosionField.kt
  4. 8
      app/src/main/java/io/legado/app/ui/widget/anima/explosion_field/OnAnimatorListener.kt
  5. 76
      app/src/main/java/io/legado/app/ui/widget/anima/explosion_field/Utils.kt

@ -1,4 +1,4 @@
package io.legado.app.ui.widget
package io.legado.app.ui.widget.anima
import android.animation.Animator
import android.animation.AnimatorSet
@ -61,7 +61,8 @@ class RotateLoading : View {
loadingColor = Color.WHITE
thisWidth = dpToPx(context, DEFAULT_WIDTH.toFloat())
shadowPosition = dpToPx(getContext(), DEFAULT_SHADOW_POSITION.toFloat())
speedOfDegree = DEFAULT_SPEED_OF_DEGREE
speedOfDegree =
DEFAULT_SPEED_OF_DEGREE
if (null != attrs) {
val typedArray = context.obtainStyledAttributes(attrs, R.styleable.RotateLoading)
@ -70,8 +71,14 @@ class RotateLoading : View {
R.styleable.RotateLoading_loading_width,
dpToPx(context, DEFAULT_WIDTH.toFloat())
)
shadowPosition = typedArray.getInt(R.styleable.RotateLoading_shadow_position, DEFAULT_SHADOW_POSITION)
speedOfDegree = typedArray.getInt(R.styleable.RotateLoading_loading_speed, DEFAULT_SPEED_OF_DEGREE)
shadowPosition = typedArray.getInt(
R.styleable.RotateLoading_shadow_position,
DEFAULT_SHADOW_POSITION
)
speedOfDegree = typedArray.getInt(
R.styleable.RotateLoading_loading_speed,
DEFAULT_SPEED_OF_DEGREE
)
typedArray.recycle()
}
speedOfArc = (speedOfDegree / 4).toFloat()

@ -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…
Cancel
Save