如何在视图和动态壁纸中适应 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 动画的内容,这意味着以下任何一项:

  1. center-crop - 适合 100% 的容器(在本例中为屏幕),需要时在侧面(顶部和底部或左侧和右侧)进行裁剪。不拉伸任何东西。这意味着内容看起来不错,但可能不会显示所有内容。
  2. fit-center - 拉伸以适应 width/height
  3. 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()
        }
    }
}

问题

  1. 给定一个 GIF 动画,我如何用上述每种方式缩放它?
  2. 是否可以对这两种情况使用一个解决方案?
  3. 是否可以使用 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
                    ...