如何在视图和动态壁纸中适合 GIF 动画的内容?

Posted

技术标签:

【中文标题】如何在视图和动态壁纸中适合 GIF 动画的内容?【英文标题】:How to fit content of GIF animation in View and in live wallpaper? 【发布时间】:2018-10-01 08:22:59 【问题描述】:

背景

我有一个小型动态壁纸应用程序,我想添加对它的支持以显示 GIF 动画。

为此,我找到了各种解决方案。有在视图中显示 GIF 动画的解决方案 (here),甚至还有在动态壁纸中显示的解决方案 (here)。

但是,对于他们两个,我都找不到如何将 GIF 动画的内容很好地适应它所拥有的空间,这意味着以下任何一种:

    center-crop - 适合 100% 的容器(在本例中为屏幕),在需要时在侧面(顶部和底部或左侧和右侧)进行裁剪。不拉伸任何东西。这意味着内容看起来不错,但可能不会全部显示。 fit-center - 拉伸以适应宽度/高度 center-inside - 设置为原始大小,居中,只有在太大时才会拉伸以适应宽度/高度。

问题

这些都不是关于 ImageView 的,所以我不能只使用 scaleType 属性。

我发现了什么

有一个解决方案可以为您提供 GifDrawable (here),您可以在 ImageView 中使用它,但在某些情况下它似乎很慢,我无法弄清楚如何在 LiveWallpaper 中使用它然后适合它。

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 库(或任何其他可绘制对象)用于动态壁纸,而不是使用 Movie 类?如果有,怎么做?

编辑:找到如何缩放 2 种后,我仍然需要知道如何根据第三种类型进行缩放,并且还想知道为什么它在方向更改后一直崩溃,以及为什么它并不总是显示立即预览。

我还想知道在这里显示 GIF 动画的最佳方式是什么,因为目前我只是刷新画布 ~60fps(每 2 帧之间等待 1000/60),而不考虑文件中的内容。

项目可用here

【问题讨论】:

【参考方案1】:

如果您的项目中有Glide,您可以轻松加载 Gif,因为它为您的 ImageView 提供绘图 GIF,并且支持许多缩放选项(如中心或给定宽度和...)。

Glide.with(context)
    .load(imageUrl or resourceId)
    .asGif()
    .fitCenter() //or other scaling options as you like
    .into(imageView);

【讨论】:

我可以使用 Glide 制作 GIF 动画吗?此外,您的代码没有意义,因为这里没有 imageView (甚至没有布局)。这是一个动态壁纸。请在动态壁纸中展示如何做到这一点 是的,您可以将 Glide 用于 GIF 以及静态图像。对于壁纸部分,您可以使用回调来检索 GIF 并使用 this answer 设置壁纸,而不是为 into 部分使用 imageView。 我看不出 Glide 如何适合这个链接。 Glide 可以加载和调整 GIF 的大小并在它的回调中(使用.into(Target);,您可以使用我提供的链接将 GIF 传递给 WallpaperManager。 请显示完整的示例代码。我记得我试过这个,它对我不起作用。另外,您确定使用 Glide 可以很好地处理 GIF 吗?即使文件很大也没有内存问题?【参考方案2】:

好的,我想我知道如何缩放内容了。不知道为什么应用程序有时在方向更改时仍然崩溃,以及为什么应用程序有时不立即显示预览。

项目可用here

center-inside,代码为:

    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())
    

对于center-crop,代码为:

    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())
    

对于fit-center,我可以使用这个:

                    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
                    ...

【讨论】:

【参考方案3】:

改变影片的宽度和高度:

在onDraw方法中movie.draw之前添加这段代码

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

缩放以填充和缩放以适应:

已经有一个很好的答案:

https://***.com/a/38898699/5675325

【讨论】:

这是哪种缩放类型?你能告诉我其他的怎么做吗? 再一次,我提到的缩放类型中的哪一种?对我来说,您似乎选择了拉伸它,这意味着没有保存纵横比,这意味着我所写的内容都不是我希望实现的。我不应该也让画布移动吗?关于链接,他们称之为“缩放以适应”的链接看起来像是中心,但它把内容放在了错误的位置(底部而不是纵向中心)。称为“缩放填充”的是中心裁剪,但我什至看不到它被绘制(在肖像上)。请尝试一下。

以上是关于如何在视图和动态壁纸中适合 GIF 动画的内容?的主要内容,如果未能解决你的问题,请参考以下文章

如何将几张静态JPG图片转化成动态GIF的图片?

Windows10动态壁纸

如何在Canvas上显示gif动画 SWT

自己制作动图怎么加LOGO或者水印

安卓怎么让壁纸动起来

python-tkinter如何打开动图(.gif)?