Better SetSpeed() function

Better onAnimationLoopListener
Add onLoaded function
This commit is contained in:
oupson 2018-11-09 22:54:56 +01:00
parent 208625b35e
commit d74effa341
5 changed files with 103 additions and 84 deletions

View File

@ -13,26 +13,31 @@ import java.net.URL
/**
* Class to play APNG
*/
class ApngAnimator {
class ApngAnimator(val context: Context) {
var isPlaying = true
private set(value) {
field = value
}
private var frames = ArrayList<Frame>()
private var myHandler: Handler = Handler()
private var counter = 0
private val generatedFrame = ArrayList<Bitmap>()
private var speed: Int? = null
private var lastFrame: Frame? = null
var speed: Float? = null
set(value) {
field = value
try {
pause()
play()
} catch ( e : Exception) {
e.printStackTrace()
}
}
private var bitmapBuffer: Bitmap? = null
private var background: Bitmap? = null
private var imageView: ImageView? = null
private var anim: CustomAnimationDrawable? = null
private var activeAnimation: CustomAnimationDrawable? = null
private var currentDrawable = 0
private var animationLoopListener: AnimationListener? = null
private var doOnLoaded : (ApngAnimator) -> Unit = {}
private var AnimationLoopListener : () -> Unit = {}
private var duration : ArrayList<Float>? = null
/**
* Load into an imageview
* @param imageView Image view selected.
@ -47,14 +52,14 @@ class ApngAnimator {
* @param file The file to load
* @throws NotApngException
*/
fun load(file: File, frameDuration: Int? = null, animationListener: AnimationListener? = null) {
fun load(file: File, speed: Float? = null) {
doAsync {
this@ApngAnimator.speed = speed
// Download PNG
APNGDisassembler(file.readBytes()).pngList.apply {
draw(this)
}
setupAnimationDrawableAndStart(frameDuration, animationListener)
setupAnimationDrawableAndStart()
}
}
@ -67,14 +72,15 @@ class ApngAnimator {
* in the APNG will be used instead.
* @throws NotApngException
*/
fun loadUrl(context: Context, url: URL, frameDuration: Int? = null, animationListener: AnimationListener? = null) {
fun loadUrl(url: URL, speed: Float? = null) {
doAsync(exceptionHandler = { e -> e.printStackTrace() }) {
this@ApngAnimator.speed = speed
// Download PNG
APNGDisassembler(Loader().load(context, url)).pngList.apply {
draw(this)
}
setupAnimationDrawableAndStart(frameDuration, animationListener)
setupAnimationDrawableAndStart()
}
}
@ -87,13 +93,13 @@ class ApngAnimator {
* in the APNG will be used instead.
* @throws NotApngException
*/
fun load(byteArray: ByteArray, frameDuration: Int? = null, animationListener: AnimationListener? = null) {
fun load(byteArray: ByteArray, speed: Float? = null) {
doAsync {
this@ApngAnimator.speed = speed
APNGDisassembler(byteArray).pngList.apply {
draw(this)
}
setupAnimationDrawableAndStart(frameDuration, animationListener)
setupAnimationDrawableAndStart()
}
}
@ -103,18 +109,9 @@ class ApngAnimator {
* @param frameDuration The duration to show each frame. If this is null then the duration specified
* in the APNG will be used instead.
*/
private fun setupAnimationDrawableAndStart(frameDuration: Int? = null, animationListener: AnimationListener? = null) {
private fun setupAnimationDrawableAndStart() {
doAsync {
var innerAnimationListener: CustomAnimationDrawable.AnimationListener? = null
animationListener?.apply {
innerAnimationListener = object : CustomAnimationDrawable.AnimationListener {
override fun onAnimationLooped() {
animationListener.onAnimationLooped()
}
}
}
anim = toAnimationDrawable(innerAnimationListener, frameDuration)
anim = toAnimationDrawable()
activeAnimation = anim
uiThread {
imageView?.apply {
@ -122,6 +119,7 @@ class ApngAnimator {
setImageDrawable(activeAnimation)
}
activeAnimation?.start()
doOnLoaded(this@ApngAnimator)
}
}
}
@ -135,12 +133,13 @@ class ApngAnimator {
* in the APNG will be used instead.
* @throws NotApngException
*/
fun load(context: Context, string: String, frameDuration: Int? = null, animationListener: AnimationListener? = null) {
fun load(string: String, speed : Float? = null) {
this.speed = speed
if (string.contains("http") || string.contains("https")) {
val url = URL(string)
loadUrl(context, url, frameDuration, animationListener)
loadUrl(url, speed)
} else if (File(string).exists()) {
load(File(string), frameDuration, animationListener)
load(File(string), speed)
}
}
@ -149,6 +148,7 @@ class ApngAnimator {
*/
private fun draw(extractedFrame: ArrayList<Frame>) {
// Set last frame
duration = ArrayList()
frames = extractedFrame
bitmapBuffer = Bitmap.createBitmap(frames[0].maxWidth!!, frames[0].maxHeight!!, Bitmap.Config.ARGB_8888)
for (i in 0 until frames.size) {
@ -183,6 +183,7 @@ class ApngAnimator {
} else {
bitmapBuffer = btm
}
duration?.add(it.delay / (speed ?: 1f))
}
}
@ -192,23 +193,26 @@ class ApngAnimator {
val animResume = CustomAnimationDrawable()
activeAnimation?.stop()
val currentFrame = activeAnimation!!.current
val dura = ArrayList<Float>()
frameLoop@ for (i in 0 until anim?.numberOfFrames!!) {
val checkFrame = activeAnimation!!.getFrame(i)
if (checkFrame === currentFrame) {
val frameIndex = i
for (k in frameIndex until activeAnimation!!.numberOfFrames) {
val frame = activeAnimation!!.getFrame(k)
animResume.addFrame(frame, activeAnimation?.getDuration(k)!!)
animResume.addFrame(frame, (duration!![k] / (speed ?: 1f)).toInt())
dura.add(duration!![k])
}
for (k in 0 until frameIndex) {
val frame = activeAnimation!!.getFrame(k)
animResume.addFrame(frame, activeAnimation?.getDuration(k)!!)
animResume.addFrame(frame, (duration!![k] / (speed ?: 1f)).toInt())
dura.add(duration!![k])
}
activeAnimation = animResume
imageView?.setImageDrawable(activeAnimation)
activeAnimation?.setOnAnimationLoopListener(AnimationLoopListener)
imageView?.invalidate()
duration = dura
break@frameLoop
}
}
@ -219,36 +223,25 @@ class ApngAnimator {
activeAnimation?.start()
}
fun setOnAnimationLoopListener(animationLoopListener : () -> Unit) {
AnimationLoopListener = animationLoopListener
anim?.setOnAnimationLoopListener(animationLoopListener)
}
fun onLoaded(f : (ApngAnimator) -> Unit) {
doOnLoaded = f
}
/**
* Converts the generated frames into an animation drawable ([CustomAnimationDrawable])
*
* @param animationListener The listener that will be invoked when there are specific animation events.
* @param frameDuration The duration to show each frame. If this is null then the duration specified
* in the APNG will be used instead.
*/
private fun toAnimationDrawable(animationListener: CustomAnimationDrawable.AnimationListener? = null,
frameDuration: Int? = null): CustomAnimationDrawable {
private fun toAnimationDrawable( ): CustomAnimationDrawable {
return CustomAnimationDrawable().apply {
for (i in 0 until generatedFrame.size) {
addFrame(BitmapDrawable(generatedFrame[i]), frameDuration
?: frames[i].delay.toInt())
}
animationListener?.let { listener ->
this.setAnimationListener(listener)
addFrame(BitmapDrawable(generatedFrame[i]), ((frames[i].delay).toInt() / (speed ?: 1f)).toInt())
}
}
}
/**
* Interface that exposes callbacks for events during the animation.
*/
interface AnimationListener {
/**
* The animation has performed a loop.
*/
fun onAnimationLooped()
}
}

View File

@ -1,36 +1,23 @@
package oupson.apng
import android.graphics.drawable.AnimationDrawable
import oupson.apng.CustomAnimationDrawable.AnimationListener
/**
* Extension of the [AnimationDrawable] that provides an [AnimationListener]. This will allow
* for the caller to listen for specific animation related events.
*/
internal class CustomAnimationDrawable : AnimationDrawable() {
private var onAnimationLoop : () -> Unit = {}
/**
* Interface that exposes callbacks for events during the animation.
*/
interface AnimationListener {
/**
* The animation has performed a loop.
*/
fun onAnimationLooped()
}
private var animationListener: AnimationListener? = null
fun setAnimationListener(animationListener: AnimationListener) {
this.animationListener = animationListener
fun setOnAnimationLoopListener( f : () -> Unit) {
onAnimationLoop = f
}
override fun selectDrawable(index: Int): Boolean {
val drawableChanged = super.selectDrawable(index)
if (index != 0 && index == numberOfFrames - 1) {
animationListener?.onAnimationLooped()
onAnimationLoop()
}
return drawableChanged

View File

@ -46,12 +46,12 @@ class Main2Activity : AppCompatActivity() {
}
fun load() {
val animator = ApngAnimator().loadInto(imageView3)
val animator = ApngAnimator(applicationContext).loadInto(imageView3)
val uri = intent.data
if (uri.toString().contains("file:///")) {
try {
if (isApng(File(uri.path).readBytes())) {
animator.load(this, uri.path)
animator.load(uri.path)
} else {
imageView3.setImageBitmap(BitmapFactory.decodeFile(uri.path))
Snackbar.make(constraint, "Not an APNG, and verified !", Snackbar.LENGTH_LONG).show()
@ -62,7 +62,7 @@ class Main2Activity : AppCompatActivity() {
}
} else {
try {
animator.load(this, getImageRealPath(contentResolver, uri, null))
animator.load(getImageRealPath(contentResolver, uri, null))
} catch (e: NotApngException) {
imageView3.setImageBitmap(BitmapFactory.decodeFile(getImageRealPath(contentResolver, uri, null)))
Snackbar.make(constraint, "Not an APNG", Snackbar.LENGTH_LONG).show()

View File

@ -3,9 +3,13 @@ package oupson.apngcreator
import android.os.Bundle
import android.support.v7.app.AppCompatActivity
import android.util.Log
import android.widget.SeekBar
import com.squareup.picasso.Picasso
import kotlinx.android.synthetic.main.activity_main.*
import oupson.apng.ApngAnimator
import android.widget.Toast
class MainActivity : AppCompatActivity() {
@ -17,15 +21,35 @@ class MainActivity : AppCompatActivity() {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
val animationListener = object : ApngAnimator.AnimationListener {
override fun onAnimationLooped() {
Log.d("TEST", "Animation LOOPED!")
animator = ApngAnimator(this).loadInto(imageView).apply {
load(imageUrl)
onLoaded {
setOnAnimationLoopListener {
Log.e("app-test", "onLoop")
}
}
}
animator = ApngAnimator().loadInto(imageView).apply {
load(this@MainActivity, imageUrl, null, animationListener)
}
this.seekBar.setOnSeekBarChangeListener(object : SeekBar.OnSeekBarChangeListener {
internal var progress = 0
// When Progress value changed.
override fun onProgressChanged(seekBar: SeekBar, progressValue: Int, fromUser: Boolean) {
progress = progressValue
}
// Notification that the user has started a touch gesture.
override fun onStartTrackingTouch(seekBar: SeekBar) {
}
// Notification that the user has finished a touch gesture
override fun onStopTrackingTouch(seekBar: SeekBar) {
Log.e("TAG" , (seekBar.progress.toFloat() / 100f).toString())
animator.speed = (seekBar.progress.toFloat() / 100f)
}
})
Picasso.get().load(imageUrl).into(imageView2)

View File

@ -13,7 +13,7 @@
android:layout_marginEnd="8dp"
android:layout_marginBottom="8dp"
android:text="pause"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintBottom_toTopOf="@+id/seekBar"
app:layout_constraintEnd_toEndOf="parent" />
<Button
@ -23,7 +23,21 @@
android:layout_marginStart="8dp"
android:layout_marginBottom="8dp"
android:text="play"
app:layout_constraintBottom_toTopOf="@+id/seekBar"
app:layout_constraintStart_toStartOf="parent" />
<SeekBar
android:id="@+id/seekBar"
style="@style/Widget.AppCompat.SeekBar.Discrete"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginStart="8dp"
android:layout_marginEnd="8dp"
android:layout_marginBottom="4dp"
android:max="200"
android:progress="10"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent" />
<ImageView
@ -50,4 +64,5 @@
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:srcCompat="@android:color/transparent" />
</android.support.constraint.ConstraintLayout>