如何在视图和动态壁纸中适应 GIF 动画的内容?
How to fit content of GIF animation in View and in live wallpaper?
背景
我有一个小的动态壁纸应用程序,我想添加对它的支持以显示 GIF 动画。
为此,我找到了各种解决方案。有在视图中显示GIF动画的解决方案(here), and there is even a solution for showing it in a live wallpaper (here)。
但是,对于他们两个,我找不到如何在 space 中很好地适应 GIF 动画的内容,这意味着以下任何一项:
- center-crop - 适合 100% 的容器(在本例中为屏幕),需要时在侧面(顶部和底部或左侧和右侧)进行裁剪。不拉伸任何东西。这意味着内容看起来不错,但可能不会显示所有内容。
- fit-center - 拉伸以适应 width/height
- center-inside - 设置为原始大小、居中并拉伸以适合 width/height 仅当太大时。
问题
None 其中实际上是关于 ImageView 的,所以我不能只使用 scaleType 属性。
我发现了什么
有一个解决方案可以给你一个GifDrawable(here),你可以在ImageView中使用它,但在某些情况下似乎很慢,我不知道如何使用它在 LiveWallpaper 中,然后适合它。
动态壁纸GIF处理的主要代码如下(here):
class GIFWallpaperService : WallpaperService() {
override fun onCreateEngine(): WallpaperService.Engine {
val movie = Movie.decodeStream(resources.openRawResource(R.raw.cinemagraphs))
return GIFWallpaperEngine(movie)
}
private inner class GIFWallpaperEngine(private val movie: Movie) : WallpaperService.Engine() {
private val frameDuration = 20
private var holder: SurfaceHolder? = null
private var visible: Boolean = false
private val handler: Handler = Handler()
private val drawGIF = Runnable { draw() }
private fun draw() {
if (visible) {
val canvas = holder!!.lockCanvas()
canvas.save()
movie.draw(canvas, 0f, 0f)
canvas.restore()
holder!!.unlockCanvasAndPost(canvas)
movie.setTime((System.currentTimeMillis() % movie.duration()).toInt())
handler.removeCallbacks(drawGIF)
handler.postDelayed(drawGIF, frameDuration.toLong())
}
}
override fun onVisibilityChanged(visible: Boolean) {
this.visible = visible
if (visible)
handler.post(drawGIF)
else
handler.removeCallbacks(drawGIF)
}
override fun onDestroy() {
super.onDestroy()
handler.removeCallbacks(drawGIF)
}
override fun onCreate(surfaceHolder: SurfaceHolder) {
super.onCreate(surfaceHolder)
this.holder = surfaceHolder
}
}
}
视图中处理GIF动画的主要代码如下:
class CustomGifView @JvmOverloads constructor(context: Context, attrs: AttributeSet? = null, defStyleAttr: Int = 0) : View(context, attrs, defStyleAttr) {
private var gifMovie: Movie? = null
var movieWidth: Int = 0
var movieHeight: Int = 0
var movieDuration: Long = 0
var mMovieStart: Long = 0
init {
isFocusable = true
val gifInputStream = context.resources.openRawResource(R.raw.test)
gifMovie = Movie.decodeStream(gifInputStream)
movieWidth = gifMovie!!.width()
movieHeight = gifMovie!!.height()
movieDuration = gifMovie!!.duration().toLong()
}
override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) {
setMeasuredDimension(movieWidth, movieHeight)
}
override fun onDraw(canvas: Canvas) {
val now = android.os.SystemClock.uptimeMillis()
if (mMovieStart == 0L) { // first time
mMovieStart = now
}
if (gifMovie != null) {
var dur = gifMovie!!.duration()
if (dur == 0) {
dur = 1000
}
val relTime = ((now - mMovieStart) % dur).toInt()
gifMovie!!.setTime(relTime)
gifMovie!!.draw(canvas, 0f, 0f)
invalidate()
}
}
}
问题
- 给定一个 GIF 动画,我如何用上述每种方式缩放它?
- 是否可以对这两种情况使用一个解决方案?
- 是否可以使用 GifDrawable 库(或任何其他可绘制对象)作为动态壁纸,而不是电影 class?如果可以,怎么做?
编辑:在找到如何缩放 2 种之后,我还需要知道如何根据第三种进行缩放,并且还想知道为什么它在方向改变后总是崩溃,为什么它并不总是立即显示预览。
我也想知道在这里显示GIF动画的最佳方式是什么,因为目前我只是刷新canvas ~60fps(每2帧之间等待1000/60),没有考虑文件中有什么。
项目可用 here。
如果您的项目中有 Glide,您可以轻松加载 GIF,因为它为您的 ImageView 提供绘图 GIF,并且支持许多缩放选项(例如居中或给定宽度和...)。
Glide.with(context)
.load(imageUrl or resourceId)
.asGif()
.fitCenter() //or other scaling options as you like
.into(imageView);
更改影片的宽度和高度:
在movie.draw
之前的onDraw方法中添加这段代码
canvas.scale((float)this.getWidth() / (float)movie.width(),(float)this.getHeight() / (float)movie.height());
或
canvas.scale(1.9f, 1.21f); //this changes according to screen size
缩放以填充并缩放以适合:
已经有一个很好的答案:
好的,我想我知道如何缩放内容了。不确定为什么有时应用程序仍然会在方向更改时崩溃,以及为什么有时应用程序不立即显示预览。
项目可用 here。
对于中心内部,代码是:
private fun draw() {
if (!isVisible)
return
val canvas = holder!!.lockCanvas() ?: return
canvas.save()
//center-inside
val scale = Math.min(canvas.width.toFloat() / movie.width().toFloat(), canvas.height.toFloat() / movie.height().toFloat());
val x = (canvas.width.toFloat() / 2f) - (movie.width().toFloat() / 2f) * scale;
val y = (canvas.height.toFloat() / 2f) - (movie.height().toFloat() / 2f) * scale;
canvas.translate(x, y)
canvas.scale(scale, scale)
movie.draw(canvas, 0f, 0f)
canvas.restore()
holder!!.unlockCanvasAndPost(canvas)
movie.setTime((System.currentTimeMillis() % movie.duration()).toInt())
handler.removeCallbacks(drawGIF)
handler.postDelayed(drawGIF, frameDuration.toLong())
}
对于中心裁剪,代码为:
private fun draw() {
if (!isVisible)
return
val canvas = holder!!.lockCanvas() ?: return
canvas.save()
//center crop
val scale = Math.max(canvas.width.toFloat() / movie.width().toFloat(), canvas.height.toFloat() / movie.height().toFloat());
val x = (canvas.width.toFloat() / 2f) - (movie.width().toFloat() / 2f) * scale;
val y = (canvas.height.toFloat() / 2f) - (movie.height().toFloat() / 2f) * scale;
canvas.translate(x, y)
canvas.scale(scale, scale)
movie.draw(canvas, 0f, 0f)
canvas.restore()
holder!!.unlockCanvasAndPost(canvas)
movie.setTime((System.currentTimeMillis() % movie.duration()).toInt())
handler.removeCallbacks(drawGIF)
handler.postDelayed(drawGIF, frameDuration.toLong())
}
对于健身中心,我可以使用这个:
val canvasWidth = canvas.width.toFloat()
val canvasHeight = canvas.height.toFloat()
val bitmapWidth = curBitmap.width.toFloat()
val bitmapHeight = curBitmap.height.toFloat()
val scaleX = canvasWidth / bitmapWidth
val scaleY = canvasHeight / bitmapHeight
scale = if (scaleX * curBitmap.height > canvas.height) scaleY else scaleX
x = (canvasWidth / 2f) - (bitmapWidth / 2f) * scale
y = (canvasHeight / 2f) - (bitmapHeight / 2f) * scale
...
背景
我有一个小的动态壁纸应用程序,我想添加对它的支持以显示 GIF 动画。
为此,我找到了各种解决方案。有在视图中显示GIF动画的解决方案(here), and there is even a solution for showing it in a live wallpaper (here)。
但是,对于他们两个,我找不到如何在 space 中很好地适应 GIF 动画的内容,这意味着以下任何一项:
- center-crop - 适合 100% 的容器(在本例中为屏幕),需要时在侧面(顶部和底部或左侧和右侧)进行裁剪。不拉伸任何东西。这意味着内容看起来不错,但可能不会显示所有内容。
- fit-center - 拉伸以适应 width/height
- center-inside - 设置为原始大小、居中并拉伸以适合 width/height 仅当太大时。
问题
None 其中实际上是关于 ImageView 的,所以我不能只使用 scaleType 属性。
我发现了什么
有一个解决方案可以给你一个GifDrawable(here),你可以在ImageView中使用它,但在某些情况下似乎很慢,我不知道如何使用它在 LiveWallpaper 中,然后适合它。
动态壁纸GIF处理的主要代码如下(here):
class GIFWallpaperService : WallpaperService() {
override fun onCreateEngine(): WallpaperService.Engine {
val movie = Movie.decodeStream(resources.openRawResource(R.raw.cinemagraphs))
return GIFWallpaperEngine(movie)
}
private inner class GIFWallpaperEngine(private val movie: Movie) : WallpaperService.Engine() {
private val frameDuration = 20
private var holder: SurfaceHolder? = null
private var visible: Boolean = false
private val handler: Handler = Handler()
private val drawGIF = Runnable { draw() }
private fun draw() {
if (visible) {
val canvas = holder!!.lockCanvas()
canvas.save()
movie.draw(canvas, 0f, 0f)
canvas.restore()
holder!!.unlockCanvasAndPost(canvas)
movie.setTime((System.currentTimeMillis() % movie.duration()).toInt())
handler.removeCallbacks(drawGIF)
handler.postDelayed(drawGIF, frameDuration.toLong())
}
}
override fun onVisibilityChanged(visible: Boolean) {
this.visible = visible
if (visible)
handler.post(drawGIF)
else
handler.removeCallbacks(drawGIF)
}
override fun onDestroy() {
super.onDestroy()
handler.removeCallbacks(drawGIF)
}
override fun onCreate(surfaceHolder: SurfaceHolder) {
super.onCreate(surfaceHolder)
this.holder = surfaceHolder
}
}
}
视图中处理GIF动画的主要代码如下:
class CustomGifView @JvmOverloads constructor(context: Context, attrs: AttributeSet? = null, defStyleAttr: Int = 0) : View(context, attrs, defStyleAttr) {
private var gifMovie: Movie? = null
var movieWidth: Int = 0
var movieHeight: Int = 0
var movieDuration: Long = 0
var mMovieStart: Long = 0
init {
isFocusable = true
val gifInputStream = context.resources.openRawResource(R.raw.test)
gifMovie = Movie.decodeStream(gifInputStream)
movieWidth = gifMovie!!.width()
movieHeight = gifMovie!!.height()
movieDuration = gifMovie!!.duration().toLong()
}
override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) {
setMeasuredDimension(movieWidth, movieHeight)
}
override fun onDraw(canvas: Canvas) {
val now = android.os.SystemClock.uptimeMillis()
if (mMovieStart == 0L) { // first time
mMovieStart = now
}
if (gifMovie != null) {
var dur = gifMovie!!.duration()
if (dur == 0) {
dur = 1000
}
val relTime = ((now - mMovieStart) % dur).toInt()
gifMovie!!.setTime(relTime)
gifMovie!!.draw(canvas, 0f, 0f)
invalidate()
}
}
}
问题
- 给定一个 GIF 动画,我如何用上述每种方式缩放它?
- 是否可以对这两种情况使用一个解决方案?
- 是否可以使用 GifDrawable 库(或任何其他可绘制对象)作为动态壁纸,而不是电影 class?如果可以,怎么做?
编辑:在找到如何缩放 2 种之后,我还需要知道如何根据第三种进行缩放,并且还想知道为什么它在方向改变后总是崩溃,为什么它并不总是立即显示预览。
我也想知道在这里显示GIF动画的最佳方式是什么,因为目前我只是刷新canvas ~60fps(每2帧之间等待1000/60),没有考虑文件中有什么。
项目可用 here。
如果您的项目中有 Glide,您可以轻松加载 GIF,因为它为您的 ImageView 提供绘图 GIF,并且支持许多缩放选项(例如居中或给定宽度和...)。
Glide.with(context)
.load(imageUrl or resourceId)
.asGif()
.fitCenter() //or other scaling options as you like
.into(imageView);
更改影片的宽度和高度:
在movie.draw
之前的onDraw方法中添加这段代码canvas.scale((float)this.getWidth() / (float)movie.width(),(float)this.getHeight() / (float)movie.height());
或
canvas.scale(1.9f, 1.21f); //this changes according to screen size
缩放以填充并缩放以适合:
已经有一个很好的答案:
好的,我想我知道如何缩放内容了。不确定为什么有时应用程序仍然会在方向更改时崩溃,以及为什么有时应用程序不立即显示预览。
项目可用 here。
对于中心内部,代码是:
private fun draw() {
if (!isVisible)
return
val canvas = holder!!.lockCanvas() ?: return
canvas.save()
//center-inside
val scale = Math.min(canvas.width.toFloat() / movie.width().toFloat(), canvas.height.toFloat() / movie.height().toFloat());
val x = (canvas.width.toFloat() / 2f) - (movie.width().toFloat() / 2f) * scale;
val y = (canvas.height.toFloat() / 2f) - (movie.height().toFloat() / 2f) * scale;
canvas.translate(x, y)
canvas.scale(scale, scale)
movie.draw(canvas, 0f, 0f)
canvas.restore()
holder!!.unlockCanvasAndPost(canvas)
movie.setTime((System.currentTimeMillis() % movie.duration()).toInt())
handler.removeCallbacks(drawGIF)
handler.postDelayed(drawGIF, frameDuration.toLong())
}
对于中心裁剪,代码为:
private fun draw() {
if (!isVisible)
return
val canvas = holder!!.lockCanvas() ?: return
canvas.save()
//center crop
val scale = Math.max(canvas.width.toFloat() / movie.width().toFloat(), canvas.height.toFloat() / movie.height().toFloat());
val x = (canvas.width.toFloat() / 2f) - (movie.width().toFloat() / 2f) * scale;
val y = (canvas.height.toFloat() / 2f) - (movie.height().toFloat() / 2f) * scale;
canvas.translate(x, y)
canvas.scale(scale, scale)
movie.draw(canvas, 0f, 0f)
canvas.restore()
holder!!.unlockCanvasAndPost(canvas)
movie.setTime((System.currentTimeMillis() % movie.duration()).toInt())
handler.removeCallbacks(drawGIF)
handler.postDelayed(drawGIF, frameDuration.toLong())
}
对于健身中心,我可以使用这个:
val canvasWidth = canvas.width.toFloat()
val canvasHeight = canvas.height.toFloat()
val bitmapWidth = curBitmap.width.toFloat()
val bitmapHeight = curBitmap.height.toFloat()
val scaleX = canvasWidth / bitmapWidth
val scaleY = canvasHeight / bitmapHeight
scale = if (scaleX * curBitmap.height > canvas.height) scaleY else scaleX
x = (canvasWidth / 2f) - (bitmapWidth / 2f) * scale
y = (canvasHeight / 2f) - (bitmapHeight / 2f) * scale
...