背景
我正在尝试手动(逐帧)遍历动画GIF&WEBP文件的位图,以便它不仅适用于“ View ”,而且还适用于其他情况(例如动态壁纸)。
问题
动画GIF/WEBP文件仅通过使用ImageDecoder API(例如here)从Android P支持。
对于GIF,我想尝试使用Glide来完成该任务,但是失败了,所以我尝试通过使用允许加载它们的库(here,解决方案here)来克服这一问题。我认为效果很好。
对于WebP,我认为我已经找到了另一个可以在较旧的Android版本上运行的库(here,制成fork here),但是似乎在某些情况下它不能很好地处理WebP文件(报告为here)。我试图找出问题所在以及如何解决,但没有成功。
因此,假设有一天Google将通过支持库(他们将其编写为here)支持较旧的Android版本的GIF&WEBP动画,我决定尝试使用ImageDecoder来完成该任务。
问题是,查看ImageDecoder的整个API时,我们使用它的方式受到了很大的限制。我看不到如何克服其局限性。
我发现了什么
这就是ImageDecoder可用于在ImageView上显示动画WebP的方式(当然,只是示例,可用here):
class MainActivity : AppCompatActivity() {
@SuppressLint("StaticFieldLeak")
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
val source = ImageDecoder.createSource(resources, R.raw.test)
object : AsyncTask<Void, Void, Drawable?>() {
override fun doInBackground(vararg params: Void?): Drawable? {
return try {
ImageDecoder.decodeDrawable(source)
} catch (e: Exception) {
null
}
}
override fun onPostExecute(result: Drawable?) {
super.onPostExecute(result)
imageView.setImageDrawable(result)
if (result is AnimatedImageDrawable) {
result.start()
}
}
}.execute()
}
}
我试图阅读ImageDecoder和AnimatedImageDrawable的所有文档,并查看其代码,但是我看不到如何手动遍历每个帧并在它们之间等待时间。
问题
编辑:
由于我认为我已经有了想要的东西,无论是对于第三方库还是对于ImageDecoder,都能够从动画WebP中获取位图,所以我仍然想知道如何使用ImageDecoder获取帧数和当前帧,如果那是可能的。我尝试使用
ImageDecoder.decodeDrawable(source, object : ImageDecoder.OnHeaderDecodedListener...
,但是它不提供帧计数信息,并且在API中没有办法可以看到我可以转到特定的帧索引并从那里开始,或者知道一个特定的帧需要多长时间它需要转到下一帧。因此,我对这些here提出了要求。遗憾的是,我也没有找到Google的ImageDecoder适用于较旧的Android版本。
如果有某种方法可以像我对相对较新的HEIC动画文件所做的一样,这也很有趣。目前仅在Android P上受支持。
最佳答案
好的,我使用Glide library和GlideWebpDecoder library得到了一个可能的解决方案。
我不确定这是否是最好的方法,但是我认为它应该可以正常工作。下一个代码显示了如何针对动画需要显示的每个帧,将可绘制的绘制放入我创建的Bitmap实例中。这不完全是我的要求,但可能会对其他人有所帮助。
这是代码(项目可用here):
CallbackEx.kt
abstract class CallbackEx : Drawable.Callback {
override fun unscheduleDrawable(who: Drawable, what: Runnable) {}
override fun invalidateDrawable(who: Drawable) {}
override fun scheduleDrawable(who: Drawable, what: Runnable, `when`: Long) {}
}
MyAppGlideModule.kt
@GlideModule
class MyAppGlideModule : AppGlideModule()
MainActivity.kt
class MainActivity : AppCompatActivity() {
var webpDrawable: WebpDrawable? = null
var gifDrawable: GifDrawable? = null
var callback: Drawable.Callback? = null
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
useFrameByFrameDecoding()
// useNormalDecoding()
}
fun useNormalDecoding() {
//webp url : https://res.cloudinary.com/demo/image/upload/fl_awebp/bored_animation.webp
Glide.with(this)
// .load(R.raw.test)
// .load(R.raw.fast)
.load(R.raw.example2)
// .load("https://res.cloudinary.com/demo/image/upload/fl_awebp/bored_animation.webp")
.into(object : SimpleTarget<Drawable>() {
override fun onResourceReady(drawable: Drawable, transition: Transition<in Drawable>?) {
imageView.setImageDrawable(drawable)
when (drawable) {
is GifDrawable -> {
drawable.start()
}
is WebpDrawable -> {
drawable.start()
}
}
}
})
}
fun useFrameByFrameDecoding() {
//webp url : https://res.cloudinary.com/demo/image/upload/fl_awebp/bored_animation.webp
Glide.with(this)
.load(R.raw.test)
// .load(R.raw.fast)
// .load(R.raw.example2)
// .load("https://res.cloudinary.com/demo/image/upload/fl_awebp/bored_animation.webp")
.into(object : SimpleTarget<Drawable>() {
override fun onResourceReady(drawable: Drawable, transition: Transition<in Drawable>?) {
// val callback
when (drawable) {
is GifDrawable -> {
gifDrawable = drawable
val bitmap = Bitmap.createBitmap(drawable.intrinsicWidth, drawable.intrinsicHeight, Bitmap.Config.ARGB_8888)
val canvas = Canvas(bitmap)
drawable.setBounds(0, 0, bitmap.width, bitmap.height)
drawable.setLoopCount(GifDrawable.LOOP_FOREVER)
callback = object : CallbackEx() {
override fun invalidateDrawable(who: Drawable) {
who.draw(canvas)
imageView.setImageBitmap(bitmap)
Log.d("AppLog", "invalidateDrawable ${drawable.toString().substringAfter('@')} ${drawable.frameIndex}/${drawable.frameCount}")
}
}
drawable.callback = callback
drawable.start()
}
is WebpDrawable -> {
webpDrawable = drawable
val bitmap = Bitmap.createBitmap(drawable.intrinsicWidth, drawable.intrinsicHeight, Bitmap.Config.ARGB_8888)
val canvas = Canvas(bitmap)
drawable.setBounds(0, 0, bitmap.width, bitmap.height)
drawable.setLoopCount(WebpDrawable.LOOP_FOREVER)
callback = object : CallbackEx() {
override fun invalidateDrawable(who: Drawable) {
who.draw(canvas)
imageView.setImageBitmap(bitmap)
Log.d("AppLog", "invalidateDrawable ${drawable.toString().substringAfter('@')} ${drawable.frameIndex}/${drawable.frameCount}")
}
}
drawable.callback = callback
drawable.start()
}
}
}
})
}
override fun onStart() {
super.onStart()
gifDrawable?.start()
gifDrawable?.start()
}
override fun onStop() {
super.onStop()
Log.d("AppLog", "onStop")
webpDrawable?.stop()
gifDrawable?.stop()
}
}
不知道为什么
SimpleTarget
被标记为已弃用,但是我应该使用什么代替。使用类似的技术,我还发现了如何使用ImageDecoder进行操作,但是由于某种原因,它们却没有相同的功能。可用的示例项目here。
这是代码:
MainActivity.kt
class MainActivity : AppCompatActivity() {
var webpDrawable: AnimatedImageDrawable? = null
@SuppressLint("StaticFieldLeak")
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
val source = ImageDecoder.createSource(resources, R.raw.test)
object : AsyncTask<Void, Void, Drawable?>() {
override fun doInBackground(vararg params: Void?): Drawable? {
return try {
ImageDecoder.decodeDrawable(source)
} catch (e: Exception) {
null
}
}
override fun onPostExecute(drawable: Drawable?) {
super.onPostExecute(drawable)
// imageView.setImageDrawable(result)
if (drawable is AnimatedImageDrawable) {
webpDrawable = drawable
val bitmap =
Bitmap.createBitmap(drawable.intrinsicWidth, drawable.intrinsicHeight, Bitmap.Config.ARGB_8888)
val canvas = Canvas(bitmap)
drawable.setBounds(0, 0, bitmap.width, bitmap.height)
drawable.repeatCount = AnimatedImageDrawable.REPEAT_INFINITE
drawable.callback = object : Drawable.Callback {
val handler = Handler()
override fun unscheduleDrawable(who: Drawable, what: Runnable) {
Log.d("AppLog", "unscheduleDrawable")
}
override fun invalidateDrawable(who: Drawable) {
who.draw(canvas)
imageView.setImageBitmap(bitmap)
Log.d("AppLog", "invalidateDrawable")
}
override fun scheduleDrawable(who: Drawable, what: Runnable, `when`: Long) {
Log.d("AppLog", "scheduleDrawable next frame in ${`when` - SystemClock.uptimeMillis()} ms")
handler.postAtTime(what, `when`)
}
}
drawable.start()
}
}
}.execute()
}
override fun onStart() {
super.onStart()
webpDrawable?.start()
}
override fun onStop() {
super.onStop()
webpDrawable?.stop()
}
}
关于android - 是否可以获取新的ImageDecoder类以手动返回一帧又一帧的位图?,我们在Stack Overflow上找到一个类似的问题:https://stackoverflow.com/questions/53024304/