31 votes

Comment adapter le contenu d'une animation GIF à la vue et au fond d'écran ?

Contexte

J'ai une petite application de fond d'écran en direct, et je veux ajouter un support pour qu'elle puisse afficher des animations GIF.

Pour cela, j'ai trouvé différentes solutions. Il y a la solution de montrer une animation GIF dans une vue ( aquí ), et il existe même une solution pour l'afficher dans un fond d'écran en direct ( aquí ).

Cependant, dans les deux cas, je n'arrive pas à trouver comment faire tenir le contenu de l'animation GIF dans l'espace disponible, c'est-à-dire dans l'une des situations suivantes :

  1. center-crop - s'adapte à 100% du conteneur (l'écran dans ce cas), en recadrant sur les côtés (haut et bas ou gauche et droite) si nécessaire. N'étire rien. Cela signifie que le contenu semble correct, mais qu'il ne sera peut-être pas entièrement affiché.
  2. fit-center - s'étirer pour s'adapter à la largeur/hauteur
  3. center-inside - défini comme la taille originale, centré, et étiré pour s'adapter à la largeur/hauteur uniquement s'il est trop grand.

Le problème

Aucun d'entre eux ne concerne ImageView, je ne peux donc pas simplement utiliser l'attribut scaleType.

Ce que j'ai trouvé

Il existe une solution qui vous donne un GifDrawable ( aquí ), que vous pouvez utiliser dans ImageView, mais il semble que ce soit assez lent dans certains cas, et je n'arrive pas à trouver comment l'utiliser dans LiveWallpaper et ensuite l'adapter.

Le code principal de la manipulation des GIF de LiveWallpaper est le suivant ( aquí ) :

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

Le code principal pour gérer l'animation GIF dans une vue est le suivant :

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

Les questions

  1. Étant donné une animation GIF, comment puis-je la mettre à l'échelle de chacune des manières ci-dessus ?
  2. Est-il possible d'avoir une solution unique pour les deux cas ?
  3. Est-il possible d'utiliser la bibliothèque GifDrawable (ou tout autre dessinable d'ailleurs) pour le fond d'écran vivant, au lieu de la classe Movie ? Si oui, comment ?

EDIT : après avoir trouvé comment mettre à l'échelle pour 2 types, j'ai encore besoin de savoir comment mettre à l'échelle selon le troisième type, et je veux aussi savoir pourquoi il continue à planter après les changements d'orientation, et pourquoi il ne montre pas toujours l'aperçu tout de suite.

J'aimerais aussi savoir quelle est la meilleure façon d'afficher l'animation GIF ici, parce qu'actuellement je rafraîchis simplement la toile ~60fps (1000/60 d'attente entre chaque 2 images), sans tenir compte de ce qu'il y a dans le fichier.

Le projet est disponible aquí .

6voto

Adib Faramarzi Points 1189

Si vous avez Glide dans votre projet, vous pouvez facilement charger des GIFs, car il permet de dessiner des GIFs dans vos ImageViews et supporte de nombreuses options de mise à l'échelle (comme le centre ou une largeur donnée et ...).

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

1 votes

Je peux utiliser Glide pour l'animation GIF ? De plus, votre code n'a pas de sens, car il n'y a pas d'imageView ici (il n'y a même pas de layout). C'est un fond d'écran. S'il vous plaît, montrez comment le faire dans un fond d'écran vivant.

0 votes

Oui, vous pouvez utiliser Glide pour les GIFs ainsi que pour les images statiques. Pour la partie du papier peint, au lieu d'utiliser une imageView pour l'élément into vous pouvez avoir un callback pour récupérer le GIF et utiliser cette réponse pour définir le papier peint.

0 votes

Je ne vois pas comment Glide s'inscrit dans ce lien.

3voto

android developer Points 20939

OK, je pense que j'ai compris comment mettre le contenu à l'échelle. Je ne sais pas pourquoi l'application se bloque encore parfois lors d'un changement d'orientation, et pourquoi l'application n'affiche pas tout de suite l'aperçu.

Le projet est disponible aquí .

Pour le centre à l'intérieur, le code est :

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

Pour les cultures centrales, le code est le suivant :

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

pour le fit-center, je peux utiliser ceci :

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

2voto

Modifiez la largeur et la hauteur de la vidéo :

Ajoutez ce code dans la méthode onDraw avant movie.draw

canvas.scale((float)this.getWidth() / (float)movie.width(),(float)this.getHeight() /      (float)movie.height());

ou

canvas.scale(1.9f, 1.21f); //this changes according to screen size

Échelle de remplissage et échelle d'ajustement :

Il y a déjà une bonne réponse à cela :

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

1 votes

De quel type d'échelle s'agit-il ? Pouvez-vous me dire comment faire les autres aussi ?

0 votes

Encore une fois, lequel des types d'échelles que j'ai mentionnés est celui-ci ? Pour moi, il semble que vous ayez choisi de l'étirer, ce qui signifie que le rapport d'aspect n'est pas sauvegardé, et cela signifie qu'il n'y a rien de ce que j'ai écrit que je souhaite réaliser. Ne devrais-je pas également faire en sorte que la toile se déplace ? À propos du lien, celui qu'ils appellent "Scale to fit" semble être fit-center, mais il place le contenu au mauvais endroit pour moi (en bas au lieu du centre en portrait). Celui appelé "Scaling to fill" est centré, mais je ne peux même pas le voir être dessiné (en portrait). Veuillez l'essayer.

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X